egra: checkbox fixes
[iv.d.git] / cmdcon / tty.d
blob079222e91cb20be919c350c531d1df0611226792
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module iv.cmdcon.tty /*is aliced*/;
17 private:
19 import iv.alice;
20 public import iv.cmdcon;
21 import iv.vfs;
22 import iv.strex;
23 import iv.rawtty;
26 // ////////////////////////////////////////////////////////////////////////// //
27 // public void glconInit (); -- call in `visibleForTheFirstTime`
28 // public void glconDraw (); -- call in `redrawOpenGlScene` (it tries hard to not modify render modes)
29 // public bool glconKeyEvent (KeyEvent event); -- returns `true` if event was eaten
30 // public bool glconCharEvent (dchar ch); -- returns `true` if event was eaten
32 // public bool conProcessQueue (); (from iv.cmdcon)
33 // call this in your main loop to process all accumulated console commands.
34 // WARNING! this is NOT thread-safe, you MUST call this in your "processing thread", and
35 // you MUST put `consoleLock()/consoleUnlock()` around the call!
37 // ////////////////////////////////////////////////////////////////////////// //
38 public bool isConsoleVisible () nothrow @trusted @nogc { pragma(inline, true); return rConsoleVisible; } ///
39 public bool isQuitRequested () nothrow @trusted @nogc { pragma(inline, true); import core.atomic; return atomicLoad(vquitRequested); } ///
40 public void setQuitRequested () nothrow @trusted @nogc { pragma(inline, true); import core.atomic; atomicStore(vquitRequested, true); } ///
43 // ////////////////////////////////////////////////////////////////////////// //
44 /// you may call this manually, but `ttyconEvent()` will do that for you
45 public void ttyconCharInput (char ch) {
46 if (!ch) return;
47 consoleLock();
48 scope(exit) consoleUnlock();
50 if (ch == ConInputChar.PageUp) {
51 int lnx = rConsoleHeight-2;
52 if (lnx < 1) lnx = 1;
53 conskiplines += lnx;
54 conLastChange = 0;
55 return;
58 if (ch == ConInputChar.PageDown) {
59 if (conskiplines > 0) {
60 int lnx = rConsoleHeight-2;
61 if (lnx < 1) lnx = 1;
62 if ((conskiplines -= lnx) < 0) conskiplines = 0;
63 conLastChange = 0;
65 return;
68 if (ch == ConInputChar.LineUp) {
69 ++conskiplines;
70 conLastChange = 0;
71 return;
74 if (ch == ConInputChar.LineDown) {
75 if (conskiplines > 0) {
76 --conskiplines;
77 conLastChange = 0;
79 return;
82 if (ch == ConInputChar.Enter) {
83 if (conskiplines) { conskiplines = 0; conLastChange = 0; }
84 auto s = conInputBuffer;
85 if (s.length > 0) {
86 concmdAdd(s);
87 conInputBufferClear(true); // add to history
88 conLastChange = 0;
90 return;
93 //if (ch == '`' && conInputBuffer.length == 0) { concmd("r_console ona"); return; }
95 auto pcc = conInputLastChange();
96 conAddInputChar(ch);
97 if (pcc != conInputLastChange()) conLastChange = 0;
101 // ////////////////////////////////////////////////////////////////////////// //
102 __gshared char rPromptChar = '>';
103 __gshared bool rConsoleVisible = false;
104 __gshared int rConsoleHeight = 10;
105 __gshared uint rConTextColor = 0x00ff00; // rgb
106 __gshared uint rConCursorColor = 0xff7f00; // rgb
107 __gshared uint rConInputColor = 0xffff00; // rgb
108 __gshared uint rConPromptColor = 0xffffff; // rgb
109 __gshared uint rConBackColor = 0x000080; // rgb
110 shared bool vquitRequested = false;
111 __gshared char* conOutBuf = null; // tty buffer
112 __gshared uint conOBPos, conOBSize;
113 __gshared int conskiplines = 0;
116 void conOReset () nothrow @trusted @nogc { conOBPos = 0; }
118 void conOFlush () nothrow @trusted @nogc { if (conOBPos) ttyRawWrite(conOutBuf[0..conOBPos]); conOBPos = 0; }
120 void conOPut (const(char)[] s...) nothrow @trusted @nogc {
121 if (s.length == 0) return;
122 if (s.length > 1024*1024) assert(0, "wtf?!");
123 if (conOBPos+s.length >= conOBSize) {
124 import core.stdc.stdlib : realloc;
125 auto nsz = cast(uint)(conOBPos+s.length+8192);
126 //if (nsz < conOBSize) assert(0, "wtf?!");
127 auto nb = realloc(conOutBuf, nsz);
128 if (nb is null) assert(0, "wtf?!");
129 conOutBuf = cast(char*)nb;
131 conOutBuf[conOBPos..conOBPos+s.length] = s[];
132 conOBPos += cast(uint)s.length;
135 void conOInt(T) (T n) nothrow @trusted @nogc if (__traits(isIntegral, T) && !is(T == char) && !is(T == wchar) && !is(T == dchar) && !is(T == bool) && !is(T == enum)) {
136 import core.stdc.stdio : snprintf;
137 char[64] buf = void;
138 static if (__traits(isUnsigned, T)) {
139 static if (T.sizeof > 4) {
140 auto len = snprintf(buf.ptr, buf.length, "%llu", n);
141 } else {
142 auto len = snprintf(buf.ptr, buf.length, "%u", cast(uint)n);
144 } else {
145 static if (T.sizeof > 4) {
146 auto len = snprintf(buf.ptr, buf.length, "%lld", n);
147 } else {
148 auto len = snprintf(buf.ptr, buf.length, "%d", cast(int)n);
151 if (len > 0) conOPut(buf[0..len]);
154 void conOColorFG (uint c) nothrow @trusted @nogc {
155 conOPut("\x1b[38;5;");
156 conOInt(ttyRgb2Color((c>>16)&0xff, (c>>8)&0xff, c&0xff));
157 conOPut("m");
160 void conOColorBG (uint c) nothrow @trusted @nogc {
161 conOPut("\x1b[48;5;");
162 conOInt(ttyRgb2Color((c>>16)&0xff, (c>>8)&0xff, c&0xff));
163 conOPut("m");
167 // initialize glcmdcon variables and commands, sets screen size and scale
168 // NOT THREAD-SAFE! also, should be called only once.
169 private void initConsole () {
170 conRegVar!rConsoleVisible("r_console", "console visibility", ConVarAttr.Archive);
171 conRegVar!rConsoleHeight(2, 4096, "r_conheight", "console height", ConVarAttr.Archive);
172 conRegVar!rConTextColor("r_contextcolor", "console log text color, 0xrrggbb", ConVarAttr.Archive, ConVarAttr.Hex);
173 conRegVar!rConCursorColor("r_concursorcolor", "console cursor color, 0xrrggbb", ConVarAttr.Archive, ConVarAttr.Hex);
174 conRegVar!rConInputColor("r_coninputcolor", "console input color, 0xrrggbb", ConVarAttr.Archive, ConVarAttr.Hex);
175 conRegVar!rConPromptColor("r_conpromptcolor", "console prompt color, 0xrrggbb", ConVarAttr.Archive, ConVarAttr.Hex);
176 conRegVar!rConBackColor("r_conbackcolor", "console background color, 0xrrggbb", ConVarAttr.Archive, ConVarAttr.Hex);
177 conRegVar!rPromptChar("r_conpromptchar", "console prompt character", ConVarAttr.Archive);
178 conRegFunc!({
179 import core.atomic;
180 atomicStore(vquitRequested, true);
181 })("quit", "quit");
184 shared static this () { initConsole(); }
187 // ////////////////////////////////////////////////////////////////////////// //
188 __gshared bool lastWasVisible = false;
189 __gshared int lastConHgt = 0;
190 //__gshared ConDump conoldcdump;
193 /// initialize ttycmdcon. it is ok to call it repeatedly.
194 /// NOT THREAD-SAFE!
195 public void ttyconInit () {
196 // oops
200 /// render console (if it is visible)
201 public void ttyconDraw () {
202 consoleLock();
203 scope(exit) consoleUnlock();
205 auto w = ttyWidth;
206 auto h = ttyHeight;
207 if (w < 4 || h < 2) return;
209 if (!rConsoleVisible) {
210 //conDump = conoldcdump;
211 if (lastWasVisible) {
212 conOReset();
214 conOPut("\x1b7");
215 scope(exit) conOPut("\x1b8");
216 conOPut("\x1b[1;1H\x1b[0m");
217 if (lastConHgt > h) lastConHgt = h;
218 while (lastConHgt-- > 0) {
219 if (lastConHgt) conOPut("\r\x1b[B");
220 conOPut("\x1b[K");
223 conOFlush();
225 return;
228 //if (!lastWasVisible || conDump != ConDump.none) conoldcdump = conDump;
229 //conDump = ConDump.none;
231 lastWasVisible = true;
232 lastConHgt = rConsoleHeight;
233 conLastChange = cbufLastChange+1;
235 conOReset();
236 renderConsole();
237 conOFlush();
241 // ////////////////////////////////////////////////////////////////////////// //
242 __gshared uint conLastChange = 0;
243 __gshared uint conLastIBChange = 0;
244 __gshared int prevCurX = -1;
245 __gshared int prevIXOfs = 0;
248 bool renderConsole () nothrow @trusted @nogc {
249 if (conLastChange == cbufLastChange && conLastIBChange == conInputLastChange) return false;
251 immutable sw = ttyWidth, sh = ttyHeight;
252 int skipLines = conskiplines;
253 conLastChange = cbufLastChange;
254 conLastIBChange = conInputLastChange;
256 int y = rConsoleHeight;
257 if (y < 1) y = 1;
258 if (y > sh) y = sh;
260 conOPut("\x1b7");
261 scope(exit) conOPut("\x1b8");
263 conOPut("\x1b[");
264 conOInt(y);
265 conOPut(";1H\x1b[0m");
267 conOColorBG(rConBackColor);
269 auto concli = conInputBuffer;
270 int conclilen = cast(int)concli.length;
271 int concurx = conInputBufferCurX();
273 // draw command line
275 int charsInLine = sw-1; // reserve room for cursor
276 if (rPromptChar >= ' ') --charsInLine;
277 if (charsInLine < 2) charsInLine = 2; // just in case
278 int stpos = prevIXOfs;
279 if (concurx == conclilen) {
280 stpos = conclilen-charsInLine;
281 prevCurX = concurx;
282 } else if (prevCurX != concurx) {
283 // cursor position changed, fix x offset
284 if (concurx <= prevIXOfs) {
285 stpos = concurx-1;
286 } else if (concurx-prevIXOfs >= charsInLine-1) {
287 stpos = concurx-charsInLine+1;
290 if (stpos < 0) stpos = 0;
291 prevCurX = concurx;
292 prevIXOfs = stpos;
294 if (rPromptChar >= ' ') {
295 conOColorFG(rConPromptColor);
296 conOPut(rPromptChar);
298 conOColorFG(rConInputColor);
299 foreach (int pos; stpos..stpos+charsInLine+1) {
300 if (pos == concurx) {
301 conOColorBG(rConCursorColor);
302 conOColorFG(0);
303 conOPut(" ");
304 conOColorBG(rConBackColor);
305 conOColorFG(rConInputColor);
307 if (pos >= 0 && pos < conclilen) conOPut(concli.ptr[pos]);
309 conOPut("\x1b[K");
310 y -= 1;
313 // draw console text
314 conOColorFG(rConTextColor);
315 conOPut("\x1b[K\r\x1b[A");
317 void putLine(T) (auto ref T line, usize pos=0) {
318 if (y < 1 || pos >= line.length) return;
319 int w = 0, lastWordW = -1;
320 usize sp = pos, lastWordEnd = 0;
321 while (sp < line.length) {
322 char ch = line[sp++];
323 // remember last word position
324 if (/*lastWordW < 0 &&*/ (ch == ' ' || ch == '\t')) {
325 lastWordEnd = sp-1; // space will be put on next line (rough indication of line wrapping)
326 lastWordW = w;
328 if ((w += 1) > sw) {
329 w -= 1;
330 --sp;
331 // current char is non-space, and, previous char is non-space, and we have a complete word?
332 if (lastWordW > 0 && ch != ' ' && ch != '\t' && sp > pos && line[sp-1] != ' ' && line[sp-1] != '\t') {
333 // yes, split on last word boundary
334 sp = lastWordEnd;
336 break;
339 if (sp < line.length) putLine(line, sp); // recursive put tail
340 // draw line
341 if (skipLines-- <= 0) {
342 while (pos < sp) conOPut(line[pos++]);
343 y -= 1;
344 if (y >= 1) conOPut("\x1b[K\r\x1b[A");
349 consoleWriteLock();
350 scope(exit) consoleWriteUnlock();
351 foreach (/*auto*/ line; conbufLinesRev) {
352 putLine(line);
353 if (y <= 1) break;
356 while (y >= 1) {
357 conOPut("\x1b[K\r\x1b[A");
358 --y;
361 return true;
365 // ////////////////////////////////////////////////////////////////////////// //
366 public __gshared char ttyconShowKey = '`'; /// this key will be eaten
369 /// process tty event. returns `true` if event was eaten.
370 public bool ttyconEvent (TtyEvent event) {
371 if (!rConsoleVisible) {
372 if (event.key == TtyEvent.Key.Char && event.ch == ttyconShowKey) {
373 concmd("r_console 1");
374 return true;
376 return false;
379 // console is visible
380 if (event.key == TtyEvent.Key.Char && event.ch == ttyconShowKey) {
381 if (conInputBuffer.length == 0) concmd("r_console 0");
382 return true;
385 if (event.key == TtyEvent.Key.Char) {
386 if (event == "C-W") { ttyconCharInput(ConInputChar.CtrlW); return true; }
387 if (event == "C-Y") { ttyconCharInput(ConInputChar.CtrlY); return true; }
388 if (event.ch >= ' ' && event.ch < 127) ttyconCharInput(cast(char)event.ch);
389 return true;
392 if (event.key == TtyEvent.Key.Escape) { concmd("r_console 0"); return true; }
393 switch (event.key) {
394 case TtyEvent.Key.Up:
395 //if (event.modifierState&ModifierState.alt) ttyconCharInput(ConInputChar.LineUp); else ttyconCharInput(ConInputChar.Up);
396 ttyconCharInput(ConInputChar.Up);
397 return true;
398 case TtyEvent.Key.Down:
399 //if (event.modifierState&ModifierState.alt) ttyconCharInput(ConInputChar.LineDown); else ttyconCharInput(ConInputChar.Down);
400 ttyconCharInput(ConInputChar.Down);
401 return true;
402 case TtyEvent.Key.Left: ttyconCharInput(ConInputChar.Left); return true;
403 case TtyEvent.Key.Right: ttyconCharInput(ConInputChar.Right); return true;
404 case TtyEvent.Key.Home: ttyconCharInput(ConInputChar.Home); return true;
405 case TtyEvent.Key.End: ttyconCharInput(ConInputChar.End); return true;
406 case TtyEvent.Key.PageUp:
407 //if (event.modifierState&ModifierState.alt) ttyconCharInput(ConInputChar.LineUp); else ttyconCharInput(ConInputChar.PageUp);
408 ttyconCharInput(ConInputChar.PageUp);
409 return true;
410 case TtyEvent.Key.PageDown:
411 //if (event.modifierState&ModifierState.alt) ttyconCharInput(ConInputChar.LineDown); else ttyconCharInput(ConInputChar.PageDown);
412 ttyconCharInput(ConInputChar.PageDown);
413 return true;
414 case TtyEvent.Key.Backspace: ttyconCharInput(ConInputChar.Backspace); return true;
415 case TtyEvent.Key.Tab: ttyconCharInput(ConInputChar.Tab); return true;
416 case TtyEvent.Key.Enter: ttyconCharInput(ConInputChar.Enter); return true;
417 case TtyEvent.Key.Delete: ttyconCharInput(ConInputChar.Delete); return true;
418 case TtyEvent.Key.Insert: ttyconCharInput(ConInputChar.Insert); return true;
419 //case TtyEvent.Key.W: if (event.modifierState&ModifierState.ctrl) ttyconCharInput(ConInputChar.CtrlW); return true;
420 //case TtyEvent.Key.Y: if (event.modifierState&ModifierState.ctrl) ttyconCharInput(ConInputChar.CtrlY); return true;
421 default:
423 return true;