OILS / vendor / souffle / profile / UserInputReader.h View on Github | oilshell.org

329 lines, 236 significant
1/*
2 * Souffle - A Datalog Compiler
3 * Copyright (c) 2017, The Souffle Developers. All rights reserved
4 * Licensed under the Universal Permissive License v 1.0 as shown at:
5 * - https://opensource.org/licenses/UPL
6 * - <souffle root>/licenses/SOUFFLE-UPL.txt
7 */
8
9#pragma once
10
11#include <iostream> // for operator<<, flush, basic_ostream, cout, ostream
12#include <iterator> // for end, begin
13#include <string>
14#include <vector>
15
16#ifndef _MSC_VER
17#include <termios.h> // for termios, tcsetattr, tcgetattr, ECHO, ICANON, cc_t
18#include <unistd.h> // for read
19#endif
20
21namespace souffle {
22namespace profile {
23
24/*
25 * A class that reads user input a char at a time allowing for tab completion and history
26 */
27class InputReader {
28private:
29 std::string prompt = "Input: ";
30 std::vector<std::string> tab_completion;
31 std::vector<std::string> history;
32 std::string output;
33 char current_char = 0;
34 std::size_t cursor_pos = 0;
35 std::size_t hist_pos = 0;
36 std::size_t tab_pos = 0;
37 bool in_tab_complete = false;
38 bool in_history = false;
39 std::string original_hist_val;
40 std::string current_hist_val;
41 std::string current_tab_val;
42 std::string original_tab_val;
43 std::vector<std::string> current_tab_completes;
44 std::size_t original_hist_cursor_pos = 0;
45 bool inputReceived = false;
46
47public:
48 InputReader() {
49 clearTabCompletion();
50 clearHistory();
51 }
52
53 InputReader(const InputReader& other) = default;
54
55 bool hasReceivedInput() {
56 return inputReceived;
57 }
58
59 void readchar() {
60#ifndef _MSC_VER
61 char buf = 0;
62 struct termios old = {};
63 if (tcgetattr(0, &old) < 0) {
64 perror("tcsetattr()");
65 }
66 old.c_lflag &= ~ICANON;
67 old.c_lflag &= ~ECHO;
68 old.c_cc[VMIN] = 1;
69 old.c_cc[VTIME] = 0;
70 if (tcsetattr(0, TCSANOW, &old) < 0) {
71 perror("tcsetattr ICANON");
72 }
73 if (::read(0, &buf, 1) < 0) {
74 perror("read()");
75 }
76 old.c_lflag |= ICANON;
77 old.c_lflag |= ECHO;
78 if (tcsetattr(0, TCSADRAIN, &old) < 0) {
79 perror("tcsetattr ~ICANON");
80 }
81
82 current_char = buf;
83#else
84 char buf = 0;
85 if (_read(0, &buf, 1) < 0) {
86 perror("read()");
87 }
88 current_char = buf;
89#endif
90 }
91 std::string getInput() {
92 output = "";
93 current_char = 0;
94 cursor_pos = 0;
95 hist_pos = 0;
96 tab_pos = 0;
97 in_tab_complete = false;
98 in_history = false;
99
100 std::cout << this->prompt << std::flush;
101 readchar();
102 inputReceived = true;
103
104 bool escaped = false;
105 bool arrow_key = false;
106
107 while (current_char != '\n') {
108 if (arrow_key) {
109 moveCursor(current_char);
110 escaped = false;
111 arrow_key = false;
112 } else if (escaped) {
113 if (current_char == '[') {
114 arrow_key = true;
115 }
116 } else {
117 if (current_char == 27) { // esc char for arrow keys
118 escaped = true;
119 } else if (current_char == '\t') {
120 tabComplete();
121 } else {
122 if (in_history) {
123 output = current_hist_val;
124 in_history = false;
125
126 } else if (in_tab_complete) {
127 output = current_tab_val;
128 in_tab_complete = false;
129 }
130
131 if (current_char == 127) {
132 backspace();
133 } else {
134 output.insert(output.begin() + (cursor_pos++), current_char);
135 std::cout << current_char << std::flush;
136 showFullText(output);
137 }
138 }
139 }
140
141 readchar();
142 }
143
144 if (in_history) {
145 return current_hist_val;
146 }
147 if (in_tab_complete) {
148 return current_tab_val;
149 }
150
151 return output;
152 }
153
154 void setPrompt(std::string prompt) {
155 this->prompt = prompt;
156 }
157 void appendTabCompletion(std::vector<std::string> commands) {
158 tab_completion.insert(std::end(tab_completion), std::begin(commands), std::end(commands));
159 }
160 void appendTabCompletion(std::string command) {
161 tab_completion.push_back(command);
162 }
163 void clearTabCompletion() {
164 tab_completion = std::vector<std::string>();
165 }
166 void clearHistory() {
167 history = std::vector<std::string>();
168 }
169 void tabComplete() {
170 if (in_history) {
171 output = current_hist_val;
172 in_history = false;
173 }
174 if (!in_tab_complete) {
175 current_tab_completes = std::vector<std::string>();
176 original_tab_val = output;
177 bool found_tab = false;
178 for (auto& a : tab_completion) {
179 if (a.find(original_tab_val) == 0) {
180 current_tab_completes.push_back(a);
181 found_tab = true;
182 }
183 }
184 if (!found_tab) {
185 std::cout << '\a' << std::flush;
186 return;
187 } else {
188 in_tab_complete = true;
189 tab_pos = 0;
190 current_tab_val = current_tab_completes.at((unsigned)tab_pos);
191 clearPrompt(output.size());
192
193 cursor_pos = current_tab_val.size();
194 std::cout << current_tab_val << std::flush;
195 }
196 } else {
197 if (tab_pos + 1 >= current_tab_completes.size()) {
198 clearPrompt(current_tab_val.size());
199 current_tab_val = original_tab_val;
200 in_tab_complete = false;
201 cursor_pos = output.size();
202 std::cout << output << std::flush;
203 } else {
204 tab_pos++;
205
206 clearPrompt(current_tab_val.size());
207 current_tab_val = current_tab_completes.at((unsigned)tab_pos);
208
209 cursor_pos = current_tab_val.size();
210 std::cout << current_tab_val << std::flush;
211 }
212 }
213 }
214 void addHistory(std::string hist) {
215 if (history.size() > 0) {
216 // only add to history if the last command wasn't the same
217 if (hist == history.at(history.size() - 1)) {
218 return;
219 }
220 }
221 history.push_back(hist);
222 }
223 void historyUp() {
224 if (history.empty()) {
225 std::cout << '\a' << std::flush;
226 return;
227 }
228 if (in_tab_complete) {
229 output = current_tab_val;
230 in_tab_complete = false;
231 }
232 if (!in_history) {
233 original_hist_val = output;
234 original_hist_cursor_pos = cursor_pos;
235 in_history = true;
236 clearPrompt(output.size());
237 hist_pos = history.size() - 1;
238 current_hist_val = history.back();
239 cursor_pos = current_hist_val.size();
240 std::cout << current_hist_val << std::flush;
241 } else {
242 if (hist_pos > 0) {
243 hist_pos--;
244 clearPrompt(current_hist_val.size());
245 current_hist_val = history.at((unsigned)hist_pos);
246 cursor_pos = current_hist_val.size();
247 std::cout << current_hist_val << std::flush;
248 } else {
249 std::cout << '\a' << std::flush;
250 }
251 }
252 }
253 void historyDown() {
254 if (in_history) {
255 clearPrompt(current_hist_val.size());
256 if (hist_pos + 1 < history.size()) {
257 hist_pos++;
258 current_hist_val = history.at((unsigned)hist_pos);
259 cursor_pos = current_hist_val.size();
260 std::cout << current_hist_val << std::flush;
261 } else {
262 in_history = false;
263 cursor_pos = original_hist_cursor_pos;
264 std::cout << original_hist_val << std::flush;
265 }
266 } else {
267 std::cout << '\a' << std::flush;
268 }
269 }
270 void moveCursor(char direction) {
271 switch (direction) {
272 case 'A': historyUp(); break;
273 case 'B': historyDown(); break;
274 case 'C': moveCursorRight(); break;
275 case 'D': moveCursorLeft(); break;
276 default: break;
277 }
278 }
279 void moveCursorRight() {
280 if (in_history) {
281 if (cursor_pos < current_hist_val.size()) {
282 cursor_pos++;
283 std::cout << (char)27 << '[' << 'C' << std::flush;
284 }
285 } else if (in_tab_complete) {
286 if (cursor_pos < current_tab_val.size()) {
287 cursor_pos++;
288 std::cout << (char)27 << '[' << 'C' << std::flush;
289 }
290 } else if (cursor_pos < output.size()) {
291 cursor_pos++;
292 std::cout << (char)27 << '[' << 'C' << std::flush;
293 }
294 }
295 void moveCursorLeft() {
296 if (cursor_pos > 0) {
297 cursor_pos--;
298 std::cout << (char)27 << '[' << 'D' << std::flush;
299 }
300 }
301 void backspace() {
302 if (cursor_pos > 0) {
303 output.erase(output.begin() + cursor_pos - 1);
304 moveCursorLeft();
305 showFullText(output);
306 }
307 }
308 void clearPrompt(std::size_t text_len) {
309 for (std::size_t i = cursor_pos; i < text_len + 1; i++) {
310 std::cout << (char)27 << "[C" << std::flush;
311 }
312 for (std::size_t i = 0; i < text_len + 1; i++) {
313 std::cout << "\b \b" << std::flush;
314 }
315 }
316 void showFullText(const std::string& text) {
317 clearPrompt(text.size());
318 for (char i : text) {
319 std::cout << i << std::flush;
320 }
321
322 for (std::size_t i = cursor_pos; i < text.size(); i++) {
323 std::cout << "\b" << std::flush;
324 }
325 }
326};
327
328} // namespace profile
329} // namespace souffle