More lsp endpoints we were missing (#6612)

* add prepare rename

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add document color

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-04-30 19:47:36 -07:00
committed by GitHub
parent 2d77aa0d36
commit b686c79b49
10 changed files with 529 additions and 76 deletions

View File

@ -18,23 +18,24 @@ use tower_lsp::{
jsonrpc::Result as RpcResult,
lsp_types::{
CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
CodeActionProviderCapability, CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions,
CompletionParams, CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport,
DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams,
DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability,
FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams, InsertTextFormat,
MarkupContent, MarkupKind, MessageType, OneOf, Position, RelatedFullDocumentDiagnosticReport,
RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams,
SemanticTokensRegistrationOptions, SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities,
SignatureHelp, SignatureHelpOptions, SignatureHelpParams, StaticRegistrationOptions, TextDocumentItem,
TextDocumentRegistrationOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextEdit, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
},
Client, LanguageServer,
};
@ -827,6 +828,39 @@ impl Backend {
Ok(custom_notifications::UpdateCanExecuteResponse {})
}
/// Returns the new string for the code after rename.
pub fn inner_prepare_rename(
&self,
params: &TextDocumentPositionParams,
new_name: &str,
) -> RpcResult<Option<(String, String)>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(None);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(None);
};
// Parse the ast.
// I don't know if we need to do this again since it should be updated in the context.
// But I figure better safe than sorry since this will write back out to the file.
let module_id = ModuleId::default();
let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
return Ok(None);
};
// Let's convert the position to a character index.
let pos = position_to_char_index(params.position, current_code);
// Now let's perform the rename on the ast.
ast.rename_symbol(new_name, pos);
// Now recast it.
let recast = ast.recast(&Default::default(), 0);
Ok(Some((current_code.to_string(), recast)))
}
}
#[tower_lsp::async_trait]
@ -838,6 +872,7 @@ impl LanguageServer for Backend {
Ok(InitializeResult {
capabilities: ServerCapabilities {
color_provider: Some(ColorProviderCapability::Simple(true)),
code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
resolve_provider: Some(false),
@ -1459,36 +1494,19 @@ impl LanguageServer for Backend {
}
async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
let filename = params.text_document_position.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(None);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
let Some((current_code, new_code)) =
self.inner_prepare_rename(&params.text_document_position, &params.new_name)?
else {
return Ok(None);
};
// Parse the ast.
// I don't know if we need to do this again since it should be updated in the context.
// But I figure better safe than sorry since this will write back out to the file.
let module_id = ModuleId::default();
let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
return Ok(None);
};
// Let's convert the position to a character index.
let pos = position_to_char_index(params.text_document_position.position, current_code);
// Now let's perform the rename on the ast.
ast.rename_symbol(&params.new_name, pos);
// Now recast it.
let recast = ast.recast(&Default::default(), 0);
let source_range = SourceRange::new(0, current_code.len(), module_id);
let range = source_range.to_lsp_range(current_code);
let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
let range = source_range.to_lsp_range(&current_code);
Ok(Some(WorkspaceEdit {
changes: Some(HashMap::from([(
params.text_document_position.text_document.uri,
vec![TextEdit {
new_text: recast,
new_text: new_code,
range,
}],
)])),
@ -1497,6 +1515,18 @@ impl LanguageServer for Backend {
}))
}
async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
if self
.inner_prepare_rename(&params, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
.is_none()
{
return Ok(None);
}
// Return back to the client, that it is safe to use the rename behavior.
Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
}
async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
let filename = params.text_document.uri.to_string();
@ -1552,6 +1582,55 @@ impl LanguageServer for Backend {
Ok(Some(actions))
}
async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(vec![]);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(vec![]);
};
// Get the ast from our map.
let Some(ast) = self.ast_map.get(&filename) else {
return Ok(vec![]);
};
// Get the colors from the ast.
let Ok(colors) = ast.ast.document_color(current_code) else {
return Ok(vec![]);
};
Ok(colors)
}
async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(vec![]);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(vec![]);
};
// Get the ast from our map.
let Some(ast) = self.ast_map.get(&filename) else {
return Ok(vec![]);
};
let pos_start = position_to_char_index(params.range.start, current_code);
let pos_end = position_to_char_index(params.range.end, current_code);
// Get the colors from the ast.
let Ok(Some(presentation)) = ast.ast.color_presentation(&params.color, pos_start, pos_end) else {
return Ok(vec![]);
};
Ok(vec![presentation])
}
}
/// Get completions from our stdlib.
@ -1662,3 +1741,48 @@ async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T>
Some(f(value))
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_position_to_char_index_first_line() {
let code = r#"def foo():
return 42"#;
let position = Position::new(0, 3);
let index = position_to_char_index(position, code);
assert_eq!(index, 3);
}
#[test]
fn test_position_to_char_index() {
let code = r#"def foo():
return 42"#;
let position = Position::new(1, 4);
let index = position_to_char_index(position, code);
assert_eq!(index, 15);
}
#[test]
fn test_position_to_char_index_with_newline() {
let code = r#"def foo():
return 42"#;
let position = Position::new(2, 0);
let index = position_to_char_index(position, code);
assert_eq!(index, 12);
}
#[test]
fn test_position_to_char_at_end() {
let code = r#"def foo():
return 42"#;
let position = Position::new(1, 8);
let index = position_to_char_index(position, code);
assert_eq!(index, 19);
}
}