CLIte/clite/vi.js
2023-12-14 12:53:46 +10:00

413 lines
8.1 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');
var cmd_buff = '';
var file = null;
var mode = 0; // 0 view mode, 1 command mode, 2 edit mode
var cursor_line = 0;
var cursor_col = 0;
var changed = false;
var num = false;
var message = '';
var cols;
var rows;
var lines = [];
var topLine = 0;
var bottomLine = 0;
const VIMODE_VIEW = 0;
const VIMODE_CMD = 1;
const VIMODE_EDIT = 2;
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
`);
}
for (var i=1; i<args.length; i++) {
if (args[i][0] == '-') {
for (var j=1; j<args[i].length; j++) {
switch (args[i][j]) {
case '?':
help();
return 0;
break;
default:
stdio.fprintf(io.stderr,'unknown argument: -%c\n',args[i][j]);
}
}
}else if (file == null) {
file = clite.resolvePath(args[i],false);
}else{
stdio.fprintf(io.stderr,'unknown argument: %s\n',args[i]);
}
}
if (!stdio.isatty(io.stdin))
return 1;
function calculateBottomLine() {
bottomLine = lines.length;
var r = rows;
var i = lines.length-1;
for (; i>-1; i--) {
r -= lines[i][1];
if (r<0)
i++;
if (r <= 0)
break;
}
bottomLine = i;
}
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 cursor_screen = [0,cursor_col+1];
var k = 0;
if (num) {
cursor_screen[1] += 4;
for (var i=start; i<end; i++,k++) {
if (i == cursor_line)// {
cursor_screen[0] = k;
curses.printw('% 4d %s\n',i+1,lines[i][0]);
}
}else{
for (var i=start; i<end; i++,k++) {
if (i == cursor_line)// {
cursor_screen[0] = k;
curses.printw(' %s\n',lines[i][0]);
}
}
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 != 1)
curses.move(cursor_screen[0],cursor_screen[1]);
}
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) {
if (line.length > cols) {
var count = Math.ceil(line.length/cols);
lines.push([line,count]);
return;
}
lines.push([line,1]);
});
if (lines.length < 1)
lines.push(['',0]);
cursor_line = lines.length-1;
calculateBottomLine();
topLine = bottomLine;
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[0]+'\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)';
}
}
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][0].length)
cursor_col++;
showAt(topLine);
}
function setLine(str) {
lines[cursor_line][0] = str;
var count = Math.ceil(lines[cursor_line][0].length/cols);
lines[cursor_line][1] = count;
calculateBottomLine();
changed = true;
}
function insertLine(str) {
var l = [str,str.length];
lines.splice(cursor_line+1,0,l);
cursor_col = 0;
cursorDn();
changed = true;
}
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();
rows = curses.getmaxy();
curses.cbreak();
curses.noecho();
curses.printw('Loading...');
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][0];
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][0];
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][1]) {
var l = lines[cursor_line][0];
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col+1);
setLine(b+key+e);
showAt(topLine);
}
break;
default:
if (typeof key === 'string' && key.length == 1) {
var l = lines[cursor_line][0];
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col);
setLine(b+key+e);
cursor_col++;
showAt(topLine);
}
}
}
curses.getch(input);
}
curses.getch(input);
return null;
});
}