rdenadai/improvements-v0.0.2 #2

Merged
rdenadai merged 136 commits from rdenadai/improvements-v0.0.2 into main 2026-02-24 02:07:11 +00:00
Owner
No description provided.
- Add theme module with 4 built-in themes: One Dark Pro, Dracula, Monokai Pro, Catppuccin Mocha
- Replace all hardcoded colors in editor.slint and main.slint with theme properties
- Add command palette modal (Ctrl+P) with search filtering
- Wire theme switching: palette -> Rust -> Slint color properties + syntax colors
- Persist selected theme to config (settings.json)
- Default theme: One Dark Pro
- Fix calc-line: use floor instead of round for accurate line hit-testing
- Fix calc-col: clamp to valid range with proper rounding
- Add double-click timeout (400ms Timer) replacing fragile click-count
- Throttle drag updates: only update selection when line/col changes
- Current-line highlight now reactive (line-idx == cursor-line) in Slint
- Remove refresh_styled_lines from click/selection/double-click handlers
- Optimize select_word_at: use byte-level iteration instead of Vec<char>
- Use floor(x / char-width) instead of round(x / char-width - 0.5)
- Clicking anywhere within a character cell now places cursor at that column
- round(x / char-width) places cursor at nearest character boundary
- Left half of char -> cursor before it, right half -> cursor after it
- Before first char shows Col 0, on first char shows Col 1, etc.
- Line remains 1-indexed (Ln 1 for first line)
- Clicking anywhere on a character places cursor at its left edge
- Fixes inability to position cursor before first character on a line
- Selection now highlights only the selected characters, not entire lines
- Selection overlay positioned using char-width * column offsets
- Current-line highlight suppressed only on lines with active selection
- Normalized selection direction (start <= end) for consistent rendering
- Removed unused is-in-selection function
Selection highlighting is now embedded in styled text spans instead of
using absolute-positioned overlays. Each StyledSegment has a 'selected'
field, and spans are split at selection column boundaries during
build_styled_lines. This eliminates char-width accumulation errors that
caused selection highlighting to drift by 2-3px over long lines.

- Add split_segments_for_selection() for column-accurate span splitting
- Add selection-drag callback for real-time selection during mouse drag
- Refresh styled lines on click (clear), drag (update), and double-click
- Add get_normalized_selection() helper for extracting selection state
- Add max-column field to EditorConfig (default: 120)
- Display vertical ruler line at max-column position
- Implement auto-wrap on space when exceeding max-column
- Increase cursor width from 2px to 3px for better visibility
Remove double-counted viewport offsets (flick.viewport-x/y) from cursor
rectangle. Since the cursor is a child of the Flickable, scrolling is
already applied automatically. Also remove unused cursor-measure Text
element and fix indentation.
- Cursor clamping: Route mouse drag events through Rust callback
  (cursor-drag) for proper column clamping to line length. Prevents
  cursor from being positioned beyond the last character.

- Horizontal scroll: ensure_cursor_visible now handles both vertical
  and horizontal scrolling to keep cursor in view.

- Soft word-wrap: Lines exceeding max_column are visually split into
  multiple display lines at word boundaries. Adds visual cursor
  positioning (cursor-visual-line/col) separate from logical position.
  Gutter hides line numbers for continuation lines. Selection overlay
  accounts for logical column offsets.

- Visual-to-logical coordinate conversion for mouse events (click,
  drag, double-click) to handle wrapped line mapping correctly.
Cursor clamping:
- set_cursor_position() now takes a buffer ref and always clamps
  line to [0, total_lines-1] and column to [0, line_len]. This is
  the single authoritative cursor setter (Zed/VSCode pattern).
- Eliminated all direct window.set_cursor_line/set_cursor_column
  calls across keyboard.rs, mouse.rs, clipboard.rs, file.rs, tabs.rs.
- Removed standalone clamp_column_to_line() — clamping is built-in.

Syntax highlighting:
- Skip tree-sitter nodes inside ERROR ancestors — their highlight
  boundaries are unreliable and cause valid code to change colors.
- Normalize overlapping spans: when multiple spans cover the same
  byte range, keep the narrowest (most specific) match.
- Sort spans by start position, then by width descending, before
  normalization to ensure correct specificity ordering.
Always set cursor to (0,0) when opening a file or switching tabs.
Previously, opening a file set cursor to line index 1 (visual line 2).
- Call update_cursor_display() after set_cursor_position(0, 0) on
  file open and tab switch. Previously cursor-visual-line/col stayed
  stale from the previous file, causing the cursor to render at the
  wrong line despite status bar showing Ln 1, Col 0.

- Add cursor_width config (settings.json: cursor_width, default 2.0).
  Maps to EditorConfig.cursor-width in Slint, controlling cursor
  thickness. Configurable from 1px (thin) to any desired width.
