siku2 / rust-monaco

Rust WASM bindings for the Monaco Editor
Apache License 2.0
77 stars 25 forks source link

Allow arbitrary options on CodeEditor #20

Closed max-sixty closed 2 years ago

max-sixty commented 2 years ago

Follow-up to https://github.com/siku2/rust-monaco/issues/5

Any thoughts for the easiest way to allow arbitrary options?

Would we need to make a Rust type that's equivalent to IStandaloneEditorConstructionOptions and derive PartialEq on it?

I started with something like this with the hope that it would be quick, but it requires PartialEq:

diff --git a/src/yew/mod.rs b/src/yew/mod.rs
index 473f9b0..1ddac6c 100644
--- a/src/yew/mod.rs
+++ b/src/yew/mod.rs
@@ -1,17 +1,20 @@

 #[derive(Clone, Debug, PartialEq, Properties)]
-pub struct CodeEditorProps {
+pub struct CodeEditorProps<OPT: std::cmp::PartialEq + Into<IStandaloneEditorConstructionOptions> = IStandaloneEditorConstructionOptions> {
     #[prop_or_default]
     pub link: Option<CodeEditorLink>,
     /// Changing the options will cause the editor to be re-created.
     #[prop_or_default]
-    pub options: Option<Rc<CodeEditorOptions>>,
+    pub options: Option<Rc<OPT>>,
     #[prop_or_default]
     pub model: Option<TextModel>,
     /// This could be called multiple times if the `options` field changes.

(No urgency from my end, so no rush on responding)

siku2 commented 2 years ago

There's nothing stopping us from implementing PartialEq on IStandaloneEditorConstructionOptions itself. Actually, I'm 99% sure we can just derive it here: https://github.com/siku2/rust-monaco/blob/d9d1a1ba9e7f5c77394a34fdd6a8b7b4960ff647/src/sys/editor.rs#L1044-L1047

The reason CodeEditorOptions exists is because IStandaloneEditorConstructionOptions is very bulky and can be a bit confusing because of how it implements Deref for IEditorConstructionOptions, which in turns derefs to IEditorOptions. Given the sheer amount of options though, I don't think CodeEditorOptions can possibly cover all of them so I believe you're on the right track.

I suggest we change the options prop to be Option<IStandaloneEditorConstructionOptions> (note the lack of an Rc. The IStandaloneEditorConstructionOptions struct is basically just a wrapper around a pointer because the underlying data lives on the js side).

After adding the necessary derives to IStandaloneEditorConstructionOptions, we really only need to change the following lines:

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct CodeEditorProps {
    #[prop_or_default]
    pub link: Option<CodeEditorLink>,
    /// Changing the options will cause the editor to be re-created.
    #[prop_or_default]
-   pub options: Option<Rc<CodeEditorOptions>>,
+   pub options: Option<IStandaloneEditorConstructionOptions>,
    #[prop_or_default]
    pub model: Option<TextModel>,
    /// This could be called multiple times if the `options` field changes.
    /// You can use this to initialise the editor
    #[prop_or_default]
    pub on_editor_created: Callback<CodeEditorLink>,
}

// [snip]

impl Component for CodeEditor {
    fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
        let el = self
            .node_ref
            .cast::<HtmlElement>()
            .expect("failed to resolve editor element");

        let props = ctx.props();
-       let editor = CodeEditorModel::create(&el, props.options.as_deref());
+       let editor = CodeEditorModel::create_with_sys_options(&el, props.options.as_deref());

        if let Some(model) = &props.model {
            // initially we only update the model if it was actually given as a prop.
            // this way a value or model can be given in the options and it won't be
            // detached immediately
            editor.set_model(model)
        }

        self.editor = Some(editor);
        self.emit_editor_created(ctx);
    }
}

CodeEditorOptions already has the to_sys_options method, meaning it's still possible to use the simplified interface using <CodeEditor options={ CodeEditorOptions::default().to_sys_options() } />.