Skip to content

Rewrite interactive mode: ratatui TUI with completion, syntax highlighting, and more#11

Open
tobias-fire wants to merge 126 commits intomainfrom
rewrite
Open

Rewrite interactive mode: ratatui TUI with completion, syntax highlighting, and more#11
tobias-fire wants to merge 126 commits intomainfrom
rewrite

Conversation

@tobias-fire
Copy link

Summary

This PR replaces the rustyline-based REPL with a full-featured terminal UI built on ratatui + tui-textarea + crossterm.

Core TUI

  • Three-pane layout: scrollable output pane, multi-line input textarea, status bar footer
  • SQL syntax highlighting in the input area and history search popup
  • Streaming progress bar during query execution
  • Mouse support: click to position cursor, click to accept completion items

Editing

  • Multi-line input with Shift/Alt+Enter to insert newlines
  • Ctrl+Z / Ctrl+Y undo/redo
  • Ctrl+E — open current query in $EDITOR; content loads back on save
  • Alt+F — format SQL in-place using sqlformat
  • Bracket matching highlight

Completion & Search

  • Tab — context-aware completion: SQL keywords/tables/columns, slash command names, @-prefixed file paths
  • Ctrl+R — reverse history search with cursor navigation (Left/Right/Ctrl+A)
  • Ctrl+Space — fuzzy schema search overlay
  • Function signature hint popup
  • Common-prefix completion when multiple candidates share a prefix

Slash commands

Command Description
/run @<file>|<sql> Execute SQL from a file or inline
/benchmark [N] @<file>|<sql> N timed runs + 1 warmup, live stats, Ctrl+C to stop
/watch [N] @<file>|<sql> Re-run every N seconds, Ctrl+C to stop
/qh [limit] [minutes] Query history from information_schema
/refresh Refresh schema completion cache
/view Open last result in csvlens viewer

Dot commands

.format = client:auto|vertical|horizontal|<server-format>, .setting = value, etc.

Transactions

Full Firebolt transaction support (protocol 2.4): BEGIN/COMMIT/ROLLBACK with a TXN badge in the status bar. Server-driven parameter updates (firebolt-update-parameters, firebolt-remove-parameters) are propagated back to the TUI context.

Connection monitoring

  • Startup and background schema cache refresh; retries until information_schema queries succeed (handles "Cluster not yet healthy")
  • Red footer with "✗ No server connection" when server is unreachable
  • Auto-reconnect: schema cache is refreshed and "Reconnected." is shown when the server becomes available

Output & rendering

  • Client-side table renderer (client:auto, client:vertical, client:horizontal) replacing comfy-table
  • client:auto is now the default in interactive mode
  • Headless (non-interactive) mode uses the same renderer; cleaner output without URL/stats noise
  • Structured exit codes: 0 = success, 1 = query error, 2 = system error

Ctrl+H help overlay

Floating help window listing all keybindings and slash commands.

Test plan

  • cargo test passes
  • Interactive TUI starts and displays output/input/status panes
  • Tab completion suggests tables, columns, functions, and file paths
  • Ctrl+R opens history search; Left/Right move cursor within search
  • Ctrl+E opens $EDITOR; edited content loads back
  • /benchmark 3 SELECT 1; runs warmup + 3 timed runs with live stats
  • /watch 2 SELECT now(); re-runs every 2s; Ctrl+C stops it
  • BEGIN; ... COMMIT; shows TXN badge; server param updates are reflected
  • Disconnecting server shows red footer; reconnecting shows "Reconnected." and restores completion
  • cargo run -- --core SELECT 1; works headlessly with correct exit code

🤖 Generated with Claude Code

tobias-fire and others added 30 commits February 6, 2026 16:52
Implements --format=auto option that renders JSONLines_Compact output
as formatted tables with automatic content wrapping.

Features:
- New table_renderer module for parsing JSONLines_Compact format
- Dynamic terminal-width-aware table rendering using comfy-table
- Automatic cell content wrapping to fit terminal width
- Support for multiple DATA messages (accumulates before rendering)
- Graceful error handling with fallback to raw output
- Works in both single-query and REPL modes
- Compatible with existing flags (--verbose, --concise)
- Can be saved as default with --update-defaults

