CLIte/clite/vi.js
2023-12-08 08:19:22 +10:00

409 lines
8 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 = '';
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',args[i][j]);
}
}
}else if (file == null) {
file = clite.resolvePath(args[i],false);
}else{
stdio.fprintf(io.stderr,'unknown argument: %s',args[i]);
}
}
if (!stdio.isatty(io.stdin))
return 1;
var cols;
var rows;
var lines = [];
var topLine = 0;
var bottomLine = 0;
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;
if (end > lines.length)
end = lines.length;
curses.clear();
if (num) {
for (var i=start; i<end; i++) {
if (i == cursor_line) {
var cursor = '█';
if (cursor_col == 0) {
curses.printw('% 4d%c%s\n',i+1,cursor,lines[i][0]);
}else{
if (cursor_col > lines[i][0].length)
cursor_col = lines[i][0].length;
var l = lines[i][0];
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col);
curses.printw('% 4d %s%c%s\n',i+1,b,cursor,e);
}
}else{
curses.printw('% 4d %s\n',i+1,lines[i][0]);
}
}
}else{
for (var i=start; i<end; i++) {
if (i == cursor_line) {
var cursor = '█';
if (cursor_col == 0) {
curses.printw('%c%s\n',cursor,lines[i][0]);
}else{
if (cursor_col > lines[i][0].length)
cursor_col = lines[i][0].length;
var l = lines[i][0];
var b = l.substring(0,cursor_col);
var e = l.substring(cursor_col);
curses.printw(' %s%c%s\n',b,cursor,e);
}
}else{
curses.printw(' %s\n',lines[i][0]);
}
}
}
curses.printw('%s\t\t\t\t\t\t%d,%d\n',message,cursor_line,cursor_col);
}
function prepFile(fd) {
if (!fd) {
stdio.write(io.stderr,'could not open file: '+file);
io.exit(1);
return;
}
var st = stdio.fstat(fd);
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');
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:;
}
}
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');
return 1;
}
var fd = stdio.open(file,stdio.flags.O_RDONLY|stdio.flags.O_CREAT,prepFile);
if (!fd) {
stdio.write(io.stderr,'could not open file: '+file);
return 1;
}
curses.initscr();
cols = curses.getmaxx();
rows = curses.getmaxy()-2;
curses.cbreak();
curses.noecho();
curses.printw('Loading...');
function input(key) {
if (mode == 0) { // view mode
//message = '';
switch (key) {
case 'Up':
case 'ArrowUp':
cursorUp();
break;
case 'Down':
case 'ArrowDown':
cursorDn();
break;
case 'Left':
case 'ArrowLeft':
cursorLt();
break;
case 'Right':
case 'ArrowRight':
cursorRt();
break;
case 'i':
curses.noecho();
mode = 2;
message = 'INSERT';
showAt(topLine);
break;
case 'a':
cursor_col = lines[cursor_line][0].length;
curses.noecho();
mode = 2;
message = 'INSERT';
showAt(topLine);
break;
case ':':
curses.echo();
term.ttyctrl('iset',':');
cmd_buff = ':';
mode = 1;
showAt(topLine);
break;
}
}else if (mode == 1) { // command mode
//message = '';
switch (key) {
case 'Escape':
mode = 0;
cmd_buff = '';
curses.noecho();
showAt(topLine);
break;
case 'Enter':
if (!doCommand(cmd_buff))
return;
showAt(topLine);
cmd_buff = '';
term.ttyctrl('iset','');
break;
default:
if (key.length == 1)
cmd_buff+=key;
break;
}
}else{ // 2 edit mode
message = 'INSERT';
switch (key) {
case 'Escape':
mode = 0;
curses.noecho();
message = '';
showAt(topLine);
break;
case 'Up':
case 'ArrowUp':
cursorUp();
break;
case 'Down':
case 'ArrowDown':
cursorDn();
break;
case 'Left':
case 'ArrowLeft':
cursorLt();
break;
case 'Right':
case 'ArrowRight':
cursorRt();
break;
case '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 '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 = e.length;
showAt(topLine);
break;
case 'Delete':
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 (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;
});
}