egra: moved "disabled" and "accept/cancel" properties to the base widget class
[iv.d.git] / ttymisc.d
blob3c3179ddfc260a90ebd05ed06e3687ad878a2701
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 /**
20 * Read string from TTY with autocompletion.
22 * WARNING! Maximum str length is 64!
24 * Params:
25 * prompt = input prompt
26 * strlist = list of autocompletions
27 * str = initial string value
29 * Returns:
30 * entered string or empty string on cancel
32 * Throws:
33 * Exception on TTY mode errors
35 string ttyReadString (string prompt, const(string)[] strlist=null, string str=string.init) @trusted {
36 import core.sys.posix.unistd : STDOUT_FILENO, write;
37 import std.algorithm : min, max;
38 import iv.rawtty : ttyIsRedirected, ttyGetMode, ttySetMode, ttySetRaw, ttyReadKeyByte, ttyWidth, ttyHeight, TTYMode;
39 import iv.autocomplete : autocomplete;
41 static void wrt (const(char)[] s) @trusted nothrow @nogc {
42 if (s.length) write(STDOUT_FILENO, s.ptr, s.length);
45 static void beep () @trusted nothrow @nogc => wrt("\x07");
47 if (ttyIsRedirected) throw new Exception("TTY is redirected");
48 auto oldMode = ttyGetMode();
49 scope(exit) ttySetMode(oldMode);
50 if (oldMode != TTYMode.RAW) {
51 if (ttySetRaw() == TTYMode.BAD) throw new Exception("can't change TTY mode to raw");
54 uint prevLines = 0; // # of previous written lines in 'tab list'
56 void upPrevLines () {
57 char[32] num;
58 usize pos = num.length;
59 auto n = prevLines;
60 num[--pos] = 'A';
61 do {
62 num[--pos] = cast(char)('0'+n%10);
63 n /= 10;
64 } while (n);
65 num[--pos] = '[';
66 num[--pos] = '\e';
67 wrt(num[pos..$]);
70 // clear prompt and hint lines
71 // return cursor to prompt line
72 void clearHints () {
73 if (prevLines) {
74 wrt("\r\x1b[K");
75 foreach (; 0..prevLines) wrt("\n\r\x1b[K");
76 upPrevLines();
77 prevLines = 0;
81 bool prevWasReturn = false;
82 wrt("\x1b[0m");
83 scope(exit) {
84 clearHints();
85 wrt("\r\x1b[K");
87 for (;;) {
88 int ch;
89 wrt("\r\x1b[0m");
90 wrt(prompt);
91 wrt("\x1b[37;1m");
92 wrt(str);
93 wrt("\x1b[0m\x1b[K");
94 // try to see if we have something to show here
95 if (strlist.length) {
96 auto ac = autocomplete(str, strlist);
97 if (ac.length > 0) {
98 //stdout.writeln("\r\n", ac);
99 auto s = ac[0];
100 s = s[str.length..$];
101 wrt("\x1b[0;1m");
102 wrt(s);
103 foreach (; 0..s.length) wrt("\x08");
104 wrt("\x1b[0m");
107 //stdout.flush();
108 ch = ttyReadKeyByte();
109 if (ch < 0 || ch == 3 || ch == 4) { str = ""; break; } // error, ^C or ^D
110 if (ch == 10) {
111 // return
112 if (strlist.length == 0) break; // we have no autocompletion variants
113 // if there is exactly one full match, return it
114 auto ac = autocomplete(str, strlist);
115 if (ac.length == 1) { str = ac[0]; break; } // the match is ok
116 if (prevWasReturn) break; // this is second return in a row
117 // else do autocomplete
118 ch = 9;
119 if (ac.length) beep(); // 'cause #9 will not beep in this case
120 prevWasReturn = true; // next 'return' will force return
121 } else {
122 // reset 'double return' flag
123 prevWasReturn = false;
125 if (ch == 23 || ch == 25) {
126 // ^W or ^Y
127 str = "";
128 continue;
130 if (ch == 27) {
131 // esc
132 ch = ttyReadKeyByte();
133 if (ch == 27) { str = ""; break; }
134 clearHints();
135 do {
136 version(readstring_debug) {
137 import std.stdio : stdout;
138 stdout.writef("ch: %3d", ch);
139 if (ch >= 32 && ch != 127) stdout.write("'", cast(char)ch, "'");
140 stdout.writeln();
142 if (ch != '[' && ch != ';' && (ch < '0' || ch > '9')) {
143 version(readstring_debug) wrt("DONE!");
144 break;
146 } while ((ch = ttyReadKeyByte(100)) >= 0);
148 if (ch == 8 || ch == 127) {
149 // backspace
150 if (str.length) str = str[0..$-1]; else beep();
151 continue;
153 if (ch == 9) {
154 // tab
155 clearHints();
156 prevLines = 0;
157 auto ac = autocomplete(str, strlist);
158 if (ac.length != 1) beep();
159 if (ac.length) {
160 str = ac[0];
161 if (ac.length > 1) {
162 usize maxlen, rc;
163 ac = ac[1..$];
164 //sort!"a<b"(ac);
165 // calculate maximum item length
166 foreach (immutable s; ac) maxlen = max(maxlen, s.length);
167 ++maxlen; // plus space
168 if (maxlen < ttyWidth) {
169 rc = ttyWidth/maxlen;
170 } else {
171 rc = 1;
173 prevLines = min(ac.length/rc+(ac.length%rc != 0), ttyHeight-1);
174 wrt("\r\n\x1b[1m"); // skip prompt line
175 foreach (immutable i, immutable s; ac) {
176 if (i && i%rc == 0) wrt("\n");
177 wrt(s);
178 wrt(" ");
180 wrt("\x1b[0m");
181 upPrevLines();
184 continue;
186 if (ch > 32 && ch < 127 && str.length < 64) {
187 str ~= ch;
188 continue;
190 beep();
192 return str;