Dependencies added:
- terminal_size 0.3 for terminal width detection
- comfy-table 6.2 for table rendering
- home version constraint to avoid edition2024 issues

All tests passing (41/41).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add display_width() function to correctly calculate line width ignoring ANSI escape codes
- Enable ContentArrangement::Dynamic for proper content wrapping in expanded mode
- Add special handling for single-column chunks with wide content using UpperBoundary constraint
- Make should_use_expanded_mode() consistent with rendering by applying same truncation logic
- Add max_value_length parameter to render functions for context-specific truncation (1000 chars for expanded, 10000 for horizontal)
- Add comprehensive tests for expanded mode, truncation, and ANSI width calculation

Fixes alignment issues where very long truncated values (like settings_names with 1000+ chars) caused table borders to extend beyond terminal width. All chunks now align properly at the right edge.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Use LowerBoundary constraints instead of fixed boundaries for multi-column chunks to prevent unnecessary content wrapping
- Skip bottom border of non-last chunks to eliminate double borders between chunks within the same row
- Improves readability by ensuring column names display on single lines and reducing repetitive border lines

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Use lighter '--' borders for header separators (between column names and values)
- Use heavier '==' borders for chunk separators (between different column groups)
- This emphasizes the separation between chunks while keeping headers lighter

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Use '==' borders at the top of each chunk (except first) to emphasize new section
- Use '--' borders for bottom, header separators, and internal structure
- Creates clearer visual separation where each chunk begins

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove bottom border of non-last chunks since the next chunk's top border provides separation
- Reduces visual clutter by having only one separator line (==) between chunks
- Last chunk still has bottom border for proper closure

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Integrate csvlens library to provide an interactive viewer for query
results in REPL mode. Users can type \view or press Ctrl+V (then Enter)
to open the last query result in a full-screen viewer with vim-like
navigation, search, and scrolling capabilities.

Features:
- Store last query result in Context for viewing
- Convert query results to CSV format with proper escaping
- Launch csvlens viewer with temporary CSV file
- Support both \view text command and Ctrl+V keybind
- Add \help command to show available commands
- Handle error cases gracefully (no results, query errors, empty data)

Implementation:
- New viewer module (src/viewer.rs) for csvlens integration
- CSV conversion functions in table_renderer with RFC 4180 escaping
- Temporary file creation using process ID for uniqueness
- Command detection in REPL loop for \view and \help
- Ctrl+V keybind inserts \view command (user presses Enter to execute)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces the complex chunked expanded mode with a cleaner vertical format
that displays each row as a two-column table (column name | value). This
eliminates ~145 lines of chunking logic while improving readability.

Changes:
- Replace render_table_expanded() with render_table_vertical()
- Rename all "expanded" terminology to "vertical" throughout codebase
- Update format option from --format=expanded to --format=vertical
- Simplify row display: "Row N:" header with simple two-column table
- Column names in cyan bold, values with natural wrapping
- Update all tests to match new format

Benefits: Simpler code, cleaner output, easier to scan, no chunk boundaries

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces content-aware vertical mode detection with simple math based on
available space per column. Adds two configurable parameters for control.

Changes:
- Add --min-col-width (default: 15) to control vertical mode threshold
- Add --max-cell-length (default: 1000) for content truncation
- Replace should_use_vertical_mode with simple calculation:
  terminal_width / num_columns < min_col_width
- Update horizontal table renderer to use equal column widths
- Set explicit ColumnConstraint for predictable layout
- Remove all content inspection from decision logic

Benefits:
- Predictable behavior independent of content
- User-configurable thresholds via command-line options
- Simpler code with no content-aware logic
- Equal column widths for consistent visual alignment

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace eprintln!("") and println!("") with eprintln!() and println!()
to fix clippy warnings about unnecessary empty string arguments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add #[allow(dead_code)] attributes with explanatory comments to:
- JsonLineMessage::Start fields (query_id, request_id, query_label)
- ResultColumn.column_type field

