Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions acdc-editor-wasm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Granular syntax highlighting for all macros (image, video, audio, footnote,
link, icon, kbd, btn, menu, stem, pass, xref): target in green, bracket
content in dark pink.
- **Line numbers** - the editor now has line numbers!

## [0.3.0] - 2026-02-14

Expand Down
1 change: 1 addition & 0 deletions acdc-editor-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Clipboard",
"CssStyleDeclaration",
"Document",
"DomTokenList",
"Element",
Expand Down
58 changes: 57 additions & 1 deletion acdc-editor-wasm/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ struct EditorState {
backdrop: Element,
preview: Element,
parse_status: HtmlElement,
line_numbers_pre: Element,
line_numbers_container: Element,
editor_container: HtmlElement,
/// Timer handle for the debounced parse. 0 means no pending timer.
debounce_handle: Cell<i32>,
}
Expand All @@ -53,6 +56,17 @@ impl EditorState {
.get_element_by_id("parse-status")
.ok_or("missing #parse-status")?
.dyn_into()?;
let line_numbers_container = doc
.get_element_by_id("line-numbers")
.ok_or("missing #line-numbers")?;
let line_numbers_pre = line_numbers_container
.query_selector("pre")
.map_err(|_| "failed to query #line-numbers pre")?
.ok_or("missing pre inside #line-numbers")?;
let editor_container: HtmlElement = line_numbers_container
.parent_element()
.ok_or("missing .editor-container")?
.dyn_into()?;

Ok(Self {
window,
Expand All @@ -61,6 +75,9 @@ impl EditorState {
backdrop,
preview,
parse_status,
line_numbers_pre,
line_numbers_container,
editor_container,
debounce_handle: Cell::new(0),
})
}
Expand Down Expand Up @@ -122,6 +139,42 @@ fn set_parse_status(state: &EditorState, msg: &str, is_error: bool) {
}
}

// ---------------------------------------------------------------------------
// Line number gutter
// ---------------------------------------------------------------------------

fn update_line_numbers(state: &EditorState) {
let value = state.editor.value();
let line_count = value.chars().filter(|&c| c == '\n').count() + 1;
let numbers: String = (1..=line_count)
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join("\n");
state.line_numbers_pre.set_text_content(Some(&numbers));

// Resize gutter to fit the widest line number: digits × 1ch + 1rem padding
let digits = digit_count(line_count);
let width = format!("calc({digits}ch + 1rem)");
let _ = state
.editor_container
.style()
.set_property("--gutter-width", &width);
}

/// Number of decimal digits in `n` (minimum 1).
const fn digit_count(n: usize) -> u32 {
if n == 0 {
return 1;
}
let mut v = n;
let mut d: u32 = 0;
while v > 0 {
v /= 10;
d += 1;
}
d
}

// ---------------------------------------------------------------------------
// Unified parse + highlight + preview
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -155,11 +208,14 @@ fn parse_and_update_both(state: &EditorState) {
set_parse_status(state, &msg, true);
}
}
update_line_numbers(state);
}

fn sync_scroll(state: &EditorState) {
state.backdrop.set_scroll_top(state.editor.scroll_top());
let scroll_top = state.editor.scroll_top();
state.backdrop.set_scroll_top(scroll_top);
state.backdrop.set_scroll_left(state.editor.scroll_left());
state.line_numbers_container.set_scroll_top(scroll_top);
}

/// Cancel any pending debounce timer and schedule a new parse + update.
Expand Down
1 change: 1 addition & 0 deletions acdc-editor-wasm/www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ <h1 class="sr-only">acdc — Live AsciiDoc Editor & Preview</h1>
<div class="editor-pane">
<div class="pane-label">Editor</div>
<div class="editor-container">
<div id="line-numbers"><pre></pre></div>
<textarea id="editor" spellcheck="false"></textarea>
<div id="editor-backdrop">
<pre id="highlight"></pre>
Expand Down
34 changes: 30 additions & 4 deletions acdc-editor-wasm/www/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,39 @@ html, body {
position: relative;
flex: 1;
overflow: hidden; /* Handle scrolling within textarea/backdrop */
--gutter-width: 3.5rem;
}

#editor {
#line-numbers {
position: absolute;
top: 0;
left: 0;
width: 100%;
width: var(--gutter-width);
height: 100%;
overflow: hidden;
z-index: 1;
pointer-events: none;
background: #f0f1f3;
border-right: 1px solid #dee2e6;
box-sizing: border-box;
}

#line-numbers pre {
font-family: "Droid Sans Mono", "DejaVu Sans Mono", monospace;
font-size: 14px;
line-height: 1.5;
padding: 1rem 0.5rem 1rem 0;
margin: 0;
text-align: right;
color: #999;
user-select: none;
}

#editor {
position: absolute;
top: 0;
left: var(--gutter-width);
width: calc(100% - var(--gutter-width));
height: 100%;
background: transparent;
color: transparent;
Expand All @@ -121,8 +147,8 @@ html, body {
#editor-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
left: var(--gutter-width);
width: calc(100% - var(--gutter-width));
height: 100%;
pointer-events: none; /* Let clicks pass through to textarea */
overflow: hidden;
Expand Down
Loading