Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "Editor.h"
28#include <AK/StringBuilder.h>
29#include <ctype.h>
30#include <stdio.h>
31#include <sys/ioctl.h>
32#include <unistd.h>
33
34namespace Line {
35
36Editor::Editor()
37{
38 m_pending_chars = ByteBuffer::create_uninitialized(0);
39 struct winsize ws;
40 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
41 m_num_columns = 80;
42 else
43 m_num_columns = ws.ws_col;
44}
45
46Editor::~Editor()
47{
48 if (m_initialized)
49 tcsetattr(0, TCSANOW, &m_default_termios);
50}
51
52void Editor::add_to_history(const String& line)
53{
54 if ((m_history.size() + 1) > m_history_capacity)
55 m_history.take_first();
56 m_history.append(line);
57}
58
59void Editor::clear_line()
60{
61 for (size_t i = 0; i < m_cursor; ++i)
62 fputc(0x8, stdout);
63 fputs("\033[K", stdout);
64 fflush(stdout);
65 m_buffer.clear();
66 m_cursor = 0;
67}
68
69void Editor::insert(const String& string)
70{
71 m_pending_chars.append(string.characters(), string.length());
72 if (m_cursor == m_buffer.size()) {
73 m_buffer.append(string.characters(), string.length());
74 m_cursor = m_buffer.size();
75 return;
76 }
77
78 m_buffer.ensure_capacity(m_buffer.size() + string.length());
79 m_chars_inserted_in_the_middle += string.length();
80 for (size_t i = 0; i < string.length(); ++i)
81 m_buffer.insert(m_cursor + i, string[i]);
82 m_cursor += string.length();
83}
84
85void Editor::insert(const char ch)
86{
87 m_pending_chars.append(&ch, 1);
88 if (m_cursor == m_buffer.size()) {
89 m_buffer.append(ch);
90 m_cursor = m_buffer.size();
91 return;
92 }
93
94 m_buffer.insert(m_cursor, ch);
95 ++m_chars_inserted_in_the_middle;
96 ++m_cursor;
97}
98
99void Editor::register_character_input_callback(char ch, Function<bool(Editor&)> callback)
100{
101 if (m_key_callbacks.contains(ch)) {
102 dbg() << "Key callback registered twice for " << ch;
103 ASSERT_NOT_REACHED();
104 }
105 m_key_callbacks.set(ch, make<KeyCallback>(move(callback)));
106}
107
108void Editor::stylize(const Span& span, const Style& style)
109{
110 auto starting_map = m_spans_starting.get(span.beginning()).value_or({});
111
112 if (!starting_map.contains(span.end()))
113 m_refresh_needed = true;
114
115 starting_map.set(span.end(), style);
116
117 m_spans_starting.set(span.beginning(), starting_map);
118
119 auto ending_map = m_spans_ending.get(span.end()).value_or({});
120
121 if (!ending_map.contains(span.beginning()))
122 m_refresh_needed = true;
123 ending_map.set(span.beginning(), style);
124
125 m_spans_ending.set(span.end(), ending_map);
126}
127
128void Editor::cut_mismatching_chars(String& completion, const String& other, size_t start_compare)
129{
130 size_t i = start_compare;
131 while (i < completion.length() && i < other.length() && completion[i] == other[i])
132 ++i;
133 completion = completion.substring(0, i);
134}
135
136String Editor::get_line(const String& prompt)
137{
138 fputs(prompt.characters(), stdout);
139 fflush(stdout);
140
141 m_history_cursor = m_history.size();
142 m_cursor = 0;
143 for (;;) {
144 char keybuf[16];
145 ssize_t nread = read(0, keybuf, sizeof(keybuf));
146 // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
147 if (nread == 0)
148 exit(0);
149 if (nread < 0) {
150 if (errno == EINTR) {
151 if (m_was_interrupted) {
152 m_was_interrupted = false;
153 if (!m_buffer.is_empty())
154 printf("^C");
155 }
156 if (m_was_resized) {
157 m_was_resized = false;
158 printf("\033[2K\r");
159 m_buffer.clear();
160
161 struct winsize ws;
162 int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
163 ASSERT(rc == 0);
164 m_num_columns = ws.ws_col;
165
166 return String::empty();
167 }
168 m_buffer.clear();
169 putchar('\n');
170 return String::empty();
171 }
172 perror("read failed");
173 // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
174 exit(2);
175 }
176
177 auto do_delete = [&] {
178 if (m_cursor == m_buffer.size()) {
179 fputc('\a', stdout);
180 fflush(stdout);
181 return;
182 }
183 m_buffer.remove(m_cursor);
184 };
185 for (ssize_t i = 0; i < nread; ++i) {
186 char ch = keybuf[i];
187 if (ch == 0)
188 continue;
189
190 switch (m_state) {
191 case InputState::ExpectBracket:
192 if (ch == '[') {
193 m_state = InputState::ExpectFinal;
194 continue;
195 } else {
196 m_state = InputState::Free;
197 break;
198 }
199 case InputState::ExpectFinal:
200 switch (ch) {
201 case 'A': // up
202 if (m_history_cursor > 0)
203 --m_history_cursor;
204 clear_line();
205 if (m_history_cursor < m_history.size())
206 insert(m_history[m_history_cursor]);
207 m_state = InputState::Free;
208 continue;
209 case 'B': // down
210 if (m_history_cursor < m_history.size())
211 ++m_history_cursor;
212 clear_line();
213 if (m_history_cursor < m_history.size())
214 insert(m_history[m_history_cursor]);
215 m_state = InputState::Free;
216 continue;
217 case 'D': // left
218 if (m_cursor > 0) {
219 --m_cursor;
220 fputs("\033[D", stdout);
221 fflush(stdout);
222 }
223 m_state = InputState::Free;
224 continue;
225 case 'C': // right
226 if (m_cursor < m_buffer.size()) {
227 ++m_cursor;
228 fputs("\033[C", stdout);
229 fflush(stdout);
230 }
231 m_state = InputState::Free;
232 continue;
233 case 'H':
234 if (m_cursor > 0) {
235 fprintf(stdout, "\033[%zuD", m_cursor);
236 fflush(stdout);
237 m_cursor = 0;
238 }
239 m_state = InputState::Free;
240 continue;
241 case 'F':
242 if (m_cursor < m_buffer.size()) {
243 fprintf(stdout, "\033[%zuC", m_buffer.size() - m_cursor);
244 fflush(stdout);
245 m_cursor = m_buffer.size();
246 }
247 m_state = InputState::Free;
248 continue;
249 case '3':
250 do_delete();
251 m_state = InputState::ExpectTerminator;
252 continue;
253 default:
254 dbgprintf("Shell: Unhandled final: %02x (%c)\n", ch, ch);
255 m_state = InputState::Free;
256 continue;
257 }
258 break;
259 case InputState::ExpectTerminator:
260 m_state = InputState::Free;
261 continue;
262 case InputState::Free:
263 if (ch == 27) {
264 m_state = InputState::ExpectBracket;
265 continue;
266 }
267 break;
268 }
269
270 auto cb = m_key_callbacks.get(ch);
271 if (cb.has_value()) {
272 if (!cb.value()->callback(*this)) {
273 continue;
274 }
275 }
276
277 if (ch == '\t') {
278 if (!on_tab_complete_first_token || !on_tab_complete_other_token)
279 continue;
280
281 bool is_empty_token = m_cursor == 0 || m_buffer[m_cursor - 1] == ' ';
282 m_times_tab_pressed++;
283
284 int token_start = m_cursor - 1;
285 if (!is_empty_token) {
286 while (token_start >= 0 && m_buffer[token_start] != ' ')
287 --token_start;
288 ++token_start;
289 }
290
291 bool is_first_token = true;
292 for (int i = token_start - 1; i >= 0; --i) {
293 if (m_buffer[i] != ' ') {
294 is_first_token = false;
295 break;
296 }
297 }
298
299 String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start);
300 Vector<String> suggestions;
301
302 if (is_first_token)
303 suggestions = on_tab_complete_first_token(token);
304 else
305 suggestions = on_tab_complete_other_token(token);
306
307 if (m_times_tab_pressed > 1 && !suggestions.is_empty()) {
308 size_t longest_suggestion_length = 0;
309
310 for (auto& suggestion : suggestions)
311 longest_suggestion_length = max(longest_suggestion_length, suggestion.length());
312
313 size_t num_printed = 0;
314 putchar('\n');
315 for (auto& suggestion : suggestions) {
316 size_t next_column = num_printed + suggestion.length() + longest_suggestion_length + 2;
317
318 if (next_column > m_num_columns) {
319 putchar('\n');
320 num_printed = 0;
321 }
322
323 num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.characters());
324 }
325
326 StringBuilder builder;
327 builder.append('\n');
328 builder.append(prompt);
329 builder.append(m_buffer.data(), m_cursor);
330 builder.append(m_buffer.data() + m_cursor, m_buffer.size() - m_cursor);
331 fputs(builder.to_string().characters(), stdout);
332 fflush(stdout);
333
334 m_cursor = m_buffer.size();
335 }
336
337 suggestions.clear_with_capacity();
338 continue;
339 }
340
341 m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
342
343 auto do_backspace = [&] {
344 if (m_cursor == 0) {
345 fputc('\a', stdout);
346 fflush(stdout);
347 return;
348 }
349 m_buffer.remove(m_cursor - 1);
350 --m_cursor;
351 putchar(8);
352 vt_save_cursor();
353 vt_clear_to_end_of_line();
354 for (size_t i = m_cursor; i < m_buffer.size(); ++i)
355 fputc(m_buffer[i], stdout);
356 vt_restore_cursor();
357 };
358
359 if (ch == 8 || ch == m_termios.c_cc[VERASE]) {
360 do_backspace();
361 continue;
362 }
363 if (ch == m_termios.c_cc[VWERASE]) {
364 bool has_seen_nonspace = false;
365 while (m_cursor > 0) {
366 if (isspace(m_buffer[m_cursor - 1])) {
367 if (has_seen_nonspace)
368 break;
369 } else {
370 has_seen_nonspace = true;
371 }
372 do_backspace();
373 }
374 continue;
375 }
376 if (ch == m_termios.c_cc[VKILL]) {
377 while (m_cursor > 0)
378 do_backspace();
379 continue;
380 }
381 if (ch == 0xc) { // ^L
382 printf("\033[3J\033[H\033[2J"); // Clear screen.
383 fputs(prompt.characters(), stdout);
384 for (size_t i = 0; i < m_buffer.size(); ++i)
385 fputc(m_buffer[i], stdout);
386 if (m_cursor < m_buffer.size())
387 printf("\033[%zuD", m_buffer.size() - m_cursor); // Move cursor N steps left.
388 fflush(stdout);
389 continue;
390 }
391 if (ch == 0x01) { // ^A
392 if (m_cursor > 0) {
393 printf("\033[%zuD", m_cursor);
394 fflush(stdout);
395 m_cursor = 0;
396 }
397 continue;
398 }
399 if (ch == m_termios.c_cc[VEOF]) { // Normally ^D
400 if (m_buffer.is_empty()) {
401 printf("<EOF>\n");
402 exit(0);
403 }
404 continue;
405 }
406 if (ch == 0x05) { // ^E
407 if (m_cursor < m_buffer.size()) {
408 printf("\033[%zuC", m_buffer.size() - m_cursor);
409 fflush(stdout);
410 m_cursor = m_buffer.size();
411 }
412 continue;
413 }
414 if (ch == '\n') {
415 putchar('\n');
416 fflush(stdout);
417 auto string = String::copy(m_buffer);
418 m_buffer.clear();
419 return string;
420 }
421
422 insert(ch);
423 }
424 refresh_display();
425 }
426}
427
428void Editor::refresh_display()
429{
430 if (on_display_refresh)
431 on_display_refresh(*this);
432
433 if (!m_refresh_needed && m_cursor == m_buffer.size()) {
434 // just write the characters out and continue
435 // no need to refresh the entire line
436 char null = 0;
437 m_pending_chars.append(&null, 1);
438 fputs((char*)m_pending_chars.data(), stdout);
439 m_pending_chars.clear();
440 fflush(stdout);
441 return;
442 }
443
444 // ouch, reflow entire line
445 // FIXME: handle multiline stuff
446 vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
447 vt_save_cursor();
448 auto current_line = cursor_line();
449 vt_clear_lines(current_line - 1, num_lines() - current_line);
450 vt_move_relative(-num_lines() + 1, -offset_in_line() + m_chars_inserted_in_the_middle);
451 vt_clear_to_end_of_line();
452 HashMap<u32, Style> empty_styles {};
453 for (size_t i = 0; i < m_buffer.size(); ++i) {
454 auto ends = m_spans_ending.get(i).value_or(empty_styles);
455 auto starts = m_spans_starting.get(i).value_or(empty_styles);
456 if (ends.size()) {
457 // go back to defaults
458 vt_apply_style(find_applicable_style(i));
459 }
460 if (starts.size()) {
461 // set new options
462 vt_apply_style(starts.begin()->value); // apply some random style that starts here
463 }
464 fputc(m_buffer[i], stdout);
465 }
466 vt_apply_style({}); // don't bleed to EOL
467 vt_restore_cursor();
468 vt_move_relative(0, m_chars_inserted_in_the_middle);
469 fflush(stdout);
470 m_pending_chars.clear();
471 m_refresh_needed = false;
472 m_chars_inserted_in_the_middle = 0;
473}
474
475void Editor::vt_move_relative(int x, int y)
476{
477 char x_op = 'A', y_op = 'D';
478 if (x > 0)
479 x_op = 'B';
480 else
481 x = -x;
482 if (y > 0)
483 y_op = 'C';
484 else
485 y = -y;
486
487 if (x > 0)
488 printf("\033[%d%c", x, x_op);
489 if (y > 0)
490 printf("\033[%d%c", y, y_op);
491}
492
493Style Editor::find_applicable_style(size_t offset) const
494{
495 // walk through our styles and find one that fits in the offset
496 for (auto& entry : m_spans_starting) {
497 if (entry.key > offset)
498 continue;
499 for (auto& style_value : entry.value) {
500 if (style_value.key <= offset)
501 continue;
502 return style_value.value;
503 }
504 }
505 return {};
506}
507
508void Editor::vt_apply_style(const Style& style)
509{
510 printf(
511 "\033[%d;%d;%d;%d;%dm",
512 style.bold() ? 1 : 22,
513 style.underline() ? 4 : 24,
514 style.italic() ? 3 : 23,
515 (int)style.foreground() + 30,
516 (int)style.background() + 40);
517}
518
519void Editor::vt_clear_lines(size_t count_above, size_t count_below)
520{
521 // go down count_below lines
522 if (count_below > 0)
523 printf("\033[%dB", (int)count_below);
524 // then clear lines going upwards
525 for (size_t i = 0; i < count_below + count_above; ++i)
526 fputs("\033[2K\033[A", stdout);
527}
528
529void Editor::vt_save_cursor()
530{
531 fputs("\033[s", stdout);
532 fflush(stdout);
533}
534
535void Editor::vt_restore_cursor()
536{
537 fputs("\033[u", stdout);
538 fflush(stdout);
539}
540
541void Editor::vt_clear_to_end_of_line()
542{
543 fputs("\033[K", stdout);
544 fflush(stdout);
545}
546}