Add validation in LspCommand::to_lsp + check for inverted ranges (#22731)

#22690 logged errors and flipped the range in this case. Instead it
brings more visibility to the issue to return errors.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-01-06 15:00:36 -07:00 committed by GitHub
parent 1c223d8940
commit 141393232e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 134 additions and 162 deletions

View file

@ -1134,7 +1134,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let end = PointUtf16::new(end_row, end_column); let end = PointUtf16::new(end_row, end_column);
let range = if start > end { end..start } else { start..end }; let range = if start > end { end..start } else { start..end };
highlights.push(lsp::DocumentHighlight { highlights.push(lsp::DocumentHighlight {
range: range_to_lsp(range.clone()), range: range_to_lsp(range.clone()).unwrap(),
kind: Some(lsp::DocumentHighlightKind::READ), kind: Some(lsp::DocumentHighlightKind::READ),
}); });
} }

View file

@ -1,4 +1,5 @@
use crate::{range_to_lsp, Diagnostic}; use crate::{range_to_lsp, Diagnostic};
use anyhow::Result;
use collections::HashMap; use collections::HashMap;
use lsp::LanguageServerId; use lsp::LanguageServerId;
use std::{ use std::{
@ -54,16 +55,16 @@ pub struct Summary {
impl DiagnosticEntry<PointUtf16> { impl DiagnosticEntry<PointUtf16> {
/// Returns a raw LSP diagnostic used to provide diagnostic context to LSP /// Returns a raw LSP diagnostic used to provide diagnostic context to LSP
/// codeAction request /// codeAction request
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic { pub fn to_lsp_diagnostic_stub(&self) -> Result<lsp::Diagnostic> {
let code = self let code = self
.diagnostic .diagnostic
.code .code
.clone() .clone()
.map(lsp::NumberOrString::String); .map(lsp::NumberOrString::String);
let range = range_to_lsp(self.range.clone()); let range = range_to_lsp(self.range.clone())?;
lsp::Diagnostic { Ok(lsp::Diagnostic {
code, code,
range, range,
severity: Some(self.diagnostic.severity), severity: Some(self.diagnostic.severity),
@ -71,7 +72,7 @@ impl DiagnosticEntry<PointUtf16> {
message: self.diagnostic.message.clone(), message: self.diagnostic.message.clone(),
data: self.diagnostic.data.clone(), data: self.diagnostic.data.clone(),
..Default::default() ..Default::default()
} })
} }
} }

View file

@ -1849,14 +1849,19 @@ pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
Unclipped(PointUtf16::new(point.line, point.character)) Unclipped(PointUtf16::new(point.line, point.character))
} }
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range { pub fn range_to_lsp(range: Range<PointUtf16>) -> Result<lsp::Range> {
let mut start = point_to_lsp(range.start); if range.start > range.end {
let mut end = point_to_lsp(range.end); Err(anyhow!(
if start > end { "Inverted range provided to an LSP request: {:?}-{:?}",
log::error!("range_to_lsp called with inverted range {start:?}-{end:?}"); range.start,
mem::swap(&mut start, &mut end); range.end
))
} else {
Ok(lsp::Range {
start: point_to_lsp(range.start),
end: point_to_lsp(range.end),
})
} }
lsp::Range { start, end }
} }
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> { pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {

View file

@ -45,6 +45,31 @@ pub fn lsp_formatting_options(settings: &LanguageSettings) -> lsp::FormattingOpt
} }
} }
pub(crate) fn file_path_to_lsp_url(path: &Path) -> Result<lsp::Url> {
match lsp::Url::from_file_path(path) {
Ok(url) => Ok(url),
Err(()) => Err(anyhow!(
"Invalid file path provided to LSP request: {path:?}"
)),
}
}
pub(crate) fn make_text_document_identifier(path: &Path) -> Result<lsp::TextDocumentIdentifier> {
Ok(lsp::TextDocumentIdentifier {
uri: file_path_to_lsp_url(path)?,
})
}
pub(crate) fn make_lsp_text_document_position(
path: &Path,
position: PointUtf16,
) -> Result<lsp::TextDocumentPositionParams> {
Ok(lsp::TextDocumentPositionParams {
text_document: make_text_document_identifier(path)?,
position: point_to_lsp(position),
})
}
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug { pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
type Response: 'static + Default + Send + std::fmt::Debug; type Response: 'static + Default + Send + std::fmt::Debug;
@ -65,7 +90,7 @@ pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
buffer: &Buffer, buffer: &Buffer,
language_server: &Arc<LanguageServer>, language_server: &Arc<LanguageServer>,
cx: &AppContext, cx: &AppContext,
) -> <Self::LspRequest as lsp::request::Request>::Params; ) -> Result<<Self::LspRequest as lsp::request::Request>::Params>;
async fn response_from_lsp( async fn response_from_lsp(
self, self,
@ -202,13 +227,8 @@ impl LspCommand for PrepareRename {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::TextDocumentPositionParams { ) -> Result<lsp::TextDocumentPositionParams> {
lsp::TextDocumentPositionParams { make_lsp_text_document_position(path, self.position)
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
}
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -325,17 +345,12 @@ impl LspCommand for PerformRename {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::RenameParams { ) -> Result<lsp::RenameParams> {
lsp::RenameParams { Ok(lsp::RenameParams {
text_document_position: lsp::TextDocumentPositionParams { text_document_position: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
new_name: self.new_name.clone(), new_name: self.new_name.clone(),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -455,17 +470,12 @@ impl LspCommand for GetDefinition {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::GotoDefinitionParams { ) -> Result<lsp::GotoDefinitionParams> {
lsp::GotoDefinitionParams { Ok(lsp::GotoDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -555,17 +565,12 @@ impl LspCommand for GetDeclaration {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::GotoDeclarationParams { ) -> Result<lsp::GotoDeclarationParams> {
lsp::GotoDeclarationParams { Ok(lsp::GotoDeclarationParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -648,17 +653,12 @@ impl LspCommand for GetImplementation {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::GotoImplementationParams { ) -> Result<lsp::GotoImplementationParams> {
lsp::GotoImplementationParams { Ok(lsp::GotoImplementationParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -748,17 +748,12 @@ impl LspCommand for GetTypeDefinition {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::GotoTypeDefinitionParams { ) -> Result<lsp::GotoTypeDefinitionParams> {
lsp::GotoTypeDefinitionParams { Ok(lsp::GotoTypeDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -1061,20 +1056,15 @@ impl LspCommand for GetReferences {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::ReferenceParams { ) -> Result<lsp::ReferenceParams> {
lsp::ReferenceParams { Ok(lsp::ReferenceParams {
text_document_position: lsp::TextDocumentPositionParams { text_document_position: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
context: lsp::ReferenceContext { context: lsp::ReferenceContext {
include_declaration: true, include_declaration: true,
}, },
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -1237,17 +1227,12 @@ impl LspCommand for GetDocumentHighlights {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::DocumentHighlightParams { ) -> Result<lsp::DocumentHighlightParams> {
lsp::DocumentHighlightParams { Ok(lsp::DocumentHighlightParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -1391,22 +1376,12 @@ impl LspCommand for GetSignatureHelp {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_cx: &AppContext, _cx: &AppContext,
) -> lsp::SignatureHelpParams { ) -> Result<lsp::SignatureHelpParams> {
let url_result = lsp::Url::from_file_path(path); Ok(lsp::SignatureHelpParams {
if url_result.is_err() { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
log::error!("an invalid file path has been specified");
}
lsp::SignatureHelpParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_result.expect("invalid file path"),
},
position: point_to_lsp(self.position),
},
context: None, context: None,
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -1505,16 +1480,11 @@ impl LspCommand for GetHover {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::HoverParams { ) -> Result<lsp::HoverParams> {
lsp::HoverParams { Ok(lsp::HoverParams {
text_document_position_params: lsp::TextDocumentPositionParams { text_document_position_params: make_lsp_text_document_position(path, self.position)?,
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -1728,16 +1698,13 @@ impl LspCommand for GetCompletions {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::CompletionParams { ) -> Result<lsp::CompletionParams> {
lsp::CompletionParams { Ok(lsp::CompletionParams {
text_document_position: lsp::TextDocumentPositionParams::new( text_document_position: make_lsp_text_document_position(path, self.position)?,
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
point_to_lsp(self.position),
),
context: Some(self.context.clone()), context: Some(self.context.clone()),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -2075,12 +2042,14 @@ impl LspCommand for GetCodeActions {
buffer: &Buffer, buffer: &Buffer,
language_server: &Arc<LanguageServer>, language_server: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::CodeActionParams { ) -> Result<lsp::CodeActionParams> {
let relevant_diagnostics = buffer let mut relevant_diagnostics = Vec::new();
for entry in buffer
.snapshot() .snapshot()
.diagnostics_in_range::<_, language::PointUtf16>(self.range.clone(), false) .diagnostics_in_range::<_, language::PointUtf16>(self.range.clone(), false)
.map(|entry| entry.to_lsp_diagnostic_stub()) {
.collect::<Vec<_>>(); relevant_diagnostics.push(entry.to_lsp_diagnostic_stub()?);
}
let supported = let supported =
Self::supported_code_action_kinds(language_server.adapter_server_capabilities()); Self::supported_code_action_kinds(language_server.adapter_server_capabilities());
@ -2102,11 +2071,9 @@ impl LspCommand for GetCodeActions {
supported supported
}; };
lsp::CodeActionParams { Ok(lsp::CodeActionParams {
text_document: lsp::TextDocumentIdentifier::new( text_document: make_text_document_identifier(path)?,
lsp::Url::from_file_path(path).unwrap(), range: range_to_lsp(self.range.to_point_utf16(buffer))?,
),
range: range_to_lsp(self.range.to_point_utf16(buffer)),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
context: lsp::CodeActionContext { context: lsp::CodeActionContext {
@ -2114,7 +2081,7 @@ impl LspCommand for GetCodeActions {
only, only,
..lsp::CodeActionContext::default() ..lsp::CodeActionContext::default()
}, },
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -2286,15 +2253,12 @@ impl LspCommand for OnTypeFormatting {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::DocumentOnTypeFormattingParams { ) -> Result<lsp::DocumentOnTypeFormattingParams> {
lsp::DocumentOnTypeFormattingParams { Ok(lsp::DocumentOnTypeFormattingParams {
text_document_position: lsp::TextDocumentPositionParams::new( text_document_position: make_lsp_text_document_position(path, self.position)?,
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
point_to_lsp(self.position),
),
ch: self.trigger.clone(), ch: self.trigger.clone(),
options: self.options.clone(), options: self.options.clone(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -2792,14 +2756,14 @@ impl LspCommand for InlayHints {
buffer: &Buffer, buffer: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::InlayHintParams { ) -> Result<lsp::InlayHintParams> {
lsp::InlayHintParams { Ok(lsp::InlayHintParams {
text_document: lsp::TextDocumentIdentifier { text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(), uri: file_path_to_lsp_url(path)?,
}, },
range: range_to_lsp(self.range.to_point_utf16(buffer)), range: range_to_lsp(self.range.to_point_utf16(buffer))?,
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -2949,15 +2913,12 @@ impl LspCommand for LinkedEditingRange {
buffer: &Buffer, buffer: &Buffer,
_server: &Arc<LanguageServer>, _server: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> lsp::LinkedEditingRangeParams { ) -> Result<lsp::LinkedEditingRangeParams> {
let position = self.position.to_point_utf16(&buffer.snapshot()); let position = self.position.to_point_utf16(&buffer.snapshot());
lsp::LinkedEditingRangeParams { Ok(lsp::LinkedEditingRangeParams {
text_document_position_params: lsp::TextDocumentPositionParams::new( text_document_position_params: make_lsp_text_document_position(path, position)?,
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
point_to_lsp(position),
),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(

View file

@ -1,4 +1,4 @@
use crate::{lsp_command::LspCommand, lsp_store::LspStore}; use crate::{lsp_command::LspCommand, lsp_store::LspStore, make_text_document_identifier};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use gpui::{AppContext, AsyncAppContext, Model}; use gpui::{AppContext, AsyncAppContext, Model};
@ -53,13 +53,11 @@ impl LspCommand for ExpandMacro {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> ExpandMacroParams { ) -> Result<ExpandMacroParams> {
ExpandMacroParams { Ok(ExpandMacroParams {
text_document: lsp::TextDocumentIdentifier { text_document: make_text_document_identifier(path)?,
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position), position: point_to_lsp(self.position),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -179,13 +177,13 @@ impl LspCommand for OpenDocs {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> OpenDocsParams { ) -> Result<OpenDocsParams> {
OpenDocsParams { Ok(OpenDocsParams {
text_document: lsp::TextDocumentIdentifier { text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(), uri: lsp::Url::from_file_path(path).unwrap(),
}, },
position: point_to_lsp(self.position), position: point_to_lsp(self.position),
} })
} }
async fn response_from_lsp( async fn response_from_lsp(
@ -292,10 +290,10 @@ impl LspCommand for SwitchSourceHeader {
_: &Buffer, _: &Buffer,
_: &Arc<LanguageServer>, _: &Arc<LanguageServer>,
_: &AppContext, _: &AppContext,
) -> SwitchSourceHeaderParams { ) -> Result<SwitchSourceHeaderParams> {
SwitchSourceHeaderParams(lsp::TextDocumentIdentifier { Ok(SwitchSourceHeaderParams(make_text_document_identifier(
uri: lsp::Url::from_file_path(path).unwrap(), path,
}) )?))
} }
async fn response_from_lsp( async fn response_from_lsp(

View file

@ -3487,7 +3487,18 @@ impl LspStore {
}; };
let file = File::from_dyn(buffer.file()).and_then(File::as_local); let file = File::from_dyn(buffer.file()).and_then(File::as_local);
if let (Some(file), Some(language_server)) = (file, language_server) { if let (Some(file), Some(language_server)) = (file, language_server) {
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); let lsp_params = match request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx)
{
Ok(lsp_params) => lsp_params,
Err(err) => {
log::error!(
"Preparing LSP request to {} failed: {}",
language_server.name(),
err
);
return Task::ready(Err(err));
}
};
let status = request.status(); let status = request.status();
return cx.spawn(move |this, cx| async move { return cx.spawn(move |this, cx| async move {
if !request.check_capabilities(language_server.adapter_server_capabilities()) { if !request.check_capabilities(language_server.adapter_server_capabilities()) {
@ -3536,11 +3547,7 @@ impl LspStore {
let result = lsp_request.await; let result = lsp_request.await;
let response = result.map_err(|err| { let response = result.map_err(|err| {
log::warn!( log::warn!("LSP request to {} failed: {}", language_server.name(), err);
"Generic lsp request to {} failed: {}",
language_server.name(),
err
);
err err
})?; })?;