LSP commands: Move offset encoding onto the Location type

<https://github.com/helix-editor/helix/pull/11486> introduced a Location
type in the LSP commands module which unified helpers like
`jump_to_location`. This change moves `OffsetEncoding` onto that type.

`SymbolInformationItem` and `PickerDiagnostic` already had fields for
carrying the offset encoding. We would want a similar setup for goto
definition/references as well (for supporting multiple language servers
with that feature) but those use the `Location` type. By moving
`OffsetEncoding` onto `Location` we make future changes to allow
mulitple language servers possible for goto definition/references
features and also simplify some calls for symbols and diagnostics.
pull/12731/head
Michael Davis 2025-01-29 17:25:12 -05:00
parent c9dc940428
commit ba116b47a0
No known key found for this signature in database
1 changed files with 41 additions and 36 deletions

View File

@ -61,14 +61,19 @@ macro_rules! language_server_with_feature {
}}; }};
} }
/// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri`. /// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri` and adds
/// the server's offset encoding.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
struct Location { struct Location {
uri: Uri, uri: Uri,
range: lsp::Range, range: lsp::Range,
offset_encoding: OffsetEncoding,
} }
fn lsp_location_to_location(location: lsp::Location) -> Option<Location> { fn lsp_location_to_location(
location: lsp::Location,
offset_encoding: OffsetEncoding,
) -> Option<Location> {
let uri = match location.uri.try_into() { let uri = match location.uri.try_into() {
Ok(uri) => uri, Ok(uri) => uri,
Err(err) => { Err(err) => {
@ -79,13 +84,13 @@ fn lsp_location_to_location(location: lsp::Location) -> Option<Location> {
Some(Location { Some(Location {
uri, uri,
range: location.range, range: location.range,
offset_encoding,
}) })
} }
struct SymbolInformationItem { struct SymbolInformationItem {
location: Location, location: Location,
symbol: lsp::SymbolInformation, symbol: lsp::SymbolInformation,
offset_encoding: OffsetEncoding,
} }
struct DiagnosticStyles { struct DiagnosticStyles {
@ -98,7 +103,6 @@ struct DiagnosticStyles {
struct PickerDiagnostic { struct PickerDiagnostic {
location: Location, location: Location,
diag: lsp::Diagnostic, diag: lsp::Diagnostic,
offset_encoding: OffsetEncoding,
} }
fn location_to_file_location(location: &Location) -> Option<FileLocation> { fn location_to_file_location(location: &Location) -> Option<FileLocation> {
@ -110,12 +114,7 @@ fn location_to_file_location(location: &Location) -> Option<FileLocation> {
Some((path.into(), line)) Some((path.into(), line))
} }
fn jump_to_location( fn jump_to_location(editor: &mut Editor, location: &Location, action: Action) {
editor: &mut Editor,
location: &Location,
offset_encoding: OffsetEncoding,
action: Action,
) {
let (view, doc) = current!(editor); let (view, doc) = current!(editor);
push_jump(view, doc); push_jump(view, doc);
@ -124,7 +123,13 @@ fn jump_to_location(
editor.set_error(err); editor.set_error(err);
return; return;
}; };
jump_to_position(editor, path, location.range, offset_encoding, action); jump_to_position(
editor,
path,
location.range,
location.offset_encoding,
action,
);
} }
fn jump_to_position( fn jump_to_position(
@ -220,9 +225,9 @@ fn diag_picker(
location: Location { location: Location {
uri: uri.clone(), uri: uri.clone(),
range: diag.range, range: diag.range,
offset_encoding: ls.offset_encoding(),
}, },
diag, diag,
offset_encoding: ls.offset_encoding(),
}); });
} }
} }
@ -286,7 +291,7 @@ fn diag_picker(
flat_diag, flat_diag,
styles, styles,
move |cx, diag, action| { move |cx, diag, action| {
jump_to_location(cx.editor, &diag.location, diag.offset_encoding, action); jump_to_location(cx.editor, &diag.location, action);
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
view.diagnostics_handler view.diagnostics_handler
.immediately_show_diagnostic(doc, view.id); .immediately_show_diagnostic(doc, view.id);
@ -314,10 +319,10 @@ pub fn symbol_picker(cx: &mut Context) {
location: lsp::Location::new(file.uri.clone(), symbol.selection_range), location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
container_name: None, container_name: None,
}, },
offset_encoding,
location: Location { location: Location {
uri: uri.clone(), uri: uri.clone(),
range: symbol.selection_range, range: symbol.selection_range,
offset_encoding,
}, },
}); });
for child in symbol.children.into_iter().flatten() { for child in symbol.children.into_iter().flatten() {
@ -355,9 +360,9 @@ pub fn symbol_picker(cx: &mut Context) {
location: Location { location: Location {
uri: doc_uri.clone(), uri: doc_uri.clone(),
range: symbol.location.range, range: symbol.location.range,
offset_encoding,
}, },
symbol, symbol,
offset_encoding,
}) })
.collect(), .collect(),
lsp::DocumentSymbolResponse::Nested(symbols) => { lsp::DocumentSymbolResponse::Nested(symbols) => {
@ -410,7 +415,7 @@ pub fn symbol_picker(cx: &mut Context) {
symbols, symbols,
(), (),
move |cx, item, action| { move |cx, item, action| {
jump_to_location(cx.editor, &item.location, item.offset_encoding, action); jump_to_location(cx.editor, &item.location, action);
}, },
) )
.with_preview(move |_editor, item| location_to_file_location(&item.location)) .with_preview(move |_editor, item| location_to_file_location(&item.location))
@ -467,9 +472,9 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
location: Location { location: Location {
uri, uri,
range: symbol.location.range, range: symbol.location.range,
offset_encoding,
}, },
symbol, symbol,
offset_encoding,
}) })
}) })
.collect(); .collect();
@ -521,7 +526,7 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
[], [],
(), (),
move |cx, item, action| { move |cx, item, action| {
jump_to_location(cx.editor, &item.location, item.offset_encoding, action); jump_to_location(cx.editor, &item.location, action);
}, },
) )
.with_preview(|_editor, item| location_to_file_location(&item.location)) .with_preview(|_editor, item| location_to_file_location(&item.location))
@ -853,17 +858,12 @@ impl Display for ApplyEditErrorKind {
} }
/// Precondition: `locations` should be non-empty. /// Precondition: `locations` should be non-empty.
fn goto_impl( fn goto_impl(editor: &mut Editor, compositor: &mut Compositor, locations: Vec<Location>) {
editor: &mut Editor,
compositor: &mut Compositor,
locations: Vec<Location>,
offset_encoding: OffsetEncoding,
) {
let cwdir = helix_stdx::env::current_working_dir(); let cwdir = helix_stdx::env::current_working_dir();
match locations.as_slice() { match locations.as_slice() {
[location] => { [location] => {
jump_to_location(editor, location, offset_encoding, Action::Replace); jump_to_location(editor, location, Action::Replace);
} }
[] => unreachable!("`locations` should be non-empty for `goto_impl`"), [] => unreachable!("`locations` should be non-empty for `goto_impl`"),
_locations => { _locations => {
@ -880,30 +880,35 @@ fn goto_impl(
}, },
)]; )];
let picker = Picker::new(columns, 0, locations, cwdir, move |cx, location, action| { let picker = Picker::new(columns, 0, locations, cwdir, |cx, location, action| {
jump_to_location(cx.editor, location, offset_encoding, action) jump_to_location(cx.editor, location, action)
}) })
.with_preview(move |_editor, location| location_to_file_location(location)); .with_preview(|_editor, location| location_to_file_location(location));
compositor.push(Box::new(overlaid(picker))); compositor.push(Box::new(overlaid(picker)));
} }
} }
} }
fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<Location> { fn to_locations(
definitions: Option<lsp::GotoDefinitionResponse>,
offset_encoding: OffsetEncoding,
) -> Vec<Location> {
match definitions { match definitions {
Some(lsp::GotoDefinitionResponse::Scalar(location)) => { Some(lsp::GotoDefinitionResponse::Scalar(location)) => {
lsp_location_to_location(location).into_iter().collect() lsp_location_to_location(location, offset_encoding)
.into_iter()
.collect()
} }
Some(lsp::GotoDefinitionResponse::Array(locations)) => locations Some(lsp::GotoDefinitionResponse::Array(locations)) => locations
.into_iter() .into_iter()
.flat_map(lsp_location_to_location) .flat_map(|location| lsp_location_to_location(location, offset_encoding))
.collect(), .collect(),
Some(lsp::GotoDefinitionResponse::Link(locations)) => locations Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
.into_iter() .into_iter()
.map(|location_link| { .map(|location_link| {
lsp::Location::new(location_link.target_uri, location_link.target_range) lsp::Location::new(location_link.target_uri, location_link.target_range)
}) })
.flat_map(lsp_location_to_location) .flat_map(|location| lsp_location_to_location(location, offset_encoding))
.collect(), .collect(),
None => Vec::new(), None => Vec::new(),
} }
@ -924,11 +929,11 @@ where
cx.callback( cx.callback(
future, future,
move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| { move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
let items = to_locations(response); let items = to_locations(response, offset_encoding);
if items.is_empty() { if items.is_empty() {
editor.set_error("No definition found."); editor.set_error("No definition found.");
} else { } else {
goto_impl(editor, compositor, items, offset_encoding); goto_impl(editor, compositor, items);
} }
}, },
); );
@ -991,12 +996,12 @@ pub fn goto_reference(cx: &mut Context) {
let items: Vec<Location> = response let items: Vec<Location> = response
.into_iter() .into_iter()
.flatten() .flatten()
.flat_map(lsp_location_to_location) .flat_map(|location| lsp_location_to_location(location, offset_encoding))
.collect(); .collect();
if items.is_empty() { if items.is_empty() {
editor.set_error("No references found."); editor.set_error("No references found.");
} else { } else {
goto_impl(editor, compositor, items, offset_encoding); goto_impl(editor, compositor, items);
} }
}, },
); );