Support delete to end of line and delete line.

This commit is contained in:
2025-11-29 21:40:07 -08:00
parent b41946c470
commit 40d33e1847
6 changed files with 139 additions and 62 deletions

16
.idea/workspace.xml generated
View File

@@ -34,24 +34,10 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment=""> <list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.junie/guidelines.md" beforeDir="false" afterPath="$PROJECT_DIR$/.junie/guidelines.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Editor.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Editor.h" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIFrontend.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIFrontend.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIFrontend.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIFrontend.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIInputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/KKeymap.cc" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" /> <change beforePath="$PROJECT_DIR$/KKeymap.cc" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/KKeymap.h" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalFrontend.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalFrontend.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.cc" afterDir="false" /> <change beforePath="$PROJECT_DIR$/TerminalInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalInputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalRenderer.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.h" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -156,7 +142,7 @@
<option name="number" value="Default" /> <option name="number" value="Default" />
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1764457173148</updated> <updated>1764457173148</updated>
<workItem from="1764457174208" duration="22207000" /> <workItem from="1764457174208" duration="22512000" />
</task> </task>
<servers /> <servers />
</component> </component>

View File

@@ -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<std::ptrdiff_t>(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<std::ptrdiff_t>(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 --- // --- Navigation ---
// (helper removed) // (helper removed)
@@ -1208,6 +1282,8 @@ InstallDefaultCommands()
CommandRegistry::Register({CommandId::Newline, "newline", "Insert newline at cursor", cmd_newline}); 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::Backspace, "backspace", "Delete char before cursor", cmd_backspace});
CommandRegistry::Register({CommandId::DeleteChar, "delete-char", "Delete char at cursor", cmd_delete_char}); 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 // Navigation
CommandRegistry::Register({CommandId::MoveLeft, "left", "Move cursor left", cmd_move_left}); CommandRegistry::Register({CommandId::MoveLeft, "left", "Move cursor left", cmd_move_left});
CommandRegistry::Register({CommandId::MoveRight, "right", "Move cursor right", cmd_move_right}); CommandRegistry::Register({CommandId::MoveRight, "right", "Move cursor right", cmd_move_right});

View File

@@ -29,6 +29,8 @@ enum class CommandId {
Newline, // insert a newline at cursor Newline, // insert a newline at cursor
Backspace, // delete char before cursor (may join lines) Backspace, // delete char before cursor (may join lines)
DeleteChar, // delete char at 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) // Navigation (basic)
MoveLeft, MoveLeft,
MoveRight, MoveRight,

View File

@@ -62,6 +62,33 @@ map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput
break; 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<int>(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<char>(ascii_key) : '?';
std::string arg(1, c);
out = {true, CommandId::UnknownKCommand, arg, 0};
return true;
}
out.hasCommand = false;
return true;
}
if (is_ctrl) { if (is_ctrl) {
if (key == SDLK_k || key == SDLK_KP_EQUALS) { if (key == SDLK_k || key == SDLK_KP_EQUALS) {
k_prefix = true; 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 handled earlier
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<int>(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<char>(ascii_key) : '?';
std::string arg(1, c);
out = {true, CommandId::UnknownKCommand, arg, 0};
return true;
}
out.hasCommand = false;
return true;
}
return false; return false;
} }

View File

@@ -9,6 +9,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
if (ctrl) { if (ctrl) {
switch (k) { switch (k) {
case 'd':
out = CommandId::KillLine;
return true; // C-k C-d
case 'x': case 'x':
out = CommandId::SaveAndQuit; out = CommandId::SaveAndQuit;
return true; // C-k C-x return true; // C-k C-x
@@ -20,6 +23,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
} }
} else { } else {
switch (k) { switch (k) {
case 'd':
out = CommandId::KillToEOL;
return true; // C-k d
case 's': case 's':
out = CommandId::Save; out = CommandId::Save;
return true; // C-k s return true; // C-k s
@@ -45,6 +51,9 @@ KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool
{ {
const int k = KLowerAscii(ascii_key); const int k = KLowerAscii(ascii_key);
switch (k) { switch (k) {
case 'd':
out = CommandId::DeleteChar; // C-d
return true;
case 'n': case 'n':
out = CommandId::MoveDown; out = CommandId::MoveDown;
return true; return true;

View File

@@ -105,6 +105,28 @@ map_key_to_command(const int ch, bool &k_prefix, bool &esc_meta, MappedInput &ou
return true; return true;
} }
// Generic Control-chord lookup (after handling special prefixes/cancel) // 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<char>(ascii_key) : '?';
std::string arg(1, c);
out = {true, CommandId::UnknownKCommand, arg, 0};
}
return true;
}
if (ch >= 1 && ch <= 26) { if (ch >= 1 && ch <= 26) {
int ascii_key = 'a' + (ch - 1); int ascii_key = 'a' + (ch - 1);
CommandId id; CommandId id;
@@ -143,29 +165,7 @@ map_key_to_command(const int ch, bool &k_prefix, bool &esc_meta, MappedInput &ou
return true; return true;
} }
if (k_prefix) { // k_prefix handled earlier
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<char>(ascii_key) : '?';
std::string arg(1, c);
out = {true, CommandId::UnknownKCommand, arg, 0};
}
return true;
}
// Printable ASCII // Printable ASCII
if (ch >= 0x20 && ch <= 0x7E) { if (ch >= 0x20 && ch <= 0x7E) {