Drag to select terminal text and copy it with Ctrl-Shift-C. Built as a
custom selection model (xterm.js-style) rather than native selection,
because the virtual scroll only keeps ~60 rows in the DOM and rebuilds
them every frame — native selection would be destroyed and couldn't span
scrolled-out rows.
core.rs:
- selection_text(start, end) extracts a (row,col)→(row,col) range straight
from history_rows + screen_cache, so copy is correct across the whole
buffer regardless of what is currently rendered
- TerminalRow::text_in_cols slices a row by grid columns (using the
segment `cols` widths), trimming trailing whitespace
component.rs:
- Selection { anchor, head } in logical buffer coords; mousedown/move/up
drag updates it; point_to_cell maps pixels→cell (padding + scrollTop)
- selection_rects computes per-row highlight rects clipped to the rendered
window; rendered as an overlay inside the translated rows layer so the
highlight follows scrolling without splitting text segments
- cell_size signal kept in sync with viewport measurement
- Ctrl-Shift-C copies via navigator.clipboard.write_text; Ctrl-C is left
untouched (still SIGINT); typing clears the selection
style: .terminal-selection overlay, user-select:none on the grid, rows
above the highlight via z-index.
web-sys: add DomRect, MouseEvent, Navigator, Clipboard.
Adds point_to_cell / selection_rects / selection_text unit tests (16
terminal tests total). Marks P0 done in TERMINAL_ROADMAP.md.
Known follow-up: no auto-scroll when dragging past the viewport edge
(scroll first, then select); wide+combining column slicing is approximate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
2.3 KiB
TOML
82 lines
2.3 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = ["app", "server", "app_config"]
|
|
|
|
[workspace.dependencies]
|
|
app_config = { path = "app_config" }
|
|
icons = { path = "../ui/crates/icons", features = ["leptos"] }
|
|
registry = { path = "../ui/app_crates/registry" }
|
|
leptos_ui = { path = "../ui/crates/leptos_ui" }
|
|
tw_merge = { path = "../ui/crates/tw_merge/tw_merge", features = ["variant"] }
|
|
tw_merge_variants = { path = "../ui/crates/tw_merge/tw_merge_variants" }
|
|
|
|
axum = { version = "0.8", features = ["ws"] }
|
|
console_error_panic_hook = "0.1"
|
|
console_log = "1"
|
|
futures-util = "0.3"
|
|
leptos = { version = "0.8", features = ["nightly"] }
|
|
leptos_axum = "0.8"
|
|
leptos_meta = "0.8"
|
|
leptos_router = { version = "0.8", features = ["nightly"] }
|
|
log = "0.4"
|
|
portable-pty = "0.9"
|
|
redis = { version = "0.32", features = ["tokio-comp"] }
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
strum = { version = "0.27", features = ["derive"] }
|
|
time = { version = "0.3", features = ["serde", "wasm-bindgen"] }
|
|
tokio = { version = "1", features = ["rt-multi-thread", "sync"] }
|
|
tower = "0.5"
|
|
tower-http = { version = "0.6", features = ["fs"] }
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
validator = { version = "0.20", features = ["derive"] }
|
|
vt100 = "0.15"
|
|
heck = "0.5"
|
|
js-sys = "0.3"
|
|
wasm-bindgen = "0.2"
|
|
wasm-bindgen-futures = "0.4"
|
|
web-sys = { version = "0.3", default-features = false, features = [
|
|
"BinaryType",
|
|
"Clipboard",
|
|
"ClipboardEvent",
|
|
"CloseEvent",
|
|
"console",
|
|
"DataTransfer",
|
|
"Document",
|
|
"DomRect",
|
|
"DomRectReadOnly",
|
|
"Element",
|
|
"ErrorEvent",
|
|
"Event",
|
|
"EventTarget",
|
|
"HtmlElement",
|
|
"KeyboardEvent",
|
|
"Location",
|
|
"MessageEvent",
|
|
"MouseEvent",
|
|
"Navigator",
|
|
"ResizeObserver",
|
|
"ResizeObserverEntry",
|
|
"WebSocket",
|
|
"WheelEvent",
|
|
"Window",
|
|
] }
|
|
|
|
[[workspace.metadata.leptos]]
|
|
name = "base_path_demo"
|
|
bin-package = "server"
|
|
lib-package = "app"
|
|
site-root = "target/site"
|
|
site-pkg-dir = "pkg"
|
|
tailwind-input-file = "style/tailwind.css"
|
|
assets-dir = "public"
|
|
site-addr = "127.0.0.1:3100"
|
|
reload-port = 3101
|
|
browserquery = "defaults"
|
|
bin-features = []
|
|
bin-default-features = false
|
|
lib-features = ["hydrate"]
|
|
lib-default-features = false
|