These fields are part of the Firebolt JSONLines_Compact protocol and
required for deserialization but not currently used by the renderer.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add 11 new test functions validating Firebolt's JSONLines_Compact
serialization format for all data types:
- BIGINT (JSON string for precision)
- NUMERIC/DECIMAL (JSON string for exact decimals)
- INT (JSON number)
- DOUBLE/REAL (JSON number)
- DATE/TIMESTAMP (ISO format strings)
- ARRAY (JSON array)
- TEXT (JSON string with unicode)
- BYTEA (hex-encoded string)
- GEOGRAPHY (WKB hex string)
- BOOLEAN/NULL
- CSV null handling differences

These tests verify that format_value() correctly handles Firebolt's
type-specific serialization patterns without requiring type-aware logic.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Delete the display_width() function and its test test_display_width()
as they were added but never used. The comfy_table library handles
ANSI escape sequence width calculation internally with
ContentArrangement::Dynamic.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add comprehensive documentation for Firebolt's type-specific JSON
serialization patterns:

1. Added detailed function documentation for format_value() and
   format_value_csv() explaining how each Firebolt type maps to JSON

2. Added new "Data Type Handling in JSONLines_Compact Format" section
   to CLAUDE.md with:
   - Complete type mapping table (INT, BIGINT, NUMERIC, DATE, etc.)
   - Explanation of why BIGINT/NUMERIC use strings (precision)
   - How BYTEA and GEOGRAPHY are hex-encoded
   - Client-side rendering behavior
   - Note that column_type field is available for future use

This documents the actual Firebolt serialization behavior validated
by the comprehensive test suite added in previous commits.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Change println! to print! for raw body output to match upstream behavior.
The server response already includes trailing newlines, so println! was
adding an extra unwanted newline.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
SQL NULL values are now rendered in dark gray color to distinguish
them from the string "NULL". This improves readability by making it
immediately clear which values are actual nulls vs string data.

Changes:
- NULL values displayed in Color::DarkGrey in both horizontal and
  vertical table rendering modes
- Added tests to verify NULL rendering doesn't crash
- Works in both render_table() and render_table_vertical()

To verify: Run a query with NULL values in a terminal:
  fb --core --format=auto "SELECT NULL as n, 'NULL' as s"

The real NULL will appear in darker gray while the string 'NULL'
will display in normal color.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add explicit client-side vs server-side rendering modes using prefix notation
(client:auto, client:vertical, client:horizontal for client rendering;
PSQL, JSON, CSV, etc. for server rendering). Interactive sessions default
to client:auto for pretty tables, while non-interactive sessions default to
PSQL for backward compatibility. Include helpful warnings when users
accidentally omit the client: prefix, and clarify Ctrl+V+Enter behavior
in documentation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace verbose multi-line statistics with a single clean line showing
only relevant metrics: row count with thousand separators and scanned
bytes with smart KB/MB/GB formatting broken down by local (cache) and
remote (storage). Statistics appear between Time and Request Id for
better readability. Respects --concise flag to suppress all metadata.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Document client-side rendering with client: prefix notation, interactive
result exploration via csvlens viewer, smart statistics formatting, and
updated keyboard shortcuts. Include practical example using
information_schema.engine_query_history.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The home crate was accidentally added but is not used anywhere in the
codebase. The project uses the dirs crate for home directory operations
instead.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…le-column results

- Apply row (10,000) and byte (1MB) limits only in interactive TTY mode;
  non-interactive output and csvlens viewer always receive the full result
- Single-column results get 5x the normal max cell length (5,000 vs 1,000)
- Track interactive mode via Context.is_interactive set from main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements regex-based syntax highlighting for SQL queries in the
interactive REPL mode with industry-standard color scheme.

Features:
- Keywords (SELECT, FROM, WHERE): Bright Blue
- Functions (COUNT, AVG): Bright Cyan
- Strings ('text'): Bright Yellow
- Numbers (42, 3.14): Bright Magenta
- Comments (-- text): Bright Black (gray)
- Operators: Default (subtle)

Configuration:
- Auto-enabled in interactive TTY mode
- Disabled via --no-color flag
- Respects NO_COLOR environment variable
- Auto-disabled for piped/redirected output

Implementation:
- Regex-based highlighting (no new dependencies)
- 13 comprehensive unit tests
- Graceful error handling
- Colorblind-accessible color scheme based on DuckDB, pgcli, and
  accessibility research

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements regex-based syntax highlighting for SQL queries in the
interactive REPL mode with industry-standard color scheme.

