Skip to content
Open
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
4 changes: 4 additions & 0 deletions sqlit/core/binding_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def get_binding_contexts(ctx: InputContext) -> set[str]:
contexts.add("query")
if ctx.vim_mode == VimMode.INSERT:
contexts.add("query_insert")
elif ctx.vim_mode == VimMode.VISUAL:
contexts.add("query_visual")
elif ctx.vim_mode == VimMode.VISUAL_LINE:
contexts.add("query_visual_line")
else:
contexts.add("query_normal")
if ctx.autocomplete_visible:
Expand Down
48 changes: 48 additions & 0 deletions sqlit/core/keymap.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,57 @@ def _build_action_keys(self) -> list[ActionKeyDef]:
ActionKeyDef("F", "cursor_find_char_back", "query_normal"),
ActionKeyDef("t", "cursor_till_char", "query_normal"),
ActionKeyDef("T", "cursor_till_char_back", "query_normal"),
ActionKeyDef("v", "enter_visual_mode", "query_normal"),
ActionKeyDef("V", "enter_visual_line_mode", "query_normal"),
ActionKeyDef("x", "delete_char", "query_normal"),
ActionKeyDef("a", "append_insert_mode", "query_normal"),
ActionKeyDef("A", "append_line_end", "query_normal"),
# Query (visual mode - charwise)
ActionKeyDef("escape", "exit_visual_mode", "query_visual"),
ActionKeyDef("v", "exit_visual_mode", "query_visual", primary=False),
ActionKeyDef("V", "switch_to_visual_line_mode", "query_visual"),
ActionKeyDef("y", "visual_yank", "query_visual"),
ActionKeyDef("d", "visual_delete", "query_visual"),
ActionKeyDef("x", "visual_delete", "query_visual", primary=False),
ActionKeyDef("c", "visual_change", "query_visual"),
ActionKeyDef("enter", "visual_execute", "query_visual"),
ActionKeyDef("h", "cursor_left", "query_visual"),
ActionKeyDef("j", "cursor_down", "query_visual"),
ActionKeyDef("k", "cursor_up", "query_visual"),
ActionKeyDef("l", "cursor_right", "query_visual"),
ActionKeyDef("w", "cursor_word_forward", "query_visual"),
ActionKeyDef("W", "cursor_WORD_forward", "query_visual"),
ActionKeyDef("b", "cursor_word_back", "query_visual"),
ActionKeyDef("B", "cursor_WORD_back", "query_visual"),
ActionKeyDef("0", "cursor_line_start", "query_visual"),
ActionKeyDef("circumflex_accent", "cursor_first_non_blank", "query_visual"),
ActionKeyDef("dollar_sign", "cursor_line_end", "query_visual"),
ActionKeyDef("G", "cursor_last_line", "query_visual"),
ActionKeyDef("g", "g_leader_key", "query_visual"),
ActionKeyDef("percent_sign", "cursor_matching_bracket", "query_visual"),
ActionKeyDef("f", "cursor_find_char", "query_visual"),
ActionKeyDef("F", "cursor_find_char_back", "query_visual"),
ActionKeyDef("t", "cursor_till_char", "query_visual"),
ActionKeyDef("T", "cursor_till_char_back", "query_visual"),
ActionKeyDef("down", "cursor_down", "query_visual", primary=False),
ActionKeyDef("up", "cursor_up", "query_visual", primary=False),
ActionKeyDef("left", "cursor_left", "query_visual", primary=False),
ActionKeyDef("right", "cursor_right", "query_visual", primary=False),
# Query (visual line mode)
ActionKeyDef("escape", "exit_visual_line_mode", "query_visual_line"),
ActionKeyDef("V", "exit_visual_line_mode", "query_visual_line", primary=False),
ActionKeyDef("v", "switch_to_visual_mode", "query_visual_line"),
ActionKeyDef("y", "visual_line_yank", "query_visual_line"),
ActionKeyDef("d", "visual_line_delete", "query_visual_line"),
ActionKeyDef("x", "visual_line_delete", "query_visual_line", primary=False),
ActionKeyDef("c", "visual_line_change", "query_visual_line"),
ActionKeyDef("j", "cursor_down", "query_visual_line"),
ActionKeyDef("k", "cursor_up", "query_visual_line"),
ActionKeyDef("G", "cursor_last_line", "query_visual_line"),
ActionKeyDef("g", "g_leader_key", "query_visual_line"),
ActionKeyDef("down", "cursor_down", "query_visual_line", primary=False),
ActionKeyDef("up", "cursor_up", "query_visual_line", primary=False),
ActionKeyDef("enter", "visual_line_execute", "query_visual_line"),
# Query (insert mode)
ActionKeyDef("escape", "exit_insert_mode", "query_insert"),
ActionKeyDef("ctrl+enter", "execute_query_insert", "query_insert"),
Expand Down
2 changes: 2 additions & 0 deletions sqlit/core/vim.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ class VimMode(Enum):

NORMAL = "NORMAL"
INSERT = "INSERT"
VISUAL = "VISUAL"
VISUAL_LINE = "VISUAL LINE"
4 changes: 4 additions & 0 deletions sqlit/domains/query/state/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
from .query_focused import QueryFocusedState
from .query_insert import QueryInsertModeState
from .query_normal import QueryNormalModeState
from .query_visual import QueryVisualModeState
from .query_visual_line import QueryVisualLineModeState

__all__ = [
"AutocompleteActiveState",
"QueryFocusedState",
"QueryInsertModeState",
"QueryNormalModeState",
"QueryVisualModeState",
"QueryVisualLineModeState",
]
3 changes: 3 additions & 0 deletions sqlit/domains/query/state/query_normal.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def _setup_actions(self) -> None:
# Undo/redo
self.allows("undo", help="Undo")
self.allows("redo", help="Redo")
# Visual modes
self.allows("enter_visual_mode", label="Visual", help="Enter visual mode")
self.allows("enter_visual_line_mode", label="Visual Line", help="Enter visual line mode")

def get_display_bindings(self, app: InputContext) -> tuple[list[DisplayBinding], list[DisplayBinding]]:
left: list[DisplayBinding] = []
Expand Down
109 changes: 109 additions & 0 deletions sqlit/domains/query/state/query_visual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Query editor visual (charwise) mode state."""

from __future__ import annotations

from sqlit.core.input_context import InputContext
from sqlit.core.state_base import DisplayBinding, State, resolve_display_key
from sqlit.core.vim import VimMode


class QueryVisualModeState(State):
"""Query editor in VISUAL mode (v)."""

help_category = "Query Editor (Visual)"

def _setup_actions(self) -> None:
self.allows(
"exit_visual_mode",
label="Exit Visual",
help="Exit visual mode",
)
self.forbids("enter_visual_mode")
self.forbids("enter_insert_mode")
self.forbids("delete_leader_key")
self.forbids("yank_leader_key")
self.forbids("change_leader_key")
# Switch to visual line
self.allows("switch_to_visual_line_mode", help="Switch to visual line mode")
# Visual operators
self.allows("visual_yank", label="Yank", help="Yank selection")
self.allows("visual_delete", label="Delete", help="Delete selection")
self.allows("visual_change", label="Change", help="Change selection")
self.allows("visual_execute", label="Execute", help="Execute selection")
# All cursor motions
self.allows("cursor_left", help="Move cursor left")
self.allows("cursor_right", help="Move cursor right")
self.allows("cursor_up", help="Move cursor up")
self.allows("cursor_down", help="Move cursor down")
self.allows("cursor_word_forward", help="Move to next word")
self.allows("cursor_WORD_forward", help="Move to next WORD")
self.allows("cursor_word_back", help="Move to previous word")
self.allows("cursor_WORD_back", help="Move to previous WORD")
self.allows("cursor_first_non_blank", help="Move to first non-blank")
self.allows("cursor_line_start", help="Move to line start")
self.allows("cursor_line_end", help="Move to line end")
self.allows("cursor_last_line", help="Move to last line")
self.allows("cursor_matching_bracket", help="Move to matching bracket")
self.allows("cursor_find_char", help="Find char forward")
self.allows("cursor_find_char_back", help="Find char backward")
self.allows("cursor_till_char", help="Move till char forward")
self.allows("cursor_till_char_back", help="Move till char backward")
self.allows("g_leader_key", help="Go motions (menu)")
self.allows("g_first_line", help="Go to first line")

def get_display_bindings(self, app: InputContext) -> tuple[list[DisplayBinding], list[DisplayBinding]]:
left: list[DisplayBinding] = []
seen: set[str] = set()

left.append(
DisplayBinding(
key=resolve_display_key("exit_visual_mode") or "<esc>",
label="Exit Visual",
action="exit_visual_mode",
)
)
seen.add("exit_visual_mode")
left.append(
DisplayBinding(
key=resolve_display_key("visual_yank") or "y",
label="Yank",
action="visual_yank",
)
)
seen.add("visual_yank")
left.append(
DisplayBinding(
key=resolve_display_key("visual_delete") or "d",
label="Delete",
action="visual_delete",
)
)
seen.add("visual_delete")
left.append(
DisplayBinding(
key=resolve_display_key("visual_change") or "c",
label="Change",
action="visual_change",
)
)
seen.add("visual_change")
left.append(
DisplayBinding(
key=resolve_display_key("visual_execute") or "<enter>",
label="Execute",
action="visual_execute",
)
)
seen.add("visual_execute")

if self.parent:
parent_left, _ = self.parent.get_display_bindings(app)
for binding in parent_left:
if binding.action not in seen:
left.append(binding)
seen.add(binding.action)

return left, []

def is_active(self, app: InputContext) -> bool:
return app.focus == "query" and app.vim_mode == VimMode.VISUAL
114 changes: 114 additions & 0 deletions sqlit/domains/query/state/query_visual_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Query editor visual line mode state."""

