Serenity Operating System
at hosted 546 lines 18 kB view raw
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}