Features:
- Keywords (SELECT, FROM, WHERE): Bright Blue
- Functions (COUNT, AVG): Bright Cyan
- Strings ('text'): Bright Yellow
- Numbers (42, 3.14): Bright Magenta
- Comments (-- text): Bright Black (gray)
- Operators: Default (subtle)

Configuration:
- Auto-enabled in interactive TTY mode
- Disabled via --no-color flag
- Respects NO_COLOR environment variable
- Auto-disabled for piped/redirected output

Implementation:
- Regex-based highlighting (no new dependencies)
- 13 comprehensive unit tests
- Graceful error handling
- Colorblind-accessible color scheme based on DuckDB, pgcli, and
  accessibility research

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement context-aware auto-completion that suggests table names and
column names from the database schema. The completion system queries
information_schema on startup and caches results for fast lookups.

Key features:
- Auto-complete table names and column names
- Async schema cache refresh (non-blocking startup)
- Support for message-based query response format
- Configurable via --no-completion and --completion-cache-ttl flags
- Runtime control with 'set completion = on/off'
- Manual refresh with \refresh command

Implementation details:
- New completion module with SqlCompleter, SchemaCache, and context detector
- Queries information_schema.tables, columns, and routines
- Thread-safe cache using Arc<RwLock<T>>
- Graceful error handling and fallback

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements context-aware, frequency-based suggestion ordering with schema exploration support. Tables and columns now appear based on query context, usage frequency, and relevance, with system schemas appropriately deprioritized. Schema names can be completed directly and typing 'schema.' shows all tables in that schema.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements auto-completion for SQL functions with lowest priority (below columns, above system schemas). Functions complete with opening parenthesis for immediate argument typing. Operators are filtered out using routine_type != 'OPERATOR' from information_schema.routines.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rewrites the interactive REPL using ratatui + tui-textarea, replacing
rustyline entirely. The headless (single-query) path is unchanged.

Layout: 3-pane vertical split — scrollable output pane (bottom-anchored),
multi-line input area with tui-textarea, 1-line status bar.

Key features:
- Multi-line SQL editing with Shift+Enter for explicit newlines
- File-backed history (same ~/.firebolt/fb_history format) with
  Up/Down and Ctrl+Up/Down navigation, deduplication, 10k cap
- Kitty keyboard protocol for Shift+Enter disambiguation
- Mouse scroll routed to output pane; Shift+drag still selects text
- Ctrl+C cancels input or in-flight query via CancellationToken
- Ctrl+V / \view opens csvlens (suspends ratatui, resumes after)
- Query output routed through TuiMsg channel (Line / StyledLines)
  to avoid ANSI round-trips for table rendering
- Custom TUI table renderer with Unicode box-drawing borders,
  smart column-width algorithm, auto horizontal/vertical switching,
  per-column header (cyan), NULL (dark gray), error (red) styling
- Control characters in cell values replaced with spaces to prevent
  ratatui misalignment (fixes query_text newline rendering bug)
- SQL echo in output pane: ❯ green+bold, SQL text yellow
- Stats lines (Time:/Scanned:) rendered in dark gray
- Spinner + elapsed time shown while query is running

New files: src/tui/{mod,layout,output_pane,history}.rs, src/tui_msg.rs
Removed:   src/repl_helper.rs (rustyline adapter no longer needed)
Added:     src/docs/{tui,output_pipeline,table_rendering,completion}.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements readline-style incremental reverse search over history:
- Ctrl+R activates search mode; subsequent presses cycle to older matches
- Characters appended to the search query narrow the match in real time
- Backspace removes the last search character and re-searches from most-recent
- Enter accepts the current match into the textarea
- Escape / Ctrl+G restores the textarea to its pre-search content
- Any other key (arrows, etc.) accepts the match then re-dispatches normally

