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:
@ -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