Skip to content

Commit 7b3d7b2

Browse files
committed
Add runtime language detection
1 parent c4c1bf5 commit 7b3d7b2

24 files changed

Lines changed: 393 additions & 301 deletions

File tree

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ jobs:
2020
with:
2121
all-features: false
2222
no-default-features: true
23-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
23+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
2424

2525
test:
2626
uses: dioxuslabs/dioxus-ci/.github/workflows/test.yml@v0.1.0
2727
with:
2828
no-default-features: true
29-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
29+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
3030

3131
fmt:
3232
uses: dioxuslabs/dioxus-ci/.github/workflows/fmt.yml@v0.1.0
@@ -36,11 +36,11 @@ jobs:
3636
with:
3737
all-features: false
3838
no-default-features: true
39-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
39+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
4040

4141
clippy:
4242
uses: dioxuslabs/dioxus-ci/.github/workflows/clippy.yml@v0.1.0
4343
with:
4444
all-features: false
4545
no-default-features: true
46-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
46+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web

.github/workflows/nightly.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ jobs:
1919
toolchain: nightly
2020
all-features: false
2121
no-default-features: true
22-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
22+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
2323

2424
test:
2525
uses: dioxuslabs/dioxus-ci/.github/workflows/test.yml@v0.1.0
2626
with:
2727
toolchain: nightly
2828
no-default-features: true
29-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
29+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
3030

3131
clippy:
3232
uses: dioxuslabs/dioxus-ci/.github/workflows/clippy.yml@v0.1.0
3333
with:
3434
toolchain: nightly
3535
all-features: false
3636
no-default-features: true
37-
features: dioxus-code/all-languages dioxus-code-docsite/web dioxus-code-live-input/web
37+
features: dioxus-code/all-languages dioxus-code-editor/web dioxus-code-docsite/web dioxus-code-live-input/web
3838

3939
web-demo:
4040
uses: dioxuslabs/dioxus-ci/.github/workflows/web-build.yml@v0.1.0

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ categories = ["gui", "web-programming"]
2323
[workspace.dependencies]
2424
dioxus-code = { version = "0.1.1", path = "." }
2525
dioxus-code-editor = { version = "0.1.2", path = "code-editor", default-features = false }
26+
betlang = { version = "0.1.0" }
2627

