KCL: Custom snippet values for kcl-in-kcl (#7163)
In #7156, I allowed KCL to set specific snippet completions for each arg of each function. They're optional -- if you don't set one, it'll fall back to the type-driven defaults. That PR only worked for KCL stdlib functions defined in Rust. This PR enables the same feature, but for functions defined in KCL.
This commit is contained in:
@ -10,7 +10,9 @@ use tower_lsp::lsp_types::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
execution::annotations,
|
execution::annotations,
|
||||||
parsing::{
|
parsing::{
|
||||||
ast::types::{Annotation, ImportSelector, ItemVisibility, Node, NonCodeValue, VariableKind},
|
ast::types::{
|
||||||
|
Annotation, Expr, ImportSelector, ItemVisibility, LiteralValue, Node, NonCodeValue, VariableKind,
|
||||||
|
},
|
||||||
token::NumericSuffix,
|
token::NumericSuffix,
|
||||||
},
|
},
|
||||||
ModuleId,
|
ModuleId,
|
||||||
@ -620,8 +622,6 @@ impl FnData {
|
|||||||
return "clone(${0:part001})".to_owned();
|
return "clone(${0:part001})".to_owned();
|
||||||
} else if self.name == "hole" {
|
} else if self.name == "hole" {
|
||||||
return "hole(${0:holeSketch}, ${1:%})".to_owned();
|
return "hole(${0:holeSketch}, ${1:%})".to_owned();
|
||||||
} else if self.name == "circle" {
|
|
||||||
return "circle(center = [${0:3.14}, ${1:3.14}], diameter = ${2:3.14})".to_owned();
|
|
||||||
}
|
}
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
@ -685,6 +685,8 @@ pub struct ArgData {
|
|||||||
/// This is helpful if the type is really basic, like "number" -- that won't tell the user much about
|
/// This is helpful if the type is really basic, like "number" -- that won't tell the user much about
|
||||||
/// how this argument is meant to be used.
|
/// how this argument is meant to be used.
|
||||||
pub docs: Option<String>,
|
pub docs: Option<String>,
|
||||||
|
/// If given, LSP should use these as completion items.
|
||||||
|
pub snippet_array: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ArgData {
|
impl fmt::Display for ArgData {
|
||||||
@ -712,6 +714,7 @@ pub enum ArgKind {
|
|||||||
impl ArgData {
|
impl ArgData {
|
||||||
fn from_ast(arg: &crate::parsing::ast::types::Parameter) -> Self {
|
fn from_ast(arg: &crate::parsing::ast::types::Parameter) -> Self {
|
||||||
let mut result = ArgData {
|
let mut result = ArgData {
|
||||||
|
snippet_array: Default::default(),
|
||||||
name: arg.identifier.name.clone(),
|
name: arg.identifier.name.clone(),
|
||||||
ty: arg.type_.as_ref().map(|t| t.to_string()),
|
ty: arg.type_.as_ref().map(|t| t.to_string()),
|
||||||
docs: None,
|
docs: None,
|
||||||
@ -740,6 +743,30 @@ impl ArgData {
|
|||||||
p.value
|
p.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if p.key.name == "snippetArray" {
|
||||||
|
let Expr::ArrayExpression(arr) = &p.value else {
|
||||||
|
panic!(
|
||||||
|
"Invalid value for `snippetArray`, expected array literal, found {:?}",
|
||||||
|
p.value
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for s in &arr.elements {
|
||||||
|
let Expr::Literal(lit) = s else {
|
||||||
|
panic!(
|
||||||
|
"Invalid value in `snippetArray`, all items must be string literals but found {:?}",
|
||||||
|
s
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let LiteralValue::String(litstr) = &lit.inner.value else {
|
||||||
|
panic!(
|
||||||
|
"Invalid value in `snippetArray`, all items must be string literals but found {:?}",
|
||||||
|
s
|
||||||
|
);
|
||||||
|
};
|
||||||
|
items.push(litstr.to_owned());
|
||||||
|
}
|
||||||
|
result.snippet_array = Some(items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -761,6 +788,19 @@ impl ArgData {
|
|||||||
} else {
|
} else {
|
||||||
format!("{} = ", self.name)
|
format!("{} = ", self.name)
|
||||||
};
|
};
|
||||||
|
if let Some(vals) = &self.snippet_array {
|
||||||
|
let mut snippet = label.to_owned();
|
||||||
|
snippet.push('[');
|
||||||
|
let n = vals.len();
|
||||||
|
for (i, val) in vals.iter().enumerate() {
|
||||||
|
snippet.push_str(&format!("${{{}:{}}}", index + i, val));
|
||||||
|
if i != n - 1 {
|
||||||
|
snippet.push_str(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snippet.push(']');
|
||||||
|
return Some((index + n - 1, snippet));
|
||||||
|
}
|
||||||
match self.ty.as_deref() {
|
match self.ty.as_deref() {
|
||||||
Some(s) if s.starts_with("number") => Some((index, format!(r#"{label}${{{}:3.14}}"#, index))),
|
Some(s) if s.starts_with("number") => Some((index, format!(r#"{label}${{{}:3.14}}"#, index))),
|
||||||
Some("Point2d") => Some((
|
Some("Point2d") => Some((
|
||||||
|
@ -1040,8 +1040,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let snippet = circle_fn.to_autocomplete_snippet();
|
let snippet = circle_fn.to_autocomplete_snippet();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet,
|
snippet, r#"circle(center = [${0:0}, ${1:0}], diameter = ${2:3.14})"#,
|
||||||
r#"circle(center = [${0:3.14}, ${1:3.14}], diameter = ${2:3.14})"#
|
"actual = left, expected = right"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +33,12 @@ export fn circle(
|
|||||||
/// Sketch to extend, or plane or surface to sketch on.
|
/// Sketch to extend, or plane or surface to sketch on.
|
||||||
@sketch_or_surface: Sketch | Plane | Face,
|
@sketch_or_surface: Sketch | Plane | Face,
|
||||||
/// The center of the circle.
|
/// The center of the circle.
|
||||||
|
@(snippetArray = ["0", "0"])
|
||||||
center: Point2d,
|
center: Point2d,
|
||||||
/// The radius of the circle. Incompatible with `diameter`.
|
/// The radius of the circle. Incompatible with `diameter`.
|
||||||
radius?: number(Length),
|
radius?: number(Length),
|
||||||
/// The diameter of the circle. Incompatible with `radius`.
|
/// The diameter of the circle. Incompatible with `radius`.
|
||||||
|
@(include_in_snippet = true)
|
||||||
diameter?: number(Length),
|
diameter?: number(Length),
|
||||||
/// Create a new tag which refers to this circle.
|
/// Create a new tag which refers to this circle.
|
||||||
tag?: tag,
|
tag?: tag,
|
||||||
|
Reference in New Issue
Block a user