|
1 | 1 | use std::{ |
| 2 | + borrow::Cow, |
2 | 3 | io::{ErrorKind, Write}, |
3 | 4 | path::{Path, PathBuf}, |
4 | 5 | sync::{Arc, mpsc}, |
5 | 6 | }; |
6 | 7 |
|
7 | 8 | use cow_utils::CowUtils; |
8 | 9 | use miette::LabeledSpan; |
| 10 | +use percent_encoding::AsciiSet; |
| 11 | +#[cfg(not(windows))] |
| 12 | +use std::fs::canonicalize as strict_canonicalize; |
9 | 13 |
|
10 | 14 | use crate::{ |
11 | 15 | Error, NamedSource, OxcDiagnostic, Severity, |
@@ -142,13 +146,12 @@ impl DiagnosticService { |
142 | 146 | std::env::var("TERMINAL_EMULATOR").is_ok_and(|x| x.eq("JetBrains-JediTerm")); |
143 | 147 |
|
144 | 148 | let path_ref = path.as_ref(); |
145 | | - let path_display = if is_jetbrains { |
146 | | - format!("file://{}", path_ref.to_string_lossy()) |
147 | | - } else { |
| 149 | + let path_display = if is_jetbrains { from_file_path(path_ref) } else { None }; |
| 150 | + let path_display = path_display.unwrap_or_else(|| { |
148 | 151 | let relative_path = path_ref.strip_prefix(cwd).unwrap_or(path_ref).to_string_lossy(); |
149 | 152 | let normalized_path = relative_path.cow_replace('\\', "/"); |
150 | 153 | normalized_path.to_string() |
151 | | - }; |
| 154 | + }); |
152 | 155 |
|
153 | 156 | let source = Arc::new(NamedSource::new(path_display, source_text.to_owned())); |
154 | 157 | diagnostics |
@@ -269,3 +272,81 @@ impl DiagnosticService { |
269 | 272 | } |
270 | 273 | } |
271 | 274 | } |
| 275 | + |
| 276 | +// The following from_file_path and strict_canonicalize implementations are from tower-lsp-community/tower-lsp-server |
| 277 | +// available under the MIT License or Apache 2.0 License. |
| 278 | +// |
| 279 | +// Copyright (c) 2023 Eyal Kalderon |
| 280 | +// https://github.com/tower-lsp-community/tower-lsp-server/blob/85506ddcbd108c514438e0b62e0eb858c812adcf/src/uri_ext.rs |
| 281 | + |
| 282 | +const ASCII_SET: AsciiSet = |
| 283 | + // RFC3986 allows only alphanumeric characters, `-`, `.`, `_`, and `~` in the path. |
| 284 | + percent_encoding::NON_ALPHANUMERIC |
| 285 | + .remove(b'-') |
| 286 | + .remove(b'.') |
| 287 | + .remove(b'_') |
| 288 | + .remove(b'~') |
| 289 | + // we do not want path separators to be percent-encoded |
| 290 | + .remove(b'/'); |
| 291 | + |
| 292 | +fn from_file_path<A: AsRef<Path>>(path: A) -> Option<String> { |
| 293 | + let path = path.as_ref(); |
| 294 | + |
| 295 | + let fragment = if path.is_absolute() { |
| 296 | + Cow::Borrowed(path) |
| 297 | + } else { |
| 298 | + match strict_canonicalize(path) { |
| 299 | + Ok(path) => Cow::Owned(path), |
| 300 | + Err(_) => return None, |
| 301 | + } |
| 302 | + }; |
| 303 | + |
| 304 | + if cfg!(windows) { |
| 305 | + // we want to parse a triple-slash path for Windows paths |
| 306 | + // it's a shorthand for `file://localhost/C:/Windows` with the `localhost` omitted. |
| 307 | + // We encode the driver Letter `C:` as well. LSP Specification allows it. |
| 308 | + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#uri |
| 309 | + Some(format!( |
| 310 | + "file:///{}", |
| 311 | + percent_encoding::utf8_percent_encode( |
| 312 | + &fragment.to_string_lossy().cow_replace('\\', "/"), |
| 313 | + &ASCII_SET |
| 314 | + ) |
| 315 | + )) |
| 316 | + } else { |
| 317 | + Some(format!( |
| 318 | + "file://{}", |
| 319 | + percent_encoding::utf8_percent_encode(&fragment.to_string_lossy(), &ASCII_SET) |
| 320 | + )) |
| 321 | + } |
| 322 | +} |
| 323 | + |
| 324 | +/// On Windows, rewrites the wide path prefix `\\?\C:` to `C:` |
| 325 | +/// Source: https://stackoverflow.com/a/70970317 |
| 326 | +#[inline] |
| 327 | +#[cfg(windows)] |
| 328 | +fn strict_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> { |
| 329 | + use std::io; |
| 330 | + |
| 331 | + fn impl_(path: PathBuf) -> std::io::Result<PathBuf> { |
| 332 | + let head = path.components().next().ok_or(io::Error::other("empty path"))?; |
| 333 | + let disk_; |
| 334 | + let head = if let std::path::Component::Prefix(prefix) = head { |
| 335 | + if let std::path::Prefix::VerbatimDisk(disk) = prefix.kind() { |
| 336 | + disk_ = format!("{}:", disk as char); |
| 337 | + Path::new(&disk_) |
| 338 | + .components() |
| 339 | + .next() |
| 340 | + .ok_or(io::Error::other("failed to parse disk component"))? |
| 341 | + } else { |
| 342 | + head |
| 343 | + } |
| 344 | + } else { |
| 345 | + head |
| 346 | + }; |
| 347 | + Ok(std::iter::once(head).chain(path.components().skip(1)).collect()) |
| 348 | + } |
| 349 | + |
| 350 | + let canon = std::fs::canonicalize(path)?; |
| 351 | + impl_(canon) |
| 352 | +} |
0 commit comments