- File > Configuration opens ~/.config/ehwaz/settings.json in the
  editor as a regular tab (creates the file if it doesn't exist).
- When saving settings.json, the editor automatically detects it's
  the config file and reloads: font, max_column, cursor_width,
  theme, and re-highlights the current file.
- Status bar shows 'Configuration saved and reloaded' on config save.
- Replace col * char-width with cursor-pos-measurer Text element that
  measures actual cursor-text-before string width for pixel-perfect cursor X
- Handle soft-wrap: cursor-text-before now uses visual line segment text
  instead of full logical line, ensuring correct position on wrapped lines
- Add opacity (0.5) for wide cursors (> 3px) so text remains visible
- Update ensure_cursor_visible horizontal scroll to use measured offset
- Add cursor-text-before change handler to trigger scroll following
Move cursor Rectangle from standalone position (after text in z-order)
into each line's code area, declared BEFORE the text HorizontalLayout.
This ensures text renders ON TOP of the cursor, so wide/block cursors
show the cursor color as a background highlight while keeping syntax-
colored text fully visible.

The cursor X position uses cursor-x-offset (measured from cursor-pos-
measurer Text element) for pixel-accurate alignment with the rendered
text, regardless of font metrics rounding.
rdenadai force-pushed rdenadai/improvements-v0.0.2 from 1c391ab984 to 10f42f4424 2026-02-11 18:44:37 +00:00 Compare
- Measurement-based pixel-to-column conversion using Slint text measurer
- GraphemeCursor-based boundary detection (new grapheme.rs module)
- Emoji classification via emojis crate with font fallback
- Fixed delete_range double-snap bug (backspace/delete deleting 2 chars)
- Fixed binary search off-by-one in visual_to_logical_px
- Fixed selection rendering to use measured cell width
- Emoji spans rendered with Noto Color Emoji font family
- Modular buffer layer: grapheme.rs, emoji.rs, offset.rs
- Replaced cell-width estimation with Slint text measurement for pixel→column
- Added click-measurer Text element for binary-search based column detection
- Added selection-start-px/selection-end-px measured pixel properties
- Normalized selection direction for backwards selections
- Wrapped lines fall back to cell-width for selection overlay
- Add offset_width_px field to StyledLineData to track measured pixel width of wrapped line offsets
- Implement calculate_offset_widths() to measure the pixel position of each wrapped line's starting offset
- Fix Slint selection rendering logic to use measured pixel positions for wrapped lines
- Use selection-starts-here and selection-ends-here conditions to determine when to apply measured positions
- Subtract offset-width-px from measured positions to get correct relative position on wrapped lines

This fixes the issue where selection highlights appeared displaced to the right on wrapped lines.
The selection now correctly highlights text including the first character on wrapped lines.
This is a pre-existing issue where the first character of a word selected via
Alt+D or double-click is not highlighted. The selection data internally is
correct, but the rendering appears to skip the first character.

Investigation findings:
- Word selection algorithm (select_word_at) correctly identifies word boundaries
- Measurement function (measure_text_width_up_to_col) correctly measures text width
- Pixel-to-column conversion (pixel_x_to_col) correctly converts mouse position
- Selection rendering logic in Slint appears correct
- The issue persists before and after wrapped-line selection fix

Possible root causes:
1. Rounding or edge case in text measurement
2. Slint rendering artifact or measurement quirk
3. Off-by-one in how selection-start-px is calculated or used
4. Text layout or font rendering issue causing visual misalignment

Further investigation requires:
- Running editor with debug output to capture actual pixel values
- Testing with different fonts and configurations
- Checking Slint's text measurement behavior with edge cases
The issue was in measure_text_width_up_to_col() where column 0 (start of line)
was being measured through Slint's text measurement API. Slint returns
approximately 10px for an empty string (likely due to implicit cursor width or
padding in the Text element).

Root cause:
- When col=0, the function would measure an empty string ("")
- Slint's get_click_measure_width() returned ~10px instead of 0px
- This offset was used as the selection start pixel position
- The selection rectangle was thus offset by 10px to the right
- The first character fell outside the selection rectangle

Fix:
- Short-circuit the measurement function to return 0.0 immediately when col=0
- Column 0 is always at the start of the line with no pixel offset
- This ensures selection starts at the actual beginning of the line

This fixes the pre-existing issue where Alt+D or double-clicking on the first
word would not highlight the first character visually (though the selection
data internally was correct).
- Add EmojiTextView component for span-based text rendering with emoji
- Split text blocks into StyledLine spans via build_plain_text_lines_with_emoji()
- Detect emoji in paragraphs/headings/list items using is_emoji_grapheme()
- CodeBlockView already fixed to use span.is-emoji for font switching
- Fallback to regular Text element when no emoji present (zero perf cost)
- Dynamic block cursor width: cell-width for regular chars, emoji-font width for emojis
- Opaque block cursor with inverted char overlay for visibility
- Emoji position correction using boolean-guarded font measurers
  (prevents jump bug when no emojis exist before cursor)
char-measurer was measuring 'X' WITH letter-spacing, so char-width
already included it. Then cell-width = char-width + letter-spacing
double-counted, making cell-width ~1px too wide per letter-spacing px.

This caused:
- Column ruler drawn too far right (wrapping appeared 'much before')
- Dense cursor block wider than the actual character cell
- Content width inflated, affecting horizontal scroll

Fix: remove letter-spacing from char-measurer so char-width = pure
glyph width. cell-width = glyph + letter-spacing is now correct.

Also fix max_line_length_for() to count chars instead of bytes.
The emoji mono measurer was missing letter-spacing, so the correction
only subtracted glyph width, not glyph+letter-spacing. This left
extra letter-spacing pixels per emoji before cursor, displacing it
rightward on lines with emojis (visible on line 350 of the screenshot).
- optimize app/buffer hot paths and bracket matching allocations
- add incremental highlighter cursor/tree reuse
- deduplicate UI buffer display sync flow
- update RESULT and README

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- extract snippet catalog from main.rs into src/snippets.rs
- move clipboard state into AppState and remove thread-local clipboard
- switch Buffer line/slice to Cow-based access and remove history clone churn
- add criterion benchmark scaffold for buffer insert and undo/redo flows
- refresh README and RESULT documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Limit block-level links to exact link-only text for markdown/html links, and add parser regression tests for inline link scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture inline link metadata in markdown parser and render clickable link-text overlays in markdown preview without re-linking the entire paragraph.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rdenadai/ehwaz!2
No description provided.