2728
[package]
2829
name = "dioxus-code"
@@ -43,7 +44,11 @@ rustdoc-args = ["--cfg", "docsrs"]
4344
[features]
4445
default = ["macro"]
4546
macro = ["dep:dioxus-code-macro", "dioxus-code-macro/lang-rust"]
46-
runtime = ["arborium/lang-rust", "dep:arborium-tree-sitter"]
47+
runtime = [
48+
"arborium/lang-rust",
49+
"dep:arborium-tree-sitter",
50+
"dep:betlang",
51+
]
4752
all-languages = [
4853
"runtime",
4954
"arborium/all-languages",
@@ -260,6 +265,7 @@ arborium-theme = "2.16.0"
260265
arborium-tree-sitter = { version = "2.16.0", optional = true }
261266
dioxus = { version = "0.7.0", default-features = false, features = ["lib"] }
262267
dioxus-code-macro = { version = "0.1.0", path = "dioxus-code-macro", default-features = false, optional = true }
268+
betlang = { workspace = true, optional = true }
263269

264270
[build-dependencies]
265271
arborium = { version = "2.16.0", default-features = false }

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ A small Dioxus component for rendering source code with proper highlighting. Par
2121

2222
Two ways to highlight:
2323

24-
- **[`code!`] macro** — parses at compile time and embeds the highlighted spans. Default.
25-
- **[`SourceCode`]** — parses at runtime. Opt in with the `runtime` feature for dynamic source text.
24+
- **[`code!`] macro** — parses at compile time. The runtime ships only the spans, no parser. Default.
25+
- **[`SourceCode`]** — parses at runtime. Opt in with the `runtime` feature when the source isn't known until the user types it.
2626

2727
## Quick start
2828

@@ -51,7 +51,7 @@ When the file extension is ambiguous, pass [`CodeOptions::builder`] with [`CodeO
5151

5252
## Runtime highlighting
5353

54-
For editor-style use cases with dynamic source text:
54+
For editor-style use cases where the source isn't known at compile time:
5555

5656
```toml
5757
[dependencies]
@@ -60,23 +60,24 @@ dioxus-code = { version = "0.1", features = ["runtime"] }
6060

6161
```rust
6262
# use dioxus::prelude::*;
63-
use dioxus_code::{Code, Language, SourceCode, Theme};
63+
use dioxus_code::{Code, CodeOptions, Language, SourceCode, Theme};
6464
# let user_input = String::new();
6565
# let _ =
6666
rsx! {
6767
Code {
68-
src: SourceCode::new(Language::Rust, user_input),
68+
src: SourceCode::new(user_input)
69+
.with_options(CodeOptions::builder().with_language(Language::Rust)),
6970
theme: Theme::GITHUB_LIGHT,
7071
}
7172
}
7273
# ;
7374
```
7475

75-
Pass a [`Language`] variant when building [`SourceCode`]. The `runtime` feature includes Rust; enable the matching `lang-*` feature, or `all-languages`, for additional grammars.
76+
Language can be set explicitly with the same [`CodeOptions`] builder used by [`code!`], or auto-detected from the source. The default `runtime` feature includes Rust; pass `lang-python`, `lang-toml`, or `all-languages` for the rest.
7677

7778
## Editor
7879

79-
[`dioxus-code-editor`] is a sibling crate that pairs the highlighter with a textarea input layer:
80+
[`dioxus-code-editor`] is a sibling crate that pairs the highlighter with a `contenteditable` input layer:
8081

8182
```rust
8283
# use dioxus::prelude::*;
@@ -130,7 +131,7 @@ Code {
130131

131132
```sh
132133
dx serve --example dioxus-code-basic # macro + runtime side by side
133-
dx serve --example dioxus-code-macro-only # compile-time highlighted spans
134+
dx serve --example dioxus-code-macro-only # compile-time only, no parser in the binary
134135
dx serve --example dioxus-code-live-input # textarea bound to runtime highlighter
135136
```
136137

build.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -104,25 +104,6 @@ fn main() {
104104
));
105105
}
106106

107-
generated.push_str(
108-
r#" /// Every syntax theme, in declaration order.
109-
///
110-
/// ```rust
111-
/// use dioxus_code::Theme;
112-
/// assert!(Theme::ALL.contains(&Theme::TOKYO_NIGHT));
113-
/// ```
114-
pub const ALL: &'static [Theme] = &[
115-
"#,
116-
);
117-
for theme in &themes {
118-
generated.push_str(&format!(" Self::{},\n", theme.const_name));
119-
}
120-
generated.push_str(
121-
r#" ];
122-
123-
"#,
124-
);
125-
126107
generated.push_str(
127108
r#"}
128109
"#,

code-editor/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
1717

1818
[dependencies]
1919
dioxus = { version = "0.7.0", default-features = false, features = ["lib"] }
20-
dioxus-code = { workspace = true, features = ["runtime"] }
20+
dioxus-code = { workspace = true, features = ["lang-toml", "lang-python"] }
2121

2222
[target.'cfg(target_arch = "wasm32")'.dependencies]
2323
wasm-bindgen = "0.2"
@@ -30,4 +30,6 @@ web-sys = { version = "0.3", features = [
3030
] }
3131

3232
[features]
33-
all-languages = ["dioxus-code/all-languages"]
33+
default = ["desktop"]
34+
desktop = ["dioxus/desktop", "dioxus/launch"]
35+
web = ["dioxus/web", "dioxus/launch"]

code-editor/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The component is controlled — drive [`CodeEditorProps::value`] from your own s
5353
| prop | description |
5454
|---|---|
5555
| [`CodeEditorProps::value`] | Current editor contents. |
56-
| [`CodeEditorProps::language`] | Syntax grammar selection. Pass a [`Language`] variant (for example [`Language::Rust`]) or use [`Language::from_slug`] for runtime slugs. |
56+
| [`CodeEditorProps::language`] | Tree-sitter grammar selection. Pass a [`Language`] variant (for example [`Language::Rust`]) or use [`Language::from_slug`] for custom slugs. |
5757
| [`CodeEditorProps::theme`] | Syntax theme selection shared with [`dioxus-code`](https://crates.io/crates/dioxus-code); accepts [`Theme`] or [`CodeTheme`]. |
5858
| [`CodeEditorProps::line_numbers`] | Show a one-based line gutter. Defaults to `true`. |
5959
| [`CodeEditorProps::read_only`] | Disable editing while preserving highlighting. |

code-editor/src/lib.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use dioxus::prelude::*;
55
pub use dioxus_code::Language;
66
#[cfg(test)]
77
use dioxus_code::Theme;
8-
use dioxus_code::advanced::{Buffer, CodeThemeStyles, HighlightError, TokenSpan};
8+
use dioxus_code::advanced::{Buffer, CodeThemeStyles, TokenSpan};
99
#[cfg(test)]
1010
use dioxus_code::advanced::{HighlightSegment, HighlightedSource};
1111
use dioxus_code::{CodeTheme, SourceCode};
@@ -71,11 +71,6 @@ pub struct CodeEditorProps {
7171
pub oninput: EventHandler<String>,
7272
}
7373

74-
struct EditorBuffer {
75-
buffer: Option<Buffer>,
76-
language: Language,
77-
}
78-
7974
/// Editable syntax-highlighted code surface.
8075
///
8176
/// The component is controlled by [`CodeEditorProps::value`]; update that value
@@ -101,15 +96,10 @@ struct EditorBuffer {
10196
/// ```
10297
#[component]
10398
pub fn CodeEditor(props: CodeEditorProps) -> Element {
104-
let state = use_hook({
99+
let buffer = use_hook({
105100
let value = props.value.clone();
106101
let language = props.language;
107-
move || {
108-
Rc::new(RefCell::new(EditorBuffer {
109-
buffer: Buffer::new(language, value).ok(),
110-
language,
111-
}))
112-
}
102+
move || Rc::new(RefCell::new(Buffer::new(language, value).ok()))
113103
});
114104
let edit_tracker = use_hook(|| {
115105
Rc::new(RefCell::new(edit_capture::InputEditTracker::new(
@@ -119,30 +109,28 @@ pub fn CodeEditor(props: CodeEditorProps) -> Element {
119109

120110
let edit = edit_tracker.borrow_mut().take_for_render(&props.value);
121111
let snapshot = {
122-
let mut slot = state.borrow_mut();
123-
if slot.language != props.language {
124-
slot.buffer = Buffer::new(props.language, props.value.clone()).ok();
125-
slot.language = props.language;
112+
let mut buffer_slot = buffer.borrow_mut();
113+
if buffer_slot.is_none() {
114+
*buffer_slot = Buffer::new(props.language, props.value.clone()).ok();
126115
}
127116

128-
match slot.buffer.as_mut() {
117+
match buffer_slot.as_mut() {
129118
Some(buffer) => {
119+
if buffer.language() != props.language {
120+
let _ = buffer.set_language(props.language);
121+
}
130122
if buffer.source() != props.value {
131123
let result = match edit {
132-
Some(edit) => match buffer.edit(edit, props.value.clone()) {
133-
Ok(()) => Ok(()),
134-
Err(HighlightError::InvalidEdit { .. }) => {
135-
buffer.replace(props.value.clone())
136-
}
137-
Err(error) => Err(error),
138-
},
124+
Some(edit) => buffer.edit(edit, props.value.clone()),
139125
None => buffer.replace(props.value.clone()),
140126
};
141127
let _ = result;
142128
}
143129
buffer.highlighted()
144130
}
145-
None => SourceCode::new(props.language, props.value.clone()).into(),
131+
None => SourceCode::new(props.value.clone())
132+
.with_language(props.language)
133+
.into(),
146134
}
147135
};
148136
let lines = snapshot.lines();

code-editor/src/main.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use dioxus::prelude::*;
2+
use dioxus_code::Theme;
3+
use dioxus_code_editor::{CodeEditor, Language};
4+
5+
const DEMO_CSS: Asset = asset!("/assets/demo.css");
6+
7+
const STARTER: &str = r#"pub fn luminance(rgb: (u8, u8, u8)) -> f32 {
8+
let (r, g, b) = rgb;
9+
0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32
10+
}
11+
"#;
12+
13+
fn main() {
14+
dioxus::launch(App);
15+
}
16+
17+
#[component]
18+
fn App() -> Element {
19+
let mut source = use_signal(|| STARTER.to_string());
20+
let language = Language::detect(&source()).unwrap_or(Language::Rust);
21+
let language_label = language.slug();
22+
23+
rsx! {
24+
document::Stylesheet { href: DEMO_CSS }
25+
main { class: "shell",
26+
section { class: "toolbar",
27+
h1 { "Code editor component" }
28+
span { "{language_label}" }
29+
}
30+
CodeEditor {
31+
value: source(),
32+
language,
33+
theme: Theme::TOKYO_NIGHT,
34+
oninput: move |value| source.set(value),
35+
}
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)