From 40d33e184760b5f53b361aa9a03d09ccae3ef445 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 29 Nov 2025 21:40:07 -0800 Subject: [PATCH] Support delete to end of line and delete line. --- .idea/workspace.xml | 16 +-------- Command.cc | 76 +++++++++++++++++++++++++++++++++++++++++ Command.h | 2 ++ GUIInputHandler.cc | 52 +++++++++++++++------------- KKeymap.cc | 9 +++++ TerminalInputHandler.cc | 46 ++++++++++++------------- 6 files changed, 139 insertions(+), 62 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e272582..afd40db 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -34,24 +34,10 @@ - - - - - - - - - - - - - - diff --git a/Command.cc b/Command.cc index 3c59460..8eb47e8 100644 --- a/Command.cc +++ b/Command.cc @@ -720,6 +720,80 @@ cmd_delete_char(CommandContext &ctx) } +static bool +cmd_kill_to_eol(CommandContext &ctx) +{ + Buffer *buf = ctx.editor.CurrentBuffer(); + if (!buf) { + ctx.editor.SetStatus("No buffer to edit"); + return false; + } + ensure_at_least_one_line(*buf); + auto &rows = buf->Rows(); + std::size_t y = buf->Cury(); + std::size_t x = buf->Curx(); + int repeat = ctx.count > 0 ? ctx.count : 1; + for (int i = 0; i < repeat; ++i) { + if (y >= rows.size()) + break; + if (x < rows[y].size()) { + // delete from cursor to end of line + rows[y].erase(x); + } else if (y + 1 < rows.size()) { + // at EOL: delete the newline (join with next line) + rows[y] += rows[y + 1]; + rows.erase(rows.begin() + static_cast(y + 1)); + } else { + // nothing to delete + break; + } + } + buf->SetDirty(true); + ensure_cursor_visible(ctx.editor, *buf); + return true; +} + + +static bool +cmd_kill_line(CommandContext &ctx) +{ + Buffer *buf = ctx.editor.CurrentBuffer(); + if (!buf) { + ctx.editor.SetStatus("No buffer to edit"); + return false; + } + ensure_at_least_one_line(*buf); + auto &rows = buf->Rows(); + std::size_t y = buf->Cury(); + std::size_t x = buf->Curx(); + (void)x; // cursor x will be reset to 0 + int repeat = ctx.count > 0 ? ctx.count : 1; + for (int i = 0; i < repeat; ++i) { + if (rows.empty()) + break; + if (rows.size() == 1) { + // last remaining line: clear its contents + rows[0].clear(); + y = 0; + } else if (y < rows.size()) { + // erase current line; keep y pointing at the next line + rows.erase(rows.begin() + static_cast(y)); + if (y >= rows.size()) { + // deleted last line; move to previous + y = rows.size() - 1; + } + } else { + // out of range + y = rows.empty() ? 0 : rows.size() - 1; + } + } + buf->SetCursor(0, y); + buf->SetDirty(true); + ensure_cursor_visible(ctx.editor, *buf); + return true; +} + + // --- Navigation --- // (helper removed) @@ -1208,6 +1282,8 @@ InstallDefaultCommands() CommandRegistry::Register({CommandId::Newline, "newline", "Insert newline at cursor", cmd_newline}); CommandRegistry::Register({CommandId::Backspace, "backspace", "Delete char before cursor", cmd_backspace}); CommandRegistry::Register({CommandId::DeleteChar, "delete-char", "Delete char at cursor", cmd_delete_char}); + CommandRegistry::Register({CommandId::KillToEOL, "kill-to-eol", "Delete to end of line", cmd_kill_to_eol}); + CommandRegistry::Register({CommandId::KillLine, "kill-line", "Delete entire line", cmd_kill_line}); // Navigation CommandRegistry::Register({CommandId::MoveLeft, "left", "Move cursor left", cmd_move_left}); CommandRegistry::Register({CommandId::MoveRight, "right", "Move cursor right", cmd_move_right}); diff --git a/Command.h b/Command.h index 6d37725..0e796c7 100644 --- a/Command.h +++ b/Command.h @@ -29,6 +29,8 @@ enum class CommandId { Newline, // insert a newline at cursor Backspace, // delete char before cursor (may join lines) DeleteChar, // delete char at cursor (may join lines) + KillToEOL, // delete from cursor to end of line; if at EOL, delete newline + KillLine, // delete the entire current line (including newline) // Navigation (basic) MoveLeft, MoveRight, diff --git a/GUIInputHandler.cc b/GUIInputHandler.cc index e46532b..3871f1c 100644 --- a/GUIInputHandler.cc +++ b/GUIInputHandler.cc @@ -62,6 +62,33 @@ map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput break; } + // If we are in k-prefix, the very next key must be interpreted via the + // C-k keymap first, even if Control is held (e.g., C-k C-d). + if (k_prefix) { + k_prefix = false; + // Normalize SDL key to ASCII where possible + int ascii_key = 0; + if (key >= SDLK_SPACE && key <= SDLK_z) { + ascii_key = static_cast(key); + } + bool ctrl2 = (mod & KMOD_CTRL) != 0; + if (ascii_key != 0) { + ascii_key = KLowerAscii(ascii_key); + CommandId id; + if (KLookupKCommand(ascii_key, ctrl2, id)) { + out = {true, id, "", 0}; + return true; + } + // Unknown k-command: report the typed character + char c = (ascii_key >= 0x20 && ascii_key <= 0x7e) ? static_cast(ascii_key) : '?'; + std::string arg(1, c); + out = {true, CommandId::UnknownKCommand, arg, 0}; + return true; + } + out.hasCommand = false; + return true; + } + if (is_ctrl) { if (key == SDLK_k || key == SDLK_KP_EQUALS) { k_prefix = true; @@ -91,30 +118,7 @@ map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput } } - if (k_prefix) { - k_prefix = false; - // Normalize SDL key to ASCII where possible - int ascii_key = 0; - if (key >= SDLK_SPACE && key <= SDLK_z) { - ascii_key = static_cast(key); - } - bool ctrl2 = (mod & KMOD_CTRL) != 0; - if (ascii_key != 0) { - ascii_key = KLowerAscii(ascii_key); - CommandId id; - if (KLookupKCommand(ascii_key, ctrl2, id)) { - out = {true, id, "", 0}; - return true; - } - // Unknown k-command: report the typed character - char c = (ascii_key >= 0x20 && ascii_key <= 0x7e) ? static_cast(ascii_key) : '?'; - std::string arg(1, c); - out = {true, CommandId::UnknownKCommand, arg, 0}; - return true; - } - out.hasCommand = false; - return true; - } + // k_prefix handled earlier return false; } diff --git a/KKeymap.cc b/KKeymap.cc index adc820d..a623de3 100644 --- a/KKeymap.cc +++ b/KKeymap.cc @@ -9,6 +9,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool if (ctrl) { switch (k) { + case 'd': + out = CommandId::KillLine; + return true; // C-k C-d case 'x': out = CommandId::SaveAndQuit; return true; // C-k C-x @@ -20,6 +23,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool } } else { switch (k) { + case 'd': + out = CommandId::KillToEOL; + return true; // C-k d case 's': out = CommandId::Save; return true; // C-k s @@ -45,6 +51,9 @@ KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool { const int k = KLowerAscii(ascii_key); switch (k) { + case 'd': + out = CommandId::DeleteChar; // C-d + return true; case 'n': out = CommandId::MoveDown; return true; diff --git a/TerminalInputHandler.cc b/TerminalInputHandler.cc index 3033476..e501f0d 100644 --- a/TerminalInputHandler.cc +++ b/TerminalInputHandler.cc @@ -105,6 +105,28 @@ map_key_to_command(const int ch, bool &k_prefix, bool &esc_meta, MappedInput &ou return true; } // Generic Control-chord lookup (after handling special prefixes/cancel) + // IMPORTANT: if we're in k-prefix, the very next key must be interpreted + // via the C-k keymap first, even if it's a Control chord like C-d. + if (k_prefix) { + k_prefix = false; // consume the prefix for this one key + bool ctrl = false; + int ascii_key = ch; + if (ch >= 1 && ch <= 26) { + ctrl = true; + ascii_key = 'a' + (ch - 1); + } + ascii_key = KLowerAscii(ascii_key); + CommandId id; + if (KLookupKCommand(ascii_key, ctrl, id)) { + out = {true, id, "", 0}; + } else { + char c = (ascii_key >= 0x20 && ascii_key <= 0x7e) ? static_cast(ascii_key) : '?'; + std::string arg(1, c); + out = {true, CommandId::UnknownKCommand, arg, 0}; + } + return true; + } + if (ch >= 1 && ch <= 26) { int ascii_key = 'a' + (ch - 1); CommandId id; @@ -143,29 +165,7 @@ map_key_to_command(const int ch, bool &k_prefix, bool &esc_meta, MappedInput &ou return true; } - if (k_prefix) { - k_prefix = false; // single next key only - // Determine if this is a control chord (e.g., C-x) and normalize - bool ctrl = false; - int ascii_key = ch; - if (ch >= 1 && ch <= 26) { - ctrl = true; - ascii_key = 'a' + (ch - 1); - } - // For letters, normalize to lowercase ASCII - ascii_key = KLowerAscii(ascii_key); - - CommandId id; - if (KLookupKCommand(ascii_key, ctrl, id)) { - out = {true, id, "", 0}; - } else { - // Show unknown k-command message with the typed character - char c = (ascii_key >= 0x20 && ascii_key <= 0x7e) ? static_cast(ascii_key) : '?'; - std::string arg(1, c); - out = {true, CommandId::UnknownKCommand, arg, 0}; - } - return true; - } + // k_prefix handled earlier // Printable ASCII if (ch >= 0x20 && ch <= 0x7E) {