Skip to content

Editor: in-editor search/replace on the textarea (Phase 1a) #23

Description

@BorisTyshkevich

Split from #22. Phase 1 of the textarea editor enhancement track.

Context

The editor (src/ui/editor.js) is a <textarea> overlaid on a <pre> for syntax highlighting. The existing undo-safe insertion path (applyEdit via execCommand('insertText') with fallback splice) is the correct seam for all text mutations. Match highlights must live in the overlay layer — not inside the textarea.

DOM structure

The relevant layout (from editor.js and styles.css):

.sql-editor  (display: flex; position: relative)
  .sql-gutter
  .sql-area  (position: relative; flex: 1; overflow: hidden)
    .sql-pre   (position: absolute; inset: 0; pointer-events: none)
    .sql-textarea  (position: absolute; inset: 0)

Both new elements — the search overlay <pre> and the find bar widget — must be mounted inside .sql-area, not .sql-editor. .sql-editor includes the gutter; .sql-area is the positioned text layer. An absolutely-positioned child of .sql-area sits over the text and textarea only, with correct coordinate origin.

Overlay strategy

Add a second, absolutely-positioned <pre> inside .sql-area, alongside the existing syntax <pre>. It has color: transparent and identical font/padding/line-height, and carries only <mark> spans at match offsets. Scroll-synced with the textarea the same way the syntax <pre> already is. This approach leaves renderHighlightInto untouched.

Scope

Pure module src/core/editor-search.js

  • findMatches(sql, pattern, { caseSensitive, regex }){ matches: [{start, end}], error: string|null }. On invalid regex returns { matches: [], error: 'invalid regex' } — never throws.
  • applyReplace(sql, match, replacement) → new SQL string with that one match replaced.
  • applyReplaceAll(sql, matches, replacement) → new SQL string with all matches replaced (applied right-to-left so prior offsets stay valid).

UI additions in src/ui/editor.js

  • mountSearchBar(app) — appends the search overlay <pre> and the find bar <div class="search-bar"> (hidden by default) to .sql-area; registers app.dom.searchOverlayPre and app.dom.searchBar.
  • renderSearchMatches(app, matches, activeIdx) — rebuilds the search overlay <pre> with <mark> spans for all matches; adds class="active" to the current one.

Find bar contents:

  • Search input + case-sensitive toggle + regex toggle.
  • Replace input row (shown only in replace mode).
  • Next / Prev / Replace / Replace All buttons.

Keyboard shortcuts — registered on the textarea keydown listener (not in shortcuts.js global handler; the browser fires native find before a global handler can intercept Cmd+F):

  • Cmd/Ctrl+F → open find bar, focus search input.
  • Cmd/Ctrl+Shift+F → open in replace mode.
  • Enter / Shift+Enter while find input is focused → next / previous match.
  • Esc → close bar, return focus to textarea.

Navigation: textarea.setSelectionRange(match.start, match.end) to select the active match; scroll the textarea so it is visible.

Replace flow:

  1. setSelectionRange to the active match.
  2. applyEdit(ta, replacement) — fires the existing input listener which syncs tab.sql and repaints.
    No extra sync call needed.

Tab switch: close the find bar and clear match highlights when the active tab changes.

Acceptance criteria

  • Search works on multi-line SQL; the active match scrolls into view.
  • Cmd/Ctrl+F opens the find bar without triggering browser native find.
  • Cmd/Ctrl+Shift+F opens in replace mode.
  • Enter/Shift+Enter on the find input steps next/prev.
  • Esc closes the bar and restores textarea focus.
  • Invalid regex shows an error indicator; does not throw or break editor input.
  • Replace current and Replace all update tab.sql, set tab.dirty = true, and are undoable with ⌘Z / ⌘⇧Z.
  • Match highlights in the overlay scroll in lockstep with the textarea.
  • Find bar closes when switching tabs.
  • src/core/editor-search.js is at 100% coverage.
  • src/ui/editor.js changes maintain its existing coverage threshold.

Non-goals

  • CodeMirror or any editor library.
  • Regex replace with capture groups (plain string replacement only).
  • Code folding, multi-cursor, autocomplete, live validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions