From 205f2a325e2404af07424d3b78399bb98d7be213 Mon Sep 17 00:00:00 2001 From: Shrey Bana Date: Tue, 25 Feb 2025 16:51:25 +0530 Subject: [PATCH] feat: Added support for completions in monaco editor. - Monaco input type now accepts a vector of json values to be used for completions. - Added JS-FFI(via wasm-bindgen) to create monaco completions object. This is required as we have to call another JS-FFI to register this object, and that FFI itself accepts a foreign JS type, so needed to write the constructor in JS. - Completions get registered on render & cleaned up on de-render. --- Cargo.lock | 1 + crates/frontend/Cargo.toml | 1 + crates/frontend/assets/utils.js | 73 +++++++++++++++++++ .../src/components/default_config_form.rs | 2 +- .../frontend/src/components/dimension_form.rs | 2 +- crates/frontend/src/components/input.rs | 20 +++-- .../frontend/src/components/monaco_editor.rs | 41 ++++++++++- .../src/components/type_template_form.rs | 2 +- 8 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 crates/frontend/assets/utils.js diff --git a/Cargo.lock b/Cargo.lock index 1a6131fe6..2e4ea4aa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2085,6 +2085,7 @@ dependencies = [ "once_cell", "reqwest", "serde", + "serde-wasm-bindgen", "serde_json", "strum", "strum_macros", diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index 397466173..aaa81f5ca 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -37,6 +37,7 @@ web-sys = { version = "0.3.64", features = [ "Window", "Storage", ] } +serde-wasm-bindgen = "0.6.5" [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] diff --git a/crates/frontend/assets/utils.js b/crates/frontend/assets/utils.js new file mode 100644 index 000000000..efc487d6b --- /dev/null +++ b/crates/frontend/assets/utils.js @@ -0,0 +1,73 @@ +function toStr(v) { + switch (typeof v) { + case "string": + return v; + case "object": + return JSON.stringify(v); + default: + return v.toString(); + } +} + +/// Returns a `CompletionItemProvider` https://microsoft.github.io/monaco-editor/docs.html#interfaces/languages.CompletionItemProvider.html +export function newSuggestionsProvider(suggestions) { + if (typeof suggestions !== "object" || !Array.isArray(suggestions)) { + throw new Error(`${suggestions} is not an array!`); + } + + let triggers = []; + for (const s of suggestions) { + switch (typeof s) { + case "string": + triggers.push('"'); + triggers.push("'"); + break; + // Not sure if we should help in such cases... + case "object": + if (s !== null) { + if (Array.isArray(s)) { + triggers.push("["); + } else { + triggers.push("{"); + } + } else if (s == null) { + triggers.push("n"); + } + break; + case "number": + // While technically not part of JsonSchema, + // if we can, then why not. + case "bigint": + const d = s.toString()[0]; + triggers.push(d) + break; + // Screw these. + case "undefined": + case "function": + case "symbol": + default: + console.debug(`Un-supported type in suggestions: ${typeof s}, skipping.`); + } + } + // Just in case duplicates lead to some bug. + triggers = [...new Set(triggers)]; + console.debug(`Trigger characters for suggestions: ${triggers}`); + + return { + provideCompletionItems: function (model, position) { + return { + suggestions: suggestions.map((s) => ({ + // This is the text that shows up in the completion hover. + label: toStr(s), + // FIXME Coupling w/ private Enum, this can break w/ a newer version of + // monaco. + kind: 15, // Maps to the `Enum` varaint of `Kind`. + insertText: toStr(s), + detail: "Enum variant", + documentation: "Json Enum", + })), + }; + }, + triggerCharacters: triggers, + }; +} diff --git a/crates/frontend/src/components/default_config_form.rs b/crates/frontend/src/components/default_config_form.rs index d66baec22..713f6c8e1 100644 --- a/crates/frontend/src/components/default_config_form.rs +++ b/crates/frontend/src/components/default_config_form.rs @@ -288,7 +288,7 @@ where on_change=Callback::new(move |new_config_schema| { config_schema_ws.set(new_config_schema) }) - r#type=InputType::Monaco + r#type=InputType::Monaco(vec![]) /> diff --git a/crates/frontend/src/components/input.rs b/crates/frontend/src/components/input.rs index c4ba837df..850f60fc5 100644 --- a/crates/frontend/src/components/input.rs +++ b/crates/frontend/src/components/input.rs @@ -20,7 +20,7 @@ pub enum InputType { Number, Integer, Toggle, - Monaco, + Monaco(Vec), Select(EnumVariants), Disabled, } @@ -31,7 +31,7 @@ impl InputType { InputType::Text | InputType::Disabled | InputType::Toggle - | InputType::Monaco + | InputType::Monaco(_) | InputType::Select(_) => "text", InputType::Number | InputType::Integer => "number", @@ -51,13 +51,13 @@ impl From<(SchemaType, EnumVariants)> for InputType { SchemaType::Single(JsonSchemaType::Boolean) => InputType::Toggle, SchemaType::Single(JsonSchemaType::String) => InputType::Text, SchemaType::Single(JsonSchemaType::Null) => InputType::Disabled, - SchemaType::Single(JsonSchemaType::Array) => InputType::Monaco, - SchemaType::Single(JsonSchemaType::Object) => InputType::Monaco, + SchemaType::Single(JsonSchemaType::Array) => InputType::Monaco(vec![]), + SchemaType::Single(JsonSchemaType::Object) => InputType::Monaco(vec![]), SchemaType::Multiple(types) if types.contains(&JsonSchemaType::Object) || types.contains(&JsonSchemaType::Array) => { - InputType::Monaco + InputType::Monaco(vec![]) } SchemaType::Multiple(_) => InputType::Text, } @@ -69,7 +69,8 @@ impl From<(SchemaType, EnumVariants, Operator)> for InputType { (schema_type, enum_variants, operator): (SchemaType, EnumVariants, Operator), ) -> Self { if operator == Operator::In { - return InputType::Monaco; + let EnumVariants(ev) = enum_variants; + return InputType::Monaco(ev); } InputType::from((schema_type, enum_variants)) @@ -300,10 +301,12 @@ pub fn monaco_input( on_change: Callback, schema_type: SchemaType, #[prop(default = false)] disabled: bool, + suggestions: Vec, #[prop(default = None)] operator: Option, ) -> impl IntoView { let id = store_value(id); let schema_type = store_value(schema_type); + let suggestions = store_value(suggestions); let (value_rs, value_ws) = create_signal(value); let (expand_rs, expand_ws) = create_signal(false); let (error_rs, error_ws) = create_signal::>(None); @@ -418,6 +421,7 @@ pub fn monaco_input( language=Languages::Json classes=vec!["h-full"] + suggestions=suggestions.get_value() />