The input pane is replaced by a two-line search overlay with a cyan border:
  (reverse-i-search)`query':
  <matched entry, truncated to pane width>

Status bar shows contextual hint: "Enter accept  Ctrl+R older  Esc cancel"
while search is active.

Search is case-insensitive substring match walking from most-recent entry
backward; HistorySearch struct lives in src/tui/history_search.rs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows live row count in the running pane while a query streams results.
The query thread sends TuiMsg::Progress(n) after each data batch; the
TUI updates progress_rows and renders "⠸ 1.4s  12,345 rows received".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keywords (cyan/bold), strings (yellow), numbers (magenta), comments (gray)
and functions (blue) are highlighted as the user types.  Implemented by
computing byte-range spans from the existing regex patterns in highlight.rs,
then post-processing the ratatui buffer after tui-textarea renders — this
works around tui-textarea 0.7 having no built-in multi-color highlight API.

Also adds the tree-sitter + devgen-tree-sitter-sql crates and a new
sql_parser.rs module (create_parser / sql_language) that Phase 6 will use
for AST-based completion context detection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tobias-fire and others added 30 commits February 24, 2026 15:42
For server-side parameters (anything that modifies args.extra), send a
SELECT 1 with the new setting to verify the server accepts it before
persisting the change. Local-only settings (format, completion) and
unset commands skip validation. On rejection, echo the set statement
and show the server's error message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove unused imports, delete dead code where safe, suppress with
#[allow(dead_code)] where the code is part of an unfinished subsystem
(ANSI highlighter, schema cache, context detector). Remove needless
mut bindings and unused variables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the old comfy-table-based render_table / render_table_vertical
with render_table_plain, which calls the same TUI line renderer used in
interactive mode and converts TuiLines to plain Unicode text.  Output
now uses box-drawing characters (┌─┐│╞═╡) in all modes.

Remove: comfy-table dependency, render_table, render_table_vertical,
should_use_vertical_mode, and the tests that covered them.

Update integration tests to check for │ instead of +/|, and relax the
wide-table test since vertical/horizontal layout depends on terminal
width at runtime.

Update README: client:auto is the default in all modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ide format

- Remove URL printing for set/unset and server-updated-URL in headless
  mode (only --verbose still shows URLs)
- Client-side format in headless: emit Time/Scanned/Request Id to stdout
  right after the table, matching TUI output-pane behaviour
- Server-side format: keep Time/Scanned/Request Id on stderr (scripting)
- Update test to reflect the new stdout/stderr split

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server-side formats (PSQL, JSON, CSV, etc.) now produce no timing,
scan stats, or request ID in headless mode — stdout is raw server
output only. Stats remain visible in the TUI for all formats.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove --concise and --no-spinner flags entirely
- Spinner now shown in non-interactive mode only for client-side formats
  (mirroring when stats are shown); auth spinner follows same rule
- Stats always shown for client-side formats, never for server-side
- ^C cancellation message removed; system engine hint TUI-only
- README updated: scripting examples, stdout/stderr section, flags list
- Tests updated: remove all --concise usage, delete redundant test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Client-only settings (format, completion) now use a dot-prefix syntax
instead of the SQL SET command, which is reserved for server parameters:

  .format = client:auto       -- set output format
  .format = JSON              -- switch to server-side rendering
  .format =                   -- reset to default (client:auto)
  .completion = off           -- disable tab completion
  .completion = on            -- re-enable tab completion
  .format                     -- show current value

Behaviour changes:
- `set completion = ...` in the TUI shows a clear error redirecting to
  the dot syntax; in pipe mode it is not intercepted (sent to server)
- `unset format` now resets to `client:auto` (was `PSQL`)
- `set format = ...` still works for backward compat (both client: and
  server-side values); only the dot syntax is documented as canonical
- Dot commands work in TUI (Enter handler) and non-interactive pipe mode
- Viewer flash message updated to show `.format = client:auto`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t format list

- Fix dot command errors corrupting TUI: route output through a temporary
  channel instead of falling back to eprintln! while in raw mode
- .format now only accepts client:* formats; server-side formats require
  --format or set output_format=<value>; at runtime
- Tab completion for .format only suggests client:auto/vertical/horizontal
- Add tab completion for dot commands (both key name and value)
- Add dot_command_hint() for server validation failures on known client settings
- Remove unsupported CSV and JSON formats from README, args help, and
  completion candidates; replace with correct list: PSQL, JSON_Compact,
  JSON_CompactLimited, JSONLines_Compact, TabSeparatedWithNamesAndTypes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Columns were the only candidate type (tables, schemas, functions all had it)
not receiving a usage frequency bonus. Frequently-used column names now rank
higher within their priority class, matching the behaviour of the other types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lookup

Two bugs prevented column usage bonuses from being applied:

1. extract_column_names required FROM to appear after SELECT, so queries
   written as 'FROM table SELECT col' (Firebolt allows this) never had their
   columns tracked. Now extracts the SELECT list by scanning forward from
   SELECT to the next major clause (FROM/WHERE/GROUP/ORDER/etc.), regardless
   of what came before SELECT.

2. PriorityScorer::score passed the fully-qualified column name
   (e.g. 'engine_user_query_history.query_text') to the usage tracker, but
   the tracker stores bare names ('query_text'). The lookup always returned 0.
   Now strips the table prefix before the get_count call for columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ressions

- Remove apply_output_limits() from query.rs (inline limits still used)
- Remove is_auto_display() and should_use_colors() from args.rs
- Remove get_args() #[allow(dead_code)] annotation
- Remove item_type field from FuzzyItem in fuzzy_completer.rs
- Remove push_prompt() from output_pane.rs; remove allow on push_prompt_highlighted
- Remove detect_context, CompletionContext, KEYWORD_PATTERN, find_last_keyword,
  is_inside_string_or_comment from context_detector.rs (replaced by context_analyzer)
- Remove get_completions() from schema_cache.rs (unused after context_detector cleanup)

All 173 unit tests and 32 integration tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the block cursor rests on '(', ')', '[', or ']', the matching
bracket is highlighted in bold LightCyan so both ends of the pair are
immediately visible.

Implementation:
- find_matching_paren(): scans forward (from '('/']') or backward (from
  ')'/']') tracking nesting depth; works across newlines in multiline queries
- apply_textarea_highlights(): computes the cursor's byte offset, calls
  find_matching_paren, and applies the paren style on top of syntax
  highlighting for the matched character
- Cursor character itself keeps its existing reversed block-cursor style;
  only the non-cursor end receives the cyan highlight

Six unit tests cover forward, backward, nested, square bracket, multiline,
and no-match cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The matching bracket now gets Color::Indexed(234) as background —
the same dark-gray used for the current-line highlight — so the
highlight is consistent with the existing visual language of the TUI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For /benchmark and /watch, only the SQL argument is passed to sqlformat;
the command prefix (including any optional numeric count) is kept verbatim.
For /run the argument is a file path so formatting is skipped entirely.

Plain SQL (no slash prefix) is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously Alt+F broke on /benchmark<newline>SELECT because
find_slash_arg_col requires the command and SQL to be on the same line.

Adds slash_cmd_sql_offset() which handles both layouts:
  /benchmark 5 SELECT …    ← SQL same line (existing)
  /benchmark 5<newline>SELECT … ← SQL on next line (new)
  /benchmark<newline>SELECT …   ← bare command, SQL on next line (new)

/run is now treated the same as /benchmark and /watch (its SQL argument
is formatted) rather than being skipped entirely.

Five unit tests cover same-line, same-line-with-count, next-line,
next-line-with-count, and no-match cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Transactions (BEGIN / COMMIT / ROLLBACK) now work correctly end-to-end:

Protocol version
  Bump Firebolt-Protocol-Version header from 2.3 to 2.4, which is
  required by the server to enable transaction support.

Response header handling (query.rs)
  - apply_update_parameters(): new helper that correctly parses the
    comma-separated "key=value,key=value" format of
    Firebolt-Update-Parameters and applies each pair via set_args.
    The previous code passed the whole string as a single set command,
    which silently corrupted multi-value headers.
  - remove_parameters(): new helper that parses the comma-separated key
    list in Firebolt-Remove-Parameters and removes each from args.extra.
    The previous code called unset_args with the full comma-joined string,
    which only worked for single-key responses.
  - Firebolt-Reset-Session: new handler — clears transaction_id and
    transaction_sequence_id from the session. The server sends this header
    after a successful COMMIT or ROLLBACK.

Transaction state (context.rs)
  in_transaction() — returns true when transaction_id is present in
  args.extra (injected by the server via Firebolt-Update-Parameters after
  BEGIN, removed via Reset-Session / Remove-Parameters after COMMIT /
  ROLLBACK).

TUI status bar (tui/mod.rs)
  Show a bold dark-orange " TXN " badge in the status bar whenever
  in_transaction() is true, so the user always knows they are inside an
  open transaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
execute_queries clones context before spawning the query task, so
transaction_id (set via Firebolt-Update-Parameters after BEGIN) was
only applied to the clone and never reached self.context.

Add TuiMsg::ParamUpdate(Vec<String>) which carries the updated extras
list. query.rs sends it whenever any of the three transaction-related
response headers are processed; drain_query_output applies it to the
live self.context so in_transaction() and the TXN badge work correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unit tests (query.rs) — no server required:
- apply_update_parameters: single pair, multiple pairs, empty string,
  trailing comma, preserves pre-existing extras
- remove_parameters: single key, multiple keys, exact prefix match
  (doesn't touch transaction_id_extra), preserves unrelated extras,
  empty-string no-op
- in_transaction_lifecycle: full BEGIN→mid-sequence-bump→reset→
  second-transaction state machine

Integration tests (tests/cli.rs):
- test_transaction_begin_commit_succeeds
- test_transaction_begin_rollback_succeeds
- test_transaction_id_appears_in_url_after_begin (--verbose)
- test_transaction_id_absent_from_url_after_commit (--verbose)
- test_transaction_id_absent_from_url_after_rollback (--verbose)
- test_transaction_dml_commit  (CREATE/BEGIN/INSERT/COMMIT/SELECT/DROP)
- test_transaction_dml_rollback (CREATE/BEGIN/INSERT/ROLLBACK/SELECT/DROP)
- test_transaction_sequential_transactions (two back-to-back transactions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. push_custom_settings: exclude transaction_id and
   transaction_sequence_id from the "Settings: ..." echo line.
   These are server-managed internal params, not user-visible settings.

2. Schema refresh: suppress the DDL-triggered schema cache refresh when
   a transaction is open.  BEGIN returns zero columns (same DDL signal
   as CREATE TABLE), causing a schema query to fire inside the open
   transaction — which then fails.  The refresh defers naturally to
   after COMMIT/ROLLBACK, which is correct since BEGIN doesn't change
   any schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Context::without_transaction() which returns a clone with
transaction_id and transaction_sequence_id stripped from args.extra
and the URL rebuilt.

Use it in all three places that fire internal queries:
- schema cache auto-refresh (DDL-triggered, drain_query_output)
- schema cache manual refresh (/refresh command, do_refresh)
- setting validation (validate_setting in execute_queries)

This also removes the earlier in_transaction() guard on the DDL
schema refresh, which is no longer needed since the context passed to
refresh() is now always transaction-free.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The in_transaction() guard was already dropped in the previous commit
(without_transaction() makes the refresh safe regardless of whether a
transaction is open). Remove the now-incorrect comment that still
described that old special case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tui/mod.rs:
- slash commands: call history.reset_navigation() at entry so Up/Down
  navigation is consistent after /run, /benchmark, /watch, etc.
- execute_queries: fix extra_before baseline — capture it from the
  already-stripped test_ctx instead of self.context so the "did this
  set command add a new parameter" check isn't confused by transaction
  params being present in one but absent in the other
- complete_file_paths: replace unwrap() fallback on read_dir(".") with
  a graceful return of an empty Vec, preventing a panic if the working
  directory becomes unreadable
- do_refresh: route schema refresh errors through context.emit_err()
  instead of eprintln! so they appear in the TUI output pane rather
  than corrupting the terminal

schema_cache.rs:
- do_refresh: all warning/error messages now go through context.emit_err()
  so they are displayed correctly in TUI mode

viewer.rs:
- early-return conditions (no result, query errors, empty columns/rows)
  now return Err() so run_viewer can show them as flash messages instead
  of printing to the suspended terminal
- delete the temp CSV file after csvlens exits
- update tests to assert is_err() for the early-return cases

query.rs:
- emit "^C" to the output channel when a query is cancelled so the TUI
  output pane shows a visible cancellation indicator

args.rs:
- replace format!("&output_format=JSONLines_Compact") with a plain
  string (no interpolation arguments)

CLAUDE.md:
- replace stale rustyline/Ctrl+O/Ctrl+V descriptions with accurate
  ratatui TUI behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…connect pinger

- Set context.tui_output_tx = Some(bg_tx) in TuiApp::new() so all cloned
  contexts (schema refresh, do_refresh) inherit a real sender instead of
  falling back to eprintln! and corrupting the display
- Add bg_rx drain in event loop: routes Warning:/Error: lines to output pane
- Add ConnectionStatus(bool) TuiMsg variant emitted by every schema refresh
- On ConnectionStatus(false): show host/db in red in the status bar and
  spawn a background pinger (SELECT 1, once per second) until reconnected
- On ConnectionStatus(true): restore green indicator, stop pinger, trigger
  a silent schema refresh to populate completions after reconnection
- schema_cache::do_refresh now returns Err when the tables query fails so
  callers can distinguish "server unreachable" from partial failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rintln!

Removed the erroneous ctx.tui_output_tx = None that was stripping the output
channel from the reconnect-triggered schema refresh. When the server starts up
partially (e.g. "Cluster not yet healthy"), the pinger's SELECT 1 would succeed
triggering a reconnect refresh, but schema warnings would fall back to eprintln!
and corrupt the TUI display. Now all warnings from reconnect refresh appear in
the output pane like other background errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oter

- Initialize connected=true (optimistic); avoids spurious double-refresh when
  the startup refresh succeeds on the first try
- When pinger detects reconnection, emit "Reconnected. Refreshing schema cache..."
  to the output pane before spawning the schema refresh task
- Footer now shows " ✗ No server connection" appended to the red host|db span
  so the disconnected state is unambiguous

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the pinger used query_silent("SELECT 1") which returns Ok for any
HTTP 200 response, including {"errors": [{"description": "Cluster not yet
healthy"}]}. This caused premature ConnectionStatus(true) during cluster startup.

- Add ping_server() to query.rs: sends SELECT 42 via query_silent, then scans
  the response body for a top-level "errors" key — returns Err if found
- Startup schema refresh: ping_server first; only run cache.refresh() on Ok,
  otherwise send ConnectionStatus(false) and let the pinger handle it
- Pinger: replace query_silent with ping_server for the same reason

The schema refresh is now only attempted once the server can actually execute
queries, not merely accept TCP connections or return HTTP 200 with error JSON.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… queries succeed

The previous ping_server(SELECT 42) approach failed because SELECT 42 is a trivial
constant that the server handles immediately, while information_schema queries still
fail with "Cluster not yet healthy" during startup.

Changes:
- schema_cache::do_refresh: run tables query first; if the response body contains
  a top-level "errors" key (server starting up), return Err silently without
  emitting warnings. Only run columns/functions/signatures queries after tables
  succeeds, avoiding wasted requests against an unavailable cluster.
- Remove ping_server() from query.rs — no longer needed.
- Replace startup spawn + separate pinger with spawn_schema_retry_loop() that
  calls cache.refresh() directly every 1s until it returns Ok. On first failure
  sends ConnectionStatus(false) (red footer); on success sends ConnectionStatus(true).
  A /dev/null channel suppresses repeated retry warnings from the output pane.
- drain_bg_output ConnectionStatus(true): schema is already populated by the retry
  loop, so just show "Reconnected." without spawning an additional refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the startup retry loop sent ConnectionStatus(false), drain_bg_output
saw ping_active=false and spawned a second retry loop. The second loop
immediately hit is_refreshing()=true (set by the still-running first loop)
and returned Ok(()), sending ConnectionStatus(true) without populating the
schema — causing "Reconnected." to appear but completions to not work.

Fix: set ping_active=true before spawning the initial retry loop in run(),
so drain_bg_output's ConnectionStatus(false) handler skips the duplicate spawn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After cache.refresh() returns Ok(()), check that functions or tables are
non-empty. If both are empty the refresh completed via the is_refreshing()
early-exit path without populating the cache, so retry instead of signalling
a successful reconnection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant