mirror of
https://codeberg.org/TicklishHoneyBee/CLIte.git
synced 2026-03-11 09:04:37 +00:00
537 lines
10 KiB
JavaScript
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);
|
|
});
|
|
|
|
}
|