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/>.
20 * Read string from TTY with autocompletion.
22 * WARNING! Maximum str length is 64!
25 * prompt = input prompt
26 * strlist = list of autocompletions
27 * str = initial string value
30 * entered string or empty string on cancel
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'
58 usize pos
= num
.length
;
62 num
[--pos
] = cast(char)('0'+n
%10);
70 // clear prompt and hint lines
71 // return cursor to prompt line
75 foreach (; 0..prevLines
) wrt("\n\r\x1b[K");
81 bool prevWasReturn
= false;
94 // try to see if we have something to show here
96 auto ac
= autocomplete(str, strlist
);
98 //stdout.writeln("\r\n", ac);
100 s
= s
[str.length
..$];
103 foreach (; 0..s
.length
) wrt("\x08");
108 ch
= ttyReadKeyByte();
109 if (ch
< 0 || ch
== 3 || ch
== 4) { str = ""; break; } // error, ^C or ^D
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
119 if (ac
.length
) beep(); // 'cause #9 will not beep in this case
120 prevWasReturn
= true; // next 'return' will force return
122 // reset 'double return' flag
123 prevWasReturn
= false;
125 if (ch
== 23 || ch
== 25) {
132 ch
= ttyReadKeyByte();
133 if (ch
== 27) { str = ""; break; }
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
, "'");
142 if (ch
!= '[' && ch
!= ';' && (ch
< '0' || ch
> '9')) {
143 version(readstring_debug
) wrt("DONE!");
146 } while ((ch
= ttyReadKeyByte(100)) >= 0);
148 if (ch
== 8 || ch
== 127) {
150 if (str.length
) str = str[0..$-1]; else beep();
157 auto ac
= autocomplete(str, strlist
);
158 if (ac
.length
!= 1) beep();
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
;
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");
186 if (ch
> 32 && ch
< 127 && str.length
< 64) {