Serenity Operating System
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

LibLine: Add live styling support

This patchset adds an stylization interface to LibLine, and breaks
multiline editing.
With the most adorable Style constructor I've ever seen :^)

authored by

AnotherTest and committed by
Andreas Kling
c2f8a5ff 2c5faa8f

+328 -31
+128 -28
Libraries/LibLine/Editor.cpp
··· 35 35 36 36 Editor::Editor() 37 37 { 38 + m_pending_chars = ByteBuffer::create_uninitialized(0); 38 39 struct winsize ws; 39 40 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) 40 41 m_num_columns = 80; ··· 67 68 68 69 void Editor::insert(const String& string) 69 70 { 70 - fputs(string.characters(), stdout); 71 - fflush(stdout); 72 - 71 + m_pending_chars.append(string.characters(), string.length()); 73 72 if (m_cursor == m_buffer.size()) { 74 73 m_buffer.append(string.characters(), string.length()); 75 74 m_cursor = m_buffer.size(); 76 75 return; 77 76 } 78 77 79 - vt_save_cursor(); 80 - vt_clear_to_end_of_line(); 81 - for (size_t i = m_cursor; i < m_buffer.size(); ++i) 82 - fputc(m_buffer[i], stdout); 83 - vt_restore_cursor(); 84 - 85 78 m_buffer.ensure_capacity(m_buffer.size() + string.length()); 79 + m_chars_inserted_in_the_middle += string.length(); 86 80 for (size_t i = 0; i < string.length(); ++i) 87 81 m_buffer.insert(m_cursor + i, string[i]); 88 82 m_cursor += string.length(); ··· 90 84 91 85 void Editor::insert(const char ch) 92 86 { 93 - putchar(ch); 94 - fflush(stdout); 95 - 87 + m_pending_chars.append(&ch, 1); 96 88 if (m_cursor == m_buffer.size()) { 97 89 m_buffer.append(ch); 98 90 m_cursor = m_buffer.size(); 99 91 return; 100 92 } 101 93 102 - vt_save_cursor(); 103 - vt_clear_to_end_of_line(); 104 - for (size_t i = m_cursor; i < m_buffer.size(); ++i) 105 - fputc(m_buffer[i], stdout); 106 - vt_restore_cursor(); 107 - 108 94 m_buffer.insert(m_cursor, ch); 95 + ++m_chars_inserted_in_the_middle; 109 96 ++m_cursor; 110 97 } 111 98 ··· 118 105 m_key_callbacks.set(ch, make<KeyCallback>(move(callback))); 119 106 } 120 107 108 + void 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 + 121 128 void Editor::cut_mismatching_chars(String& completion, const String& other, size_t start_compare) 122 129 { 123 130 size_t i = start_compare; ··· 174 181 return; 175 182 } 176 183 m_buffer.remove(m_cursor); 177 - fputs("\033[3~", stdout); 178 - fflush(stdout); 179 - vt_save_cursor(); 180 - vt_clear_to_end_of_line(); 181 - for (size_t i = m_cursor; i < m_buffer.size(); ++i) 182 - fputc(m_buffer[i], stdout); 183 - vt_restore_cursor(); 184 184 }; 185 - 186 185 for (ssize_t i = 0; i < nread; ++i) { 187 186 char ch = keybuf[i]; 188 187 if (ch == 0) ··· 379 378 do_backspace(); 380 379 continue; 381 380 } 382 - if (ch == 0xc) { // ^L 381 + if (ch == 0xc) { // ^L 383 382 printf("\033[3J\033[H\033[2J"); // Clear screen. 384 383 fputs(prompt.characters(), stdout); 385 384 for (size_t i = 0; i < m_buffer.size(); ++i) ··· 422 421 423 422 insert(ch); 424 423 } 424 + refresh_display(); 425 425 } 426 426 } 427 427 428 + void 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 + 475 + void 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 + 493 + Style 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 + 508 + void 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 + 519 + void 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 + 428 529 void Editor::vt_save_cursor() 429 530 { 430 531 fputs("\033[s", stdout); ··· 442 543 fputs("\033[K", stdout); 443 544 fflush(stdout); 444 545 } 445 - 446 546 }
+46 -3
Libraries/LibLine/Editor.h
··· 27 27 #pragma once 28 28 29 29 #include <AK/BinarySearch.h> 30 + #include <AK/ByteBuffer.h> 30 31 #include <AK/FileSystemPath.h> 31 32 #include <AK/Function.h> 32 33 #include <AK/HashMap.h> ··· 35 36 #include <AK/String.h> 36 37 #include <AK/Vector.h> 37 38 #include <LibCore/DirIterator.h> 39 + #include <Libraries/LibLine/Span.h> 40 + #include <Libraries/LibLine/Style.h> 38 41 #include <sys/stat.h> 39 42 #include <termios.h> 40 43 ··· 76 79 77 80 void register_character_input_callback(char ch, Function<bool(Editor&)> callback); 78 81 79 - Function<Vector<String>(const String&)> on_tab_complete_first_token = nullptr; 80 - Function<Vector<String>(const String&)> on_tab_complete_other_token = nullptr; 82 + Function<Vector<String>(const String&)> on_tab_complete_first_token; 83 + Function<Vector<String>(const String&)> on_tab_complete_other_token; 84 + Function<void(Editor&)> on_display_refresh; 81 85 82 86 // FIXME: we will have to kindly ask our instantiators to set our signal handlers 83 87 // since we can not do this cleanly ourselves (signal() limitation: cannot give member functions) ··· 92 96 void insert(const String&); 93 97 void insert(const char); 94 98 void cut_mismatching_chars(String& completion, const String& other, size_t start_compare); 99 + void stylize(const Span&, const Style&); 100 + void strip_styles() 101 + { 102 + m_spans_starting.clear(); 103 + m_spans_ending.clear(); 104 + m_refresh_needed = true; 105 + } 95 106 96 107 const struct termios& termios() const { return m_termios; } 97 108 const struct termios& default_termios() const { return m_default_termios; } ··· 100 111 void vt_save_cursor(); 101 112 void vt_restore_cursor(); 102 113 void vt_clear_to_end_of_line(); 114 + void vt_clear_lines(size_t count_above, size_t count_below = 0); 115 + void vt_move_relative(int x, int y); 116 + void vt_apply_style(const Style&); 117 + 118 + Style find_applicable_style(size_t offset) const; 119 + 120 + void refresh_display(); 121 + 122 + // FIXME: These three will report the wrong value because they do not 123 + // take the length of the prompt into consideration, and it does not 124 + // appear that we can figure that out easily 125 + size_t num_lines() const 126 + { 127 + return (m_buffer.size() + m_num_columns) / m_num_columns; 128 + } 129 + 130 + size_t cursor_line() const 131 + { 132 + return (m_cursor + m_num_columns) / m_num_columns; 133 + } 134 + 135 + size_t offset_in_line() const 136 + { 137 + auto offset = m_cursor % m_num_columns; 138 + return offset; 139 + } 103 140 104 141 Vector<char, 1024> m_buffer; 142 + ByteBuffer m_pending_chars; 105 143 size_t m_cursor { 0 }; 144 + size_t m_chars_inserted_in_the_middle { 0 }; 106 145 size_t m_times_tab_pressed { 0 }; 107 146 size_t m_num_columns { 0 }; 108 147 ··· 126 165 }; 127 166 InputState m_state { InputState::Free }; 128 167 129 - bool m_initialized = false; 168 + HashMap<u32, HashMap<u32, Style>> m_spans_starting; 169 + HashMap<u32, HashMap<u32, Style>> m_spans_ending; 170 + 171 + bool m_initialized { false }; 172 + bool m_refresh_needed { false }; 130 173 }; 131 174 132 175 }
+46
Libraries/LibLine/Span.h
··· 1 + /* 2 + * Copyright (c) 2020, The SerenityOS developers. 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 + #pragma once 28 + #include <stdlib.h> 29 + 30 + namespace Line { 31 + class Span { 32 + public: 33 + Span(size_t start, size_t end) 34 + : m_beginning(start) 35 + , m_end(end) 36 + { 37 + } 38 + 39 + size_t beginning() const { return m_beginning; } 40 + size_t end() const { return m_end; } 41 + 42 + private: 43 + size_t m_beginning { 0 }; 44 + size_t m_end { 0 }; 45 + }; 46 + }
+108
Libraries/LibLine/Style.h
··· 1 + /* 2 + * Copyright (c) 2020, The SerenityOS developers. 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 + #pragma once 28 + #include <stdlib.h> 29 + 30 + namespace Line { 31 + 32 + class Style { 33 + public: 34 + enum class Color : int { 35 + Default = 9, 36 + Black = 0, 37 + Red, 38 + Green, 39 + Yellow, 40 + Blue, 41 + Magenta, 42 + Cyan, 43 + White, 44 + // TODO: it appears that we do not support these SGR options 45 + BrightBlack = 60, 46 + BrightRed, 47 + BrightGreen, 48 + BrightYellow, 49 + BrightBlue, 50 + BrightMagenta, 51 + BrightCyan, 52 + BrightWhite, 53 + }; 54 + 55 + struct UnderlineTag { 56 + }; 57 + struct BoldTag { 58 + }; 59 + struct ItalicTag { 60 + }; 61 + struct Background { 62 + explicit Background(Color color) 63 + : m_color(color) 64 + { 65 + } 66 + Color m_color; 67 + }; 68 + struct Foreground { 69 + explicit Foreground(Color color) 70 + : m_color(color) 71 + { 72 + } 73 + Color m_color; 74 + }; 75 + 76 + static constexpr UnderlineTag Underline {}; 77 + static constexpr BoldTag Bold {}; 78 + static constexpr ItalicTag Italic {}; 79 + 80 + // prepare for the horror of templates 81 + template <typename T, typename... Rest> 82 + Style(const T& style_arg, Rest... rest) 83 + : Style(rest...) 84 + { 85 + set(style_arg); 86 + } 87 + Style() {} 88 + 89 + bool underline() const { return m_underline; } 90 + bool bold() const { return m_bold; } 91 + bool italic() const { return m_italic; } 92 + Color background() const { return m_background; } 93 + Color foreground() const { return m_foreground; } 94 + 95 + void set(const ItalicTag&) { m_italic = true; } 96 + void set(const BoldTag&) { m_bold = true; } 97 + void set(const UnderlineTag&) { m_underline = true; } 98 + void set(const Background& bg) { m_background = bg.m_color; } 99 + void set(const Foreground& fg) { m_foreground = fg.m_color; } 100 + 101 + private: 102 + bool m_underline { false }; 103 + bool m_bold { false }; 104 + bool m_italic { false }; 105 + Color m_background { Color::Default }; 106 + Color m_foreground { Color::Default }; 107 + }; 108 + }