Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/commands/lsp.rs')
-rw-r--r--helix-term/src/commands/lsp.rs525
1 files changed, 138 insertions, 387 deletions
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 0494db3e..773957c6 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -1,21 +1,14 @@
use futures_util::{stream::FuturesOrdered, FutureExt};
use helix_lsp::{
- block_on,
- lsp::{
- self, CodeAction, CodeActionOrCommand, CodeActionTriggerKind, DiagnosticSeverity,
- NumberOrString,
- },
- util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range},
- Client, LanguageServerId, OffsetEncoding,
+ block_on, lsp, util::lsp_range_to_range, Client, LanguageServerId, OffsetEncoding,
};
use tokio_stream::StreamExt;
-use tui::{text::Span, widgets::Row};
+use tui::text::Span;
use super::{align_view, push_jump, Align, Context, Editor};
use helix_core::{
- diagnostic::DiagnosticProvider, syntax::config::LanguageServerFeature,
- text_annotations::InlineAnnotation, Selection, Uri,
+ syntax::config::LanguageServerFeature, text_annotations::InlineAnnotation, Selection, Uri,
};
use helix_stdx::path;
use helix_view::{
@@ -23,7 +16,7 @@ use helix_view::{
editor::Action,
handlers::lsp::SignatureHelpInvoked,
theme::Style,
- Document, View,
+ Diagnostic, Document, DocumentId, View,
};
use crate::{
@@ -32,7 +25,7 @@ use crate::{
ui::{self, overlay::overlaid, FileLocation, Picker, Popup, PromptEvent},
};
-use std::{cmp::Ordering, collections::HashSet, fmt::Display, future::Future, path::Path};
+use std::{collections::HashSet, fmt::Display, future::Future};
/// Gets the first language server that is attached to a document which supports a specific feature.
/// If there is no configured language server that supports the feature, this displays a status message.
@@ -56,31 +49,48 @@ macro_rules! language_server_with_feature {
}};
}
-/// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri` and adds
-/// the server's offset encoding.
+/// A wrapper around `lsp::Location`.
#[derive(Debug, Clone, PartialEq, Eq)]
-struct Location {
+pub struct Location {
uri: Uri,
- range: lsp::Range,
- offset_encoding: OffsetEncoding,
+ range: helix_view::Range,
}
-fn lsp_location_to_location(
- location: lsp::Location,
- offset_encoding: OffsetEncoding,
-) -> Option<Location> {
- let uri = match location.uri.try_into() {
- Ok(uri) => uri,
- Err(err) => {
- log::warn!("discarding invalid or unsupported URI: {err}");
- return None;
- }
- };
- Some(Location {
- uri,
- range: location.range,
- offset_encoding,
- })
+impl Location {
+ fn lsp(location: lsp::Location, offset_encoding: OffsetEncoding) -> Option<Self> {
+ let uri = match location.uri.try_into() {
+ Ok(uri) => uri,
+ Err(err) => {
+ log::warn!("discarding invalid or unsupported URI: {err}");
+ return None;
+ }
+ };
+ Some(Self {
+ uri,
+ range: helix_view::Range::Lsp {
+ range: location.range,
+ offset_encoding,
+ },
+ })
+ }
+
+ fn file_location<'a>(&'a self, editor: &Editor) -> Option<FileLocation<'a>> {
+ let (path_or_id, doc) = match &self.uri {
+ Uri::File(path) => ((&**path).into(), None),
+ Uri::Scratch(doc_id) => ((*doc_id).into(), editor.documents.get(doc_id)),
+ _ => return None,
+ };
+ let lines = match self.range {
+ helix_view::Range::Lsp { range, .. } => {
+ Some((range.start.line as usize, range.end.line as usize))
+ }
+ helix_view::Range::Document(range) => doc.map(|doc| {
+ let text = doc.text().slice(..);
+ (text.char_to_line(range.start), text.char_to_line(range.end))
+ }),
+ };
+ Some((path_or_id, lines))
+ }
}
struct SymbolInformationItem {
@@ -97,63 +107,57 @@ struct DiagnosticStyles {
struct PickerDiagnostic {
location: Location,
- diag: lsp::Diagnostic,
-}
-
-fn location_to_file_location(location: &Location) -> Option<FileLocation<'_>> {
- let path = location.uri.as_path()?;
- let line = Some((
- location.range.start.line as usize,
- location.range.end.line as usize,
- ));
- Some((path.into(), line))
+ diag: Diagnostic,
}
fn jump_to_location(editor: &mut Editor, location: &Location, action: Action) {
let (view, doc) = current!(editor);
push_jump(view, doc);
- let Some(path) = location.uri.as_path() else {
- let err = format!("unable to convert URI to filepath: {:?}", location.uri);
- editor.set_error(err);
- return;
+ let doc_id = match &location.uri {
+ Uri::Scratch(doc_id) => {
+ editor.switch(*doc_id, action);
+ *doc_id
+ }
+ Uri::File(path) => match editor.open(path, action) {
+ Ok(doc_id) => doc_id,
+ Err(err) => {
+ editor.set_error(format!("failed to open path: {:?}: {:?}", path, err));
+ return;
+ }
+ },
+ _ => return,
};
- jump_to_position(
- editor,
- path,
- location.range,
- location.offset_encoding,
- action,
- );
+
+ jump_to_position(editor, doc_id, location.range, action);
}
fn jump_to_position(
editor: &mut Editor,
- path: &Path,
- range: lsp::Range,
- offset_encoding: OffsetEncoding,
+ doc_id: DocumentId,
+ range: helix_view::Range,
action: Action,
) {
- let doc = match editor.open(path, action) {
- Ok(id) => doc_mut!(editor, &id),
- Err(err) => {
- let err = format!("failed to open path: {:?}: {:?}", path, err);
- editor.set_error(err);
- return;
- }
+ let Some(doc) = editor.documents.get_mut(&doc_id) else {
+ return;
};
let view = view_mut!(editor);
- // TODO: convert inside server
- let new_range = if let Some(new_range) = lsp_range_to_range(doc.text(), range, offset_encoding)
- {
- new_range
- } else {
- log::warn!("lsp position out of bounds - {:?}", range);
- return;
+ let selection = match range {
+ helix_view::Range::Lsp {
+ range,
+ offset_encoding,
+ } => {
+ let Some(range) = lsp_range_to_range(doc.text(), range, offset_encoding) else {
+ log::warn!("lsp position out of bounds - {:?}", range);
+ return;
+ };
+ range.into()
+ }
+ helix_view::Range::Document(range) => Selection::single(range.start, range.end),
};
// we flip the range so that the cursor sits on the start of the symbol
// (for example start of the function).
- doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
+ doc.set_selection(view.id, selection);
if action.align_view(view, doc.id()) {
align_view(doc, view, Align::Center);
}
@@ -204,40 +208,25 @@ type DiagnosticsPicker = Picker<PickerDiagnostic, DiagnosticStyles>;
fn diag_picker(
cx: &Context,
- diagnostics: impl IntoIterator<Item = (Uri, Vec<(lsp::Diagnostic, DiagnosticProvider)>)>,
+ diagnostics: impl IntoIterator<Item = (Uri, Vec<Diagnostic>)>,
format: DiagnosticsFormat,
) -> DiagnosticsPicker {
- // TODO: drop current_path comparison and instead use workspace: bool flag?
-
// flatten the map to a vec of (url, diag) pairs
let mut flat_diag = Vec::new();
for (uri, diags) in diagnostics {
flat_diag.reserve(diags.len());
- for (diag, provider) in diags {
- if let Some(ls) = provider
- .language_server_id()
- .and_then(|id| cx.editor.language_server_by_id(id))
- {
- flat_diag.push(PickerDiagnostic {
- location: Location {
- uri: uri.clone(),
- range: diag.range,
- offset_encoding: ls.offset_encoding(),
- },
- diag,
- });
- }
+ for diag in diags {
+ flat_diag.push(PickerDiagnostic {
+ location: Location {
+ uri: uri.clone(),
+ range: diag.range,
+ },
+ diag,
+ });
}
}
- flat_diag.sort_by(|a, b| {
- a.diag
- .severity
- .unwrap_or(lsp::DiagnosticSeverity::HINT)
- .cmp(&b.diag.severity.unwrap_or(lsp::DiagnosticSeverity::HINT))
- });
-
let styles = DiagnosticStyles {
hint: cx.editor.theme.get("hint"),
info: cx.editor.theme.get("info"),
@@ -249,11 +238,12 @@ fn diag_picker(
ui::PickerColumn::new(
"severity",
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
+ use helix_core::diagnostic::Severity::*;
match item.diag.severity {
- Some(DiagnosticSeverity::HINT) => Span::styled("HINT", styles.hint),
- Some(DiagnosticSeverity::INFORMATION) => Span::styled("INFO", styles.info),
- Some(DiagnosticSeverity::WARNING) => Span::styled("WARN", styles.warning),
- Some(DiagnosticSeverity::ERROR) => Span::styled("ERROR", styles.error),
+ Some(Hint) => Span::styled("HINT", styles.hint),
+ Some(Info) => Span::styled("INFO", styles.info),
+ Some(Warning) => Span::styled("WARN", styles.warning),
+ Some(Error) => Span::styled("ERROR", styles.error),
_ => Span::raw(""),
}
.into()
@@ -263,11 +253,12 @@ fn diag_picker(
item.diag.source.as_deref().unwrap_or("").into()
}),
ui::PickerColumn::new("code", |item: &PickerDiagnostic, _| {
- match item.diag.code.as_ref() {
- Some(NumberOrString::Number(n)) => n.to_string().into(),
- Some(NumberOrString::String(s)) => s.as_str().into(),
- None => "".into(),
- }
+ item.diag
+ .code
+ .as_ref()
+ .map(|c| c.as_string())
+ .unwrap_or_default()
+ .into()
}),
ui::PickerColumn::new("message", |item: &PickerDiagnostic, _| {
item.diag.message.as_str().into()
@@ -305,7 +296,7 @@ fn diag_picker(
.immediately_show_diagnostic(doc, view.id);
},
)
- .with_preview(move |_editor, diag| location_to_file_location(&diag.location))
+ .with_preview(|editor, diag| diag.location.file_location(editor))
.truncate_start(false)
}
@@ -329,8 +320,10 @@ pub fn symbol_picker(cx: &mut Context) {
},
location: Location {
uri: uri.clone(),
- range: symbol.selection_range,
- offset_encoding,
+ range: helix_view::Range::Lsp {
+ range: symbol.selection_range,
+ offset_encoding,
+ },
},
});
for child in symbol.children.into_iter().flatten() {
@@ -348,9 +341,7 @@ pub fn symbol_picker(cx: &mut Context) {
let request = language_server.document_symbols(doc.identifier()).unwrap();
let offset_encoding = language_server.offset_encoding();
let doc_id = doc.identifier();
- let doc_uri = doc
- .uri()
- .expect("docs with active language servers must be backed by paths");
+ let doc_uri = doc.uri();
async move {
let symbols = match request.await? {
@@ -365,8 +356,10 @@ pub fn symbol_picker(cx: &mut Context) {
.map(|symbol| SymbolInformationItem {
location: Location {
uri: doc_uri.clone(),
- range: symbol.location.range,
- offset_encoding,
+ range: helix_view::Range::Lsp {
+ range: symbol.location.range,
+ offset_encoding,
+ },
},
symbol,
})
@@ -433,7 +426,7 @@ pub fn symbol_picker(cx: &mut Context) {
jump_to_location(cx.editor, &item.location, action);
},
)
- .with_preview(move |_editor, item| location_to_file_location(&item.location))
+ .with_preview(|editor, item| item.location.file_location(editor))
.truncate_start(false);
compositor.push(Box::new(overlaid(picker)))
@@ -490,8 +483,10 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
Some(SymbolInformationItem {
location: Location {
uri,
- range: symbol.location.range,
- offset_encoding,
+ range: helix_view::Range::Lsp {
+ range: symbol.location.range,
+ offset_encoding,
+ },
},
symbol,
})
@@ -559,7 +554,7 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
jump_to_location(cx.editor, &item.location, action);
},
)
- .with_preview(|_editor, item| location_to_file_location(&item.location))
+ .with_preview(|editor, item| item.location.file_location(editor))
.with_dynamic_query(get_symbols, None)
.truncate_start(false);
@@ -568,11 +563,10 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
pub fn diagnostics_picker(cx: &mut Context) {
let doc = doc!(cx.editor);
- if let Some(uri) = doc.uri() {
- let diagnostics = cx.editor.diagnostics.get(&uri).cloned().unwrap_or_default();
- let picker = diag_picker(cx, [(uri, diagnostics)], DiagnosticsFormat::HideSourcePath);
- cx.push_layer(Box::new(overlaid(picker)));
- }
+ let uri = doc.uri();
+ let diagnostics = cx.editor.diagnostics.get(&uri).cloned().unwrap_or_default();
+ let picker = diag_picker(cx, [(uri, diagnostics)], DiagnosticsFormat::HideSourcePath);
+ cx.push_layer(Box::new(overlaid(picker)));
}
pub fn workspace_diagnostics_picker(cx: &mut Context) {
@@ -582,249 +576,6 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
cx.push_layer(Box::new(overlaid(picker)));
}
-struct CodeActionOrCommandItem {
- lsp_item: lsp::CodeActionOrCommand,
- language_server_id: LanguageServerId,
-}
-
-impl ui::menu::Item for CodeActionOrCommandItem {
- type Data = ();
- fn format(&self, _data: &Self::Data) -> Row<'_> {
- match &self.lsp_item {
- lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
- lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
- }
- }
-}
-
-/// Determines the category of the `CodeAction` using the `CodeAction::kind` field.
-/// Returns a number that represent these categories.
-/// Categories with a lower number should be displayed first.
-///
-///
-/// While the `kind` field is defined as open ended in the LSP spec (any value may be used)
-/// in practice a closed set of common values (mostly suggested in the LSP spec) are used.
-/// VSCode displays each of these categories separately (separated by a heading in the codeactions picker)
-/// to make them easier to navigate. Helix does not display these headings to the user.
-/// However it does sort code actions by their categories to achieve the same order as the VScode picker,
-/// just without the headings.
-///
-/// The order used here is modeled after the [vscode sourcecode](https://github.com/microsoft/vscode/blob/eaec601dd69aeb4abb63b9601a6f44308c8d8c6e/src/vs/editor/contrib/codeAction/browser/codeActionWidget.ts>)
-fn action_category(action: &CodeActionOrCommand) -> u32 {
- if let CodeActionOrCommand::CodeAction(CodeAction {
- kind: Some(kind), ..
- }) = action
- {
- let mut components = kind.as_str().split('.');
- match components.next() {
- Some("quickfix") => 0,
- Some("refactor") => match components.next() {
- Some("extract") => 1,
- Some("inline") => 2,
- Some("rewrite") => 3,
- Some("move") => 4,
- Some("surround") => 5,
- _ => 7,
- },
- Some("source") => 6,
- _ => 7,
- }
- } else {
- 7
- }
-}
-
-fn action_preferred(action: &CodeActionOrCommand) -> bool {
- matches!(
- action,
- CodeActionOrCommand::CodeAction(CodeAction {
- is_preferred: Some(true),
- ..
- })
- )
-}
-
-fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool {
- matches!(
- action,
- CodeActionOrCommand::CodeAction(CodeAction {
- diagnostics: Some(diagnostics),
- ..
- }) if !diagnostics.is_empty()
- )
-}
-
-pub fn code_action(cx: &mut Context) {
- let (view, doc) = current!(cx.editor);
-
- let selection_range = doc.selection(view.id).primary();
-
- let mut seen_language_servers = HashSet::new();
-
- let mut futures: FuturesOrdered<_> = doc
- .language_servers_with_feature(LanguageServerFeature::CodeAction)
- .filter(|ls| seen_language_servers.insert(ls.id()))
- // TODO this should probably already been filtered in something like "language_servers_with_feature"
- .filter_map(|language_server| {
- let offset_encoding = language_server.offset_encoding();
- let language_server_id = language_server.id();
- let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
- // Filter and convert overlapping diagnostics
- let code_action_context = lsp::CodeActionContext {
- diagnostics: doc
- .diagnostics()
- .iter()
- .filter(|&diag| {
- selection_range
- .overlaps(&helix_core::Range::new(diag.range.start, diag.range.end))
- })
- .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
- .collect(),
- only: None,
- trigger_kind: Some(CodeActionTriggerKind::INVOKED),
- };
- let code_action_request =
- language_server.code_actions(doc.identifier(), range, code_action_context)?;
- Some((code_action_request, language_server_id))
- })
- .map(|(request, ls_id)| async move {
- let Some(mut actions) = request.await? else {
- return anyhow::Ok(Vec::new());
- };
-
- // remove disabled code actions
- actions.retain(|action| {
- matches!(
- action,
- CodeActionOrCommand::Command(_)
- | CodeActionOrCommand::CodeAction(CodeAction { disabled: None, .. })
- )
- });
-
- // Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
- // Many details are modeled after vscode because language servers are usually tested against it.
- // VScode sorts the codeaction two times:
- //
- // First the codeactions that fix some diagnostics are moved to the front.
- // If both codeactions fix some diagnostics (or both fix none) the codeaction
- // that is marked with `is_preferred` is shown first. The codeactions are then shown in separate
- // submenus that only contain a certain category (see `action_category`) of actions.
- //
- // Below this done in in a single sorting step
- actions.sort_by(|action1, action2| {
- // sort actions by category
- let order = action_category(action1).cmp(&action_category(action2));
- if order != Ordering::Equal {
- return order;
- }
- // within the categories sort by relevancy.
- // Modeled after the `codeActionsComparator` function in vscode:
- // https://github.com/microsoft/vscode/blob/eaec601dd69aeb4abb63b9601a6f44308c8d8c6e/src/vs/editor/contrib/codeAction/browser/codeAction.ts
-
- // if one code action fixes a diagnostic but the other one doesn't show it first
- let order = action_fixes_diagnostics(action1)
- .cmp(&action_fixes_diagnostics(action2))
- .reverse();
- if order != Ordering::Equal {
- return order;
- }
-
- // if one of the codeactions is marked as preferred show it first
- // otherwise keep the original LSP sorting
- action_preferred(action1)
- .cmp(&action_preferred(action2))
- .reverse()
- });
-
- Ok(actions
- .into_iter()
- .map(|lsp_item| CodeActionOrCommandItem {
- lsp_item,
- language_server_id: ls_id,
- })
- .collect())
- })
- .collect();
-
- if futures.is_empty() {
- cx.editor
- .set_error("No configured language server supports code actions");
- return;
- }
-
- cx.jobs.callback(async move {
- let mut actions = Vec::new();
-
- while let Some(output) = futures.next().await {
- match output {
- Ok(mut lsp_items) => actions.append(&mut lsp_items),
- Err(err) => log::error!("while gathering code actions: {err}"),
- }
- }
-
- let call = move |editor: &mut Editor, compositor: &mut Compositor| {
- if actions.is_empty() {
- editor.set_error("No code actions available");
- return;
- }
- let mut picker = ui::Menu::new(actions, (), move |editor, action, event| {
- if event != PromptEvent::Validate {
- return;
- }
-
- // always present here
- let action = action.unwrap();
- let Some(language_server) = editor.language_server_by_id(action.language_server_id)
- else {
- editor.set_error("Language Server disappeared");
- return;
- };
- let offset_encoding = language_server.offset_encoding();
-
- match &action.lsp_item {
- lsp::CodeActionOrCommand::Command(command) => {
- log::debug!("code action command: {:?}", command);
- editor.execute_lsp_command(command.clone(), action.language_server_id);
- }
- lsp::CodeActionOrCommand::CodeAction(code_action) => {
- log::debug!("code action: {:?}", code_action);
- // we support lsp "codeAction/resolve" for `edit` and `command` fields
- let mut resolved_code_action = None;
- if code_action.edit.is_none() || code_action.command.is_none() {
- if let Some(future) = language_server.resolve_code_action(code_action) {
- if let Ok(code_action) = helix_lsp::block_on(future) {
- resolved_code_action = Some(code_action);
- }
- }
- }
- let resolved_code_action =
- resolved_code_action.as_ref().unwrap_or(code_action);
-
- if let Some(ref workspace_edit) = resolved_code_action.edit {
- let _ = editor.apply_workspace_edit(offset_encoding, workspace_edit);
- }
-
- // if code action provides both edit and command first the edit
- // should be applied and then the command
- if let Some(command) = &code_action.command {
- editor.execute_lsp_command(command.clone(), action.language_server_id);
- }
- }
- }
- });
- picker.move_down(); // pre-select the first item
-
- let popup = Popup::new("code-action", picker)
- .with_scrollbar(false)
- .auto_close(true);
-
- compositor.replace_or_push("code-action", popup);
- };
-
- Ok(Callback::EditorCompositor(Box::new(call)))
- });
-}
-
#[derive(Debug)]
pub struct ApplyEditError {
pub kind: ApplyEditErrorKind,
@@ -865,20 +616,26 @@ fn goto_impl(editor: &mut Editor, compositor: &mut Compositor, locations: Vec<Lo
let columns = [ui::PickerColumn::new(
"location",
|item: &Location, cwdir: &std::path::PathBuf| {
- let path = if let Some(path) = item.uri.as_path() {
- path.strip_prefix(cwdir).unwrap_or(path).to_string_lossy()
+ use std::fmt::Write;
+ let mut path = if let Some(path) = item.uri.as_path() {
+ path.strip_prefix(cwdir)
+ .unwrap_or(path)
+ .to_string_lossy()
+ .to_string()
} else {
- item.uri.to_string().into()
+ item.uri.to_string()
};
-
- format!("{path}:{}", item.range.start.line + 1).into()
+ if let helix_view::Range::Lsp { range, .. } = item.range {
+ write!(path, ":{}", range.start.line + 1).unwrap();
+ }
+ path.into()
},
)];
let picker = Picker::new(columns, 0, locations, cwdir, |cx, location, action| {
jump_to_location(cx.editor, location, action)
})
- .with_preview(|_editor, location| location_to_file_location(location));
+ .with_preview(|editor, location| location.file_location(editor));
compositor.push(Box::new(overlaid(picker)));
}
}
@@ -906,12 +663,14 @@ where
match response {
Ok((response, offset_encoding)) => match response {
Some(lsp::GotoDefinitionResponse::Scalar(lsp_location)) => {
- locations.extend(lsp_location_to_location(lsp_location, offset_encoding));
+ locations.extend(Location::lsp(lsp_location, offset_encoding));
}
Some(lsp::GotoDefinitionResponse::Array(lsp_locations)) => {
- locations.extend(lsp_locations.into_iter().flat_map(|location| {
- lsp_location_to_location(location, offset_encoding)
- }));
+ locations.extend(
+ lsp_locations
+ .into_iter()
+ .flat_map(|location| Location::lsp(location, offset_encoding)),
+ );
}
Some(lsp::GotoDefinitionResponse::Link(lsp_locations)) => {
locations.extend(
@@ -923,9 +682,7 @@ where
location_link.target_range,
)
})
- .flat_map(|location| {
- lsp_location_to_location(location, offset_encoding)
- }),
+ .flat_map(|location| Location::lsp(location, offset_encoding)),
);
}
None => (),
@@ -935,13 +692,7 @@ where
}
let call = move |editor: &mut Editor, compositor: &mut Compositor| {
if locations.is_empty() {
- editor.set_error(match feature {
- LanguageServerFeature::GotoDeclaration => "No declaration found.",
- LanguageServerFeature::GotoDefinition => "No definition found.",
- LanguageServerFeature::GotoTypeDefinition => "No type definition found.",
- LanguageServerFeature::GotoImplementation => "No implementation found.",
- _ => "No location found.",
- });
+ editor.set_error("No definition found.");
} else {
goto_impl(editor, compositor, locations);
}
@@ -1011,7 +762,7 @@ pub fn goto_reference(cx: &mut Context) {
lsp_locations
.into_iter()
.flatten()
- .flat_map(|location| lsp_location_to_location(location, offset_encoding)),
+ .flat_map(|location| Location::lsp(location, offset_encoding)),
),
Err(err) => log::error!("Error requesting references: {err}"),
}
@@ -1146,7 +897,7 @@ pub fn rename_symbol(cx: &mut Context) {
let Some(language_server) = doc
.language_servers_with_feature(LanguageServerFeature::RenameSymbol)
- .find(|ls| language_server_id.is_none_or(|id| id == ls.id()))
+ .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
else {
cx.editor
.set_error("No configured language server supports symbol renaming");