libcurses (ncurses) starter, and switch less to using it

This commit is contained in:
Lisa Milne 2023-11-28 22:33:20 +10:00
parent cb5de9ed6d
commit 0fe4544107
7 changed files with 282 additions and 112 deletions

View file

@ -322,6 +322,7 @@ clite.commands.load('less',function(args,env,io) {
var stdlib = io.include('stdlib');
var stdio = io.include('stdio');
var term = io.include('term');
var curses = io.include('curses');
var clite = io.include('clite');
var file = null;
@ -354,18 +355,20 @@ Options:
}
}
var cols;
var rows;
var lines = [];
var topLine = 0;
var bottomLine = 0;
function showAt(line) {
var start = line;
var end = line+tty.lines;
var end = line+rows;
if (end > lines.length)
end = lines.length;
tty.clear();
curses.clear();
for (var i=start; i<end; i++) {
tty.draw(lines[i]+'\n');
curses.printw(lines[i]+'\n');
}
}
@ -386,15 +389,15 @@ Options:
var parts = txt.split('\n');
parts.forEach(function(line) {
if (line.length > tty.chars) {
var sects = line.match(new RegExp('(.{1,'+tty.chars+'})','g'));
if (line.length > cols) {
var sects = line.match(new RegExp('(.{1,'+cols+'})','g'));
lines.push.apply(lines,sects);
return;
}
lines.push(line);
});
bottomLine = (lines.length-tty.lines);
bottomLine = (lines.length-rows);
showAt(0);
}
@ -415,15 +418,19 @@ Options:
}
}
var tty = term.opentty();
tty.setRaw(false);
curses.initscr();
cols = curses.getmaxx();
rows = curses.getmaxy();
curses.cbreak();
curses.printw('Loading...');
tty.handleKeys(function(e) {
switch (e.key) {
function input(key) {
switch (key) {
case 'Escape':
case 'q':
term.closetty(tty);
curses.endwin();
io.exit(0);
return;
break;
case 'Up':
case 'ArrowUp':
@ -441,7 +448,7 @@ Options:
break;
case 'PageUp':
if (topLine > 0) {
topLine -= tty.lines-1;
topLine -= rows-1;
if (topLine < 0)
topLine = 0;
showAt(topLine);
@ -449,7 +456,7 @@ Options:
break;
case 'PageDown':
if (topLine < bottomLine) {
topLine += tty.lines-1;
topLine += rows-1;
if (topLine >= bottomLine)
topLine = bottomLine;
showAt(topLine);
@ -464,8 +471,10 @@ Options:
showAt(topLine);
break;
}
});
tty.draw('Loading...');
curses.getch(input);
}
curses.getch(input);
return null;
});

View file

@ -7,7 +7,7 @@ header h1 {line-height:40px; font-size:30px;}
section {}
section.content {position:relative; overflow:hidden;}
section.content div#terminal {width:100%; min-width: 500px; max-width:1000px; max-height:100%; font-size:14px; line-height:20px; margin:0 auto; overflow:hidden; overflow-y:auto; scrollbar-width:none;}
section.content div#tty {width:100%; min-width: 500px; max-width:1000px; margin:0 auto; overflow:hidden; overflow-y:auto; scrollbar-width:none; z-index:99; background:#000000;}
section.content div#termalt {width:100%; min-width: 500px; max-width:1000px; margin:0 auto; overflow:hidden; overflow-y:auto; scrollbar-width:none; z-index:99; background:#000000;}
section.content article {unicode-bidi: embed; white-space: pre-wrap;}
section.content form {display:flex;}
section.content form label, section.content form input, section.content form input:focus {display:block; border:none; margin:0; padding:0; font-family:monospace; font-size:14px; line-height:20px; background-color:#000000; color:#FFFFFF; outline:none;}

View file

@ -433,6 +433,11 @@ readme.txt:/usr/clite/readme:0:0:-rw-r--r--`;
}
function init8(data) {
clite.core.execSafe(clite.libs.data);
clite.core.load.script('clite/libcurses.js',init9);
}
function init9(data) {
clite.core.execSafe(clite.libs.data);
clite.libs.data = null;
clite.libs.load = null;
@ -466,10 +471,10 @@ readme.txt:/usr/clite/readme:0:0:-rw-r--r--`;
exit:null, // filled in by fork()
include:null, // filled in by fork()
};
var pid = clite.lib.fork(env,io,init9);
var pid = clite.lib.fork(env,io,init10);
}
}
function init9(env,io) {
function init10(env,io) {
clite.proc.setLogin(io.pid,1);
clite.lib.exec('/bin/sh',['sh'],env,io);
}
@ -1789,12 +1794,19 @@ clite.log = {
clite.term = {
data:{
type:'text',
show:true,
show:false,
isalt:false,
form:null,
field:null,
handler:null,
prompt:'',
buff:''
buff:'',
alt:{
israwOut:false,
israwIn:false,
lines:0,
cols:0
}
},
events:{
keydown:function(e) {
@ -1805,8 +1817,13 @@ clite.term = {
return true;
},
keyup:function(e) {
if (clite.term.tty.data != null)
return clite.term.tty.data.data.onkeypress(e);
if (clite.term.data.isalt && clite.term.data.alt.israwIn) {
if (clite.term.data.handler != null)
clite.term.data.handler(e.key);
e.target.value = '';
clite.term.data.buff = '';
return false;
}
if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) {
if (e.key == 'Enter') {
clite.term.data.buff = '';
@ -1829,23 +1846,76 @@ clite.term = {
});
}
},
tty:{
data:null
},
clear:function() {
if (clite.term.data.isalt) {
document.getElementById('termalt').innerHTML = '';
return;
}
document.getElementById('terminal').innerHTML = '';
},
writeLine:function(txt) {
if (clite.term.data.isalt) {
var t = document.getElementById('termalt');
if (!t)
t = clite.term.genAlt();
if (clite.term.data.alt.israwOut) {
t.innerHTML += txt;
if (clite.term.data.show)
clite.term.genForm();
return;
}
var a = document.createElement('article');
a.innerHTML = clite.lib.htmlEncode(txt);
t.appendChild(a);
if (clite.term.data.show)
clite.term.genForm();
return;
}
var a = document.createElement('article');
a.innerHTML = clite.lib.htmlEncode(txt);
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(a);
if (document.getElementById('form'))
if (clite.term.data.show)
clite.term.genForm();
t.scrollTop = t.scrollHeight;
},
genAlt:function() {
var t = document.getElementById('termalt');
if (!t) {
t = document.createElement('div');
t.id = 'termalt';
}
var term = document.getElementById('terminal');
var p = term.parentNode;
t.style.width = term.offsetWidth+'px';
if (p.offsetHeight > term.offsetHeight) {
t.style.minHeight = p.offsetHeight+'px';
}else{
t.style.minHeight = term.offsetHeight+'px';
}
t.style.position = 'absolute';
t.style.top = term.offsetTop+'px';
t.style.left = term.offsetLeft+'px';
p.appendChild(t);
// some calculations to help with putting content in
var s = document.createElement('span');
s.innerText = 'W';
t.appendChild(s);
// 20 should not be hardcoded
clite.term.data.alt.lines = parseInt((t.offsetHeight/20)-1);
// TODO: shouldn't need the 0.8 thing, but urgh
clite.term.data.alt.cols = parseInt((t.offsetWidth/s.offsetWidth)*0.8);
t.removeChild(s);
clite.term.data.buff = '';
return t;
},
genForm:function() {
var f = document.getElementById('form');
if (!clite.term.data.show) {
@ -1857,19 +1927,28 @@ clite.term = {
f = document.createElement('form');
f.id = 'form';
f.innerHTML = '';
var l = document.createElement('label');
l.innerHTML = clite.lib.htmlEncode(clite.term.data.prompt);
f.appendChild(l);
if (!clite.term.isalt) {
var l = document.createElement('label');
l.innerHTML = clite.lib.htmlEncode(clite.term.data.prompt);
f.appendChild(l);
}
var i = document.createElement('input');
i.type = clite.term.getType();
i.value = clite.term.data.buff;
f.appendChild(i);
clite.term.data.form = f;
clite.term.data.field = i;
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(f);
if (clite.term.data.isalt) {
var t = document.getElementById('termalt');
if (!t)
t = clite.term.genAlt();
t.appendChild(f);
}else{
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(f);
}
i.onkeydown = clite.term.events.keydown;
i.onkeyup = clite.term.events.keyup;
i.onfocusout = clite.term.events.refocus;
@ -1893,6 +1972,7 @@ clite.term = {
}
}
f.onsubmit = function() {return false;};
clite.term.events.refocus();
},
setPass:function(is) {
clite.term.data.type = (!!is) ? 'password' : 'text';
@ -1944,78 +2024,45 @@ clite.term = {
clite.term.data.show = true;
clite.term.genForm();
break;
case 'alt': // switch to the alternate frame buffer
if (typeof v === 'boolean') {
clite.term.data.isalt = v;
}
if (!clite.term.data.isalt) {
var t = document.getElementById('termalt');
if (t)
t.parentNode.removeChild(t);
}else{
clite.term.genAlt();
}
clite.term.genForm();
return clite.term.data.isalt;
break;
case 'rawout':
if (!clite.term.data.isalt)
return false;
if (typeof v === 'boolean') {
clite.term.data.alt.israwOut = v;
}
return clite.term.data.alt.israwOut;
break;
case 'rawin':
if (!clite.term.data.isalt)
return false;
if (typeof v === 'boolean') {
clite.term.data.alt.israwIn = v;
}
return clite.term.data.alt.israwIn;
break;
case 'atton':
break;
case 'attoff':
break;
case 'echo':
break;
default:
return false;
}
},
api:{
// gets an empty element with key events (allows view/less to display content in a clean element)
opentty:function() {
var tty = {
handleKeys:function(cb) {
this.data.cb = cb;
},
draw:function(data) {
if (typeof data == 'string') {
this.data.el.innerHTML += clite.lib.htmlEncode(data);
return true;
}else if (data instanceof HTMLImageElement) {
this.data.el.appendChild(data);
return true;
}
return false;
},
clear:function() {
this.data.el.innerHTML = '';
},
setRaw:function(r) {
this.data.el.style.whiteSpace = ((!!r) ? 'normal' : 'pre-wrap');
},
data:{
cb:null,
el:null,
onkeypress:function(e) {
try{
return this.cb(e);
}catch(err) {}
}
}
};
clite.term.tty.data = tty;
tty.data.el = document.createElement('div');
tty.data.el.id = 'tty';
tty.data.el.onkeypress = tty.data.onkeypress;
var term = document.getElementById('terminal');
var p = term.parentNode;
tty.data.el.style.width = term.offsetWidth+'px';
if (p.offsetHeight > term.offsetHeight) {
tty.data.el.style.minHeight = p.offsetHeight+'px';
}else{
tty.data.el.style.minHeight = term.offsetHeight+'px';
}
tty.data.el.style.position = 'absolute';
tty.data.el.style.top = term.offsetTop+'px';
tty.data.el.style.left = term.offsetLeft+'px';
p.appendChild(tty.data.el);
// some calculations to help with putting content in
var s = document.createElement('span');
s.innerText = 'W';
tty.data.el.appendChild(s);
// 20 should not be hardcoded
tty.lines = parseInt((tty.data.el.offsetHeight/20)-1);
// TODO: shouldn't need the 0.8 thing, but urgh
tty.chars = parseInt((tty.data.el.offsetWidth/s.offsetWidth)*0.8);
tty.data.el.removeChild(s);
clite.term.events.refocus();
return tty;
},
closetty:function(tty) {
try{
tty.data.el.parentNode.removeChild(tty.data.el);
clite.term.tty.data = null;
delete tty;
}catch(err){}
}
}
};
@ -2144,7 +2191,5 @@ clite.lib = {
exit:function(pid,v) {
if (pid > -1)
clite.proc.exit(pid);
if (clite.term.tty.data != null)
clite.term.api.closetty(clite.term.tty.data);
}
}

View file

@ -97,6 +97,18 @@ return Object.create({
parts.push(s);
// TODO: resolve all paths passed as arguments? - catches ~ and * etc
return parts;
},
ptrnew:function() {
return Object.create({value:null});
},
ptrset:function(ptr,val) {
ptr.value = val;
},
ptrget:function(ptr) {
if (typeof ptr.value === 'undefined')
return null;
return ptr.value;
}
});

108
clite/libcurses.js Normal file
View file

@ -0,0 +1,108 @@
clite.libs.data = function() {
clite.libs.load('libcurses','curses',function(io,env) {
return Object.create({
isinit:false,
types:{
A_NULL:0,
A_BOLD:1,
},
attmap:function(t) {
switch (t) {
case this.types.A_BOLD:
return 'bold';
break;
}
return null;
},
initscr:function() {
// curses only works on a tty
if (!clite.io.isatty(io.pid,io.stdin))
return false;
this.isinit = clite.term.ttyctrl('alt',true);
},
endwin:function() {
if (!this.isinit)
return;
clite.term.ttyctrl('rawin',false);
clite.term.ttyctrl('rawout',false);
this.isinit = clite.term.ttyctrl('alt',false);
},
cbreak:function() {
if (!this.isinit)
return;
clite.term.ttyctrl('rawin',true);
},
clear:function() {
if (!this.isinit)
return;
clite.term.clear();
},
noecho:function() {
if (!this.isinit)
return;
clite.term.ttyctrl('echo',false);
},
printw:function(fmt) {
if (!this.isinit)
return;
var args = Array.from(arguments);
args.shift();
clite.io.vfprintf(io.pid,io.stdout,fmt,args);
},
getch:function(cb) {
if (!this.isinit)
return false;
return clite.io.read(io.pid,io.stdin,cb);
},
refresh:function() {
// no op, drawing isn't queued
},
attron:function(a) {
if (!this.isinit)
return;
a = this.attmap(a);
clite.term.ttyctrl('atton',a);
},
attroff:function(a) {
if (!this.isinit)
return;
a = this.attmap(a);
clite.term.ttyctrl('attoff',a);
},
getmaxx:function() {
if (!this.isinit)
return 0;
return clite.term.data.alt.cols;
},
getmaxy:function() {
if (!this.isinit)
return 0;
return clite.term.data.alt.lines;
},
getmaxyx:function(ptry,ptrx) {
if (!this.isinit)
return false;
ptrx.value = clite.term.data.alt.cols;
ptry.value = clite.term.data.alt.lines;
return true;
}
});
});
};

View file

@ -11,14 +11,6 @@ return Object.create({
ttyctrl:function(fn,v) {
return clite.term.ttyctrl(fn,v);
},
// gets an empty element with key events (allows view/less to display content in a clean element)
opentty:function() {
return clite.term.api.opentty();
},
closetty:function(tty) {
return clite.term.api.closetty(tty);
}
});

View file

@ -367,6 +367,10 @@ clite.commands.load('sh',function(args,env,io) {
}
// set the prompt
term.ttyctrl('prompt',prompt);
// ensure input is enabled
term.ttyctrl('show');
// and that we're on the main framebuffer
term.ttyctrl('alt',false);
// then read from stdin
stdio.read(io.stdin,input);
}