Support basic killing/yanking.

This commit is contained in:
2025-11-24 14:35:44 -08:00
parent d3591331a5
commit 1227d0abf4

302
main.c
View File

@@ -137,6 +137,9 @@ void delete_next_word(void);
void find_prev_word(void); void find_prev_word(void);
void delete_prev_word(void); void delete_prev_word(void);
void delete_row(int at); void delete_row(int at);
void killring_start_with_char(unsigned char ch);
void killring_append_char(unsigned char ch);
void killring_prepend_char(unsigned char ch);
void killring_flush(void); void killring_flush(void);
void killring_yank(void); void killring_yank(void);
void row_append_row(struct erow *row, char *s, int len); void row_append_row(struct erow *row, char *s, int len);
@@ -207,6 +210,7 @@ struct editor_t {
struct erow *row; struct erow *row;
struct erow *killring; struct erow *killring;
int killing; /* are we in a contiguous delete sequence? */ int killing; /* are we in a contiguous delete sequence? */
int suppress_killrow; /* internal flag: don't add to killring inside delete_row */
char *filename; char *filename;
int dirty; int dirty;
int dirtyex; int dirtyex;
@@ -224,6 +228,7 @@ struct editor_t {
.row = NULL, .row = NULL,
.killring = NULL, .killring = NULL,
.killing = 0, .killing = 0,
.suppress_killrow = 0,
.filename = NULL, .filename = NULL,
.dirty = 0, .dirty = 0,
.dirtyex = 0, .dirtyex = 0,
@@ -252,6 +257,8 @@ init_editor(void)
editor.rowoffs = editor.coloffs = 0; editor.rowoffs = editor.coloffs = 0;
editor.row = NULL; editor.row = NULL;
editor.killring = NULL; editor.killring = NULL;
editor.killing = 0;
editor.suppress_killrow = 0;
editor.msg[0] = '\0'; editor.msg[0] = '\0';
editor.msgtm = 0; editor.msgtm = 0;
@@ -277,8 +284,11 @@ reset_editor(void)
editor.filename = NULL; editor.filename = NULL;
} }
erow_free(editor.killring); if (editor.killring != NULL) {
editor.killring = NULL; erow_free(editor.killring);
free(editor.killring);
editor.killring = NULL;
}
init_editor(); init_editor();
} }
@@ -322,9 +332,9 @@ nibble_to_hex(char c)
{ {
c &= 0xf; c &= 0xf;
if (c < 10) { if (c < 10) {
return c + 0x30; return (char)('0' + c);
} }
return c + 0x41; return (char)('A' + (c - 10));
} }
@@ -547,21 +557,99 @@ erow_insert(int at, char *s, int len)
void void
killring_flush(void) killring_flush(void)
{ {
erow_free(editor.killring); if (editor.killring != NULL) {
editor.killring = NULL; erow_free(editor.killring);
free(editor.killring);
editor.killring = NULL;
}
} }
void void
killring_yank() killring_yank(void)
{ {
if (editor.killring == NULL) { if (editor.killring == NULL) {
return; return;
} }
/* Insert killring contents at the cursor without clearing the ring. */ /*
for (int i = 0; i < editor.killring->size; i++) { * Insert killring contents at the cursor without clearing the ring.
insertch((unsigned char)editor.killring->line[i]); * Interpret '\n' as an actual newline() rather than inserting a raw 0x0A
} * byte, so yanked content preserves lines correctly.
*/
for (int i = 0; i < editor.killring->size; i++) {
unsigned char ch = (unsigned char)editor.killring->line[i];
if (ch == '\n') {
newline();
}
else {
insertch(ch);
}
}
}
void
killring_start_with_char(unsigned char ch)
{
struct erow *row = NULL;
if (editor.killring != NULL) {
erow_free(editor.killring);
free(editor.killring);
editor.killring = NULL;
}
editor.killring = malloc(sizeof(struct erow));
assert(editor.killring != NULL);
assert(erow_init(editor.killring, 0) == 0);
/* append one char to empty killring without affecting editor.dirty */
row = editor.killring;
row->line = realloc(row->line, row->size + 2);
assert(row->line != NULL);
row->line[row->size] = ch;
row->size++;
row->line[row->size] = '\0';
erow_update(row);
}
void
killring_append_char(unsigned char ch)
{
struct erow *row = NULL;
if (editor.killring == NULL) {
killring_start_with_char(ch);
return;
}
row = editor.killring;
row->line = realloc(row->line, row->size + 2);
assert(row->line != NULL);
row->line[row->size] = ch;
row->size++;
row->line[row->size] = '\0';
erow_update(row);
}
void
killring_prepend_char(unsigned char ch)
{
if (editor.killring == NULL) {
killring_start_with_char(ch);
return;
}
struct erow* row = editor.killring;
row->line = realloc(row->line, row->size + 2);
assert(row->line != NULL);
memmove(&row->line[1], &row->line[0], row->size + 1);
row->line[0] = ch;
row->size++;
erow_update(row);
} }
@@ -676,28 +764,28 @@ find_next_word(void)
void void
delete_next_word(void) delete_next_word(void)
{ {
while (cursor_at_eol()) { while (cursor_at_eol()) {
move_cursor(ARROW_RIGHT); move_cursor(ARROW_RIGHT);
deletech(KILLRING_NO_OP);; deletech(KILLRING_APPEND);
} }
if (isalnum(editor.row[editor.cury].line[editor.curx])) { if (isalnum(editor.row[editor.cury].line[editor.curx])) {
while (!isspace(editor.row[editor.cury].line[editor.curx]) && !cursor_at_eol()) { while (!isspace(editor.row[editor.cury].line[editor.curx]) && !cursor_at_eol()) {
move_cursor(ARROW_RIGHT); move_cursor(ARROW_RIGHT);
deletech(KILLRING_NO_OP);; deletech(KILLRING_APPEND);
} }
return; return;
} }
if (isspace(editor.row[editor.cury].line[editor.curx])) { if (isspace(editor.row[editor.cury].line[editor.curx])) {
while (isspace(editor.row[editor.cury].line[editor.curx])) { while (isspace(editor.row[editor.cury].line[editor.curx])) {
move_cursor(ARROW_RIGHT); move_cursor(ARROW_RIGHT);
deletech(KILLRING_NO_OP);; deletech(KILLRING_APPEND);
} }
delete_next_word(); delete_next_word();
} }
} }
@@ -730,24 +818,26 @@ delete_prev_word(void)
return; return;
} }
deletech(KILLRING_NO_OP);; deletech(KILLRING_PREPEND);
while (editor.cury > 0 || editor.curx > 0) { while (editor.cury > 0 || editor.curx > 0) {
if (editor.curx == 0) { if (editor.curx == 0) {
deletech(KILLRING_NO_OP);; deletech(KILLRING_PREPEND);
} else { continue
if (!isspace(editor.row[editor.cury].line[editor.curx - 1])) {
break;
}
deletech(KILLRING_NO_OP);;
} }
if (!isspace(editor.row[editor.cury].line[editor.curx - 1])) {
break;
}
deletech(KILLRING_PREPEND);
} }
while (editor.curx > 0) { while (editor.curx > 0) {
if (isspace(editor.row[editor.cury].line[editor.curx - 1])) { if (isspace(editor.row[editor.cury].line[editor.curx - 1])) {
break; break;
} }
deletech(KILLRING_NO_OP);; deletech(KILLRING_PREPEND);
} }
} }
@@ -758,9 +848,43 @@ delete_row(int at)
return; return;
} }
/*
* Update killring with the deleted row's contents followed by a newline
* unless this deletion is an internal merge triggered by deletech at
* start-of-line. In that case, deletech will account for the single
* newline itself and we must NOT also push the entire row here.
*/
if (!editor.suppress_killrow) {
struct erow* r = &editor.row[at];
/* Start or continue the kill sequence based on editor.killing */
if (r->size > 0) {
/* push raw bytes of the line */
if (!editor.killing) {
killring_start_with_char((unsigned char)r->line[0]);
for (int i = 1; i < r->size; i++) {
killring_append_char((unsigned char)r->line[i]);
}
} else {
for (int i = 0; i < r->size; i++) {
killring_append_char((unsigned char)r->line[i]);
}
}
killring_append_char('\n');
editor.killing = 1;
} else {
if (!editor.killing) {
killring_start_with_char('\n');
}
else {
killring_append_char('\n');
}
editor.killing = 1;
}
}
erow_free(&editor.row[at]); erow_free(&editor.row[at]);
memmove(&editor.row[at], &editor.row[at+1], memmove(&editor.row[at], &editor.row[at + 1],
sizeof(struct erow) * (editor.nrows - at - 1)); sizeof(struct erow) * (editor.nrows - at - 1));
editor.nrows--; editor.nrows--;
editor.dirty++; editor.dirty++;
} }
@@ -806,6 +930,7 @@ row_delete_ch(struct erow *row, int at)
if (at < 0 || at >= row->size) { if (at < 0 || at >= row->size) {
return; return;
} }
memmove(&row->line[at], &row->line[at+1], row->size-at); memmove(&row->line[at], &row->line[at+1], row->size-at);
row->size--; row->size--;
erow_update(row); erow_update(row);
@@ -825,9 +950,11 @@ insertch(int16_t c)
erow_insert(editor.nrows, "", 0); erow_insert(editor.nrows, "", 0);
} }
/* Any insertion breaks a delete sequence for killring chaining. */
editor.killing = 0;
/* Ensure we pass a non-negative byte value to avoid assert(c > 0). */ /* Ensure we pass a non-negative byte value to avoid assert(c > 0). */
row_insert_ch(&editor.row[editor.cury], editor.curx, row_insert_ch(&editor.row[editor.cury], editor.curx,
(int16_t)(c & 0xff)); (int16_t)(c & 0xff));
editor.curx++; editor.curx++;
editor.dirty++; editor.dirty++;
} }
@@ -839,7 +966,8 @@ insertch(int16_t c)
void void
deletech(uint8_t op) deletech(uint8_t op)
{ {
struct erow *row = NULL; struct erow* row = NULL;
unsigned char dch = 0;
if (editor.cury >= editor.nrows) { if (editor.cury >= editor.nrows) {
return; return;
@@ -850,16 +978,60 @@ deletech(uint8_t op)
} }
row = &editor.row[editor.cury]; row = &editor.row[editor.cury];
/* determine which character is being deleted for killring purposes */
if (editor.curx > 0) {
dch = (unsigned char)row->line[editor.curx - 1];
}
else {
/* deleting at start of line merges with previous line: treat as newline */
dch = '\n';
}
/* update buffer */
if (editor.curx > 0) { if (editor.curx > 0) {
row_delete_ch(row, editor.curx - 1); row_delete_ch(row, editor.curx - 1);
editor.curx--; editor.curx--;
} else { }
else {
editor.curx = editor.row[editor.cury - 1].size; editor.curx = editor.row[editor.cury - 1].size;
row_append_row(&editor.row[editor.cury - 1], row->line, row_append_row(&editor.row[editor.cury - 1], row->line,
row->size); row->size);
int prev = editor.suppress_killrow;
editor.suppress_killrow = 1; /* prevent killring update on internal row delete */
delete_row(editor.cury); delete_row(editor.cury);
editor.suppress_killrow = prev;
editor.cury--; editor.cury--;
} }
/* update killring if requested */
if (op == KILLRING_FLUSH) {
killring_flush();
editor.killing = 0;
return;
}
if (op == KILLRING_NO_OP) {
/* Do not modify killring or chaining state. */
return;
}
if (!editor.killing) {
killring_start_with_char(dch);
editor.killing = 1;
return;
}
if (op == KILLING_SET) {
killring_start_with_char(dch);
editor.killing = 1;
}
else if (op == KILLRING_APPEND) {
killring_append_char(dch);
}
else if (op == KILLRING_PREPEND) {
killring_prepend_char(dch);
}
} }
@@ -1338,22 +1510,24 @@ move_cursor(int16_t c)
void void
newline(void) newline(void)
{ {
struct erow *row = NULL; struct erow *row = NULL;
if (editor.curx == 0) { if (editor.curx == 0) {
erow_insert(editor.cury, "", 0); erow_insert(editor.cury, "", 0);
} else { } else {
row = &editor.row[editor.cury]; row = &editor.row[editor.cury];
erow_insert(editor.cury + 1, &row->line[editor.curx], erow_insert(editor.cury + 1, &row->line[editor.curx],
row->size - editor.curx); row->size - editor.curx);
row = &editor.row[editor.cury]; row = &editor.row[editor.cury];
row->size = editor.curx; row->size = editor.curx;
row->line[row->size] = '\0'; row->line[row->size] = '\0';
erow_update(row); erow_update(row);
} }
editor.cury++; editor.cury++;
editor.curx = 0; editor.curx = 0;
/* Any insertion breaks a delete sequence for killring chaining. */
editor.killing = 0;
} }
@@ -1439,6 +1613,8 @@ void
process_normal(int16_t c) process_normal(int16_t c)
{ {
if (is_arrow_key(c)) { if (is_arrow_key(c)) {
/* moving the cursor breaks a delete sequence */
editor.killing = 0;
move_cursor(c); move_cursor(c);
return; return;
} }
@@ -1456,9 +1632,11 @@ process_normal(int16_t c)
case DEL_KEY: case DEL_KEY:
if (c == DEL_KEY || c == CTRL_KEY('d')) { if (c == DEL_KEY || c == CTRL_KEY('d')) {
move_cursor(ARROW_RIGHT); move_cursor(ARROW_RIGHT);
deletech(KILLRING_APPEND);
}
else {
deletech(KILLRING_PREPEND);
} }
deletech(KILLRING_NO_OP);;
break; break;
case CTRL_KEY('l'): case CTRL_KEY('l'):
display_refresh(); display_refresh();