zymosis: cosmetix
[iv.d.git] / ttymisc.d
blob47f420272117f34c06f26b5a785cc9e071e5a191
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, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 /**
21 * Read string from TTY with autocompletion.
23 * WARNING! Maximum str length is 64!
25 * Params:
26 * prompt = input prompt
27 * strlist = list of autocompletions
28 * str = initial string value
30 * Returns:
31 * entered string or empty string on cancel
33 * Throws:
34 * Exception on TTY mode errors
36 string ttyReadString (string prompt, const(string)[] strlist=null, string str=string.init) @trusted {
37 import core.sys.posix.unistd : STDOUT_FILENO, write;
38 import std.algorithm : min, max;
39 import iv.rawtty : ttyIsRedirected, ttyGetMode, ttySetMode, ttySetRaw, ttyReadKeyByte, ttyWidth, ttyHeight, TTYMode;
40 import iv.autocomplete : autocomplete;
42 static void wrt (const(char)[] s) @trusted nothrow @nogc {
43 if (s.length) write(STDOUT_FILENO, s.ptr, s.length);
46 static void beep () @trusted nothrow @nogc => wrt("\x07");
48 if (ttyIsRedirected) throw new Exception("TTY is redirected");
49 auto oldMode = ttyGetMode();
50 scope(exit) ttySetMode(oldMode);
51 if (oldMode != TTYMode.RAW) {
52 if (ttySetRaw() == TTYMode.BAD) throw new Exception("can't change TTY mode to raw");
55 uint prevLines = 0; // # of previous written lines in 'tab list'
57 void upPrevLines () {
58 char[32] num;
59 usize pos = num.length;
60 auto n = prevLines;
61 num[--pos] = 'A';
62 do {
63 num[--pos] = cast(char)('0'+n%10);
64 n /= 10;
65 } while (n);
66 num[--pos] = '[';
67 num[--pos] = '\e';
68 wrt(num[pos..$]);
71 // clear prompt and hint lines
72 // return cursor to prompt line
73 void clearHints () {
74 if (prevLines) {
75 wrt("\r\x1b[K");
76 foreach (; 0..prevLines) wrt("\n\r\x1b[K");
77 upPrevLines();
78 prevLines = 0;
82 bool prevWasReturn = false;
83 wrt("\x1b[0m");
84 scope(exit) {
85 clearHints();
86 wrt("\r\x1b[K");
88 for (;;) {
89 int ch;
90 wrt("\r\x1b[0m");
91 wrt(prompt);
92 wrt("\x1b[37;1m");
93 wrt(str);
94 wrt("\x1b[0m\x1b[K");
95 // try to see if we have something to show here
96 if (strlist.length) {
97 auto ac = autocomplete(str, strlist);
98 if (ac.length > 0) {
99 //stdout.writeln("\r\n", ac);
100 auto s = ac[0];
101 s = s[str.length..$];
102 wrt("\x1b[0;1m");
103 wrt(s);
104 foreach (; 0..s.length) wrt("\x08");
105 wrt("\x1b[0m");
108 //stdout.flush();
109 ch = ttyReadKeyByte();
110 if (ch < 0 || ch == 3 || ch == 4) { str = ""; break; } // error, ^C or ^D
111 if (ch == 10) {
112 // return
113 if (strlist.length == 0) break; // we have no autocompletion variants
114 // if there is exactly one full match, return it
115 auto ac = autocomplete(str, strlist);
116 if (ac.length == 1) { str = ac[0]; break; } // the match is ok
117 if (prevWasReturn) break; // this is second return in a row
118 // else do autocomplete
119 ch = 9;
120 if (ac.length) beep(); // 'cause #9 will not beep in this case
121 prevWasReturn = true; // next 'return' will force return
122 } else {
123 // reset 'double return' flag
124 prevWasReturn = false;
126 if (ch == 23 || ch == 25) {
127 // ^W or ^Y
128 str = "";
129 continue;
131 if (ch == 27) {
132 // esc
133 ch = ttyReadKeyByte();
134 if (ch == 27) { str = ""; break; }
135 clearHints();
136 do {
137 version(readstring_debug) {
138 import std.stdio : stdout;
139 stdout.writef("ch: %3d", ch);
140 if (ch >= 32 && ch != 127) stdout.write("'", cast(char)ch, "'");
141 stdout.writeln();
143 if (ch != '[' && ch != ';' && (ch < '0' || ch > '9')) {
144 version(readstring_debug) wrt("DONE!");
145 break;
147 } while ((ch = ttyReadKeyByte(100)) >= 0);
149 if (ch == 8 || ch == 127) {
150 // backspace
151 if (str.length) str = str[0..$-1]; else beep();
152 continue;
154 if (ch == 9) {
155 // tab
156 clearHints();
157 prevLines = 0;
158 auto ac = autocomplete(str, strlist);
159 if (ac.length != 1) beep();
160 if (ac.length) {
161 str = ac[0];
162 if (ac.length > 1) {
163 usize maxlen, rc;
164 ac = ac[1..$];
165 //sort!"a<b"(ac);
166 // calculate maximum item length
167 foreach (immutable s; ac) maxlen = max(maxlen, s.length);
168 ++maxlen; // plus space
169 if (maxlen < ttyWidth) {
170 rc = ttyWidth/maxlen;
171 } else {
172 rc = 1;
174 prevLines = min(ac.length/rc+(ac.length%rc != 0), ttyHeight-1);
175 wrt("\r\n\x1b[1m"); // skip prompt line
176 foreach (immutable i, immutable s; ac) {
177 if (i && i%rc == 0) wrt("\n");
178 wrt(s);
179 wrt(" ");
181 wrt("\x1b[0m");
182 upPrevLines();
185 continue;
187 if (ch > 32 && ch < 127 && str.length < 64) {
188 str ~= ch;
189 continue;
191 beep();
193 return str;