dioxus_codemirror 0.2.0

A Dioxus web component that wraps the CodeMirror 6 editor.
Documentation
# 📝 dioxus_codemirror

[![Crates.io](https://img.shields.io/crates/v/dioxus_codemirror.svg)](https://crates.io/crates/dioxus_codemirror)
[![docs.rs](https://img.shields.io/docsrs/dioxus_codemirror)](https://docs.rs/dioxus_codemirror)
[![CI](https://github.com/azriel91/dioxus_codemirror/workflows/CI/badge.svg)](https://github.com/azriel91/dioxus_codemirror/actions/workflows/ci.yml)

A Dioxus **web** component that wraps the [CodeMirror 6] editor, for use in
Dioxus web applications.

Demo: <https://azriel.im/dioxus_codemirror>.

> [!NOTE]
>
> 🚧 This crate is new and not yet stable; its API may change between releases.

No JavaScript build step is required: the component drives CodeMirror through a
single long-lived `document::eval` channel. CodeMirror itself is **vendored** as
a Dioxus folder asset, so there is no runtime CDN dependency.


## Features

<details open>

* [x] Pure Rust build / no `node` dependency.
* [x] Set / receive text via `Signal<String>`.
* [x] Syntax highlighting per-language (YAML, Markdown, JavaScript, CSS, HTML).
* [x] Light / dark / auto theme (auto follows `prefers-color-scheme`).
* [x] In-page LSP bridge -- synchronous, or async / worker-backed.

</details>


## Usage

Add the following to `Cargo.toml`:

```toml
[dependencies]
dioxus_codemirror = "0.1.0"
```

All bundled languages (YAML, Markdown, JavaScript, CSS, HTML) are available out
of the box. The `lang-*` Cargo features are currently no-ops; see
[Choosing languages](#choosing-languages).

In code:

```rust
use dioxus::prelude::*;
use dioxus_codemirror::{CodeMirror, Language};

#[component]
fn App() -> Element {
    let value = use_signal(|| "fn main() {}".to_string());
    rsx! {
        // Editing the editor updates `value`; setting `value` updates the editor.
        CodeMirror { value }
        pre { "{value}" }

        // Optional line-number gutter and syntax highlighting.
        CodeMirror { value, line_numbers: true, language: Language::Yaml }
    }
}
```

The editor is always editable. Props:

* `value: Signal<String>` -- two-way bound document text (required).
* `line_numbers: bool` -- show a line-number gutter (default `false`).
* `language: Language` -- syntax highlighting (default plain text). All languages
  are bundled; see [Choosing languages]#choosing-languages.
* editor feature toggles (e.g. `allow_multiple_selections: bool`) -- optional
  CodeMirror features, off by default; see [Editor features]#editor-features.
* `theme: Theme` -- color theme, `Theme::Auto` (default, follows the OS
  `prefers-color-scheme`), `Theme::Light`, or `Theme::Dark`.
* `lsp: LspBridge` -- connect an in-page language server, synchronous or async
  (optional); see [LSP]#lsp.


## Editor features

Optional CodeMirror behaviours are individual props, each named after the
CodeMirror extension it enables (API parity). All are off by default; set the
ones you want:

```rust
use dioxus_codemirror::CodeMirror;

rsx! {
    CodeMirror {
        value,
        allow_multiple_selections: true,   // Alt-click, Ctrl/Cmd-d / Ctrl/Cmd-F2 cursors
        highlight_selection_matches: true, // highlight other occurrences
        bracket_matching: true,
        close_brackets: true,
        line_wrapping: true,
    }
}
```

| Prop | CodeMirror extension |
| --- | --- |
| `allow_multiple_selections` | Allows multiple selections in the editor. Also binds `Ctrl/Cmd-d` to select the next match and `Ctrl/Cmd-F2` to select all matches. |
| `bracket_matching` | Highlight the bracket matching the one next to the cursor. |
| `close_brackets` | Auto-insert closing brackets and quotes. |
| `highlight_active_line` | Highlight the line the primary cursor is on. |
| `highlight_selection_matches` | Highlight every occurrence of the selected text, the selection included. |
| `highlight_whitespace` | Render whitespace characters visibly. |
| `indent_on_input` | Re-indent lines as you type. |
| `indent_with_tab` | Bind `Tab`/`Shift-Tab` to indent, so `Tab` inserts indentation rather than moving focus out of the editor. |
| `language` | Syntax highlighting language, e.g. `Language::Yaml`. Defaults to plain text (`None`). |
| `line_numbers` | Show a line-number gutter. |
| `line_wrapping` | Wrap long lines instead of scrolling horizontally. |
| `read_only` | Make the document read-only, |
| `rectangular_selection` | Allow rectangular (block) selection via `Alt`-drag. |
| `tab_size` | Width of a tab in spaces. |
| `theme` | Color theme, e.g. `Theme::Dark`. Defaults to `Theme::Auto`. |


## Choosing languages

Every supported language -- `Language::Yaml`, `Markdown`, `Javascript`, `Css`,
`Html` -- is bundled and ready to use; no feature selection is required.

The `lang-yaml`, `lang-markdown`, `lang-javascript`, `lang-css`, `lang-html`, and
`lang-all` Cargo features still exist but are currently **no-ops**: the whole
language superset is shipped regardless. This is because Dioxus cannot yet serve
a build-script-generated, per-feature asset folder -- see
[DioxusLabs/dioxus#4426]. Once that lands, the features will once again trim the
shipped JS to only the languages you enable, so it is worth setting them now to
match the languages you actually use.

To add a language not listed above, see [`DEVELOPMENT.md`](DEVELOPMENT.md).

[DioxusLabs/dioxus#4426]: https://github.com/DioxusLabs/dioxus/issues/4426


## LSP

`CodeMirror` takes an optional `LspBridge`, which connects the editor's
`@codemirror/lsp-client` to an in-page language server. There are two transport
flavours, chosen by which constructor you call:

* **Synchronous** -- `LspBridge::lsp_bridge_from_server` drives an [`LspServer`],
  the extension point a real in-page language server implements. It is
  request/response: each message from the editor is handed to the server, and
  the messages it *returns* are forwarded straight back.

* **Async / worker-capable** -- `LspBridge::lsp_bridge_from_server_async` drives
  an [`LspServerAsync`], for a server running in a **Web Worker** (or off the
  main thread), or one that emits **diagnostics** after a processing step.
  Instead of returning replies, the server pushes messages onto an `LspPusher`
  whenever they are ready -- which is what makes **server-initiated, unprompted
  messages** (e.g. `textDocument/publishDiagnostics`) work.

The `example` ships mock servers for both flavours. Replace either mock with your
WASM language server to get genuine language features. See
[`DEVELOPMENT.md`](DEVELOPMENT.md) for the wire protocol and architecture.


## Development

See [`DEVELOPMENT.md`](DEVELOPMENT.md) for the architecture, the message
protocol, the vendored CodeMirror assets, and how to add a language.

To run the bundled example:

```sh
dx serve --platform web -p example
```


## License

Licensed under either of

* Apache License, Version 2.0, ([LICENSE-APACHE]LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT]LICENSE-MIT or https://opensource.org/licenses/MIT)

at your option.


### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

[CodeMirror 6]: https://codemirror.net/
[`Language`]: dioxus_codemirror/src/language.rs
[`LspServer`]: dioxus_codemirror/src/lsp/lsp_server.rs
[`LspServerAsync`]: dioxus_codemirror/src/lsp/lsp_server_async.rs