Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve handling of different URI schemes over LSP #9633

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions helix-stdx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ homepage.workspace = true
dunce = "1.0"
etcetera = "0.8"
ropey = { version = "1.6.1", default-features = false }
thiserror = "1.0.57"
url = "2.5.0"
which = "6.0"

[dev-dependencies]
Expand Down
1 change: 1 addition & 0 deletions helix-stdx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod env;
pub mod path;
pub mod rope;
pub mod uri;
25 changes: 25 additions & 0 deletions helix-stdx/src/uri.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::path::PathBuf;

use thiserror::Error;
use url::Url;

#[derive(Debug, Error)]
pub enum FilePathError<'a> {
#[error("unsupported scheme in URI: {0}")]
UnsupportedScheme(&'a Url),
#[error("unable to convert URI to file path: {0}")]
UnableToConvert(&'a Url),
}

/// Converts a [`Url`] into a [`PathBuf`].
///
/// Unlike [`Url::to_file_path`], this method respects the uri's scheme
/// and returns `Ok(None)` if the scheme was not "file".
pub fn uri_to_file_path(uri: &Url) -> Result<PathBuf, FilePathError> {
if uri.scheme() == "file" {
uri.to_file_path()
.map_err(|_| FilePathError::UnableToConvert(uri))
} else {
Err(FilePathError::UnsupportedScheme(uri))
}
}
12 changes: 6 additions & 6 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use helix_lsp::{
util::lsp_range_to_range,
LspProgressMap,
};
use helix_stdx::path::get_relative_path;
use helix_stdx::{path::get_relative_path, uri::uri_to_file_path};
use helix_view::{
align_view,
document::DocumentSavedEventResult,
Expand Down Expand Up @@ -723,10 +723,10 @@ impl Application {
}
}
Notification::PublishDiagnostics(mut params) => {
let path = match params.uri.to_file_path() {
let path = match uri_to_file_path(&params.uri) {
Ok(path) => path,
Err(_) => {
log::error!("Unsupported file URI: {}", params.uri);
Err(err) => {
log::error!("{err}");
return;
}
};
Expand Down Expand Up @@ -1127,10 +1127,10 @@ impl Application {
..
} = params;

let path = match uri.to_file_path() {
let path = match uri_to_file_path(&uri) {
Ok(path) => path,
Err(err) => {
log::error!("unsupported file URI: {}: {:?}", uri, err);
log::error!("{err}");
return lsp::ShowDocumentResult { success: false };
}
};
Expand Down
41 changes: 24 additions & 17 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tui::{
use super::{align_view, push_jump, Align, Context, Editor};

use helix_core::{syntax::LanguageServerFeature, text_annotations::InlineAnnotation, Selection};
use helix_stdx::path;
use helix_stdx::{path, uri::uri_to_file_path};
use helix_view::{
document::{DocumentInlayHints, DocumentInlayHintsId},
editor::Action,
Expand Down Expand Up @@ -79,6 +79,7 @@ impl ui::menu::Item for lsp::Location {
// With the preallocation above and UTF-8 paths already, this closure will do one (1)
// allocation, for `to_file_path`, else there will be two (2), with `to_string_lossy`.
let mut write_path_to_res = || -> Option<()> {
// We don't use `uri_to_file_path` here, since we've already checked the scheme.
let path = self.uri.to_file_path().ok()?;
res.push_str(&path.strip_prefix(cwdir).unwrap_or(&path).to_string_lossy());
Some(())
Expand Down Expand Up @@ -110,7 +111,7 @@ impl ui::menu::Item for SymbolInformationItem {
if current_doc_path.as_ref() == Some(&self.symbol.location.uri) {
self.symbol.name.as_str().into()
} else {
match self.symbol.location.uri.to_file_path() {
match uri_to_file_path(&self.symbol.location.uri) {
Ok(path) => {
let get_relative_path = path::get_relative_path(path.as_path());
format!(
Expand All @@ -120,7 +121,7 @@ impl ui::menu::Item for SymbolInformationItem {
)
.into()
}
Err(_) => format!("{} ({})", &self.symbol.name, &self.symbol.location.uri).into(),
_ => format!("{} ({})", &self.symbol.name, &self.symbol.location.uri).into(),
}
}
}
Expand Down Expand Up @@ -182,13 +183,20 @@ impl ui::menu::Item for PickerDiagnostic {
}
}

fn location_to_file_location(location: &lsp::Location) -> FileLocation {
let path = location.uri.to_file_path().unwrap();
let line = Some((
location.range.start.line as usize,
location.range.end.line as usize,
));
(path.into(), line)
/// Creates a [`FileLocation`] from an [`lsp::Location`], for use with picker previews.
///
/// This will return [`None`] if the location uri's scheme is not "file".
fn location_to_file_location(location: &lsp::Location) -> Option<FileLocation> {
match uri_to_file_path(&location.uri) {
Ok(path) => {
let line = Some((
location.range.start.line as usize,
location.range.end.line as usize,
));
Some((path.into(), line))
}
Err(_) => None,
}
}

fn jump_to_location(
Expand All @@ -200,11 +208,10 @@ fn jump_to_location(
let (view, doc) = current!(editor);
push_jump(view, doc);

let path = match location.uri.to_file_path() {
let path = match uri_to_file_path(&location.uri) {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", location.uri);
editor.set_error(err);
Err(conversion_err) => {
editor.set_error(conversion_err.to_string());
return;
}
};
Expand Down Expand Up @@ -246,7 +253,7 @@ fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url
action,
);
})
.with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
.with_preview(move |_editor, item| location_to_file_location(&item.symbol.location))
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved
.truncate_start(false)
}

Expand Down Expand Up @@ -307,7 +314,7 @@ fn diag_picker(
)
.with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
let location = lsp::Location::new(url.clone(), diag.range);
Some(location_to_file_location(&location))
location_to_file_location(&location)
})
.truncate_start(false)
}
Expand Down Expand Up @@ -831,7 +838,7 @@ fn goto_impl(
let picker = Picker::new(locations, cwdir, move |cx, location, action| {
jump_to_location(cx.editor, location, offset_encoding, action)
})
.with_preview(move |_editor, location| Some(location_to_file_location(location)));
.with_preview(move |_editor, location| location_to_file_location(location));
compositor.push(Box::new(overlaid(picker)));
}
}
Expand Down
47 changes: 31 additions & 16 deletions helix-view/src/handlers/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::path::PathBuf;

use crate::editor::Action;
use crate::Editor;
use crate::{DocumentId, ViewId};
use helix_lsp::util::generate_transaction_from_edits;
use helix_lsp::{lsp, OffsetEncoding};
use helix_stdx::uri::uri_to_file_path;

pub enum CompletionEvent {
/// Auto completion was triggered by typing a word char
Expand Down Expand Up @@ -60,6 +63,12 @@ pub enum ApplyEditErrorKind {
// InvalidEdit,
}

impl From<std::io::Error> for ApplyEditErrorKind {
fn from(err: std::io::Error) -> Self {
ApplyEditErrorKind::IoError(err)
}
}

impl ToString for ApplyEditErrorKind {
fn to_string(&self) -> String {
match self {
Expand All @@ -72,22 +81,25 @@ impl ToString for ApplyEditErrorKind {
}

impl Editor {
fn uri_to_file_path(&mut self, uri: &helix_lsp::Url) -> Result<PathBuf, ApplyEditErrorKind> {
match uri_to_file_path(uri) {
Ok(path) => Ok(path),
Err(err) => {
log::error!("{err}");
self.set_error(err.to_string());
Err(ApplyEditErrorKind::UnknownURISchema)
}
}
}

fn apply_text_edits(
&mut self,
uri: &helix_lsp::Url,
version: Option<i32>,
text_edits: Vec<lsp::TextEdit>,
offset_encoding: OffsetEncoding,
) -> Result<(), ApplyEditErrorKind> {
let path = match uri.to_file_path() {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", uri);
log::error!("{}", err);
self.set_error(err);
return Err(ApplyEditErrorKind::UnknownURISchema);
}
};
let path = self.uri_to_file_path(uri)?;

let doc_id = match self.open(&path, Action::Load) {
Ok(doc_id) => doc_id,
Expand Down Expand Up @@ -158,9 +170,9 @@ impl Editor {
for (i, operation) in operations.iter().enumerate() {
match operation {
lsp::DocumentChangeOperation::Op(op) => {
self.apply_document_resource_op(op).map_err(|io| {
self.apply_document_resource_op(op).map_err(|err| {
ApplyEditError {
kind: ApplyEditErrorKind::IoError(io),
kind: err,
failed_change_idx: i,
}
})?;
Expand Down Expand Up @@ -214,12 +226,15 @@ impl Editor {
Ok(())
}

fn apply_document_resource_op(&mut self, op: &lsp::ResourceOp) -> std::io::Result<()> {
fn apply_document_resource_op(
&mut self,
op: &lsp::ResourceOp,
) -> Result<(), ApplyEditErrorKind> {
use lsp::ResourceOp;
use std::fs;
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
let path = self.uri_to_file_path(&op.uri)?;
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
Expand All @@ -236,7 +251,7 @@ impl Editor {
}
}
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
let path = self.uri_to_file_path(&op.uri)?;
if path.is_dir() {
let recursive = op
.options
Expand All @@ -255,8 +270,8 @@ impl Editor {
}
}
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
let from = self.uri_to_file_path(&op.old_uri)?;
let to = self.uri_to_file_path(&op.new_uri)?;
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
Expand Down
Loading