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:
30
flake.lock
generated
30
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743800763,
|
||||
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
|
||||
"lastModified": 1745925850,
|
||||
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
|
||||
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744316434,
|
||||
"narHash": "sha256-lzFCg/1C39pyY2hMB2gcuHV79ozpOz/Vu15hdjiFOfI=",
|
||||
"lastModified": 1745998881,
|
||||
"narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d19cf9dfc633816a437204555afeb9e722386b76",
|
||||
"rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -36,11 +36,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1744316434,
|
||||
"narHash": "sha256-lzFCg/1C39pyY2hMB2gcuHV79ozpOz/Vu15hdjiFOfI=",
|
||||
"lastModified": 1745998881,
|
||||
"narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d19cf9dfc633816a437204555afeb9e722386b76",
|
||||
"rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -52,11 +52,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1736320768,
|
||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -78,11 +78,11 @@
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1744338850,
|
||||
"narHash": "sha256-pwMIVmsb8fjjT92n5XFDqCsplcX70qVMMT7NulumPXs=",
|
||||
"lastModified": 1745980514,
|
||||
"narHash": "sha256-CITAeiuXGjDvT5iZBXr6vKVWQwsUQLJUMFO91bfJFC4=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "5e64aecc018e6f775572609e7d7485fdba6985a7",
|
||||
"rev": "7fbdae44b0f40ea432e46fd152ad8be0f8f41ad6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -547,19 +547,10 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
|
||||
try {
|
||||
// First check if rename is possible at this position
|
||||
const prepareResult = await this.client
|
||||
.textDocumentPrepareRename({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
position: { line, character },
|
||||
})
|
||||
.catch(() => {
|
||||
// In case prepareRename is not supported,
|
||||
// we fallback to the default implementation
|
||||
return this.prepareRenameFallback(view, {
|
||||
let prepareResult = this.prepareRenameFallback(view, {
|
||||
line,
|
||||
character,
|
||||
})
|
||||
})
|
||||
|
||||
if (!prepareResult || 'defaultBehavior' in prepareResult) {
|
||||
showErrorMessage(view, 'Cannot rename this symbol')
|
||||
|
58
rust/Cargo.lock
generated
58
rust/Cargo.lock
generated
@ -713,6 +713,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csscolorparser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288"
|
||||
dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
@ -1899,6 +1908,7 @@ dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"convert_case",
|
||||
"criterion",
|
||||
"csscolorparser",
|
||||
"dashmap 6.1.0",
|
||||
"dhat",
|
||||
"expectorate",
|
||||
@ -2630,6 +2640,48 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phonenumber"
|
||||
version = "0.3.7+8.13.52"
|
||||
@ -3575,6 +3627,12 @@ version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -31,6 +31,7 @@ clap = { version = "4.5.36", default-features = false, optional = true, features
|
||||
"derive",
|
||||
] }
|
||||
convert_case = "0.8.0"
|
||||
csscolorparser = "0.7.0"
|
||||
dashmap = { workspace = true }
|
||||
dhat = { version = "0.3", optional = true }
|
||||
fnv = "1.0.7"
|
||||
|
@ -8,12 +8,11 @@ use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::engine::{PlaneName, DEFAULT_PLANE_INFO};
|
||||
use crate::errors::KclErrorDetails;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::ArtifactId;
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
engine::{PlaneName, DEFAULT_PLANE_INFO},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::NumericType, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
||||
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
||||
std::{args::TyF64, sketch::PlaneData},
|
||||
|
@ -2,13 +2,14 @@ use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
errors::Suggestion,
|
||||
lint::rule::{def_finding, Discovered, Finding},
|
||||
lint::{
|
||||
checks::offset_plane::start_sketch_on_check_specific_plane,
|
||||
rule::{def_finding, Discovered, Finding},
|
||||
},
|
||||
parsing::ast::types::{Node as AstNode, Program},
|
||||
walk::Node,
|
||||
};
|
||||
|
||||
use super::offset_plane::start_sketch_on_check_specific_plane;
|
||||
|
||||
def_finding!(
|
||||
Z0002,
|
||||
"default plane should be called versus explicitly defined",
|
||||
|
@ -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(¤t_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(¤t_code) else {
|
||||
let Some((current_code, new_code)) =
|
||||
self.inner_prepare_rename(¶ms.text_document_position, ¶ms.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(¶ms.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(¤t_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(¶ms, "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(¤t_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(¤t_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(¶ms.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);
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ use std::collections::{BTreeMap, HashMap};
|
||||
use pretty_assertions::assert_eq;
|
||||
use tower_lsp::{
|
||||
lsp_types::{
|
||||
CodeActionKind, CodeActionOrCommand, Diagnostic, SemanticTokenModifier, SemanticTokenType, TextEdit,
|
||||
WorkspaceEdit,
|
||||
CodeActionKind, CodeActionOrCommand, Diagnostic, PrepareRenameResponse, SemanticTokenModifier,
|
||||
SemanticTokenType, TextEdit, WorkspaceEdit,
|
||||
},
|
||||
LanguageServer,
|
||||
};
|
||||
@ -4146,3 +4146,173 @@ async fn kcl_test_kcl_lsp_code_actions_lint_offset_planes() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_kcl_lsp_prepare_rename() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"thing= 1"#.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
// Send rename request.
|
||||
let result = server
|
||||
.prepare_rename(tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Check the result.
|
||||
assert_eq!(
|
||||
result,
|
||||
PrepareRenameResponse::DefaultBehavior { default_behavior: true }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_kcl_lsp_document_color() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"// Add color to a revolved solid.
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> circle(center = [15, 0], radius = 5)
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)"#
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
// Send document color request.
|
||||
let result = server
|
||||
.document_color(tower_lsp::lsp_types::DocumentColorParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check the result.
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![tower_lsp::lsp_types::ColorInformation {
|
||||
range: tower_lsp::lsp_types::Range {
|
||||
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
|
||||
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
|
||||
},
|
||||
color: tower_lsp::lsp_types::Color {
|
||||
red: 1.0,
|
||||
green: 0.0,
|
||||
blue: 0.0,
|
||||
alpha: 1.0,
|
||||
},
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_kcl_lsp_color_presentation() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
let text = r#"// Add color to a revolved solid.
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> circle(center = [15, 0], radius = 5)
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)"#;
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: text.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
// Send document color request.
|
||||
let result = server
|
||||
.document_color(tower_lsp::lsp_types::DocumentColorParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check the result.
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![tower_lsp::lsp_types::ColorInformation {
|
||||
range: tower_lsp::lsp_types::Range {
|
||||
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
|
||||
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
|
||||
},
|
||||
color: tower_lsp::lsp_types::Color {
|
||||
red: 1.0,
|
||||
green: 0.0,
|
||||
blue: 0.0,
|
||||
alpha: 1.0,
|
||||
},
|
||||
}]
|
||||
);
|
||||
|
||||
// Send color presentation request.
|
||||
let result = server
|
||||
.color_presentation(tower_lsp::lsp_types::ColorPresentationParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
range: tower_lsp::lsp_types::Range {
|
||||
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
|
||||
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
|
||||
},
|
||||
color: tower_lsp::lsp_types::Color {
|
||||
red: 1.0,
|
||||
green: 0.0,
|
||||
blue: 1.0,
|
||||
alpha: 1.0,
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check the result.
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![tower_lsp::lsp_types::ColorPresentation {
|
||||
label: "#ff00ff".to_string(),
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::fmt;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -32,6 +32,21 @@ impl LiteralValue {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_color(&self) -> Option<csscolorparser::Color> {
|
||||
if let Self::String(s) = self {
|
||||
// Check if the string is a color.
|
||||
if s.starts_with('#') && s.len() == 7 {
|
||||
let Ok(c) = csscolorparser::Color::from_str(s) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
return Some(c);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LiteralValue {
|
||||
|
@ -14,7 +14,8 @@ use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::lsp_types::{
|
||||
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, SymbolKind,
|
||||
Color, ColorInformation, ColorPresentation, CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange,
|
||||
FoldingRangeKind, SymbolKind,
|
||||
};
|
||||
|
||||
pub use crate::parsing::ast::types::{
|
||||
@ -389,6 +390,99 @@ impl Node<Program> {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Find all the color strings in the program.
|
||||
/// For example `appearance(color = "#ff0000")`
|
||||
/// This is to fulfill the `documentColor` request in LSP.
|
||||
pub fn document_color<'a>(&'a self, code: &str) -> Result<Vec<ColorInformation>> {
|
||||
let colors = Rc::new(RefCell::new(vec![]));
|
||||
|
||||
let add_color = |literal: &Node<Literal>| {
|
||||
// Check if the string is a color.
|
||||
if let Some(c) = literal.value.is_color() {
|
||||
let color = ColorInformation {
|
||||
range: literal.as_source_range().to_lsp_range(code),
|
||||
color: tower_lsp::lsp_types::Color {
|
||||
red: c.r,
|
||||
green: c.g,
|
||||
blue: c.b,
|
||||
alpha: c.a,
|
||||
},
|
||||
};
|
||||
if colors.borrow().iter().any(|c| *c == color) {
|
||||
return;
|
||||
}
|
||||
colors.borrow_mut().push(color);
|
||||
}
|
||||
};
|
||||
|
||||
// The position must be within the variable declaration.
|
||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||
match node {
|
||||
crate::walk::Node::CallExpressionKw(call) => {
|
||||
if call.inner.callee.inner.name.inner.name == "appearance" {
|
||||
for arg in &call.arguments {
|
||||
if arg.label.inner.name == "color" {
|
||||
// Get the value of the argument.
|
||||
if let Expr::Literal(literal) = &arg.arg {
|
||||
add_color(literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::walk::Node::Literal(literal) => {
|
||||
// Check if the literal is a color.
|
||||
add_color(literal);
|
||||
}
|
||||
_ => {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
Ok::<bool, anyhow::Error>(true)
|
||||
})?;
|
||||
|
||||
let colors = colors.take();
|
||||
Ok(colors)
|
||||
}
|
||||
|
||||
/// This is to fulfill the `colorPresentation` request in LSP.
|
||||
pub fn color_presentation<'a>(
|
||||
&'a self,
|
||||
color: &Color,
|
||||
pos_start: usize,
|
||||
pos_end: usize,
|
||||
) -> Result<Option<ColorPresentation>> {
|
||||
let found = Rc::new(RefCell::new(false));
|
||||
// Find the literal with the same start and end.
|
||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||
match node {
|
||||
crate::walk::Node::Literal(literal) => {
|
||||
if literal.start == pos_start && literal.end == pos_end && literal.value.is_color().is_some() {
|
||||
found.replace(true);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
Ok::<bool, anyhow::Error>(true)
|
||||
})?;
|
||||
|
||||
let found = found.take();
|
||||
if !found {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let new_color = csscolorparser::Color::new(color.red, color.green, color.blue, color.alpha);
|
||||
Ok(Some(ColorPresentation {
|
||||
// The label will be what they replace the color with.
|
||||
label: new_color.to_hex_string(),
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
|
Reference in New Issue
Block a user