#include #include #include #include #include #include #include #include "buf.h" struct file file; struct buf buf; struct buf tmp_buf; /* Temporary buffer for insert mode */ struct buf yank_buf; struct line *curr_line; /* Used for '.' */ int last_key; int last_count; char last_motion[MAX_SAVED_MOTION]; char tmp_motion[MAX_SAVED_MOTION]; int tmp_count; char *minib; int minipos; struct pos cur; int disp_y; int maxx, maxy; int cur_y; int debug; void printy(int y, int attr, const char *fmt, ...) { va_list ap; char *str; attron(attr); va_start(ap, fmt); vasprintf(&str, fmt, ap); mvaddstr(y, 0, str); va_end(ap); standend(); free(str); } void clear_mini(void) { int i; for (i = 0; i < maxx; i++) mvaddch(maxy - 1, i, ' '); place_cursor(curr_line, cur.x, cur_y, 0); } static void clear_line(int y) { int i; for (i = 0; i < maxx; i++) mvaddch(y, i, ' '); } int draw_line(struct line *l, int y) { int c, i, x, _y; _y = y; clear_line(y); if (l == NULL) mvaddch(y, 0, '~'); else for (x = 0, i = 0; i < l->len; i++) { c = l->line[i]; if (c == '\t') { x = ((x + TABSTOP) / TABSTOP) * TABSTOP; continue; } mvaddch(y, x, l->line[i]); x++; if (x >= maxx) { x = 0; y++; if (y > maxy - 1) break; clear_line(y); } } return (y - _y); } static int place(struct line *l, int x, int y, struct pos *p) { int i; p->x = 0; p->y = y; for (i = 0; i < x; i++) { if (l->line[i] == '\t') p->x = ((p->x + TABSTOP) / TABSTOP) * TABSTOP; else p->x++; if (p->x >= maxx) { p->x = 0; p->y++; /* XXX what if y overflows screen? */ } } return (i); } void place_cursor(struct line *l, int x, int y, int insert) { struct pos p; int i; i = place(l, x, y, &p); /* We want the cursor at the end of the tab outside of insert mode */ if (!insert && i < l->len && l->line[i] == '\t') p.x = ((p.x + TABSTOP) / TABSTOP) * TABSTOP - 1; move(p.y, p.x); refresh(); } void place_char(struct line *l, int x, int y, int c) { struct pos p; place(l, x, y, &p); mvaddch(p.y, p.x, c); } static void update_display(void) { struct line *l; int i, y; for (y = 0, i = 0; y < maxy - 1; i++) { l = buf_get_line(&buf, disp_y + i); if (disp_y + i == cur.y) cur_y = y; y += draw_line(l, y) + 1; } place_cursor(curr_line, cur.x, cur_y, 0); } void y_inc(struct pos *pos) { pos->y++; curr_line = line_next(curr_line); if (curr_line == NULL) abort(); if (pos->x > curr_line->len) pos->x = curr_line->len - 1; if (pos->x < 0) pos->x = 0; if (pos->y > disp_y + maxy - 2) disp_y++; } void y_dec(struct pos *pos) { pos->y--; curr_line = line_prev(curr_line); if (curr_line == NULL) abort(); if (pos->x > curr_line->len) pos->x = curr_line->len - 1; if (pos->x < 0) pos->x = 0; if (pos->y < disp_y) disp_y--; } void cur_fixup(void) { if (cur.x >= curr_line->len) cur.x = curr_line->len - 1; if (cur.x < 0) cur.x = 0; } int get_count(char c) { if ((c == '0' && tmp_count != 0) || (c >= '1' && c <= '9')) { tmp_count *= 10; tmp_count += c - '0'; return (0); } return (1); } int get_motion(struct buf *b, struct cmd *cmd, int is_dot) { struct cmd m; int c, i, mp; mp = 0; memset(tmp_motion, 0, MAX_SAVED_MOTION); while (1) { if (is_dot) { c = last_motion[mp++]; if (c == 0) return (1); } else { c = getch(); if (mp < MAX_SAVED_MOTION - 1) tmp_motion[mp++] = c; } if (c == ESC) return (1); if (get_count(c) == 0) continue; /* Repeated command (like "dd") means whole line */ if (c == cmd->key) { cmd->mstart.x = 0; cmd->mstart.y = cur.y; cmd->mend = cmd->mstart; cmd->new_pos = cur; if (tmp_count == 0) tmp_count = 1; for (i = tmp_count; i > 0; i--) { if (cmd->mend.y < b->num_lines) cmd->mend.y++; else break; } tmp_count = 0; if (i != 0) return (1); return (0); } if ((cmds[c].func == NULL) || !(cmds[c].flags & CMD_ISMOTION)) { printerr("'%c' is not a valid motion\n", c); beep(); return (1); } if (cmds[c].flags & CMD_COUNT) { if (tmp_count != 0) m.count = tmp_count; else if (cmds[c].flags & CMD_COUNT0) m.count = 0; else m.count = 1; tmp_count = 0; } else if (tmp_count != 0) { beep(); return (1); } m.key = c; if ((*cmds[c].func)(&m, b) != 0) return (1); if (cmds[c].flags & CMD_MLINE) { struct line *l; cmd->mstart.x = 0; cmd->mstart.y = m.mstart.y; l = buf_get_line(b, m.mend.y + 1); if (l == NULL) return (1); cmd->mend.x = 0; cmd->mend.y = m.mend.y + 1; } else { cmd->mstart = m.mstart; cmd->mend = m.mend; } cmd->new_pos = cur; return (0); } /* NOTREACHED */ } void command_char(int c) { struct cmd cmd; int is_dot; cmd.flags = 0; if (get_count(c) == 0) return; if (c == ESC) return; if (cmds[c].func != NULL || c == '.') { if (cmds[c].flags & CMD_COUNT) { if (tmp_count != 0) cmd.count = tmp_count; else if (cmds[c].flags & CMD_COUNT0) cmd.count = 0; else cmd.count = 1; tmp_count = 0; } if (c == '.') { is_dot = 1; c = last_key; cmd.count = last_count; cmd.flags |= CMD_DOT; } else is_dot = 0; cmd.key = c; if (cmds[c].flags & CMD_MOTION) { if (get_motion(&buf, &cmd, is_dot) != 0) goto out; } else cmd.new_pos = cur; if (cmds[c].flags & CMD_EFFECT) undo_start(&cmd); if ((*cmds[c].func)(&cmd, &buf) == 0) { if (cmds[c].flags & CMD_EFFECT) { buf.dirty = 1; if (!is_dot) { last_key = c; last_count = cmd.count; memcpy(last_motion, tmp_motion, MAX_SAVED_MOTION); } } cmd.flags |= CMD_SUCCESS; } if (cmds[c].flags & CMD_EFFECT) undo_finish(&cmd); if (!(cmds[c].flags & CMD_MODE)) { cur = cmd.new_pos; if (cur.y >= buf.num_lines) cur.y = buf.num_lines - 1; curr_line = buf_get_line(&buf, cur.y); if (curr_line == NULL) abort(); cur_fixup(); } /* XXX center line if we move more than two screens away */ if (cur.y > disp_y + maxy - 2) disp_y += cur.y - (disp_y + maxy - 2); else if (cur.y < disp_y) disp_y -= disp_y - cur.y; if (disp_y + maxy - 2 > buf.num_lines) disp_y = buf.num_lines - maxy + 2; if (disp_y < 0) disp_y = 0; } else { printerr("'%c' (%d) is not a valid command\n", c, c); beep(); } out: tmp_count = 0; } static void usage(char *s) { fprintf(stderr, "%s: [-dh] [file]\n", s); } static void handle_sigint(int sig) { clear_mini(); printerr("Interrupted"); refresh(); } int main(int argc, char **argv) { int c, ret; initscr(); cbreak(); noecho(); nonl(); keypad(stdscr, 1); getmaxyx(stdscr, maxy, maxx); buf_init(&buf); buf_init(&tmp_buf); buf_init(&yank_buf); undo_init(); minib = malloc(MINIB_SIZE); memset(minib, 0, MINIB_SIZE); while ((c = getopt(argc, argv, "dh:")) != -1) { switch (c) { case 'd': debug = 1; break; case 'h': /* FALLTHROUGH */ default: usage(argv[0]); } } argc -= optind; argv += optind; if (!debug) { struct sigaction sa; sa.sa_handler = handle_sigint; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); if (sigaction(SIGINT, &sa, 0) != 0) err(1, "sigaction"); } if (argv[0]) { strlcpy(file.path, argv[0], PATHSIZE); if ((ret = file_load(&file, &buf)) != 0) errc(1, ret, "buf_load"); } curr_line = buf_get_line(&buf, 0); if (curr_line == NULL) { /* Empty buffer */ curr_line = line_alloc(NULL, 0); line_add(&buf, curr_line, 0); } update_display(); while (1) { c = getch(); if (c == -1) continue; clear_mini(); command_char(c); update_display(); } return (0); }