Code completions for @settings (#5622)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -24,6 +24,10 @@ pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
|
|||||||
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
||||||
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
|
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
|
||||||
|
|
||||||
|
pub(crate) fn settings_completion_text() -> String {
|
||||||
|
format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = mm, {SETTINGS_UNIT_ANGLE} = deg)")
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn is_significant(attr: &&Node<Annotation>) -> bool {
|
pub(super) fn is_significant(attr: &&Node<Annotation>) -> bool {
|
||||||
match attr.name() {
|
match attr.name() {
|
||||||
Some(name) => SIGNIFICANT_ATTRS.contains(&name),
|
Some(name) => SIGNIFICANT_ATTRS.contains(&name),
|
||||||
|
@ -1247,13 +1247,13 @@ impl LanguageServer for Backend {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let position = position_to_char_index(params.text_document_position.position, current_code);
|
let position = position_to_char_index(params.text_document_position.position, current_code);
|
||||||
if ast.ast.get_non_code_meta_for_position(position).is_some() {
|
if ast.ast.in_comment(position) {
|
||||||
// If we are in a code comment we don't want to show completions.
|
// If we are in a code comment we don't want to show completions.
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the completion items for the ast.
|
// Get the completion items for the ast.
|
||||||
let Ok(variables) = ast.ast.completion_items() else {
|
let Ok(variables) = ast.ast.completion_items(position) else {
|
||||||
return Ok(Some(CompletionResponse::Array(completions)));
|
return Ok(Some(CompletionResponse::Array(completions)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -644,7 +644,10 @@ async fn test_kcl_lsp_completions() {
|
|||||||
uri: "file:///test.kcl".try_into().unwrap(),
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
language_id: "kcl".to_string(),
|
language_id: "kcl".to_string(),
|
||||||
version: 1,
|
version: 1,
|
||||||
text: r#"thing= 1
|
// Blank lines to check that we get completions even in an AST newline thing.
|
||||||
|
text: r#"
|
||||||
|
|
||||||
|
thing= 1
|
||||||
st"#
|
st"#
|
||||||
.to_string(),
|
.to_string(),
|
||||||
},
|
},
|
||||||
@ -658,7 +661,7 @@ st"#
|
|||||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||||
uri: "file:///test.kcl".try_into().unwrap(),
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
},
|
},
|
||||||
position: tower_lsp::lsp_types::Position { line: 0, character: 16 },
|
position: tower_lsp::lsp_types::Position { line: 0, character: 0 },
|
||||||
},
|
},
|
||||||
context: None,
|
context: None,
|
||||||
partial_result_params: Default::default(),
|
partial_result_params: Default::default(),
|
||||||
@ -671,6 +674,7 @@ st"#
|
|||||||
// Check the completions.
|
// Check the completions.
|
||||||
if let tower_lsp::lsp_types::CompletionResponse::Array(completions) = completions {
|
if let tower_lsp::lsp_types::CompletionResponse::Array(completions) = completions {
|
||||||
assert!(completions.len() > 10);
|
assert!(completions.len() > 10);
|
||||||
|
assert!(completions.iter().any(|c| c.label == "@settings"));
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected array of completions");
|
panic!("Expected array of completions");
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ pub struct Program {
|
|||||||
|
|
||||||
impl Node<Program> {
|
impl Node<Program> {
|
||||||
/// Walk the ast and get all the variables and tags as completion items.
|
/// Walk the ast and get all the variables and tags as completion items.
|
||||||
pub fn completion_items<'a>(&'a self) -> Result<Vec<CompletionItem>> {
|
pub fn completion_items<'a>(&'a self, position: usize) -> Result<Vec<CompletionItem>> {
|
||||||
let completions = Rc::new(RefCell::new(vec![]));
|
let completions = Rc::new(RefCell::new(vec![]));
|
||||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||||
let mut findings = completions.borrow_mut();
|
let mut findings = completions.borrow_mut();
|
||||||
@ -208,8 +208,20 @@ impl Node<Program> {
|
|||||||
}
|
}
|
||||||
Ok::<bool, anyhow::Error>(true)
|
Ok::<bool, anyhow::Error>(true)
|
||||||
})?;
|
})?;
|
||||||
let x = completions.take();
|
let mut completions = completions.take();
|
||||||
Ok(x.clone())
|
|
||||||
|
if self.body.is_empty() || position <= self.body[0].start() {
|
||||||
|
// The cursor is before any items in the body, we can suggest the settings annotation as a completion.
|
||||||
|
completions.push(CompletionItem {
|
||||||
|
label: "@settings".to_owned(),
|
||||||
|
kind: Some(CompletionItemKind::STRUCT),
|
||||||
|
detail: Some("Settings attribute".to_owned()),
|
||||||
|
insert_text: Some(crate::execution::annotations::settings_completion_text()),
|
||||||
|
insert_text_format: Some(tower_lsp::lsp_types::InsertTextFormat::SNIPPET),
|
||||||
|
..CompletionItem::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(completions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all the lsp symbols in the program.
|
/// Returns all the lsp symbols in the program.
|
||||||
@ -347,32 +359,34 @@ impl Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a non code meta that includes the given character position.
|
pub fn in_comment(&self, pos: usize) -> bool {
|
||||||
pub fn get_non_code_meta_for_position(&self, pos: usize) -> Option<&NonCodeMeta> {
|
|
||||||
// Check if its in the body.
|
// Check if its in the body.
|
||||||
if self.non_code_meta.contains(pos) {
|
if self.non_code_meta.in_comment(pos) {
|
||||||
return Some(&self.non_code_meta);
|
return true;
|
||||||
}
|
}
|
||||||
let item = self.get_body_item_for_position(pos)?;
|
let item = self.get_body_item_for_position(pos);
|
||||||
|
|
||||||
// Recurse over the item.
|
// Recurse over the item.
|
||||||
let expr = match item {
|
let expr = match item {
|
||||||
BodyItem::ImportStatement(_) => None,
|
Some(BodyItem::ImportStatement(_)) => None,
|
||||||
BodyItem::ExpressionStatement(expression_statement) => Some(&expression_statement.expression),
|
Some(BodyItem::ExpressionStatement(expression_statement)) => Some(&expression_statement.expression),
|
||||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.get_expr_for_position(pos),
|
Some(BodyItem::VariableDeclaration(variable_declaration)) => {
|
||||||
BodyItem::ReturnStatement(return_statement) => Some(&return_statement.argument),
|
variable_declaration.get_expr_for_position(pos)
|
||||||
|
}
|
||||||
|
Some(BodyItem::ReturnStatement(return_statement)) => Some(&return_statement.argument),
|
||||||
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the expr's non code meta contains the position.
|
// Check if the expr's non code meta contains the position.
|
||||||
if let Some(expr) = expr {
|
if let Some(expr) = expr {
|
||||||
if let Some(non_code_meta) = expr.get_non_code_meta() {
|
if let Some(non_code_meta) = expr.get_non_code_meta() {
|
||||||
if non_code_meta.contains(pos) {
|
if non_code_meta.in_comment(pos) {
|
||||||
return Some(non_code_meta);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all the lsp folding ranges in the program.
|
// Return all the lsp folding ranges in the program.
|
||||||
@ -1112,6 +1126,15 @@ impl NonCodeNode {
|
|||||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_comment(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.value,
|
||||||
|
NonCodeValue::InlineComment { .. }
|
||||||
|
| NonCodeValue::BlockComment { .. }
|
||||||
|
| NonCodeValue::NewLineBlockComment { .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
@ -1189,14 +1212,22 @@ impl NonCodeMeta {
|
|||||||
self.non_code_nodes.entry(i).or_default().push(new);
|
self.non_code_nodes.entry(i).or_default().push(new);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, pos: usize) -> bool {
|
pub fn in_comment(&self, pos: usize) -> bool {
|
||||||
if self.start_nodes.iter().any(|node| node.contains(pos)) {
|
if self
|
||||||
|
.start_nodes
|
||||||
|
.iter()
|
||||||
|
.filter(|node| node.is_comment())
|
||||||
|
.any(|node| node.contains(pos))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.non_code_nodes
|
self.non_code_nodes.iter().any(|(_, nodes)| {
|
||||||
.iter()
|
nodes
|
||||||
.any(|(_, nodes)| nodes.iter().any(|node| node.contains(pos)))
|
.iter()
|
||||||
|
.filter(|node| node.is_comment())
|
||||||
|
.any(|node| node.contains(pos))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the non-code meta immediately before the ith node in the AST that self is attached to.
|
/// Get the non-code meta immediately before the ith node in the AST that self is attached to.
|
||||||
@ -3381,7 +3412,7 @@ fn ghi = (x) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ast_get_non_code_node() {
|
fn test_ast_in_comment() {
|
||||||
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
|
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
|
||||||
const h = 30
|
const h = 30
|
||||||
|
|
||||||
@ -3397,13 +3428,11 @@ const cylinder = startSketchOn('-XZ')
|
|||||||
"#;
|
"#;
|
||||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
|
||||||
let value = program.get_non_code_meta_for_position(50);
|
assert!(program.in_comment(50));
|
||||||
|
|
||||||
assert!(value.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ast_get_non_code_node_pipe() {
|
fn test_ast_in_comment_pipe() {
|
||||||
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
|
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
|
||||||
const h = 30
|
const h = 30
|
||||||
|
|
||||||
@ -3420,22 +3449,18 @@ const cylinder = startSketchOn('-XZ')
|
|||||||
"#;
|
"#;
|
||||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
|
||||||
let value = program.get_non_code_meta_for_position(124);
|
assert!(program.in_comment(124));
|
||||||
|
|
||||||
assert!(value.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ast_get_non_code_node_inline_comment() {
|
fn test_ast_in_comment_inline() {
|
||||||
let some_program_string = r#"const part001 = startSketchOn('XY')
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|> startProfileAt([0,0], %)
|
|> startProfileAt([0,0], %)
|
||||||
|> xLine(5, %) // lin
|
|> xLine(5, %) // lin
|
||||||
"#;
|
"#;
|
||||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
|
||||||
let value = program.get_non_code_meta_for_position(86);
|
assert!(program.in_comment(86));
|
||||||
|
|
||||||
assert!(value.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
@ -481,7 +481,6 @@ const jsAppSettings = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const errFromErrWithOutputs = (e: any): KCLError => {
|
const errFromErrWithOutputs = (e: any): KCLError => {
|
||||||
console.log(e)
|
|
||||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||||
return new KCLError(
|
return new KCLError(
|
||||||
parsed.error.kind,
|
parsed.error.kind,
|
||||||
|
Reference in New Issue
Block a user