2023-08-25 13:41:04 -07:00
|
|
|
//! Functions for generating docs for our stdlib functions.
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod gen_std_tests;
|
|
|
|
|
2024-06-19 13:57:50 -07:00
|
|
|
use std::path::Path;
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
use anyhow::Result;
|
2023-09-05 16:02:27 -07:00
|
|
|
use schemars::JsonSchema;
|
2023-08-25 13:41:04 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-09-05 16:02:27 -07:00
|
|
|
use tower_lsp::lsp_types::{
|
|
|
|
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, InsertTextFormat, MarkupContent,
|
|
|
|
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
|
|
|
};
|
2023-08-25 13:41:04 -07:00
|
|
|
|
2024-06-19 13:57:50 -07:00
|
|
|
use crate::std::Primitive;
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2023-08-25 13:41:04 -07:00
|
|
|
pub struct StdLibFnData {
|
|
|
|
/// The name of the function.
|
|
|
|
pub name: String,
|
|
|
|
/// The summary of the function.
|
|
|
|
pub summary: String,
|
|
|
|
/// The description of the function.
|
|
|
|
pub description: String,
|
|
|
|
/// The tags of the function.
|
|
|
|
pub tags: Vec<String>,
|
|
|
|
/// The args of the function.
|
|
|
|
pub args: Vec<StdLibFnArg>,
|
|
|
|
/// The return value of the function.
|
2023-09-05 16:02:27 -07:00
|
|
|
pub return_value: Option<StdLibFnArg>,
|
2023-08-25 13:41:04 -07:00
|
|
|
/// If the function is unpublished.
|
|
|
|
pub unpublished: bool,
|
|
|
|
/// If the function is deprecated.
|
|
|
|
pub deprecated: bool,
|
2024-03-13 12:56:46 -07:00
|
|
|
/// Code examples.
|
|
|
|
/// These are tested and we know they compile and execute.
|
|
|
|
pub examples: Vec<String>,
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// This struct defines a single argument to a stdlib function.
|
2023-09-05 16:02:27 -07:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
2024-09-20 16:28:52 -05:00
|
|
|
// There's a bug in ts_rs where this isn't correctly imported by StdLibFnData.
|
|
|
|
#[ts(export_to = "StdLibFnData.ts")]
|
2023-09-05 16:02:27 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2023-08-25 13:41:04 -07:00
|
|
|
pub struct StdLibFnArg {
|
|
|
|
/// The name of the argument.
|
|
|
|
pub name: String,
|
|
|
|
/// The type of the argument.
|
|
|
|
pub type_: String,
|
|
|
|
/// The schema of the argument.
|
2024-01-11 15:31:35 -08:00
|
|
|
#[ts(type = "any")]
|
2024-09-30 12:30:22 -07:00
|
|
|
pub schema: schemars::schema::RootSchema,
|
2023-08-25 13:41:04 -07:00
|
|
|
/// If the argument is required.
|
|
|
|
pub required: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StdLibFnArg {
|
2024-09-27 07:37:46 -07:00
|
|
|
/// If the argument is a primitive.
|
|
|
|
pub fn is_primitive(&self) -> Result<bool> {
|
2024-09-30 12:30:22 -07:00
|
|
|
is_primitive(&self.schema.schema.clone().into()).map(|r| r.is_some())
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
pub fn get_autocomplete_string(&self) -> Result<String> {
|
2024-09-30 12:30:22 -07:00
|
|
|
get_autocomplete_string_from_schema(&self.schema.schema.clone().into())
|
2023-08-31 22:19:23 -07:00
|
|
|
}
|
|
|
|
|
2024-04-12 13:28:58 -07:00
|
|
|
pub fn get_autocomplete_snippet(&self, index: usize) -> Result<Option<(usize, String)>> {
|
2024-09-27 15:44:44 -07:00
|
|
|
if self.type_ == "Sketch"
|
|
|
|
|| self.type_ == "SketchSet"
|
|
|
|
|| self.type_ == "Solid"
|
|
|
|
|| self.type_ == "SolidSet"
|
2024-04-23 10:31:20 -07:00
|
|
|
|| self.type_ == "SketchSurface"
|
2024-09-27 15:44:44 -07:00
|
|
|
|| self.type_ == "SketchOrSurface"
|
2024-04-23 10:31:20 -07:00
|
|
|
{
|
2024-04-12 13:28:58 -07:00
|
|
|
return Ok(Some((index, format!("${{{}:{}}}", index, "%"))));
|
2024-10-30 16:52:17 -04:00
|
|
|
} else if (self.type_ == "TagDeclarator" || self.type_ == "TagNode") && self.required {
|
2024-06-24 14:45:07 -07:00
|
|
|
return Ok(Some((index, format!("${{{}:{}}}", index, "$myTag"))));
|
|
|
|
} else if self.type_ == "TagIdentifier" && self.required {
|
|
|
|
// TODO: actually use the ast to populate this.
|
|
|
|
return Ok(Some((index, format!("${{{}:{}}}", index, "myTag"))));
|
2024-10-01 08:50:23 -05:00
|
|
|
} else if self.type_ == "[KclValue]" && self.required {
|
2024-10-01 13:11:09 -07:00
|
|
|
return Ok(Some((index, format!("${{{}:{}}}", index, "[0..9]"))));
|
2024-10-04 13:26:16 -05:00
|
|
|
} else if self.type_ == "KclValue" && self.required {
|
|
|
|
return Ok(Some((index, format!("${{{}:{}}}", index, "3"))));
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
2024-09-30 12:30:22 -07:00
|
|
|
get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index)
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
pub fn description(&self) -> Option<String> {
|
2024-09-30 12:30:22 -07:00
|
|
|
get_description_string_from_schema(&self.schema.clone())
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<StdLibFnArg> for ParameterInformation {
|
|
|
|
fn from(arg: StdLibFnArg) -> Self {
|
|
|
|
ParameterInformation {
|
|
|
|
label: ParameterLabel::Simple(arg.name.to_string()),
|
|
|
|
documentation: arg.description().map(|description| {
|
|
|
|
Documentation::MarkupContent(MarkupContent {
|
|
|
|
kind: MarkupKind::Markdown,
|
|
|
|
value: description,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This trait defines functions called upon stdlib functions to generate
|
|
|
|
/// documentation for them.
|
2023-09-05 16:02:27 -07:00
|
|
|
pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
2023-08-25 13:41:04 -07:00
|
|
|
/// The name of the function.
|
|
|
|
fn name(&self) -> String;
|
|
|
|
|
|
|
|
/// The summary of the function.
|
|
|
|
fn summary(&self) -> String;
|
|
|
|
|
|
|
|
/// The description of the function.
|
|
|
|
fn description(&self) -> String;
|
|
|
|
|
|
|
|
/// The tags of the function.
|
|
|
|
fn tags(&self) -> Vec<String>;
|
|
|
|
|
|
|
|
/// The args of the function.
|
2024-09-28 11:51:08 -07:00
|
|
|
fn args(&self, inline_subschemas: bool) -> Vec<StdLibFnArg>;
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
|
|
/// The return value of the function.
|
2024-09-28 11:51:08 -07:00
|
|
|
fn return_value(&self, inline_subschemas: bool) -> Option<StdLibFnArg>;
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
|
|
/// If the function is unpublished.
|
|
|
|
fn unpublished(&self) -> bool;
|
|
|
|
|
|
|
|
/// If the function is deprecated.
|
|
|
|
fn deprecated(&self) -> bool;
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
/// Any example code blocks.
|
|
|
|
fn examples(&self) -> Vec<String>;
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
/// The function itself.
|
|
|
|
fn std_lib_fn(&self) -> crate::std::StdFn;
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
/// Helper function to clone the boxed trait object.
|
|
|
|
fn clone_box(&self) -> Box<dyn StdLibFn>;
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
/// Return a JSON struct representing the function.
|
|
|
|
fn to_json(&self) -> Result<StdLibFnData> {
|
|
|
|
Ok(StdLibFnData {
|
|
|
|
name: self.name(),
|
|
|
|
summary: self.summary(),
|
|
|
|
description: self.description(),
|
|
|
|
tags: self.tags(),
|
2024-09-28 11:51:08 -07:00
|
|
|
args: self.args(false),
|
|
|
|
return_value: self.return_value(false),
|
2023-08-25 13:41:04 -07:00
|
|
|
unpublished: self.unpublished(),
|
|
|
|
deprecated: self.deprecated(),
|
2024-03-13 12:56:46 -07:00
|
|
|
examples: self.examples(),
|
2023-08-25 13:41:04 -07:00
|
|
|
})
|
|
|
|
}
|
2023-08-31 22:19:23 -07:00
|
|
|
|
|
|
|
fn fn_signature(&self) -> String {
|
|
|
|
let mut signature = String::new();
|
|
|
|
signature.push_str(&format!("{}(", self.name()));
|
2024-09-28 11:51:08 -07:00
|
|
|
for (i, arg) in self.args(false).iter().enumerate() {
|
2023-08-31 22:19:23 -07:00
|
|
|
if i > 0 {
|
|
|
|
signature.push_str(", ");
|
|
|
|
}
|
2024-03-07 12:35:56 -08:00
|
|
|
if arg.required {
|
|
|
|
signature.push_str(&format!("{}: {}", arg.name, arg.type_));
|
|
|
|
} else {
|
|
|
|
signature.push_str(&format!("{}?: {}", arg.name, arg.type_));
|
|
|
|
}
|
2023-08-31 22:19:23 -07:00
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
signature.push(')');
|
2024-09-28 11:51:08 -07:00
|
|
|
if let Some(return_value) = self.return_value(false) {
|
2023-09-05 16:02:27 -07:00
|
|
|
signature.push_str(&format!(" -> {}", return_value.type_));
|
|
|
|
}
|
2023-08-31 22:19:23 -07:00
|
|
|
|
|
|
|
signature
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
|
2024-04-12 13:28:58 -07:00
|
|
|
fn to_completion_item(&self) -> Result<CompletionItem> {
|
|
|
|
Ok(CompletionItem {
|
2023-09-05 16:02:27 -07:00
|
|
|
label: self.name(),
|
|
|
|
label_details: Some(CompletionItemLabelDetails {
|
|
|
|
detail: Some(self.fn_signature().replace(&self.name(), "")),
|
|
|
|
description: None,
|
|
|
|
}),
|
|
|
|
kind: Some(CompletionItemKind::FUNCTION),
|
|
|
|
detail: None,
|
|
|
|
documentation: Some(Documentation::MarkupContent(MarkupContent {
|
|
|
|
kind: MarkupKind::Markdown,
|
|
|
|
value: if !self.description().is_empty() {
|
|
|
|
format!("{}\n\n{}", self.summary(), self.description())
|
|
|
|
} else {
|
|
|
|
self.summary()
|
|
|
|
},
|
|
|
|
})),
|
|
|
|
deprecated: Some(self.deprecated()),
|
|
|
|
preselect: None,
|
|
|
|
sort_text: None,
|
|
|
|
filter_text: None,
|
2024-04-12 13:28:58 -07:00
|
|
|
insert_text: Some(self.to_autocomplete_snippet()?),
|
2023-09-05 16:02:27 -07:00
|
|
|
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
|
|
|
insert_text_mode: None,
|
|
|
|
text_edit: None,
|
|
|
|
additional_text_edits: None,
|
|
|
|
command: None,
|
|
|
|
commit_characters: None,
|
|
|
|
data: None,
|
|
|
|
tags: None,
|
2024-04-12 13:28:58 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_autocomplete_snippet(&self) -> Result<String> {
|
|
|
|
let mut args = Vec::new();
|
|
|
|
let mut index = 0;
|
2024-09-28 11:51:08 -07:00
|
|
|
for arg in self.args(true).iter() {
|
2024-04-12 13:28:58 -07:00
|
|
|
if let Some((i, arg_str)) = arg.get_autocomplete_snippet(index)? {
|
|
|
|
index = i + 1;
|
|
|
|
args.push(arg_str);
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
2024-04-12 13:28:58 -07:00
|
|
|
// We end with ${} so you can jump to the end of the snippet.
|
|
|
|
// After the last argument.
|
|
|
|
Ok(format!("{}({})${{}}", self.name(), args.join(", ")))
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn to_signature_help(&self) -> SignatureHelp {
|
2023-11-01 17:34:54 -05:00
|
|
|
// Fill this in based on the current position of the cursor.
|
2023-09-05 16:02:27 -07:00
|
|
|
let active_parameter = None;
|
|
|
|
|
|
|
|
SignatureHelp {
|
|
|
|
signatures: vec![SignatureInformation {
|
|
|
|
label: self.name(),
|
|
|
|
documentation: Some(Documentation::MarkupContent(MarkupContent {
|
|
|
|
kind: MarkupKind::Markdown,
|
|
|
|
value: if !self.description().is_empty() {
|
|
|
|
format!("{}\n\n{}", self.summary(), self.description())
|
|
|
|
} else {
|
|
|
|
self.summary()
|
|
|
|
},
|
|
|
|
})),
|
2024-09-28 11:51:08 -07:00
|
|
|
parameters: Some(self.args(true).into_iter().map(|arg| arg.into()).collect()),
|
2023-09-05 16:02:27 -07:00
|
|
|
active_parameter,
|
|
|
|
}],
|
|
|
|
active_signature: Some(0),
|
|
|
|
active_parameter,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JsonSchema for dyn StdLibFn {
|
|
|
|
fn schema_name() -> String {
|
|
|
|
"StdLibFn".to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
|
|
|
gen.subschema_for::<StdLibFnData>()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Serialize for Box<dyn StdLibFn> {
|
|
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
|
|
self.to_json().unwrap().serialize(serializer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for Box<dyn StdLibFn> {
|
|
|
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
|
|
let data = StdLibFnData::deserialize(deserializer)?;
|
|
|
|
let stdlib = crate::std::StdLib::new();
|
|
|
|
let stdlib_fn = stdlib
|
|
|
|
.get(&data.name)
|
|
|
|
.ok_or_else(|| serde::de::Error::custom(format!("StdLibFn {} not found", data.name)))?;
|
|
|
|
Ok(stdlib_fn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ts_rs::TS for dyn StdLibFn {
|
2024-05-22 14:22:07 -04:00
|
|
|
type WithoutGenerics = Self;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
fn name() -> String {
|
|
|
|
"StdLibFnData".to_string()
|
|
|
|
}
|
|
|
|
|
2024-05-22 14:22:07 -04:00
|
|
|
fn decl() -> String {
|
|
|
|
StdLibFnData::decl()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decl_concrete() -> String {
|
|
|
|
StdLibFnData::decl_concrete()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn inline() -> String {
|
|
|
|
StdLibFnData::inline()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn inline_flattened() -> String {
|
|
|
|
StdLibFnData::inline_flattened()
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
2024-05-22 14:22:07 -04:00
|
|
|
fn output_path() -> Option<&'static Path> {
|
|
|
|
StdLibFnData::output_path()
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for Box<dyn StdLibFn> {
|
|
|
|
fn clone(&self) -> Box<dyn StdLibFn> {
|
|
|
|
self.clone_box()
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-09-30 12:30:22 -07:00
|
|
|
pub fn get_description_string_from_schema(schema: &schemars::schema::RootSchema) -> Option<String> {
|
|
|
|
if let Some(metadata) = &schema.schema.metadata {
|
|
|
|
if let Some(description) = &metadata.description {
|
|
|
|
return Some(description.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(reference) = &schema.schema.reference {
|
|
|
|
if let Some(definition) = schema.definitions.get(reference.split('/').last().unwrap_or("")) {
|
|
|
|
let schemars::schema::Schema::Object(definition) = definition else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
if let Some(metadata) = &definition.metadata {
|
|
|
|
if let Some(description) = &metadata.description {
|
|
|
|
return Some(description.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have subschemas iterate over them and recursively create references.
|
|
|
|
if let Some(subschema) = &schema.schema.subschemas {
|
|
|
|
if let Some(one_of) = &subschema.one_of {
|
|
|
|
if one_of.len() == 1 {
|
|
|
|
return get_description_string_from_schema(&schemars::schema::RootSchema {
|
|
|
|
meta_schema: schema.meta_schema.clone(),
|
|
|
|
schema: one_of[0].clone().into(),
|
|
|
|
definitions: schema.definitions.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(all_of) = &subschema.all_of {
|
|
|
|
if all_of.len() == 1 {
|
|
|
|
return get_description_string_from_schema(&schemars::schema::RootSchema {
|
|
|
|
meta_schema: schema.meta_schema.clone(),
|
|
|
|
schema: all_of[0].clone().into(),
|
|
|
|
definitions: schema.definitions.clone(),
|
|
|
|
});
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
}
|
2024-09-28 11:51:08 -07:00
|
|
|
|
2024-09-30 12:30:22 -07:00
|
|
|
if let Some(any_of) = &subschema.any_of {
|
|
|
|
if any_of.len() == 1 {
|
|
|
|
return get_description_string_from_schema(&schemars::schema::RootSchema {
|
|
|
|
meta_schema: schema.meta_schema.clone(),
|
|
|
|
schema: any_of[0].clone().into(),
|
|
|
|
definitions: schema.definitions.clone(),
|
|
|
|
});
|
2024-09-28 11:51:08 -07:00
|
|
|
}
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
pub fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> {
|
2023-08-25 13:41:04 -07:00
|
|
|
match schema {
|
|
|
|
schemars::schema::Schema::Object(o) => {
|
2024-09-27 07:37:46 -07:00
|
|
|
if o.enum_values.is_some() {
|
|
|
|
// It's an enum so it's not a primitive.
|
|
|
|
return Ok(None);
|
2024-02-12 12:18:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there
|
2023-08-25 13:41:04 -07:00
|
|
|
if let Some(format) = &o.format {
|
|
|
|
if format == "uuid" {
|
2024-09-27 07:37:46 -07:00
|
|
|
return Ok(Some(Primitive::Uuid));
|
2024-04-23 15:59:12 -07:00
|
|
|
} else if format == "double"
|
|
|
|
|| format == "uint"
|
2024-07-25 21:18:52 -04:00
|
|
|
|| format == "int32"
|
2024-04-23 15:59:12 -07:00
|
|
|
|| format == "int64"
|
2024-07-09 12:24:42 -04:00
|
|
|
|| format == "uint8"
|
2024-04-23 15:59:12 -07:00
|
|
|
|| format == "uint32"
|
|
|
|
|| format == "uint64"
|
|
|
|
{
|
2024-09-27 07:37:46 -07:00
|
|
|
return Ok(Some(Primitive::Number));
|
2023-08-25 13:41:04 -07:00
|
|
|
} else {
|
|
|
|
anyhow::bail!("unknown format: {}", format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
if o.object.is_some() {
|
|
|
|
// It's an object so it's not a primitive.
|
|
|
|
return Ok(None);
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
if o.array.is_some() {
|
|
|
|
return Ok(None);
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
if o.subschemas.is_some() {
|
|
|
|
return Ok(None);
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-08-16 19:48:09 -07:00
|
|
|
if let Some(schemars::schema::SingleOrVec::Single(single)) = &o.instance_type {
|
|
|
|
if schemars::schema::InstanceType::Boolean == **single {
|
2024-09-27 07:37:46 -07:00
|
|
|
return Ok(Some(Primitive::Bool));
|
2024-08-16 19:48:09 -07:00
|
|
|
} else if schemars::schema::InstanceType::String == **single
|
|
|
|
|| schemars::schema::InstanceType::Null == **single
|
|
|
|
{
|
2024-09-27 07:37:46 -07:00
|
|
|
return Ok(Some(Primitive::String));
|
2024-08-16 19:48:09 -07:00
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
if o.reference.is_some() {
|
|
|
|
return Ok(None);
|
2024-06-23 19:19:24 -07:00
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
anyhow::bail!("unknown type: {:#?}", o)
|
|
|
|
}
|
2024-09-27 07:37:46 -07:00
|
|
|
schemars::schema::Schema::Bool(_) => Ok(Some(Primitive::Bool)),
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
}
|
2023-08-31 22:19:23 -07:00
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
fn get_autocomplete_snippet_from_schema(
|
2024-04-12 13:28:58 -07:00
|
|
|
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))));
|
2024-07-29 01:16:18 -07:00
|
|
|
} else if format == "double" {
|
2024-04-12 13:28:58 -07:00
|
|
|
return Ok(Some((index, format!(r#"${{{}:3.14}}"#, index))));
|
2024-07-29 01:16:18 -07:00
|
|
|
} else if format == "uint" || format == "int64" || format == "uint32" || format == "uint64" {
|
|
|
|
return Ok(Some((index, format!(r#"${{{}:10}}"#, index))));
|
2024-04-12 13:28:58 -07:00
|
|
|
} 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.
|
2024-09-24 19:35:23 -07:00
|
|
|
let mut i = index;
|
2024-08-12 21:39:49 -07:00
|
|
|
for (prop_name, prop) in obj_val.properties.iter() {
|
2024-04-12 13:28:58 -07:00
|
|
|
if prop_name.starts_with('_') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-08-12 21:39:49 -07:00
|
|
|
// Tolerance is a an optional property that we don't want to show in the
|
|
|
|
// autocomplete, since it is mostly for advanced users.
|
|
|
|
if prop_name == "tolerance" {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-09-24 19:35:23 -07:00
|
|
|
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(prop, i)? {
|
2024-04-12 13:28:58 -07:00
|
|
|
fn_docs.push_str(&format!("\t{}: {},\n", prop_name, snippet));
|
2024-09-24 19:35:23 -07:00
|
|
|
i = new_index + 1;
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn_docs.push('}');
|
|
|
|
|
2024-09-24 19:35:23 -07:00
|
|
|
return Ok(Some((i - 1, fn_docs)));
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2024-09-24 19:35:23 -07:00
|
|
|
let mut i = index;
|
2024-04-12 13:28:58 -07:00
|
|
|
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() {
|
2024-09-24 19:35:23 -07:00
|
|
|
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
|
|
|
i = new_index + 1;
|
2024-04-12 13:28:58 -07:00
|
|
|
fn_docs.push_str(&snippet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if let Some(items) = &subschemas.any_of {
|
|
|
|
if let Some(item) = items.iter().next() {
|
2024-09-24 19:35:23 -07:00
|
|
|
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
|
|
|
i = new_index + 1;
|
2024-04-12 13:28:58 -07:00
|
|
|
fn_docs.push_str(&snippet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
|
|
|
|
}
|
|
|
|
|
2024-09-24 19:35:23 -07:00
|
|
|
return Ok(Some((i - 1, fn_docs)));
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
|
|
|
|
2024-08-16 19:48:09 -07:00
|
|
|
if let Some(schemars::schema::SingleOrVec::Single(single)) = &o.instance_type {
|
|
|
|
if schemars::schema::InstanceType::Boolean == **single {
|
|
|
|
return Ok(Some((index, format!(r#"${{{}:false}}"#, index))));
|
|
|
|
} else if schemars::schema::InstanceType::String == **single {
|
|
|
|
return Ok(Some((index, format!(r#"${{{}:"string"}}"#, index))));
|
|
|
|
} else if schemars::schema::InstanceType::Null == **single {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
2024-04-12 13:28:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
anyhow::bail!("unknown type: {:#?}", o)
|
|
|
|
}
|
|
|
|
schemars::schema::Schema::Bool(_) => Ok(Some((index, format!(r#"${{{}:false}}"#, index)))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-27 07:37:46 -07:00
|
|
|
fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Result<String> {
|
2023-08-31 22:19:23 -07:00
|
|
|
match schema {
|
|
|
|
schemars::schema::Schema::Object(o) => {
|
2024-04-12 13:28:58 -07:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
if let Some(format) = &o.format {
|
|
|
|
if format == "uuid" {
|
|
|
|
return Ok(Primitive::Uuid.to_string());
|
2024-04-23 15:59:12 -07:00
|
|
|
} else if format == "double"
|
|
|
|
|| format == "uint"
|
|
|
|
|| format == "int64"
|
|
|
|
|| format == "uint32"
|
|
|
|
|| format == "uint64"
|
|
|
|
{
|
2023-08-31 22:19:23 -07:00
|
|
|
return Ok(Primitive::Number.to_string());
|
|
|
|
} 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 (prop_name, prop) in obj_val.properties.iter() {
|
|
|
|
if prop_name.starts_with('_') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn_docs.push_str(&format!(
|
2023-09-05 16:02:27 -07:00
|
|
|
"\t{}: {},\n",
|
2023-08-31 22:19:23 -07:00
|
|
|
prop_name,
|
|
|
|
get_autocomplete_string_from_schema(prop)?,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn_docs.push('}');
|
|
|
|
|
|
|
|
return Ok(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.
|
2024-04-12 13:28:58 -07:00
|
|
|
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)?));
|
|
|
|
}
|
|
|
|
};
|
2023-08-31 22:19:23 -07:00
|
|
|
} else if let Some(items) = &array_val.contains {
|
|
|
|
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(subschemas) = &o.subschemas {
|
|
|
|
let mut fn_docs = String::new();
|
|
|
|
if let Some(items) = &subschemas.one_of {
|
2024-04-12 13:28:58 -07:00
|
|
|
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() {
|
2023-08-31 22:19:23 -07:00
|
|
|
// Let's print out the object's properties.
|
|
|
|
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
|
|
|
|
}
|
|
|
|
} else if let Some(items) = &subschemas.any_of {
|
|
|
|
if let Some(item) = items.iter().next() {
|
|
|
|
// Let's print out the object's properties.
|
|
|
|
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(fn_docs);
|
|
|
|
}
|
|
|
|
|
2024-08-16 19:48:09 -07:00
|
|
|
if let Some(schemars::schema::SingleOrVec::Single(single)) = &o.instance_type {
|
|
|
|
if schemars::schema::InstanceType::Boolean == **single {
|
|
|
|
return Ok(Primitive::Bool.to_string());
|
|
|
|
} else if schemars::schema::InstanceType::String == **single
|
|
|
|
|| schemars::schema::InstanceType::Null == **single
|
|
|
|
{
|
|
|
|
return Ok(Primitive::String.to_string());
|
|
|
|
}
|
2023-08-31 22:19:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
anyhow::bail!("unknown type: {:#?}", o)
|
|
|
|
}
|
|
|
|
schemars::schema::Schema::Bool(_) => Ok(Primitive::Bool.to_string()),
|
|
|
|
}
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
pub fn completion_item_from_enum_schema(
|
|
|
|
schema: &schemars::schema::Schema,
|
|
|
|
kind: CompletionItemKind,
|
|
|
|
) -> Result<CompletionItem> {
|
|
|
|
// Get the docs for the schema.
|
|
|
|
let schemars::schema::Schema::Object(o) = schema else {
|
|
|
|
anyhow::bail!("expected object schema: {:#?}", schema);
|
|
|
|
};
|
2024-09-30 12:30:22 -07:00
|
|
|
let description = get_description_string_from_schema(&schemars::schema::RootSchema {
|
|
|
|
schema: o.clone(),
|
|
|
|
..Default::default()
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
2023-09-05 16:02:27 -07:00
|
|
|
let Some(enum_values) = o.enum_values.as_ref() else {
|
|
|
|
anyhow::bail!("expected enum values: {:#?}", o);
|
|
|
|
};
|
|
|
|
|
|
|
|
if enum_values.len() > 1 {
|
|
|
|
anyhow::bail!("expected only one enum value: {:#?}", o);
|
|
|
|
}
|
|
|
|
|
|
|
|
if enum_values.is_empty() {
|
|
|
|
anyhow::bail!("expected at least one enum value: {:#?}", o);
|
|
|
|
}
|
|
|
|
|
2024-04-22 14:53:49 -07:00
|
|
|
let serde_json::Value::String(ref enum_value) = enum_values[0] else {
|
|
|
|
anyhow::bail!("expected string enum value: {:#?}", enum_values[0]);
|
|
|
|
};
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
Ok(CompletionItem {
|
2024-04-22 14:53:49 -07:00
|
|
|
label: enum_value.to_string(),
|
2023-09-05 16:02:27 -07:00
|
|
|
label_details: None,
|
|
|
|
kind: Some(kind),
|
|
|
|
detail: Some(description.to_string()),
|
|
|
|
documentation: Some(Documentation::MarkupContent(MarkupContent {
|
|
|
|
kind: MarkupKind::Markdown,
|
|
|
|
value: description.to_string(),
|
|
|
|
})),
|
|
|
|
deprecated: Some(false),
|
|
|
|
preselect: None,
|
|
|
|
sort_text: None,
|
|
|
|
filter_text: None,
|
|
|
|
insert_text: None,
|
|
|
|
insert_text_format: None,
|
|
|
|
insert_text_mode: None,
|
|
|
|
text_edit: None,
|
|
|
|
additional_text_edits: None,
|
|
|
|
command: None,
|
|
|
|
commit_characters: None,
|
|
|
|
data: None,
|
|
|
|
tags: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
2024-04-12 13:28:58 -07:00
|
|
|
use super::StdLibFn;
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_serialize_function() {
|
2023-09-17 21:57:43 -07:00
|
|
|
let some_function = crate::ast::types::Function::StdLib {
|
2023-09-05 16:02:27 -07:00
|
|
|
func: Box::new(crate::std::sketch::Line),
|
|
|
|
};
|
|
|
|
let serialized = serde_json::to_string(&some_function).unwrap();
|
|
|
|
assert!(serialized.contains(r#"{"type":"StdLib""#));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deserialize_function() {
|
2024-09-28 11:51:08 -07:00
|
|
|
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{},"schemaDefinitions":{}},"args":[],"unpublished":false,"deprecated":false, "examples": []}}"#;
|
2023-09-17 21:57:43 -07:00
|
|
|
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
some_function,
|
2023-09-17 21:57:43 -07:00
|
|
|
crate::ast::types::Function::StdLib {
|
2024-06-27 15:43:49 -07:00
|
|
|
func: Box::new(crate::std::sketch::Line)
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2024-04-12 13:28:58 -07:00
|
|
|
|
|
|
|
#[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() {
|
2024-07-29 01:16:18 -07:00
|
|
|
// We test this one specifically because it has ints and floats and strings.
|
2024-04-12 13:28:58 -07:00
|
|
|
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({
|
2024-10-15 13:25:03 -07:00
|
|
|
instances: ${0:10},
|
2024-04-12 13:28:58 -07:00
|
|
|
axis: [${1:3.14}, ${2:3.14}, ${3:3.14}],
|
2024-09-24 19:35:23 -07:00
|
|
|
center: [${4:3.14}, ${5:3.14}, ${6:3.14}],
|
2024-09-27 18:23:46 -07:00
|
|
|
arcDegrees: ${7:3.14},
|
2024-09-24 19:35:23 -07:00
|
|
|
rotateDuplicates: ${8:false},
|
|
|
|
}, ${9:%})${}"#
|
2024-04-12 13:28:58 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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({
|
2024-08-12 21:39:49 -07:00
|
|
|
axis: ${0:"X"},
|
|
|
|
}, ${1:%})${}"#
|
2024-04-12 13:28:58 -07:00
|
|
|
);
|
|
|
|
}
|
2024-07-03 18:05:24 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn get_autocomplete_snippet_circle() {
|
|
|
|
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
|
|
|
|
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
|
2024-09-23 22:42:51 +10:00
|
|
|
assert_eq!(
|
|
|
|
snippet,
|
|
|
|
r#"circle({
|
|
|
|
center: [${0:3.14}, ${1:3.14}],
|
2024-09-24 19:35:23 -07:00
|
|
|
radius: ${2:3.14},
|
|
|
|
}, ${3:%})${}"#
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn get_autocomplete_snippet_arc() {
|
|
|
|
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
|
|
|
|
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
snippet,
|
|
|
|
r#"arc({
|
2024-09-27 18:23:46 -07:00
|
|
|
angleStart: ${0:3.14},
|
|
|
|
angleEnd: ${1:3.14},
|
2024-09-24 19:35:23 -07:00
|
|
|
radius: ${2:3.14},
|
|
|
|
}, ${3:%})${}"#
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-10-01 08:50:23 -05:00
|
|
|
#[test]
|
|
|
|
fn get_autocomplete_snippet_map() {
|
|
|
|
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
|
2024-10-01 13:11:09 -07:00
|
|
|
let snippet = map_fn.to_autocomplete_snippet().unwrap();
|
|
|
|
assert_eq!(snippet, r#"map(${0:[0..9]})${}"#);
|
2024-10-01 08:50:23 -05:00
|
|
|
}
|
|
|
|
|
2024-09-24 19:35:23 -07:00
|
|
|
#[test]
|
|
|
|
fn get_autocomplete_snippet_pattern_linear_2d() {
|
|
|
|
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
|
|
|
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
snippet,
|
|
|
|
r#"patternLinear2d({
|
2024-10-15 13:25:03 -07:00
|
|
|
instances: ${0:10},
|
2024-09-27 18:23:46 -07:00
|
|
|
distance: ${1:3.14},
|
|
|
|
axis: [${2:3.14}, ${3:3.14}],
|
2024-09-24 19:35:23 -07:00
|
|
|
}, ${4:%})${}"#
|
2024-09-23 22:42:51 +10:00
|
|
|
);
|
2024-07-03 18:05:24 -07:00
|
|
|
}
|
2024-10-01 13:11:09 -07:00
|
|
|
|
|
|
|
// We want to test the snippets we compile at lsp start.
|
|
|
|
#[test]
|
|
|
|
fn get_all_stdlib_autocomplete_snippets() {
|
|
|
|
let stdlib = crate::std::StdLib::new();
|
|
|
|
crate::lsp::kcl::get_completions_from_stdlib(&stdlib).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// We want to test the signatures we compile at lsp start.
|
|
|
|
#[test]
|
|
|
|
fn get_all_stdlib_signatures() {
|
|
|
|
let stdlib = crate::std::StdLib::new();
|
|
|
|
crate::lsp::kcl::get_signatures_from_stdlib(&stdlib).unwrap();
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|