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 name="ChangeListManager">
<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.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.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.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>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -156,7 +142,7 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1764457173148</updated>
<workItem from="1764457174208" duration="22207000" />
<workItem from="1764457174208" duration="22512000" />
</task>
<servers />
</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 ---
// (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});

View File

@@ -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,

View File

@@ -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<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 (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<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;
}
// k_prefix handled earlier
return false;
}

View File

@@ -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;

View File

@@ -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<char>(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<char>(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) {