universal args, more work on undo.

This commit is contained in:
2025-11-26 02:12:37 -08:00
parent faa70af9b7
commit a528b3fc3e
3 changed files with 217 additions and 85 deletions

4
TODO
View File

@@ -13,6 +13,6 @@
[x] mark/region
What would it take for ke to be your daily drives?
[ ] C-u (repeat actions)
[x] C-u (repeat actions)
[x] Alt nav (backspace, delete, f, b, etc)
[ ] undo tree (C-k u)
[-] undo tree (C-k u/U)

7
ke.1
View File

@@ -60,7 +60,9 @@ Reload the current buffer from disk.
.It C-k s
Save the file, prompting for a filename if needed. Also C-k C-s.
.It C-k u
Undo changes. Not implemented yet, placeholder.
Undo changes.
.It C-k U
Redo changes.
.It C-k x
save the file and exit. Also C-k C-x.
.It C-k y
@@ -76,6 +78,9 @@ In general, C-g cancels an operation.
Refresh the display.
.It C-s
Incremental find.
.It C-u
Universal argument. C-u followed by numbers will repeat an
operation n times.
.It C-w
Kill the region if the mark is set.
.It C-y

271
main.c
View File

@@ -149,6 +149,7 @@ struct editor_t {
char msg[80];
int mark_set;
int mark_curx, mark_cury;
int uarg, ucount; /* C-u support */
time_t msgtm;
undo_tree_t *undo;
} editor = {
@@ -170,6 +171,9 @@ struct editor_t {
.mark_set = 0,
.mark_curx = 0,
.mark_cury = 0,
.uarg = 0,
.ucount = 0,
.undo = NULL,
};
@@ -293,12 +297,19 @@ void editor_find_callback(char *query, int16_t c);
void editor_find(void);
char *editor_prompt(char*, void (*cb)(char*, int16_t));
void editor_openfile(void);
void move_cursor_once(int16_t c, int interactive);
void move_cursor(int16_t c, int interactive);
void uarg_start(void);
void uarg_digit(int d);
void uarg_clear(void);
int uarg_get(void);
void newline(void);
void process_kcommand(int16_t c);
void process_normal(int16_t c);
void process_escape(int16_t c);
int process_keypress(void);
char *get_cloc_code_lines(const char *filename);
int dump_pidfile(void);
void enable_termraw(void);
void display_clear(struct abuf *ab);
void disable_termraw(void);
@@ -2365,7 +2376,7 @@ first_nonwhitespace(struct erow *row)
void
move_cursor(int16_t c, int interactive)
move_cursor_once(int16_t c, int interactive)
{
struct erow *row;
int reps;
@@ -2480,6 +2491,17 @@ move_cursor(int16_t c, int interactive)
}
void
move_cursor(int16_t c, int interactive)
{
int n = uarg_get();
while (n-- > 0) {
move_cursor_once(c, interactive);
}
}
void
newline(void)
{
@@ -2503,89 +2525,57 @@ newline(void)
editor.cury++;
editor.curx = 0;
/* Any insertion breaks a delete sequence for killring chaining. */
/* BREAK THE KILL CHAIN \m/ */
editor.kill = 0;
}
char
*get_cloc_code_lines(const char *filename)
void
uarg_start(void)
{
char command[512];
char buffer[256];
char *result = NULL;
FILE* pipe = NULL;
size_t len = 0;
if (editor.filename == NULL) {
snprintf(command, sizeof(command),
"buffer has no associated file.");
result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL);
strcpy(result, command);
return result;
if (editor.uarg == 0) {
editor.ucount = 0;
}else {
if (editor.ucount == 0) {
editor.ucount = 1;
}
editor.ucount *= 4;
}
if (editor.dirty) {
snprintf(command, sizeof(command),
"buffer must be saved first.");
result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL);
strcpy(result, command);
return result;
}
snprintf(command,
sizeof(command),
"cloc --quiet %s | tail -2 | head -1 | awk '{print $5}'",
filename);
pipe = popen(command, "r");
if (!pipe) {
snprintf(command, sizeof(command), "Error getting LOC: %s", strerror(errno));
result = (char*)malloc(sizeof(buffer) + 1);
return NULL;
}
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
result = malloc(strlen(buffer) + 1);
assert(result != NULL);
if (result) {
strcpy(result, buffer);
pclose(pipe);
return result;
}
}
pclose(pipe);
char *zero = malloc(2);
if (zero) {
strcpy(zero, "0");
return zero;
}
return NULL;
editor.uarg = 1;
editor_set_status("C-u %d", editor.ucount);
}
static int
dump_pidfile(void)
void
uarg_digit(int d)
{
FILE* pid_file = NULL;
if ((pid_file = fopen("ke.pid", "w")) == NULL) {
editor_set_status("Failed to dump PID file: %s", strerror(errno));
return 0;
if (editor.uarg == 0) {
editor.uarg = 1;
editor.ucount = 0;
}
fprintf(pid_file, "%ld", (long)getpid());
fclose(pid_file);
editor.ucount = editor.ucount * 10 + d;
editor_set_status("C-u %d", editor.ucount);
}
return 1;
void
uarg_clear(void)
{
editor.uarg = 0;
editor.ucount = 0;
}
int
uarg_get(void)
{
int n = editor.ucount > 0 ? editor.ucount : 1;
uarg_clear();
return n;
}
@@ -2596,6 +2586,7 @@ process_kcommand(int16_t c)
int len = 0;
int jumpx = 0;
int jumpy = 0;
int reps = 0;
switch (c) {
case BACKSPACE:
@@ -2654,15 +2645,25 @@ process_kcommand(int16_t c)
return;
}
while ((editor.row[editor.cury].size - editor.curx) >
0) {
reps = uarg_get();
while (reps--) {
while ((editor.row[editor.cury].size -
editor.curx) > 0) {
process_normal(DEL_KEY);
}
if (reps) {
newline();
}
}
break;
case DEL_KEY:
case CTRL_KEY('d'):
reps = uarg_get();
while (reps--) {
delete_row(editor.cury);
}
break;
case 'e':
case CTRL_KEY('e'):
@@ -2750,7 +2751,11 @@ process_kcommand(int16_t c)
editor_redo();
break;
case 'y':
reps = uarg_get();
while (reps--) {
killring_yank();
}
break;
case ESC_KEY:
case CTRL_KEY('g'):
@@ -2772,6 +2777,20 @@ process_kcommand(int16_t c)
void
process_normal(int16_t c)
{
int reps = 0;
/* C-u handling must be the very first thing */
if (c == CTRL_KEY('u')) {
uarg_start();
return;
}
/* digits after a C-u are part of the argument */
if (editor.uarg && c >= '0' && c <= '9') {
uarg_digit(c - '0');
return;
}
if (is_arrow_key(c)) {
/* moving the cursor breaks a delete sequence */
editor.kill = 0;
@@ -2791,11 +2810,25 @@ process_normal(int16_t c)
case CTRL_KEY('d'):
case DEL_KEY:
if (c == DEL_KEY || c == CTRL_KEY('d')) {
reps = uarg_get();
while (reps-- > 0) {
move_cursor(ARROW_RIGHT, 1);
deletech(KILLRING_APPEND);
}
} else {
reps = uarg_get();
while (reps-- > 0) {
deletech(KILLRING_PREPEND);
}
}
break;
case CTRL_KEY('a'): /* beginning of line */
case HOME_KEY:
move_cursor(CTRL_KEY('a'), 1);
break;
case CTRL_KEY('e'): /* end of line */
case END_KEY:
move_cursor(CTRL_KEY('e'), 1);
break;
case CTRL_KEY('g'):
break;
@@ -2811,17 +2844,30 @@ process_normal(int16_t c)
toggle_markset();
break;
case CTRL_KEY('y'):
reps = uarg_get();
while (reps-- > 0) {
killring_yank();
}
break;
case ESC_KEY:
editor.mode = MODE_ESCAPE;
break;
default:
if (c == TAB_KEY) {
reps = uarg_get();
while (reps-- > 0) {
insertch(c);
}
} else if (c >= 0x20 && c != 0x7f) {
reps = uarg_get();
while (reps-- > 0) {
insertch(c);
}
}
break;
}
@@ -2908,6 +2954,87 @@ process_keypress(void)
}
char
*get_cloc_code_lines(const char *filename)
{
char command[512];
char buffer[256];
char *result = NULL;
FILE* pipe = NULL;
size_t len = 0;
if (editor.filename == NULL) {
snprintf(command, sizeof(command),
"buffer has no associated file.");
result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL);
strcpy(result, command);
return result;
}
if (editor.dirty) {
snprintf(command, sizeof(command),
"buffer must be saved first.");
result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL);
strcpy(result, command);
return result;
}
snprintf(command,
sizeof(command),
"cloc --quiet %s | tail -2 | head -1 | awk '{print $5}'",
filename);
pipe = popen(command, "r");
if (!pipe) {
snprintf(command, sizeof(command), "Error getting LOC: %s", strerror(errno));
result = (char*)malloc(sizeof(buffer) + 1);
return NULL;
}
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
result = malloc(strlen(buffer) + 1);
assert(result != NULL);
if (result) {
strcpy(result, buffer);
pclose(pipe);
return result;
}
}
pclose(pipe);
char *zero = malloc(2);
if (zero) {
strcpy(zero, "0");
return zero;
}
return NULL;
}
int
dump_pidfile(void)
{
FILE* pid_file = NULL;
if ((pid_file = fopen("ke.pid", "w")) == NULL) {
editor_set_status("Failed to dump PID file: %s", strerror(errno));
return 0;
}
fprintf(pid_file, "%ld", (long)getpid());
fclose(pid_file);
return 1;
}
/*
* A text editor needs the terminal to be in raw mode; but the default
* is to be in canonical (cooked) mode, which is a buffered input mode.