You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1701 lines
42 KiB
1701 lines
42 KiB
/* vi.c - You can't spell "evil" without "vi".
|
|
*
|
|
* Copyright 2015 Rob Landley <rob@landley.net>
|
|
* Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com>
|
|
*
|
|
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
|
|
|
|
USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
|
|
|
|
config VI
|
|
bool "vi"
|
|
default n
|
|
help
|
|
usage: vi [-s script] FILE
|
|
-s script: run script file
|
|
Visual text editor. Predates the existence of standardized cursor keys,
|
|
so the controls are weird and historical.
|
|
*/
|
|
|
|
#define FOR_vi
|
|
#include "toys.h"
|
|
|
|
GLOBALS(
|
|
char *s;
|
|
int vi_mode, tabstop, list;
|
|
int cur_col, cur_row, scr_row;
|
|
int drawn_row, drawn_col;
|
|
int count0, count1, vi_mov_flag;
|
|
unsigned screen_height, screen_width;
|
|
char vi_reg, *last_search;
|
|
struct str_line {
|
|
int alloc;
|
|
int len;
|
|
char *data;
|
|
} *il;
|
|
size_t screen, cursor; //offsets
|
|
//yank buffer
|
|
struct yank_buf {
|
|
char reg;
|
|
int alloc;
|
|
char* data;
|
|
} yank;
|
|
|
|
int modified;
|
|
size_t filesize;
|
|
// mem_block contains RO data that is either original file as mmap
|
|
// or heap allocated inserted data
|
|
//
|
|
//
|
|
//
|
|
struct block_list {
|
|
struct block_list *next, *prev;
|
|
struct mem_block {
|
|
size_t size;
|
|
size_t len;
|
|
enum alloc_flag {
|
|
MMAP, //can be munmap() before exit()
|
|
HEAP, //can be free() before exit()
|
|
STACK, //global or stack perhaps toybuf
|
|
} alloc;
|
|
const char *data;
|
|
} *node;
|
|
} *text;
|
|
|
|
// slices do not contain actual allocated data but slices of data in mem_block
|
|
// when file is first opened it has only one slice.
|
|
// after inserting data into middle new mem_block is allocated for insert data
|
|
// and 3 slices are created, where first and last slice are pointing to original
|
|
// mem_block with offsets, and middle slice is pointing to newly allocated block
|
|
// When deleting, data is not freed but mem_blocks are sliced more such way that
|
|
// deleted data left between 2 slices
|
|
struct slice_list {
|
|
struct slice_list *next, *prev;
|
|
struct slice {
|
|
size_t len;
|
|
const char *data;
|
|
} *node;
|
|
} *slices;
|
|
)
|
|
|
|
static const char *blank = " \n\r\t";
|
|
static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'";
|
|
|
|
//get utf8 length and width at same time
|
|
static int utf8_lnw(int *width, char *s, int bytes)
|
|
{
|
|
wchar_t wc;
|
|
int length = 1;
|
|
|
|
if (*s == '\t') *width = TT.tabstop;
|
|
else {
|
|
length = utf8towc(&wc, s, bytes);
|
|
if (length < 1) length = 0, *width = 0;
|
|
else *width = wcwidth(wc);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
|
|
{
|
|
int len = 0;
|
|
char *c = utf8_scratch;
|
|
c[*sta_p] = key;
|
|
if (!(*sta_p)) *c = key;
|
|
if (*c < 0x7F) { *sta_p = 1; return 1; }
|
|
if ((*c & 0xE0) == 0xc0) len = 2;
|
|
else if ((*c & 0xF0) == 0xE0 ) len = 3;
|
|
else if ((*c & 0xF8) == 0xF0 ) len = 4;
|
|
else {*sta_p = 0; return 0; }
|
|
|
|
(*sta_p)++;
|
|
|
|
if (*sta_p == 1) return 0;
|
|
if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
|
|
|
|
if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char* utf8_last(char* str, int size)
|
|
{
|
|
char* end = str+size;
|
|
int pos = size, len, width = 0;
|
|
for (;pos >= 0; end--, pos--) {
|
|
len = utf8_lnw(&width, end, size-pos);
|
|
if (len && width) return end;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct double_list *dlist_add_before(struct double_list **head,
|
|
struct double_list **list, char *data)
|
|
{
|
|
struct double_list *new = xmalloc(sizeof(struct double_list));
|
|
new->data = data;
|
|
if (*list == *head) *head = new;
|
|
|
|
dlist_add_nomalloc(list, new);
|
|
return new;
|
|
}
|
|
|
|
struct double_list *dlist_add_after(struct double_list **head,
|
|
struct double_list **list, char *data)
|
|
{
|
|
struct double_list *new = xmalloc(sizeof(struct double_list));
|
|
new->data = data;
|
|
|
|
if (*list) {
|
|
new->prev = *list;
|
|
new->next = (*list)->next;
|
|
(*list)->next->prev = new;
|
|
(*list)->next = new;
|
|
} else *head = *list = new->next = new->prev = new;
|
|
return new;
|
|
}
|
|
|
|
// str must be already allocated
|
|
// ownership of allocated data is moved
|
|
// data, pre allocated data
|
|
// offset, offset in whole text
|
|
// size, data allocation size of given data
|
|
// len, length of the string
|
|
// type, define allocation type for cleanup purposes at app exit
|
|
static int insert_str(const char *data, size_t offset, size_t size, size_t len,
|
|
enum alloc_flag type)
|
|
{
|
|
struct mem_block *b = xmalloc(sizeof(struct mem_block));
|
|
struct slice *next = xmalloc(sizeof(struct slice));
|
|
struct slice_list *s = TT.slices;
|
|
b->size = size;
|
|
b->len = len;
|
|
b->alloc = type;
|
|
b->data = data;
|
|
next->len = len;
|
|
next->data = data;
|
|
|
|
//mem blocks can be just added unordered
|
|
TT.text = (struct block_list *)dlist_add((struct double_list **)&TT.text,
|
|
(char *)b);
|
|
|
|
if (!s) {
|
|
TT.slices = (struct slice_list *)dlist_add(
|
|
(struct double_list **)&TT.slices,
|
|
(char *)next);
|
|
} else {
|
|
size_t pos = 0;
|
|
//search insertation point for slice
|
|
do {
|
|
if (pos<=offset && pos+s->node->len>offset) break;
|
|
pos += s->node->len;
|
|
s = s->next;
|
|
if (s == TT.slices) return -1; //error out of bounds
|
|
} while (1);
|
|
//need to cut previous slice into 2 since insert is in middle
|
|
if (pos+s->node->len>offset && pos!=offset) {
|
|
struct slice *tail = xmalloc(sizeof(struct slice));
|
|
tail->len = s->node->len-(offset-pos);
|
|
tail->data = s->node->data+(offset-pos);
|
|
s->node->len = offset-pos;
|
|
//pos = offset;
|
|
s = (struct slice_list *)dlist_add_after(
|
|
(struct double_list **)&TT.slices,
|
|
(struct double_list **)&s,
|
|
(char *)tail);
|
|
|
|
s = (struct slice_list *)dlist_add_before(
|
|
(struct double_list **)&TT.slices,
|
|
(struct double_list **)&s,
|
|
(char *)next);
|
|
} else if (pos==offset) {
|
|
// insert before
|
|
s = (struct slice_list *)dlist_add_before(
|
|
(struct double_list **)&TT.slices,
|
|
(struct double_list **)&s,
|
|
(char *)next);
|
|
} else {
|
|
// insert after
|
|
s = (struct slice_list *)dlist_add_after((struct double_list **)&TT.slices,
|
|
(struct double_list **)&s,
|
|
(char *)next);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// this will not free any memory
|
|
// will only create more slices depending on position
|
|
static int cut_str(size_t offset, size_t len)
|
|
{
|
|
struct slice_list *e, *s = TT.slices;
|
|
size_t end = offset+len;
|
|
size_t epos, spos = 0;
|
|
if (!s) return -1;
|
|
|
|
//find start and end slices
|
|
for (;;) {
|
|
if (spos<=offset && spos+s->node->len>offset) break;
|
|
spos += s->node->len;
|
|
s = s->next;
|
|
|
|
if (s == TT.slices) return -1; //error out of bounds
|
|
}
|
|
|
|
for (e = s, epos = spos; ; ) {
|
|
if (epos<=end && epos+e->node->len>end) break;
|
|
epos += e->node->len;
|
|
e = e->next;
|
|
|
|
if (e == TT.slices) return -1; //error out of bounds
|
|
}
|
|
|
|
for (;;) {
|
|
if (spos == offset && ( end >= spos+s->node->len)) {
|
|
//cut full
|
|
spos += s->node->len;
|
|
offset += s->node->len;
|
|
s = dlist_pop(&s);
|
|
if (s == TT.slices) TT.slices = s->next;
|
|
|
|
} else if (spos < offset && ( end >= spos+s->node->len)) {
|
|
//cut end
|
|
size_t clip = s->node->len - (offset - spos);
|
|
offset = spos+s->node->len;
|
|
spos += s->node->len;
|
|
s->node->len -= clip;
|
|
} else if (spos == offset && s == e) {
|
|
//cut begin
|
|
size_t clip = end - offset;
|
|
s->node->len -= clip;
|
|
s->node->data += clip;
|
|
break;
|
|
} else {
|
|
//cut middle
|
|
struct slice *tail = xmalloc(sizeof(struct slice));
|
|
size_t clip = end-offset;
|
|
tail->len = s->node->len-(offset-spos)-clip;
|
|
tail->data = s->node->data+(offset-spos)+clip;
|
|
s->node->len = offset-spos; //wrong?
|
|
s = (struct slice_list *)dlist_add_after(
|
|
(struct double_list **)&TT.slices,
|
|
(struct double_list **)&s,
|
|
(char *)tail);
|
|
break;
|
|
}
|
|
if (s == e) break;
|
|
|
|
s = s->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//find offset position in slices
|
|
static struct slice_list *slice_offset(size_t *start, size_t offset)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t spos = 0;
|
|
|
|
//find start
|
|
for ( ;s ; ) {
|
|
if (spos<=offset && spos+s->node->len>offset) break;
|
|
|
|
spos += s->node->len;
|
|
s = s->next;
|
|
|
|
if (s == TT.slices) s = 0; //error out of bounds
|
|
}
|
|
if (s) *start = spos;
|
|
return s;
|
|
}
|
|
|
|
static size_t text_strchr(size_t offset, char c)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t epos, spos = 0;
|
|
int i = 0;
|
|
|
|
//find start
|
|
if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
|
|
|
|
i = offset-spos;
|
|
epos = spos+i;
|
|
do {
|
|
for (; i < s->node->len; i++, epos++)
|
|
if (s->node->data[i] == c) return epos;
|
|
s = s->next;
|
|
i = 0;
|
|
} while (s != TT.slices);
|
|
|
|
return SIZE_MAX;
|
|
}
|
|
|
|
static size_t text_strrchr(size_t offset, char c)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t epos, spos = 0;
|
|
int i = 0;
|
|
|
|
//find start
|
|
if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
|
|
|
|
i = offset-spos;
|
|
epos = spos+i;
|
|
do {
|
|
for (; i >= 0; i--, epos--)
|
|
if (s->node->data[i] == c) return epos;
|
|
s = s->prev;
|
|
i = s->node->len-1;
|
|
} while (s != TT.slices->prev); //tail
|
|
|
|
return SIZE_MAX;
|
|
}
|
|
|
|
static size_t text_filesize()
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t pos = 0;
|
|
if (s) do {
|
|
|
|
pos += s->node->len;
|
|
s = s->next;
|
|
|
|
} while (s != TT.slices);
|
|
|
|
return pos;
|
|
}
|
|
|
|
static int text_count(size_t start, size_t end, char c)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t i, count = 0, spos = 0;
|
|
if (!(s = slice_offset(&spos, start))) return 0;
|
|
i = start-spos;
|
|
if (s) do {
|
|
for (; i < s->node->len && spos+i<end; i++)
|
|
if (s->node->data[i] == c) count++;
|
|
if (spos+i>=end) return count;
|
|
|
|
spos += s->node->len;
|
|
i = 0;
|
|
s = s->next;
|
|
|
|
} while (s != TT.slices);
|
|
|
|
return count;
|
|
}
|
|
|
|
static char text_byte(size_t offset)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t spos = 0;
|
|
//find start
|
|
if (!(s = slice_offset(&spos, offset))) return 0;
|
|
return s->node->data[offset-spos];
|
|
}
|
|
|
|
//utf-8 codepoint -1 if not valid, 0 if out_of_bounds, len if valid
|
|
//copies data to dest if dest is not 0
|
|
static int text_codepoint(char *dest, size_t offset)
|
|
{
|
|
char scratch[8] = {0};
|
|
int state = 0, finished = 0;
|
|
|
|
for (;!(finished = utf8_dec(text_byte(offset), scratch, &state)); offset++)
|
|
if (!state) return -1;
|
|
|
|
if (!finished && !state) return -1;
|
|
if (dest) memcpy(dest, scratch, 8);
|
|
|
|
return strlen(scratch);
|
|
}
|
|
|
|
static size_t text_sol(size_t offset)
|
|
{
|
|
size_t pos;
|
|
if (!TT.filesize || !offset) return 0;
|
|
else if (TT.filesize <= offset) return TT.filesize-1;
|
|
else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0;
|
|
else if (pos < offset) return pos+1;
|
|
return offset;
|
|
}
|
|
|
|
static size_t text_eol(size_t offset)
|
|
{
|
|
if (!TT.filesize) offset = 1;
|
|
else if (TT.filesize <= offset) return TT.filesize-1;
|
|
else if ((offset = text_strchr(offset, '\n')) == SIZE_MAX)
|
|
return TT.filesize-1;
|
|
return offset;
|
|
}
|
|
|
|
static size_t text_nsol(size_t offset)
|
|
{
|
|
offset = text_eol(offset);
|
|
if (text_byte(offset) == '\n') offset++;
|
|
if (offset >= TT.filesize) offset--;
|
|
return offset;
|
|
}
|
|
|
|
static size_t text_psol(size_t offset)
|
|
{
|
|
offset = text_sol(offset);
|
|
if (offset) offset--;
|
|
if (offset && text_byte(offset-1) != '\n') offset = text_sol(offset-1);
|
|
return offset;
|
|
}
|
|
|
|
static size_t text_getline(char *dest, size_t offset, size_t max_len)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
size_t end, spos = 0;
|
|
int i, j = 0;
|
|
|
|
if (dest) *dest = 0;
|
|
|
|
if (!s) return 0;
|
|
if ((end = text_strchr(offset, '\n')) == SIZE_MAX)
|
|
if ((end = TT.filesize) > offset+max_len) return 0;
|
|
|
|
//find start
|
|
if (!(s = slice_offset(&spos, offset))) return 0;
|
|
|
|
i = offset-spos;
|
|
j = end-offset+1;
|
|
if (dest) do {
|
|
for (; i < s->node->len && j; i++, j--, dest++)
|
|
*dest = s->node->data[i];
|
|
s = s->next;
|
|
i = 0;
|
|
} while (s != TT.slices && j);
|
|
|
|
if (dest) *dest = 0;
|
|
|
|
return end-offset;
|
|
}
|
|
|
|
//copying is needed when file has lot of inserts that are
|
|
//just few char long, but not always. Advanced search should
|
|
//check big slices directly and just copy edge cases.
|
|
//Also this is only line based search multiline
|
|
//and regexec should be done instead.
|
|
static size_t text_strstr(size_t offset, char *str)
|
|
{
|
|
size_t bytes, pos = offset;
|
|
char *s = 0;
|
|
do {
|
|
bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf));
|
|
if (!bytes) pos++; //empty line
|
|
else if ((s = strstr(toybuf, str))) return pos+(s-toybuf);
|
|
else pos += bytes;
|
|
} while (pos < TT.filesize);
|
|
|
|
return SIZE_MAX;
|
|
}
|
|
|
|
static void block_list_free(void *node)
|
|
{
|
|
struct block_list *d = node;
|
|
|
|
if (d->node->alloc == HEAP) free((void *)d->node->data);
|
|
else if (d->node->alloc == MMAP) munmap((void *)d->node->data, d->node->size);
|
|
|
|
free(d->node);
|
|
free(d);
|
|
}
|
|
|
|
static void linelist_unload()
|
|
{
|
|
llist_traverse((void *)TT.slices, llist_free_double);
|
|
llist_traverse((void *)TT.text, block_list_free);
|
|
TT.slices = 0, TT.text = 0;
|
|
}
|
|
|
|
static int linelist_load(char *filename)
|
|
{
|
|
if (!filename) filename = (char*)*toys.optargs;
|
|
|
|
if (filename) {
|
|
int fd = open(filename, O_RDONLY);
|
|
size_t size;
|
|
char *data;
|
|
|
|
if (fd == -1) return 0;
|
|
data = xmmap(0, size = fdlength(fd), PROT_READ, MAP_SHARED, fd, 0);
|
|
xclose(fd);
|
|
insert_str(data, 0, size, size, MMAP);
|
|
TT.filesize = text_filesize();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void write_file(char *filename)
|
|
{
|
|
struct slice_list *s = TT.slices;
|
|
struct stat st;
|
|
int fd = 0;
|
|
if (!s) return;
|
|
|
|
if (!filename) filename = (char*)*toys.optargs;
|
|
|
|
sprintf(toybuf, "%s.swp", filename);
|
|
|
|
if ( (fd = xopen(toybuf, O_WRONLY | O_CREAT | O_TRUNC)) <0) return;
|
|
|
|
do {
|
|
xwrite(fd, (void *)s->node->data, s->node->len );
|
|
s = s->next;
|
|
} while (s != TT.slices);
|
|
|
|
linelist_unload();
|
|
|
|
xclose(fd);
|
|
if (!stat(filename, &st)) chmod(toybuf, st.st_mode);
|
|
else chmod(toybuf, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
|
|
xrename(toybuf, filename);
|
|
linelist_load(filename);
|
|
|
|
}
|
|
|
|
//jump into valid offset index
|
|
//and valid utf8 codepoint
|
|
static void check_cursor_bounds()
|
|
{
|
|
char buf[8] = {0};
|
|
int len, width = 0;
|
|
if (!TT.filesize) TT.cursor = 0;
|
|
|
|
for (;;) {
|
|
if (TT.cursor < 1) {
|
|
TT.cursor = 0;
|
|
return;
|
|
} else if (TT.cursor >= TT.filesize-1) {
|
|
TT.cursor = TT.filesize-1;
|
|
return;
|
|
}
|
|
if ((len = text_codepoint(buf, TT.cursor)) < 1) {
|
|
TT.cursor--; //we are not in valid data try jump over
|
|
continue;
|
|
}
|
|
if (utf8_lnw(&width, buf, len) && width) break;
|
|
else TT.cursor--; //combine char jump over
|
|
}
|
|
}
|
|
|
|
// TT.vi_mov_flag is used for special cases when certain move
|
|
// acts differently depending is there DELETE/YANK or NOP
|
|
// Also commands such as G does not default to count0=1
|
|
// 0x1 = Command needs argument (f,F,r...)
|
|
// 0x2 = Move 1 right on yank/delete/insert (e, $...)
|
|
// 0x4 = yank/delete last line fully
|
|
// 0x10000000 = redraw after cursor needed
|
|
// 0x20000000 = full redraw needed
|
|
// 0x40000000 = count0 not given
|
|
// 0x80000000 = move was reverse
|
|
|
|
//TODO rewrite the logic, difficulties counting lines
|
|
//and with big files scroll should not rely in knowing
|
|
//absoluteline numbers
|
|
static void adjust_screen_buffer()
|
|
{
|
|
size_t c, s;
|
|
TT.cur_row = 0, TT.scr_row = 0;
|
|
if (!TT.cursor) {
|
|
TT.screen = 0;
|
|
TT.vi_mov_flag = 0x20000000;
|
|
return;
|
|
} else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
|
|
//give up, file is big, do full redraw
|
|
|
|
TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
|
|
TT.vi_mov_flag = 0x20000000;
|
|
return;
|
|
}
|
|
|
|
s = text_count(0, TT.screen, '\n');
|
|
c = text_count(0, TT.cursor, '\n');
|
|
if (s >= c) {
|
|
TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
|
|
s = c;
|
|
TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
|
|
} else {
|
|
int distance = c-s+1;
|
|
if (distance > (int)TT.screen_height) {
|
|
int n, adj = distance-TT.screen_height;
|
|
TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
|
|
for (;adj; adj--, s++)
|
|
if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
|
|
TT.screen = n+1;
|
|
}
|
|
}
|
|
|
|
TT.scr_row = s;
|
|
TT.cur_row = c;
|
|
|
|
}
|
|
|
|
//TODO search yank buffer by register
|
|
//TODO yanks could be separate slices so no need to copy data
|
|
//now only supports default register
|
|
static int vi_yank(char reg, size_t from, int flags)
|
|
{
|
|
size_t start = from, end = TT.cursor;
|
|
char *str;
|
|
|
|
memset(TT.yank.data, 0, TT.yank.alloc);
|
|
if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
|
|
else TT.cursor = start; //yank moves cursor to left pos always?
|
|
|
|
if (TT.yank.alloc < end-from) {
|
|
size_t new_bounds = (1+end-from)/1024;
|
|
new_bounds += ((1+end-from)%1024) ? 1 : 0;
|
|
new_bounds *= 1024;
|
|
TT.yank.data = xrealloc(TT.yank.data, new_bounds);
|
|
TT.yank.alloc = new_bounds;
|
|
}
|
|
|
|
//this is naive copy
|
|
for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
|
|
|
|
*str = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int vi_delete(char reg, size_t from, int flags)
|
|
{
|
|
size_t start = from, end = TT.cursor;
|
|
|
|
vi_yank(reg, from, flags);
|
|
|
|
if (TT.vi_mov_flag&0x80000000)
|
|
start = TT.cursor, end = from;
|
|
|
|
//pre adjust cursor move one right until at next valid rune
|
|
if (TT.vi_mov_flag&2) {
|
|
//TODO
|
|
}
|
|
//do slice cut
|
|
cut_str(start, end-start);
|
|
|
|
//cursor is at start at after delete
|
|
TT.cursor = start;
|
|
TT.filesize = text_filesize();
|
|
//find line start by strrchr(/n) ++
|
|
//set cur_col with crunch_n_str maybe?
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int vi_change(char reg, size_t to, int flags)
|
|
{
|
|
vi_delete(reg, to, flags);
|
|
TT.vi_mode = 2;
|
|
return 1;
|
|
}
|
|
|
|
static int cur_left(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
TT.vi_mov_flag |= 0x80000000;
|
|
for (;count && TT.cursor; count--) {
|
|
TT.cursor--;
|
|
if (text_byte(TT.cursor) == '\n') TT.cursor++;
|
|
check_cursor_bounds();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int cur_right(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1, len, width = 0;
|
|
char buf[8] = {0};
|
|
|
|
for (;count; count--) {
|
|
len = text_codepoint(buf, TT.cursor);
|
|
|
|
if (*buf == '\n') break;
|
|
else if (len > 0) TT.cursor += len;
|
|
else TT.cursor++;
|
|
|
|
for (;TT.cursor < TT.filesize;) {
|
|
if ((len = text_codepoint(buf, TT.cursor)) < 1) {
|
|
TT.cursor++; //we are not in valid data try jump over
|
|
continue;
|
|
}
|
|
|
|
if (utf8_lnw(&width, buf, len) && width) break;
|
|
else TT.cursor += len;
|
|
}
|
|
}
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
//TODO column shift
|
|
static int cur_up(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
for (;count--;) TT.cursor = text_psol(TT.cursor);
|
|
|
|
TT.vi_mov_flag |= 0x80000000;
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
//TODO column shift
|
|
static int cur_down(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
for (;count--;) TT.cursor = text_nsol(TT.cursor);
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_H(int count0, int count1, char *unused)
|
|
{
|
|
TT.cursor = text_sol(TT.screen);
|
|
return 1;
|
|
}
|
|
|
|
static int vi_L(int count0, int count1, char *unused)
|
|
{
|
|
TT.cursor = text_sol(TT.screen);
|
|
cur_down(TT.screen_height-1, 1, 0);
|
|
return 1;
|
|
}
|
|
|
|
static int vi_M(int count0, int count1, char *unused)
|
|
{
|
|
TT.cursor = text_sol(TT.screen);
|
|
cur_down(TT.screen_height/2, 1, 0);
|
|
return 1;
|
|
}
|
|
|
|
static int search_str(char *s)
|
|
{
|
|
size_t pos = text_strstr(TT.cursor+1, s);
|
|
|
|
if (TT.last_search != s) {
|
|
free(TT.last_search);
|
|
TT.last_search = xstrdup(s);
|
|
}
|
|
|
|
if (pos != SIZE_MAX) TT.cursor = pos;
|
|
check_cursor_bounds();
|
|
return 0;
|
|
}
|
|
|
|
static int vi_yy(char reg, int count0, int count1)
|
|
{
|
|
size_t history = TT.cursor;
|
|
size_t pos = text_sol(TT.cursor); //go left to first char on line
|
|
TT.vi_mov_flag |= 0x4;
|
|
|
|
for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
|
|
|
|
vi_yank(reg, pos, 0);
|
|
|
|
TT.cursor = history;
|
|
return 1;
|
|
}
|
|
|
|
static int vi_dd(char reg, int count0, int count1)
|
|
{
|
|
size_t pos = text_sol(TT.cursor); //go left to first char on line
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
|
|
for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
|
|
|
|
if (pos == TT.cursor && TT.filesize) pos--;
|
|
vi_delete(reg, pos, 0);
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_x(char reg, int count0, int count1)
|
|
{
|
|
size_t from = TT.cursor;
|
|
|
|
if (text_byte(TT.cursor) == '\n') {
|
|
cur_left(count0-1, 1, 0);
|
|
}
|
|
else {
|
|
cur_right(count0-1, 1, 0);
|
|
if (text_byte(TT.cursor) == '\n') TT.vi_mov_flag |= 2;
|
|
else cur_right(1, 1, 0);
|
|
}
|
|
|
|
vi_delete(reg, from, 0);
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_movw(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
while (count--) {
|
|
char c = text_byte(TT.cursor);
|
|
do {
|
|
if (TT.cursor > TT.filesize-1) break;
|
|
//if at empty jump to non empty
|
|
if (c == '\n') {
|
|
if (++TT.cursor > TT.filesize-1) break;
|
|
if ((c = text_byte(TT.cursor)) == '\n') break;
|
|
continue;
|
|
} else if (strchr(blank, c)) do {
|
|
if (++TT.cursor > TT.filesize-1) break;
|
|
c = text_byte(TT.cursor);
|
|
} while (strchr(blank, c));
|
|
//if at special jump to non special
|
|
else if (strchr(specials, c)) do {
|
|
if (++TT.cursor > TT.filesize-1) break;
|
|
c = text_byte(TT.cursor);
|
|
} while (strchr(specials, c));
|
|
//else jump to empty or spesial
|
|
else do {
|
|
if (++TT.cursor > TT.filesize-1) break;
|
|
c = text_byte(TT.cursor);
|
|
} while (c && !strchr(blank, c) && !strchr(specials, c));
|
|
|
|
} while (strchr(blank, c) && c != '\n'); //never stop at empty
|
|
}
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_movb(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
int type = 0;
|
|
char c;
|
|
while (count--) {
|
|
c = text_byte(TT.cursor);
|
|
do {
|
|
if (!TT.cursor) break;
|
|
//if at empty jump to non empty
|
|
if (strchr(blank, c)) do {
|
|
if (!--TT.cursor) break;
|
|
c = text_byte(TT.cursor);
|
|
} while (strchr(blank, c));
|
|
//if at special jump to non special
|
|
else if (strchr(specials, c)) do {
|
|
if (!--TT.cursor) break;
|
|
type = 0;
|
|
c = text_byte(TT.cursor);
|
|
} while (strchr(specials, c));
|
|
//else jump to empty or spesial
|
|
else do {
|
|
if (!--TT.cursor) break;
|
|
type = 1;
|
|
c = text_byte(TT.cursor);
|
|
} while (!strchr(blank, c) && !strchr(specials, c));
|
|
|
|
} while (strchr(blank, c)); //never stop at empty
|
|
}
|
|
//find first
|
|
for (;TT.cursor; TT.cursor--) {
|
|
c = text_byte(TT.cursor-1);
|
|
if (type && !strchr(blank, c) && !strchr(specials, c)) break;
|
|
else if (!type && !strchr(specials, c)) break;
|
|
}
|
|
|
|
TT.vi_mov_flag |= 0x80000000;
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_move(int count0, int count1, char *unused)
|
|
{
|
|
int count = count0*count1;
|
|
int type = 0;
|
|
char c;
|
|
|
|
if (count>1) vi_movw(count-1, 1, unused);
|
|
|
|
c = text_byte(TT.cursor);
|
|
if (strchr(specials, c)) type = 1;
|
|
TT.cursor++;
|
|
for (;TT.cursor < TT.filesize-1; TT.cursor++) {
|
|
c = text_byte(TT.cursor+1);
|
|
if (!type && (strchr(blank, c) || strchr(specials, c))) break;
|
|
else if (type && !strchr(specials, c)) break;
|
|
}
|
|
|
|
TT.vi_mov_flag |= 2;
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
|
|
static void i_insert(char *str, int len)
|
|
{
|
|
if (!str || !len) return;
|
|
|
|
insert_str(xstrdup(str), TT.cursor, len, len, HEAP);
|
|
TT.cursor += len;
|
|
TT.filesize = text_filesize();
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
}
|
|
|
|
static int vi_zero(int count0, int count1, char *unused)
|
|
{
|
|
TT.cursor = text_sol(TT.cursor);
|
|
TT.cur_col = 0;
|
|
TT.vi_mov_flag |= 0x80000000;
|
|
return 1;
|
|
}
|
|
|
|
static int vi_dollar(int count0, int count1, char *unused)
|
|
{
|
|
size_t new = text_strchr(TT.cursor, '\n');
|
|
|
|
if (new != TT.cursor) {
|
|
TT.cursor = new - 1;
|
|
TT.vi_mov_flag |= 2;
|
|
check_cursor_bounds();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void vi_eol()
|
|
{
|
|
TT.cursor = text_strchr(TT.cursor, '\n');
|
|
check_cursor_bounds();
|
|
}
|
|
|
|
static void ctrl_b()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<TT.screen_height-2; ++i) {
|
|
TT.screen = text_psol(TT.screen);
|
|
// TODO: retain x offset.
|
|
TT.cursor = text_psol(TT.screen);
|
|
}
|
|
}
|
|
|
|
static void ctrl_f()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
|
|
// TODO: real vi keeps the x position.
|
|
if (TT.screen > TT.cursor) TT.cursor = TT.screen;
|
|
}
|
|
|
|
static void ctrl_e()
|
|
{
|
|
TT.screen = text_nsol(TT.screen);
|
|
// TODO: real vi keeps the x position.
|
|
if (TT.screen > TT.cursor) TT.cursor = TT.screen;
|
|
}
|
|
|
|
static void ctrl_y()
|
|
{
|
|
TT.screen = text_psol(TT.screen);
|
|
// TODO: only if we're on the bottom line
|
|
TT.cursor = text_psol(TT.cursor);
|
|
// TODO: real vi keeps the x position.
|
|
}
|
|
|
|
//TODO check register where to push from
|
|
static int vi_push(char reg, int count0, int count1)
|
|
{
|
|
//if row changes during push original cursor position is kept
|
|
//vi inconsistancy
|
|
//if yank ends with \n push is linemode else push in place+1
|
|
size_t history = TT.cursor;
|
|
char *start = TT.yank.data;
|
|
char *eol = strchr(start, '\n');
|
|
|
|
if (start[strlen(start)-1] == '\n') {
|
|
if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX)
|
|
TT.cursor = TT.filesize;
|
|
else TT.cursor = text_nsol(TT.cursor);
|
|
} else cur_right(1, 1, 0);
|
|
|
|
i_insert(start, strlen(start));
|
|
if (eol) {
|
|
TT.vi_mov_flag |= 0x10000000;
|
|
TT.cursor = history;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int vi_find_c(int count0, int count1, char *symbol)
|
|
{
|
|
//// int count = count0*count1;
|
|
size_t pos = text_strchr(TT.cursor, *symbol);
|
|
if (pos != SIZE_MAX) TT.cursor = pos;
|
|
return 1;
|
|
}
|
|
|
|
static int vi_find_cb(int count0, int count1, char *symbol)
|
|
{
|
|
//do backward search
|
|
size_t pos = text_strrchr(TT.cursor, *symbol);
|
|
if (pos != SIZE_MAX) TT.cursor = pos;
|
|
return 1;
|
|
}
|
|
|
|
//if count is not spesified should go to last line
|
|
static int vi_go(int count0, int count1, char *symbol)
|
|
{
|
|
size_t prev_cursor = TT.cursor;
|
|
int count = count0*count1-1;
|
|
TT.cursor = 0;
|
|
|
|
if (TT.vi_mov_flag&0x40000000 && (TT.cursor = TT.filesize) > 0)
|
|
TT.cursor = text_sol(TT.cursor-1);
|
|
else if (count) {
|
|
size_t next = 0;
|
|
for ( ;count && (next = text_strchr(next+1, '\n')) != SIZE_MAX; count--)
|
|
TT.cursor = next;
|
|
TT.cursor++;
|
|
}
|
|
|
|
check_cursor_bounds(); //adjusts cursor column
|
|
if (prev_cursor > TT.cursor) TT.vi_mov_flag |= 0x80000000;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int vi_o(char reg, int count0, int count1)
|
|
{
|
|
TT.cursor = text_eol(TT.cursor);
|
|
insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP);
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
TT.vi_mode = 2;
|
|
return 1;
|
|
}
|
|
|
|
static int vi_O(char reg, int count0, int count1)
|
|
{
|
|
TT.cursor = text_psol(TT.cursor);
|
|
return vi_o(reg, count0, count1);
|
|
}
|
|
|
|
static int vi_D(char reg, int count0, int count1)
|
|
{
|
|
size_t pos = TT.cursor;
|
|
if (!count0) return 1;
|
|
vi_eol();
|
|
vi_delete(reg, pos, 0);
|
|
if (--count0) vi_dd(reg, count0, 1);
|
|
|
|
check_cursor_bounds();
|
|
return 1;
|
|
}
|
|
|
|
static int vi_I(char reg, int count0, int count1)
|
|
{
|
|
TT.cursor = text_sol(TT.cursor);
|
|
TT.vi_mode = 2;
|
|
return 1;
|
|
}
|
|
|
|
static int vi_join(char reg, int count0, int count1)
|
|
{
|
|
size_t next;
|
|
while (count0--) {
|
|
//just strchr(/n) and cut_str(pos, 1);
|
|
if ((next = text_strchr(TT.cursor, '\n')) == SIZE_MAX) break;
|
|
TT.cursor = next+1;
|
|
vi_delete(reg, TT.cursor-1, 0);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int vi_find_next(char reg, int count0, int count1)
|
|
{
|
|
if (TT.last_search) search_str(TT.last_search);
|
|
return 1;
|
|
}
|
|
|
|
//NOTES
|
|
//vi-mode cmd syntax is
|
|
//("[REG])[COUNT0]CMD[COUNT1](MOV)
|
|
//where:
|
|
//-------------------------------------------------------------
|
|
//"[REG] is optional buffer where deleted/yanked text goes REG can be
|
|
// atleast 0-9, a-z or default "
|
|
//[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
|
|
// operations they are multiplied together
|
|
//CMD is operation to be executed
|
|
//(MOV) is movement operation, some CMD does not require MOV and some
|
|
// have special cases such as dd, yy, also movements can work without
|
|
// CMD
|
|
//ex commands can be even more complicated than this....
|
|
//
|
|
struct vi_cmd_param {
|
|
const char* cmd;
|
|
unsigned flags;
|
|
int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS
|
|
};
|
|
struct vi_mov_param {
|
|
const char* mov;
|
|
unsigned flags;
|
|
int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
|
|
};
|
|
//special cases without MOV and such
|
|
struct vi_special_param {
|
|
const char *cmd;
|
|
int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1
|
|
};
|
|
struct vi_special_param vi_special[] =
|
|
{
|
|
{"D", &vi_D},
|
|
{"I", &vi_I},
|
|
{"J", &vi_join},
|
|
{"O", &vi_O},
|
|
{"n", &vi_find_next},
|
|
{"o", &vi_o},
|
|
{"p", &vi_push},
|
|
{"x", &vi_x},
|
|
{"dd", &vi_dd},
|
|
{"yy", &vi_yy},
|
|
};
|
|
//there is around ~47 vi moves
|
|
//some of them need extra params
|
|
//such as f and '
|
|
struct vi_mov_param vi_movs[] =
|
|
{
|
|
{"0", 0, &vi_zero},
|
|
{"b", 0, &vi_movb},
|
|
{"e", 0, &vi_move},
|
|
{"G", 0, &vi_go},
|
|
{"H", 0, &vi_H},
|
|
{"h", 0, &cur_left},
|
|
{"j", 0, &cur_down},
|
|
{"k", 0, &cur_up},
|
|
{"L", 0, &vi_L},
|
|
{"l", 0, &cur_right},
|
|
{"M", 0, &vi_M},
|
|
{"w", 0, &vi_movw},
|
|
{"$", 0, &vi_dollar},
|
|
{"f", 1, &vi_find_c},
|
|
{"F", 1, &vi_find_cb},
|
|
};
|
|
//change and delete unfortunately behave different depending on move command,
|
|
//such as ce cw are same, but dw and de are not...
|
|
//also dw stops at w position and cw seem to stop at e pos+1...
|
|
//so after movement we need to possibly set up some flags before executing
|
|
//command, and command needs to adjust...
|
|
struct vi_cmd_param vi_cmds[] =
|
|
{
|
|
{"c", 1, &vi_change},
|
|
{"d", 1, &vi_delete},
|
|
{"y", 1, &vi_yank},
|
|
};
|
|
|
|
static int run_vi_cmd(char *cmd)
|
|
{
|
|
int i = 0, val = 0;
|
|
char *cmd_e;
|
|
int (*vi_cmd)(char, size_t, int) = 0;
|
|
int (*vi_mov)(int, int, char*) = 0;
|
|
|
|
TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0;
|
|
TT.vi_reg = '"';
|
|
|
|
if (*cmd == '"') {
|
|
cmd++;
|
|
TT.vi_reg = *cmd; //TODO check validity
|
|
cmd++;
|
|
}
|
|
errno = 0;
|
|
val = strtol(cmd, &cmd_e, 10);
|
|
if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000;
|
|
else cmd = cmd_e;
|
|
TT.count0 = val;
|
|
|
|
for (i = 0; i < ARRAY_LEN(vi_special); i++) {
|
|
if (strstr(cmd, vi_special[i].cmd)) {
|
|
return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_LEN(vi_cmds); i++) {
|
|
if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
|
|
vi_cmd = vi_cmds[i].vi_cmd;
|
|
cmd += strlen(vi_cmds[i].cmd);
|
|
break;
|
|
}
|
|
}
|
|
errno = 0;
|
|
val = strtol(cmd, &cmd_e, 10);
|
|
if (errno || val == 0) val = 1;
|
|
else cmd = cmd_e;
|
|
TT.count1 = val;
|
|
|
|
for (i = 0; i < ARRAY_LEN(vi_movs); i++) {
|
|
if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
|
|
vi_mov = vi_movs[i].vi_mov;
|
|
TT.vi_mov_flag |= vi_movs[i].flags;
|
|
cmd++;
|
|
if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
|
|
break;
|
|
}
|
|
}
|
|
if (vi_mov) {
|
|
int prev_cursor = TT.cursor;
|
|
if (vi_mov(TT.count0, TT.count1, cmd)) {
|
|
if (vi_cmd) return (vi_cmd(TT.vi_reg, prev_cursor, TT.vi_mov_flag));
|
|
else return 1;
|
|
} else return 0; //return some error
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int run_ex_cmd(char *cmd)
|
|
{
|
|
if (cmd[0] == '/') {
|
|
search_str(&cmd[1]);
|
|
} else if (cmd[0] == '?') {
|
|
// TODO: backwards search.
|
|
} else if (cmd[0] == ':') {
|
|
if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) {
|
|
// TODO: if no !, check whether file modified.
|
|
//exit_application;
|
|
return -1;
|
|
}
|
|
else if (strstr(&cmd[1], "wq")) {
|
|
write_file(0);
|
|
return -1;
|
|
}
|
|
else if (strstr(&cmd[1], "w")) {
|
|
write_file(0);
|
|
return 1;
|
|
}
|
|
else if (strstr(&cmd[1], "set list")) {
|
|
TT.list = 1;
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
return 1;
|
|
}
|
|
else if (strstr(&cmd[1], "set nolist")) {
|
|
TT.list = 0;
|
|
TT.vi_mov_flag |= 0x30000000;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int vi_crunch(FILE *out, int cols, int wc)
|
|
{
|
|
int ret = 0;
|
|
if (wc < 32 && TT.list) {
|
|
tty_esc("1m");
|
|
ret = crunch_escape(out,cols,wc);
|
|
tty_esc("m");
|
|
} else if (wc == 0x09) {
|
|
if (out) {
|
|
int i = TT.tabstop;
|
|
for (;i--;) fputs(" ", out);
|
|
}
|
|
ret = TT.tabstop;
|
|
} else if (wc == '\n') return 0;
|
|
return ret;
|
|
}
|
|
|
|
//crunch_str with n bytes restriction for printing substrings or
|
|
//non null terminated strings
|
|
static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
|
|
int (*escout)(FILE *out, int cols, int wc))
|
|
{
|
|
int columns = 0, col, bytes;
|
|
char *start, *end;
|
|
|
|
for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
|
|
wchar_t wc;
|
|
|
|
if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
|
|
if (!escmore || wc>255 || !strchr(escmore, wc)) {
|
|
if (width-columns<col) break;
|
|
if (out) fwrite(end, bytes, 1, out);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bytes<1) {
|
|
bytes = 1;
|
|
wc = *end;
|
|
}
|
|
col = width-columns;
|
|
if (col<1) break;
|
|
if (escout) {
|
|
if ((col = escout(out, col, wc))<0) break;
|
|
} else if (out) fwrite(end, 1, bytes, out);
|
|
}
|
|
*str = end;
|
|
|
|
return columns;
|
|
}
|
|
|
|
static void draw_page()
|
|
{
|
|
unsigned y = 0;
|
|
int x = 0;
|
|
|
|
char *line = 0, *end = 0;
|
|
int bytes = 0;
|
|
|
|
//screen coordinates for cursor
|
|
int cy_scr = 0, cx_scr = 0;
|
|
|
|
//variables used only for cursor handling
|
|
int aw = 0, iw = 0, clip = 0, margin = 8;
|
|
|
|
int scroll = 0, redraw = 0;
|
|
|
|
int SSOL, SOL;
|
|
|
|
|
|
adjust_screen_buffer();
|
|
//redraw = 3; //force full redraw
|
|
redraw = (TT.vi_mov_flag & 0x30000000)>>28;
|
|
|
|
scroll = TT.drawn_row-TT.scr_row;
|
|
if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
|
|
else if (abs(scroll)>TT.screen_height/2) redraw = 3;
|
|
|
|
tty_jump(0, 0);
|
|
if (redraw&2) tty_esc("2J"), tty_esc("H"); //clear screen
|
|
else if (scroll>0) printf("\033[%dL", scroll); //scroll up
|
|
else if (scroll<0) printf("\033[%dM", -scroll); //scroll down
|
|
|
|
SOL = text_sol(TT.cursor);
|
|
bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
|
|
line = toybuf;
|
|
|
|
for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
|
|
|
|
cy_scr = y;
|
|
|
|
//draw cursor row
|
|
/////////////////////////////////////////////////////////////
|
|
//for long lines line starts to scroll when cursor hits margin
|
|
bytes = TT.cursor-SOL; // TT.cur_col;
|
|
end = line;
|
|
|
|
|
|
tty_jump(0, y);
|
|
tty_esc("2K");
|
|
//find cursor position
|
|
aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
|
|
|
|
//if we need to render text that is not inserted to buffer yet
|
|
if (TT.vi_mode == 2 && TT.il->len) {
|
|
char* iend = TT.il->data; //input end
|
|
x = 0;
|
|
//find insert end position
|
|
iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
|
|
clip = (aw+iw) - TT.screen_width+margin;
|
|
|
|
//if clipped area is bigger than text before insert
|
|
if (clip > aw) {
|
|
clip -= aw;
|
|
iend = TT.il->data;
|
|
|
|
iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
|
|
x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
|
|
} else {
|
|
iend = TT.il->data;
|
|
end = line;
|
|
|
|
//if clipped area is substring from cursor row start
|
|
aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
|
|
x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch);
|
|
x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
|
|
}
|
|
}
|
|
//when not inserting but still need to keep cursor inside screen
|
|
//margin area
|
|
else if ( aw+margin > TT.screen_width) {
|
|
clip = aw-TT.screen_width+margin;
|
|
end = line;
|
|
aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
|
|
x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch);
|
|
}
|
|
else {
|
|
end = line;
|
|
x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
|
|
}
|
|
cx_scr = x;
|
|
cy_scr = y;
|
|
x += crunch_str(&end, TT.screen_width-x, stdout, "\t\n", vi_crunch);
|
|
|
|
//start drawing all other rows that needs update
|
|
///////////////////////////////////////////////////////////////////
|
|
y = 0, SSOL = TT.screen, line = toybuf;
|
|
bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
|
|
|
|
//if we moved around in long line might need to redraw everything
|
|
if (clip != TT.drawn_col) redraw = 3;
|
|
|
|
for (; y < TT.screen_height; y++ ) {
|
|
int draw_line = 0;
|
|
if (SSOL == SOL) {
|
|
line = toybuf;
|
|
SSOL += bytes+1;
|
|
bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
|
|
continue;
|
|
} else if (redraw) draw_line++;
|
|
else if (scroll<0 && TT.screen_height-y-1<-scroll)
|
|
scroll++, draw_line++;
|
|
else if (scroll>0) scroll--, draw_line++;
|
|
|
|
tty_jump(0, y);
|
|
if (draw_line) {
|
|
tty_esc("2K");
|
|
if (line && strlen(line)) {
|
|
aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
|
|
crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
|
|
if ( *line ) printf("@");
|
|
} else printf("\033[2m~\033[m");
|
|
}
|
|
if (SSOL+bytes < TT.filesize) {
|
|
line = toybuf;
|
|
SSOL += bytes+1;
|
|
bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
|
|
} else line = 0;
|
|
}
|
|
|
|
TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
|
|
|
|
// Finished updating visual area, show status line.
|
|
tty_jump(0, TT.screen_height);
|
|
tty_esc("2K");
|
|
if (TT.vi_mode == 2) printf("\033[1m-- INSERT --\033[m");
|
|
if (!TT.vi_mode) {
|
|
cx_scr = printf("%s", TT.il->data);
|
|
cy_scr = TT.screen_height;
|
|
*toybuf = 0;
|
|
} else {
|
|
// TODO: the row,col display doesn't show the cursor column
|
|
// TODO: real vi shows the percentage by lines, not bytes
|
|
sprintf(toybuf, "%zu/%zuC %zu%% %d,%d", TT.cursor, TT.filesize,
|
|
(100*TT.cursor)/TT.filesize, TT.cur_row+1, TT.cur_col+1);
|
|
if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
|
|
}
|
|
tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height);
|
|
printf("%s", toybuf);
|
|
|
|
tty_jump(cx_scr, cy_scr);
|
|
xflush(1);
|
|
}
|
|
|
|
void vi_main(void)
|
|
{
|
|
char stdout_buf[BUFSIZ];
|
|
char keybuf[16] = {0};
|
|
char vi_buf[16] = {0};
|
|
char utf8_code[8] = {0};
|
|
int utf8_dec_p = 0, vi_buf_pos = 0;
|
|
FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
|
|
|
|
TT.il = xzalloc(sizeof(struct str_line));
|
|
TT.il->data = xzalloc(80);
|
|
TT.yank.data = xzalloc(128);
|
|
|
|
TT.il->alloc = 80, TT.yank.alloc = 128;
|
|
|
|
linelist_load(0);
|
|
TT.screen = TT.cursor = 0;
|
|
|
|
TT.vi_mov_flag = 0x20000000;
|
|
TT.vi_mode = 1, TT.tabstop = 8;
|
|
TT.screen_width = 80, TT.screen_height = 24;
|
|
|
|
terminal_size(&TT.screen_width, &TT.screen_height);
|
|
TT.screen_height -= 1;
|
|
|
|
// Avoid flicker.
|
|
setbuf(stdout, stdout_buf);
|
|
|
|
xsignal(SIGWINCH, generic_signal);
|
|
set_terminal(0, 1, 0, 0);
|
|
//writes stdout into different xterm buffer so when we exit
|
|
//we dont get scroll log full of junk
|
|
tty_esc("?1049h");
|
|
|
|
for (;;) {
|
|
int key = 0;
|
|
|
|
draw_page();
|
|
if (script) {
|
|
key = fgetc(script);
|
|
if (key == EOF) {
|
|
fclose(script);
|
|
script = 0;
|
|
key = scan_key(keybuf, -1);
|
|
}
|
|
} else key = scan_key(keybuf, -1);
|
|
|
|
if (key == -1) goto cleanup_vi;
|
|
else if (key == -3) {
|
|
toys.signal = 0;
|
|
terminal_size(&TT.screen_width, &TT.screen_height);
|
|
TT.screen_height -= 1; //TODO this is hack fix visual alignment
|
|
continue;
|
|
}
|
|
|
|
// TODO: support cursor keys in ex mode too.
|
|
if (TT.vi_mode && key>=256) {
|
|
key -= 256;
|
|
if (key==KEY_UP) cur_up(1, 1, 0);
|
|
else if (key==KEY_DOWN) cur_down(1, 1, 0);
|
|
else if (key==KEY_LEFT) cur_left(1, 1, 0);
|
|
else if (key==KEY_RIGHT) cur_right(1, 1, 0);
|
|
else if (key==KEY_HOME) vi_zero(1, 1, 0);
|
|
else if (key==KEY_END) vi_dollar(1, 1, 0);
|
|
else if (key==KEY_PGDN) ctrl_f();
|
|
else if (key==KEY_PGUP) ctrl_b();
|
|
continue;
|
|
}
|
|
|
|
if (TT.vi_mode == 1) { //NORMAL
|
|
switch (key) {
|
|
case '/':
|
|
case '?':
|
|
case ':':
|
|
TT.vi_mode = 0;
|
|
TT.il->data[0]=key;
|
|
TT.il->len++;
|
|
break;
|
|
case 'A':
|
|
vi_eol();
|
|
TT.vi_mode = 2;
|
|
break;
|
|
case 'a':
|
|
cur_right(1, 1, 0);
|
|
// FALLTHROUGH
|
|
case 'i':
|
|
TT.vi_mode = 2;
|
|
break;
|
|
case 'B'-'@':
|
|
ctrl_b();
|
|
break;
|
|
case 'E'-'@':
|
|
ctrl_e();
|
|
break;
|
|
case 'F'-'@':
|
|
ctrl_f();
|
|
break;
|
|
case 'Y'-'@':
|
|
ctrl_y();
|
|
break;
|
|
case 27:
|
|
vi_buf[0] = 0;
|
|
vi_buf_pos = 0;
|
|
break;
|
|
default:
|
|
if (key > 0x20 && key < 0x7B) {
|
|
vi_buf[vi_buf_pos] = key;//TODO handle input better
|
|
vi_buf_pos++;
|
|
if (run_vi_cmd(vi_buf)) {
|
|
memset(vi_buf, 0, 16);
|
|
vi_buf_pos = 0;
|
|
}
|
|
else if (vi_buf_pos == 16) {
|
|
vi_buf_pos = 0;
|
|
memset(vi_buf, 0, 16);
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else if (TT.vi_mode == 0) { //EX MODE
|
|
switch (key) {
|
|
case 0x7F:
|
|
case 0x08:
|
|
if (TT.il->len > 1) {
|
|
TT.il->data[--TT.il->len] = 0;
|
|
break;
|
|
}
|
|
// FALLTHROUGH
|
|
case 27:
|
|
TT.vi_mode = 1;
|
|
TT.il->len = 0;
|
|
memset(TT.il->data, 0, TT.il->alloc);
|
|
break;
|
|
case 0x0A:
|
|
case 0x0D:
|
|
if (run_ex_cmd(TT.il->data) == -1)
|
|
goto cleanup_vi;
|
|
TT.vi_mode = 1;
|
|
TT.il->len = 0;
|
|
memset(TT.il->data, 0, TT.il->alloc);
|
|
break;
|
|
default: //add chars to ex command until ENTER
|
|
if (key >= 0x20 && key < 0x7F) { //might be utf?
|
|
if (TT.il->len == TT.il->alloc) {
|
|
TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
|
|
TT.il->alloc *= 2;
|
|
}
|
|
TT.il->data[TT.il->len] = key;
|
|
TT.il->len++;
|
|
}
|
|
break;
|
|
}
|
|
} else if (TT.vi_mode == 2) {//INSERT MODE
|
|
switch (key) {
|
|
case 27:
|
|
i_insert(TT.il->data, TT.il->len);
|
|
cur_left(1, 1, 0);
|
|
TT.vi_mode = 1;
|
|
TT.il->len = 0;
|
|
memset(TT.il->data, 0, TT.il->alloc);
|
|
break;
|
|
case 0x7F:
|
|
case 0x08:
|
|
if (TT.il->len) {
|
|
char *last = utf8_last(TT.il->data, TT.il->len);
|
|
int shrink = strlen(last);
|
|
memset(last, 0, shrink);
|
|
TT.il->len -= shrink;
|
|
}
|
|
break;
|
|
case 0x0A:
|
|
case 0x0D:
|
|
//insert newline
|
|
//
|
|
TT.il->data[TT.il->len++] = '\n';
|
|
i_insert(TT.il->data, TT.il->len);
|
|
TT.il->len = 0;
|
|
memset(TT.il->data, 0, TT.il->alloc);
|
|
break;
|
|
default:
|
|
if ((key >= 0x20 || key == 0x09) &&
|
|
utf8_dec(key, utf8_code, &utf8_dec_p)) {
|
|
|
|
if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) {
|
|
TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
|
|
TT.il->alloc *= 2;
|
|
}
|
|
strcpy(TT.il->data+TT.il->len, utf8_code);
|
|
TT.il->len += utf8_dec_p;
|
|
utf8_dec_p = 0;
|
|
*utf8_code = 0;
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
cleanup_vi:
|
|
linelist_unload();
|
|
free(TT.il->data), free(TT.il), free(TT.yank.data);
|
|
tty_reset();
|
|
tty_esc("?1049l");
|
|
}
|