CLIte/clite/vi.js
2025-09-02 19:38:13 +10:00

537 lines
10 KiB
JavaScript

clite.commands.data = function() {
clite.commands.load('vi',function(args,env,io) {
var stdio = io.include('stdio');
var clite = io.include('clite');
var curses = io.include('curses'); // definitely not dark magic
var term = io.include('term');
const VIMODE_VIEW = 0;
const VIMODE_CMD = 1;
const VIMODE_EDIT = 2;
var cmd_buff = '';
var file = null;
var mode = VIMODE_VIEW; // 0 view mode, 1 command mode, 2 edit mode
var cursor_line = 0;
var cursor_col = 0;
var cursor_line_real = 0;
var cursor_col_real = 0;
var changed = false;
var num = false;
var message = '';
var cols;
var rows;
var lines = [];
var topLine = 0;
var bottomLine = 0;
function switchMode(m) {
if (m == mode)
return;
switch (m) {
case VIMODE_CMD:
mode = 1;
curses.echo();
curses.move(rows-1,0);
curses.printw(':');
cmd_buff = ':';
showAt(topLine);
break;
case VIMODE_EDIT:
mode = 2;
curses.noecho();
message = 'INSERT';
showAt(topLine);
break;
case VIMODE_VIEW:
default:
mode = 0;
cmd_buff = '';
curses.noecho();
showAt(topLine);
break;
}
}
function help() {
stdio.printf(`
vi - Visual text file editor
Usage: vi [OPTION] <FILE>
Options:
-? Print this help information
`);
}
function calculateBottomLine() {
bottomLine = lines.length;
var r = rows;
var i = lines.length-1;
for (; i>-1; i--) {
r -= lines[i].rows;
if (r<0)
i++;
if (r <= 0)
break;
}
bottomLine = i;
}
function drawCursorLine(line) {
var cl = 0;
var s = -1;
var disp = '';
var rows = 0;
var done = false;
for (var i=0; i<line.length; i++) {
if (i == cursor_col) {
cursor_line_real += rows;
cursor_col_real = cl;
done = true;
}
if (cl > cols) {
var b = '';
var e = '';
if (s > -1) {
b = disp.substring(0,s);
e = l.disp.substring(s);
}else{
b = disp.substring(0,disp.length-1);
e = disp.substring(disp.length-1);
}
disp = b+'\n'+e;
cl = e.length;
rows++;
s = -1;
}
switch (line[i]) {
case '\t':
{
if (cl > (cols-4)) {
disp += '\n';
cl = 0;
rows++;
s = -1;
break;
}
s = disp.length;
var c = 4-(s%4);
for (var j=0; j<c; j++) {
disp += ' ';
cl++;
}
break;
}
case ' ':
s = disp.length;
default:
disp += line[i];
cl++;
}
}
if (!done) {
cursor_line_real += rows;
cursor_col_real = cl;
}
curses.addstr(disp+'\n');
}
function showAt(line) {
if (line > lines.length)
line = lines.length;
if (line < 0)
line = 0;
var start = line;
var end = line+rows-2;
if (end > lines.length)
end = lines.length;
curses.clear();
var k = 0;
if (num) {
for (var i=start; i<end; i++,k++) {
if (i == cursor_line) {
curses.printw('% 4d ',i+1);
cursor_line_real = k;
drawCursorLine(lines[i].real);
if (cursor_line_real == k)
cursor_col_real += 6;
}else{
curses.printw('% 4d %s\n',i+1,lines[i].disp);
}
}
}else{
for (var i=start; i<end; i++,k++) {
if (i == cursor_line) {
curses.addch(' ');
cursor_line_real = k;
drawCursorLine(lines[i].real);
if (cursor_line_real == k)
cursor_col_real += 1;
}else{
curses.printw(' %s\n',lines[i].disp);
}
}
}
curses.move(rows-2,0);
curses.printw('%s',message);
var ct = cursor_line+','+cursor_col;
curses.move(rows-2,cols-(ct.length+5));
curses.printw('%s',ct);
curses.move(rows-1,0);
if (cmd_buff != '')
curses.printw('%s',cmd_buff);
if (mode != VIMODE_CMD)
curses.move(cursor_line_real,cursor_col_real);
}
function prepLine(line) {
var l = Object.create({
real:line,
disp:'',
rows:1
});
var cl = 0;
var s = -1;
for (var i=0; i<line.length; i++) {
if (cl > cols) {
var b = '';
var e = '';
if (s > -1) {
b = l.disp.substring(0,s);
e = l.disp.substring(s);
}else{
b = l.disp.substring(0,l.disp.length-1);
e = l.disp.substring(l.disp.length-1);
}
l.disp = b+'\n'+e;
cl = e.length;
l.rows++;
s = -1;
}
switch (line[i]) {
case '\t':
{
if (cl > (cols-4)) {
l.disp += '\n';
cl = 0;
l.rows++;
s = -1;
break;
}
s = l.disp.length;
var c = 4-(s%4);
for (var j=0; j<c; j++) {
l.disp += ' ';
cl++;
}
break;
}
case ' ':
s = l.disp.length;
default:
l.disp += line[i];
cl++;
}
}
return l;
}
function prepFile(fd) {
if (!fd) {
stdio.fprintf(io.stderr,'could not open file: %s\n',file);
io.exit(1);
return;
}
var st = stdio.fstatat(fd,0);
if ( !st || (st.type != stdio.types.FT_TEXT && st.type != stdio.types.FT_SCRIPT)) {
stdio.write(io.stderr,'can not read files of this type\n');
io.exit(1);
return;
}
var txt = stdio.readAll(fd);
stdio.close(fd);
var parts = txt.split('\n');
parts.forEach(function(line) {
lines.push(prepLine(line));
});
if (lines.length < 1)
lines.push(prepLine(''));
cursor_line = 0;
topLine = 0;
calculateBottomLine();
showAt(topLine);
}
function writeFile(force) {
var fd = stdio.open(file,stdio.flags.O_WRONLY|stdio.flags.O_TRUNC,stdio.flags.O_SYNC);
if (!fd)
return;
var data = '';
lines.forEach(function(l) {
data += l.real+'\n';
});
stdio.write(fd,data);
stdio.close(fd);
}
function doCommand(str) {
// TODO: support search
if (str[0] != ':')
return true;
var quit = false;
var write = false;
var force = false;
for (var i=1; i<str.length; i++) {
switch (str[i]) {
case 'w':
write = true;
break;
case 'q':
quit = true;
break;
case '!':
force = true;
break;
case 'n':
num = !num;
showAt(topLine);
default:;
}
}
cmd_buff = '';
if (write) {
writeFile(force);
changed = false;
}
if (quit) {
if (!changed || force) {
curses.endwin();
io.exit(0);
return false;
}else{
message = 'no write since last change (add ! to override)';
}
}
switchMode(VIMODE_VIEW);
return true;
}
function cursorUp() {
if (cursor_line > 0) {
cursor_line--;
if (cursor_line <= topLine && topLine > 0)
topLine--;
showAt(topLine);
}
}
function cursorDn() {
if (cursor_line < lines.length-1) {
cursor_line++;
calculateBottomLine();
if (topLine < bottomLine) {
topLine++;
}
showAt(topLine);
}
}
function cursorLt() {
if (cursor_col > 0)
cursor_col--;
showAt(topLine);
}
function cursorRt() {
if (cursor_col < lines[cursor_line].real.length)
cursor_col++;
showAt(topLine);
}
function setLine(str) {
lines[cursor_line] = prepLine(str);
calculateBottomLine();
changed = true;
}
function insertLine(str) {
var l = prepLine(str);
lines.splice(cursor_line+1,0,l);
cursor_col = 0;
cursorDn();
changed = true;
}
function input(key) {
if (mode == 0) { // view mode
//message = '';
switch (key) {
case curses.key.KEY_UP:
cursorUp();
break;
case curses.key.KEY_DOWN:
cursorDn();
break;
case curses.key.KEY_LEFT:
cursorLt();
break;
case curses.key.KEY_RIGHT:
cursorRt();
break;
case 'i':
switchMode(VIMODE_EDIT);
break;
case 'a':
cursor_col = lines[cursor_line][0].length;
switchMode(VIMODE_EDIT);
break;
case ':':
switchMode(VIMODE_CMD);
break;
}
}else if (mode == 1) { // command mode
//message = '';
switch (key) {
case curses.key.KEY_ESCAPE:
switchMode(VIMODE_VIEW);
break;
case curses.key.KEY_ENTER:
if (!doCommand(cmd_buff))
return;
showAt(topLine);
cmd_buff = '';
break;
case curses.key.KEY_BACKSPACE:
if (cmd_buff.length > 0) {
cmd_buff = cmd_buff.substring(0,cmd_buff.length-1);
showAt(topLine);
if (!cmd_buff.length)
switchMode(VIMODE_VIEW);
}
break;
default:
if (typeof key === 'string' && key.length == 1)
cmd_buff+=key;
break;
}
}else{ // 2 edit mode
message = 'INSERT';
switch (key) {
case curses.key.KEY_ESCAPE:
message = '';
switchMode(VIMODE_VIEW);
break;
case curses.key.KEY_UP:
cursorUp();
break;
case curses.key.KEY_DOWN:
cursorDn();
break;
case curses.key.KEY_LEFT:
cursorLt();
break;
case curses.key.KEY_RIGHT:
cursorRt();
break;
case curses.key.KEY_BACKSPACE:
if (cursor_col > 0) {
var l = lines[cursor_line].real;
var b = l.substring(0,cursor_col-1);
var e = l.substring(cursor_col);
setLine(b+e);
cursor_col--;
showAt(topLine);
}
break;
case curses.key.KEY_ENTER:
var l = lines[cursor_line].real;
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col);
setLine(b);
insertLine(e);
cursor_col = 0;
showAt(topLine);
break;
case curses.key.KEY_DL:
if (cursor_col < lines[cursor_line].real.length) {
var l = lines[cursor_line].real;
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col+1);
setLine(b+e);
showAt(topLine);
}
break;
default:
if (typeof key === 'string' && key.length == 1) {
var l = lines[cursor_line].real;
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col);
setLine(b+key+e);
cursor_col++;
showAt(topLine);
}
}
}
curses.getch(input);
}
function main(argc,argv) {
for (var i=1; i<argc; i++) {
if (argv[i][0] == '-') {
for (var j=1; j<argv[i].length; j++) {
switch (argv[i][j]) {
case '?':
help();
return 0;
break;
default:
stdio.fprintf(io.stderr,'unknown argument: -%c\n',argv[i][j]);
}
}
}else if (file == null) {
file = clite.resolvePath(argv[i],false);
}else{
stdio.fprintf(io.stderr,'unknown argument: %s\n',argv[i]);
}
}
if (!stdio.isatty(io.stdin))
return 1;
if (file == null) {
stdio.write(io.stderr,'no file specified\n');
return 1;
}
var fd = stdio.open(file,stdio.flags.O_RDONLY|stdio.flags.O_CREAT,prepFile);
if (!fd) {
stdio.fprintf(io.stderr,'could not open file: %s\n',file);
return 1;
}
curses.initscr();
cols = curses.getmaxx()-8;
rows = curses.getmaxy();
curses.cbreak();
curses.noecho();
curses.printw('Loading...');
curses.getch(input);
return null;
}
return main(args.length,args);
});
}