from __future__ import annotations

from sqlit.core.input_context import InputContext
from sqlit.core.state_base import DisplayBinding, State, resolve_display_key
from sqlit.core.vim import VimMode


class QueryVisualLineModeState(State):
"""Query editor in VISUAL LINE mode (V)."""

help_category = "Query Editor (Visual Line)"

def _setup_actions(self) -> None:
self.allows(
"exit_visual_line_mode",
label="Exit Visual",
help="Exit visual line mode",
)
# Block entering visual line mode when already in it
self.forbids("enter_visual_line_mode")
# Switch to charwise visual
self.allows("switch_to_visual_mode", help="Switch to visual mode")
# Block normal mode operators (visual mode uses direct operators)
self.forbids("enter_insert_mode")
self.forbids("delete_leader_key")
self.forbids("yank_leader_key")
self.forbids("change_leader_key")
# Visual line operators
self.allows(
"visual_line_yank",
label="Yank",
help="Yank selected lines",
)
self.allows(
"visual_line_delete",
label="Delete",
help="Delete selected lines",
)
self.allows(
"visual_line_change",
label="Change",
help="Change selected lines",
)
# Execute selected lines
self.allows(
"visual_line_execute",
label="Execute",
help="Execute selected lines",
)
# Vertical cursor movement
self.allows("cursor_up", help="Extend selection up")
self.allows("cursor_down", help="Extend selection down")
self.allows("cursor_last_line", help="Extend selection to last line")
self.allows("g_leader_key", help="Go motions (menu)")
self.allows("g_first_line", help="Extend selection to first line")

def get_display_bindings(self, app: InputContext) -> tuple[list[DisplayBinding], list[DisplayBinding]]:
left: list[DisplayBinding] = []
seen: set[str] = set()

left.append(
DisplayBinding(
key=resolve_display_key("exit_visual_line_mode") or "<esc>",
label="Exit Visual",
action="exit_visual_line_mode",
)
)
seen.add("exit_visual_line_mode")
left.append(
DisplayBinding(
key=resolve_display_key("visual_line_yank") or "y",
label="Yank",
action="visual_line_yank",
)
)
seen.add("visual_line_yank")
left.append(
DisplayBinding(
key=resolve_display_key("visual_line_delete") or "d",
label="Delete",
action="visual_line_delete",
)
)
seen.add("visual_line_delete")
left.append(
DisplayBinding(
key=resolve_display_key("visual_line_change") or "c",
label="Change",
action="visual_line_change",
)
)
seen.add("visual_line_change")
left.append(
DisplayBinding(
key=resolve_display_key("visual_line_execute") or "<enter>",
label="Execute",
action="visual_line_execute",
)
)
seen.add("visual_line_execute")

if self.parent:
parent_left, _ = self.parent.get_display_bindings(app)
for binding in parent_left:
if binding.action not in seen:
left.append(binding)
seen.add(binding.action)

return left, []

def is_active(self, app: InputContext) -> bool:
return app.focus == "query" and app.vim_mode == VimMode.VISUAL_LINE
4 changes: 4 additions & 0 deletions sqlit/domains/query/ui/mixins/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
from .query_editing_operators import QueryEditingOperatorsMixin
from .query_editing_selection import QueryEditingSelectionMixin
from .query_editing_undo import QueryEditingUndoMixin
from .query_editing_visual import QueryEditingVisualMixin
from .query_editing_visual_line import QueryEditingVisualLineMixin
from .query_execution import QueryExecutionMixin
from .query_results import QueryResultsMixin


class QueryMixin(
QueryEditingVisualMixin,
QueryEditingVisualLineMixin,
QueryEditingCommonMixin,
QueryEditingUndoMixin,
QueryEditingSelectionMixin,
Expand Down
Loading