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
11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rowdy"
version = "0.17.2"
version = "0.18.0"
edition = "2024"
rust-version = "1.86"
license = "MIT"
Expand All @@ -26,6 +26,7 @@ nucleo-matcher = "0.3.1"
rand = "0.10.1"
ratatui = "0.30.0"
ratatui-textarea = "0.9.1"
regex = "1.12.4"
reqwest = { version = "0.12.28", default-features = false, features = ["rustls-tls", "json"] }
rpassword = "7.4.0"
semver = "1"
Expand Down
90 changes: 88 additions & 2 deletions src/action/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod results;
mod saved_queries;
mod schema;
mod session;
mod substitute;
mod update;

pub use saved_queries::SavedQueryAction;
Expand Down Expand Up @@ -117,6 +118,9 @@ pub enum Action {
/// any) or the statement under the cursor; `All` rewrites the
/// whole buffer.
FormatEditor(FormatScope),
/// A keypress in the interactive `:s///c` confirm prompt. See
/// [`SubstituteConfirmAction`].
SubstituteConfirm(SubstituteConfirmAction),
/// Autocomplete popover lifecycle and navigation. See
/// `CompletionAction` for the sub-variants.
Completion(CompletionAction),
Expand Down Expand Up @@ -423,6 +427,22 @@ pub enum ResultNavAction {
Bottom,
}

/// Keys handled by the interactive `:s///c` confirm prompt, mirroring vim's
/// substitute-confirm vocabulary.
#[derive(Debug, Clone, Copy)]
pub enum SubstituteConfirmAction {
/// `y` — replace this match and advance.
Yes,
/// `n` — skip this match and advance.
No,
/// `a` — replace this and every remaining match.
All,
/// `l` — replace this match, then stop ("last").
Last,
/// `q` / `Esc` — stop without replacing the current match.
Quit,
}

#[derive(Debug, Clone, Copy)]
pub enum ResultColumnAction {
/// Swap the focused column with the visible column to its left.
Expand All @@ -442,15 +462,29 @@ pub fn apply(app: &mut App, action: Action) {
Action::ResizeSchema(delta) => schema::resize_schema(app, delta),
Action::SetPendingChord(c) => app.pending = c,
Action::EditorEvent(ev) => {
app.editor.events.on_event(ev, &mut app.editor.state);
app.editor
.events
.on_event(ev.clone(), &mut app.editor.state);
refresh_search_highlights(app, &ev);
if app.completion.is_some() {
completion::refresh(app);
} else {
completion::maybe_auto_trigger(app);
}
schedule_session_save(app);
}
Action::OpenCommand => app.overlay = Some(Overlay::Command(CommandBuffer::default())),
Action::OpenCommand => {
// Pressing `:` in Visual mode seeds the line with `'<,'>`, so a
// range substitute over the selection is one keystroke away.
let buf = if app.focus == Focus::Editor
&& app.editor.editor_mode() == edtui::EditorMode::Visual
{
CommandBuffer::with_text("'<,'>")
} else {
CommandBuffer::default()
};
app.overlay = Some(Overlay::Command(buf));
}
Action::Command(cmd) => apply_command(app, cmd),
Action::Schema(s) => schema::apply_schema(app, s),
Action::PrepareConfirmRun => query::prepare_confirm_run(app),
Expand Down Expand Up @@ -490,6 +524,7 @@ pub fn apply(app: &mut App, action: Action) {
Action::CloseHelp => app.overlay = None,
Action::HelpScroll(axis, delta) => apply_help_scroll(app, axis, delta),
Action::FormatEditor(scope) => format_editor(app, scope),
Action::SubstituteConfirm(a) => substitute::apply_confirm(app, a),
Action::Completion(c) => completion::apply(app, c),
Action::ReloadSchemaCache => schema::reload_schema_cache(app),
Action::ResetSession => session::reset_session(app),
Expand Down Expand Up @@ -798,6 +833,7 @@ fn dispatch_command(app: &mut App, cmd: command::Command) {
C::Load(name) => saved_queries::apply_load(app, name),
C::RunSaved(Some(name)) => saved_queries::apply_run_saved(app, name),
C::RunSaved(None) => saved_queries::open_run_picker(app),
C::Substitute(sub) => substitute::run(app, sub),
}
}

Expand Down Expand Up @@ -1292,6 +1328,56 @@ fn format_buffer(app: &mut App) {
schedule_session_save(app);
}

/// Keep `/`-search match highlights in sync after an editor event. Owns only
/// the `Search` highlight lifecycle — confirm-run and `:s///c` highlights are
/// managed by their own overlays, so this bails when another owner holds the
/// highlight vector.
fn refresh_search_highlights(app: &mut App, ev: &CtEvent) {
use crate::state::editor::HighlightOwner;
use ratatui::crossterm::event::KeyCode;

if !matches!(
app.editor.highlight_owner,
None | Some(HighlightOwner::Search)
) {
return;
}

let in_search = app.editor.editor_mode() == edtui::EditorMode::Search;
let pattern = app.editor.state.search_pattern();

if !in_search && app.editor.highlight_owner == Some(HighlightOwner::Search) {
// Leaving Search: edtui's `StopSearch` (Esc) clears the pattern; a
// plain Esc in Normal mode also acts as `:nohlsearch`.
let plain_esc = matches!(
ev,
CtEvent::Key(k) if k.code == KeyCode::Esc && k.modifiers.is_empty()
);
if pattern.is_empty() || plain_esc {
app.editor.state.clear_highlights();
app.editor.highlight_owner = None;
return;
}
} else if !in_search {
// Not searching and we don't own the highlights — nothing to do.
return;
}

let style = ratatui::style::Style::default().bg(app.theme.selection_bg);
let spans = crate::state::editor::search_match_spans(&app.editor.state.lines, &pattern);
if spans.is_empty() {
app.editor.state.clear_highlights();
app.editor.highlight_owner = None;
return;
}
let highlights = spans
.into_iter()
.map(|(start, end)| edtui::Highlight::new(start, end, style))
.collect();
app.editor.state.set_highlights(highlights);
app.editor.highlight_owner = Some(HighlightOwner::Search);
}

fn format_sql(sql: &str) -> String {
sqlformat::format(
sql,
Expand Down
3 changes: 3 additions & 0 deletions src/action/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(super) fn prepare_confirm_run(app: &mut App) {
app.theme.selection_fg,
);
crate::state::editor::highlight_range(&mut app.editor.state, &range, style);
app.editor.highlight_owner = Some(crate::state::editor::HighlightOwner::ConfirmRun);
app.overlay = Some(Overlay::ConfirmRun {
statement: range.text,
reason: crate::state::overlay::ConfirmRunReason::Manual,
Expand All @@ -34,6 +35,7 @@ pub(super) fn confirm_run_submit(app: &mut App) {
return;
};
crate::state::editor::clear_confirm_highlight(&mut app.editor.state);
app.editor.highlight_owner = None;
dispatch_query(app, statement);
}

Expand All @@ -43,6 +45,7 @@ pub(super) fn confirm_run_cancel(app: &mut App) {
}
app.overlay = None;
crate::state::editor::clear_confirm_highlight(&mut app.editor.state);
app.editor.highlight_owner = None;
}

pub(super) fn run_statement_under_cursor(app: &mut App) {
Expand Down
Loading
Loading