Rust side snippet completions (#2096)

* updates;

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

docs

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

remove descriptions

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

get snippet tests

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

updates

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

more autocomplete tests

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

updates

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

updates

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

updates

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

updates

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

tab to end of snippets

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

updates

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

updates

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

A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* updates

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

* updates

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

* fixes

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* emptu

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* empty

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* empty

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* empty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jess Frazelle
2024-04-12 13:28:58 -07:00
committed by GitHub
parent ba6b3d9a8d
commit 63be31971f
22 changed files with 352 additions and 45 deletions

View File

@ -121,9 +121,9 @@ const sketch001 = startSketchOn(box, "END")
// Angle to revolve (in degrees). Default is 360. // Angle to revolve (in degrees). Default is 360.
angle: number, angle: number,
// Axis of revolution. // Axis of revolution.
axis: "x" | axis: "X" |
"y" | "Y" |
"z" | "Z" |
"-X" | "-X" |
"-Y" | "-Y" |
"-Z" | "-Z" |

View File

@ -49363,21 +49363,21 @@
"description": "X-axis.", "description": "X-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"x" "X"
] ]
}, },
{ {
"description": "Y-axis.", "description": "Y-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"y" "Y"
] ]
}, },
{ {
"description": "Z-axis.", "description": "Z-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"z" "Z"
] ]
}, },
{ {

View File

@ -499,7 +499,8 @@ test('Auto complete works', async ({ page }) => {
// expect there to be three auto complete options // expect there to be three auto complete options
await expect(page.locator('.cm-completionLabel')).toHaveCount(3) await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
await page.getByText('startSketchOn').click() await page.getByText('startSketchOn').click()
await page.keyboard.type("('XY')") await page.keyboard.type("'XY'")
await page.keyboard.press('ArrowRight')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(' |> startProfi') await page.keyboard.type(' |> startProfi')
// expect there be a single auto complete option that we can just hit enter on // expect there be a single auto complete option that we can just hit enter on
@ -507,7 +508,18 @@ test('Auto complete works', async ({ page }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Enter') // accepting the auto complete, not a new line await page.keyboard.press('Enter') // accepting the auto complete, not a new line
await page.keyboard.type('([0,0], %)') await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(' |> lin') await page.keyboard.type(' |> lin')
@ -518,14 +530,20 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// finish line with comment // finish line with comment
await page.keyboard.type('(5, %) // lin') await page.keyboard.type('5')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('ArrowRight')
await page.keyboard.type(' // lin')
await page.waitForTimeout(100) await page.waitForTimeout(100)
// there shouldn't be any auto complete options for 'lin' in the comment // there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible() await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY') .toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %) |> startProfileAt([3.14, 3.14], %)
|> xLine(5, %) // lin`) |> xLine(5, %) // lin`)
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -11,7 +11,9 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards' import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { indentWithTab } from '@codemirror/commands'
import { linter, lintGutter } from '@codemirror/lint' import { linter, lintGutter } from '@codemirror/lint'
import { foldGutter } from '@codemirror/language'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections' import { processCodeMirrorRanges } from 'lib/selections'
import { EditorView, lineHighlightField } from 'editor/highlightextension' import { EditorView, lineHighlightField } from 'editor/highlightextension'
@ -26,6 +28,7 @@ import { ModelingMachineEvent } from 'machines/modelingMachine'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator' import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { Prec } from '@codemirror/state'
export const editorShortcutMeta = { export const editorShortcutMeta = {
formatCode: { formatCode: {
@ -175,13 +178,18 @@ export const TextEditor = ({
]), ]),
] as Extension[] ] as Extension[]
if (kclLSP) extensions.push(kclLSP) if (kclLSP) extensions.push(Prec.highest(kclLSP))
if (copilotLSP) extensions.push(copilotLSP) if (copilotLSP) extensions.push(copilotLSP)
// We do the tab keymap after the LSPs with the lowest precedence.
// Specifically, tab for in a completion.
extensions.push(Prec.lowest(keymap.of([indentWithTab])))
// These extensions have proven to mess with vitest // These extensions have proven to mess with vitest
if (!TEST) { if (!TEST) {
extensions.push( extensions.push(
lintGutter(), lintGutter(),
foldGutter(),
linter((_view) => { linter((_view) => {
return kclErrToDiagnostic(errors) return kclErrToDiagnostic(errors)
}), }),
@ -238,6 +246,7 @@ export const TextEditor = ({
onUpdate={onUpdate} onUpdate={onUpdate}
theme={theme} theme={theme}
onCreateEditor={(_editorView) => setEditorView(_editorView)} onCreateEditor={(_editorView) => setEditorView(_editorView)}
indentWithTab={false}
/> />
</div> </div>
) )

View File

@ -36,6 +36,7 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
position: 'absolute', position: 'absolute',
}), }),
autocompletion({ autocompletion({
defaultKeymap: true,
override: [ override: [
async (context) => { async (context) => {
if (plugin == null) return null if (plugin == null) return null

View File

@ -1,4 +1,4 @@
import { completeFromList } from '@codemirror/autocomplete' import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
import { setDiagnostics } from '@codemirror/lint' import { setDiagnostics } from '@codemirror/lint'
import { Facet } from '@codemirror/state' import { Facet } from '@codemirror/state'
import { EditorView, Tooltip } from '@codemirror/view' import { EditorView, Tooltip } from '@codemirror/view'
@ -224,6 +224,10 @@ export class LanguageServerPlugin implements PluginValue {
} }
} }
if (insertText && insertTextFormat === 2) {
return snippetCompletion(insertText, completion)
}
return completion return completion
} }
) )

View File

@ -57,11 +57,17 @@ impl StdLibFnArg {
get_type_string_from_schema(&self.schema.clone()) get_type_string_from_schema(&self.schema.clone())
} }
#[allow(dead_code)]
pub fn get_autocomplete_string(&self) -> Result<String> { pub fn get_autocomplete_string(&self) -> Result<String> {
get_autocomplete_string_from_schema(&self.schema.clone()) get_autocomplete_string_from_schema(&self.schema.clone())
} }
pub fn get_autocomplete_snippet(&self, index: usize) -> Result<Option<(usize, String)>> {
if self.type_ == "SketchGroup" || self.type_ == "ExtrudeGroup" || self.type_ == "SketchSurface" {
return Ok(Some((index, format!("${{{}:{}}}", index, "%"))));
}
get_autocomplete_snippet_from_schema(&self.schema.clone(), index)
}
pub fn description(&self) -> Option<String> { pub fn description(&self) -> Option<String> {
get_description_string_from_schema(&self.schema.clone()) get_description_string_from_schema(&self.schema.clone())
} }
@ -153,8 +159,8 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
signature signature
} }
fn to_completion_item(&self) -> CompletionItem { fn to_completion_item(&self) -> Result<CompletionItem> {
CompletionItem { Ok(CompletionItem {
label: self.name(), label: self.name(),
label_details: Some(CompletionItemLabelDetails { label_details: Some(CompletionItemLabelDetails {
detail: Some(self.fn_signature().replace(&self.name(), "")), detail: Some(self.fn_signature().replace(&self.name(), "")),
@ -174,25 +180,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
preselect: None, preselect: None,
sort_text: None, sort_text: None,
filter_text: None, filter_text: None,
insert_text: Some(format!( insert_text: Some(self.to_autocomplete_snippet()?),
"{}({})",
self.name(),
self.args()
.iter()
.enumerate()
// It is okay to unwrap here since in the `kcl-lib` tests, we would have caught
// any errors in the `self`'s signature.
.map(|(index, item)| {
let format = item.get_autocomplete_string().unwrap();
if item.type_ == "SketchGroup" || item.type_ == "ExtrudeGroup" {
format!("${{{}:{}}}", index + 1, "%")
} else {
format!("${{{}:{}}}", index + 1, format)
}
})
.collect::<Vec<_>>()
.join(",")
)),
insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: None, insert_text_mode: None,
text_edit: None, text_edit: None,
@ -201,7 +189,21 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
commit_characters: None, commit_characters: None,
data: None, data: None,
tags: None, tags: None,
})
} }
fn to_autocomplete_snippet(&self) -> Result<String> {
let mut args = Vec::new();
let mut index = 0;
for arg in self.args().iter() {
if let Some((i, arg_str)) = arg.get_autocomplete_snippet(index)? {
index = i + 1;
args.push(arg_str);
}
}
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
Ok(format!("{}({})${{}}", self.name(), args.join(", ")))
} }
fn to_signature_help(&self) -> SignatureHelp { fn to_signature_help(&self) -> SignatureHelp {
@ -350,7 +352,13 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
match array_val.max_items { match array_val.max_items {
Some(val) => { Some(val) => {
return Ok(( return Ok((
format!("[{}]", (0..val).map(|_| "number").collect::<Vec<_>>().join(", ")), format!(
"[{}]",
(0..val)
.map(|_| get_type_string_from_schema(items).unwrap().0)
.collect::<Vec<_>>()
.join(", ")
),
false, false,
)); ));
} }
@ -429,9 +437,176 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
} }
} }
pub fn get_autocomplete_snippet_from_schema(
schema: &schemars::schema::Schema,
index: usize,
) -> Result<Option<(usize, String)>> {
match schema {
schemars::schema::Schema::Object(o) => {
if let Some(serde_json::Value::Bool(nullable)) = o.extensions.get("nullable") {
if *nullable {
return Ok(None);
}
}
if o.enum_values.is_some() {
let auto_str = get_autocomplete_string_from_schema(schema)?;
return Ok(Some((index, format!("${{{}:{}}}", index, auto_str))));
}
if let Some(format) = &o.format {
if format == "uuid" {
return Ok(Some((index, format!(r#"${{{}:"tag_or_edge_fn"}}"#, index))));
} else if format == "double" || format == "uint" || format == "int64" || format == "uint32" {
return Ok(Some((index, format!(r#"${{{}:3.14}}"#, index))));
} else {
anyhow::bail!("unknown format: {}", format);
}
}
if let Some(obj_val) = &o.object {
let mut fn_docs = String::new();
fn_docs.push_str("{\n");
// Let's print out the object's properties.
for (i, (prop_name, prop)) in obj_val.properties.iter().enumerate() {
if prop_name.starts_with('_') {
continue;
}
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(prop, index + i)? {
fn_docs.push_str(&format!("\t{}: {},\n", prop_name, snippet));
}
}
fn_docs.push('}');
return Ok(Some((index + obj_val.properties.len() - 1, fn_docs)));
}
if let Some(array_val) = &o.array {
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
// Let's print out the object's properties.
match array_val.max_items {
Some(val) => {
return Ok(Some((
index + (val as usize) - 1,
format!(
"[{}]",
(0..val)
.map(|v| get_autocomplete_snippet_from_schema(items, index + (v as usize))
.unwrap()
.unwrap()
.1)
.collect::<Vec<_>>()
.join(", ")
),
)));
}
None => {
return Ok(Some((
index,
format!(
"[{}]",
get_autocomplete_snippet_from_schema(items, index)?
.ok_or_else(|| anyhow::anyhow!("expected snippet"))?
.1
),
)));
}
};
} else if let Some(items) = &array_val.contains {
return Ok(Some((
index,
format!(
"[{}]",
get_autocomplete_snippet_from_schema(items, index)?
.ok_or_else(|| anyhow::anyhow!("expected snippet"))?
.1
),
)));
}
}
if let Some(subschemas) = &o.subschemas {
let mut fn_docs = String::new();
if let Some(items) = &subschemas.one_of {
let mut had_enum_string = false;
let mut parsed_enum_values: Vec<String> = Vec::new();
for item in items {
if let schemars::schema::Schema::Object(o) = item {
if let Some(enum_values) = &o.enum_values {
for enum_value in enum_values {
if let serde_json::value::Value::String(enum_value) = enum_value {
had_enum_string = true;
parsed_enum_values.push(format!("\"{}\"", enum_value));
} else {
had_enum_string = false;
break;
}
}
if !had_enum_string {
break;
}
} else {
had_enum_string = false;
break;
}
} else {
had_enum_string = false;
break;
}
}
if had_enum_string && !parsed_enum_values.is_empty() {
return Ok(Some((index, parsed_enum_values[0].to_string())));
} else if let Some(item) = items.iter().next() {
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
fn_docs.push_str(&snippet);
}
}
} else if let Some(items) = &subschemas.any_of {
if let Some(item) = items.iter().next() {
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
fn_docs.push_str(&snippet);
}
}
} else {
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
}
return Ok(Some((index, fn_docs)));
}
if let Some(schemars::schema::SingleOrVec::Single(_string)) = &o.instance_type {
return Ok(Some((index, format!(r#"${{{}:"string"}}"#, index))));
}
anyhow::bail!("unknown type: {:#?}", o)
}
schemars::schema::Schema::Bool(_) => Ok(Some((index, format!(r#"${{{}:false}}"#, index)))),
}
}
pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Result<String> { pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Result<String> {
match schema { match schema {
schemars::schema::Schema::Object(o) => { schemars::schema::Schema::Object(o) => {
if let Some(enum_values) = &o.enum_values {
let mut parsed_enum_values: Vec<String> = Default::default();
let mut had_enum_string = false;
for enum_value in enum_values {
if let serde_json::value::Value::String(enum_value) = enum_value {
had_enum_string = true;
parsed_enum_values.push(format!("\"{}\"", enum_value));
} else {
had_enum_string = false;
break;
}
}
if had_enum_string && !parsed_enum_values.is_empty() {
return Ok(parsed_enum_values[0].to_string());
}
}
if let Some(format) = &o.format { if let Some(format) = &o.format {
if format == "uuid" { if format == "uuid" {
return Ok(Primitive::Uuid.to_string()); return Ok(Primitive::Uuid.to_string());
@ -451,9 +626,6 @@ pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) ->
continue; continue;
} }
if let Some(description) = get_description_string_from_schema(prop) {
fn_docs.push_str(&format!("\t// {}\n", description));
}
fn_docs.push_str(&format!( fn_docs.push_str(&format!(
"\t{}: {},\n", "\t{}: {},\n",
prop_name, prop_name,
@ -469,7 +641,17 @@ pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) ->
if let Some(array_val) = &o.array { if let Some(array_val) = &o.array {
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items { if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
// Let's print out the object's properties. // Let's print out the object's properties.
match array_val.max_items {
Some(val) => {
return Ok(format!(
"[{}]",
(0..val).map(|_| "number").collect::<Vec<_>>().join(", ")
));
}
None => {
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?)); return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
}
};
} else if let Some(items) = &array_val.contains { } else if let Some(items) = &array_val.contains {
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?)); return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
} }
@ -478,7 +660,36 @@ pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) ->
if let Some(subschemas) = &o.subschemas { if let Some(subschemas) = &o.subschemas {
let mut fn_docs = String::new(); let mut fn_docs = String::new();
if let Some(items) = &subschemas.one_of { if let Some(items) = &subschemas.one_of {
if let Some(item) = items.iter().next() { let mut had_enum_string = false;
let mut parsed_enum_values: Vec<String> = Vec::new();
for item in items {
if let schemars::schema::Schema::Object(o) = item {
if let Some(enum_values) = &o.enum_values {
for enum_value in enum_values {
if let serde_json::value::Value::String(enum_value) = enum_value {
had_enum_string = true;
parsed_enum_values.push(format!("\"{}\"", enum_value));
} else {
had_enum_string = false;
break;
}
}
if !had_enum_string {
break;
}
} else {
had_enum_string = false;
break;
}
} else {
had_enum_string = false;
break;
}
}
if had_enum_string && !parsed_enum_values.is_empty() {
return Ok(parsed_enum_values[0].to_string());
} else if let Some(item) = items.iter().next() {
// Let's print out the object's properties. // Let's print out the object's properties.
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?); fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
} }
@ -556,6 +767,8 @@ pub fn completion_item_from_enum_schema(
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::StdLibFn;
#[test] #[test]
fn test_serialize_function() { fn test_serialize_function() {
let some_function = crate::ast::types::Function::StdLib { let some_function = crate::ast::types::Function::StdLib {
@ -577,4 +790,66 @@ mod tests {
} }
); );
} }
#[test]
fn get_autocomplete_snippet_line() {
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
let snippet = line_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"line([${0:3.14}, ${1:3.14}], ${2:%})${}"#);
}
#[test]
fn get_autocomplete_snippet_extrude() {
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"extrude(${0:3.14}, ${1:%})${}"#);
}
#[test]
fn get_autocomplete_snippet_fillet() {
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"fillet({
radius: ${0:3.14},
tags: [${1:"tag_or_edge_fn"}],
}, ${2:%})${}"#
);
}
#[test]
fn get_autocomplete_snippet_start_sketch_on() {
let start_sketch_on_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::StartSketchOn);
let snippet = start_sketch_on_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})${}"#);
}
#[test]
fn get_autocomplete_snippet_pattern_circular_3d() {
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternCircular3d({
arcDegrees: ${0:3.14},
axis: [${1:3.14}, ${2:3.14}, ${3:3.14}],
center: [${2:3.14}, ${3:3.14}, ${4:3.14}],
repetitions: ${3:3.14},
rotateDuplicates: ${4:"string"},
}, ${5:%})${}"#
);
}
#[test]
fn get_autocomplete_snippet_revolve() {
let revolve_fn: Box<dyn StdLibFn> = Box::new(crate::std::revolve::Revolve);
let snippet = revolve_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"revolve({
axis: ${1:"X"},
}, ${2:%})${}"#
);
}
} }

View File

@ -793,7 +793,7 @@ pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMa
let combined = stdlib.combined(); let combined = stdlib.combined();
for internal_fn in combined.values() { for internal_fn in combined.values() {
completions.insert(internal_fn.name(), internal_fn.to_completion_item()); completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
} }
let variable_kinds = VariableKind::to_completion_items()?; let variable_kinds = VariableKind::to_completion_items()?;

View File

@ -45,13 +45,13 @@ pub enum RevolveAxis {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum RevolveAxisAndOrigin { pub enum RevolveAxisAndOrigin {
/// X-axis. /// X-axis.
#[serde(alias = "X")] #[serde(rename = "X", alias = "x")]
X, X,
/// Y-axis. /// Y-axis.
#[serde(alias = "Y")] #[serde(rename = "Y", alias = "y")]
Y, Y,
/// Z-axis. /// Z-axis.
#[serde(alias = "Z")] #[serde(rename = "Z", alias = "z")]
Z, Z,
/// Flip the X-axis. /// Flip the X-axis.
#[serde(rename = "-X", alias = "-x")] #[serde(rename = "-X", alias = "-x")]
@ -337,7 +337,7 @@ mod tests {
fn test_deserialize_revolve_axis() { fn test_deserialize_revolve_axis() {
let data = RevolveAxis::Axis(RevolveAxisAndOrigin::X); let data = RevolveAxis::Axis(RevolveAxisAndOrigin::X);
let mut str_json = serde_json::to_string(&data).unwrap(); let mut str_json = serde_json::to_string(&data).unwrap();
assert_eq!(str_json, "\"x\""); assert_eq!(str_json, "\"X\"");
str_json = "\"Y\"".to_string(); str_json = "\"Y\"".to_string();
let data: RevolveAxis = serde_json::from_str(&str_json).unwrap(); let data: RevolveAxis = serde_json::from_str(&str_json).unwrap();