···35353636Editor::Editor()
3737{
3838+ m_pending_chars = ByteBuffer::create_uninitialized(0);
3839 struct winsize ws;
3940 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
4041 m_num_columns = 80;
···67686869void Editor::insert(const String& string)
6970{
7070- fputs(string.characters(), stdout);
7171- fflush(stdout);
7272-7171+ m_pending_chars.append(string.characters(), string.length());
7372 if (m_cursor == m_buffer.size()) {
7473 m_buffer.append(string.characters(), string.length());
7574 m_cursor = m_buffer.size();
7675 return;
7776 }
78777979- vt_save_cursor();
8080- vt_clear_to_end_of_line();
8181- for (size_t i = m_cursor; i < m_buffer.size(); ++i)
8282- fputc(m_buffer[i], stdout);
8383- vt_restore_cursor();
8484-8578 m_buffer.ensure_capacity(m_buffer.size() + string.length());
7979+ m_chars_inserted_in_the_middle += string.length();
8680 for (size_t i = 0; i < string.length(); ++i)
8781 m_buffer.insert(m_cursor + i, string[i]);
8882 m_cursor += string.length();
···90849185void Editor::insert(const char ch)
9286{
9393- putchar(ch);
9494- fflush(stdout);
9595-8787+ m_pending_chars.append(&ch, 1);
9688 if (m_cursor == m_buffer.size()) {
9789 m_buffer.append(ch);
9890 m_cursor = m_buffer.size();
9991 return;
10092 }
10193102102- vt_save_cursor();
103103- vt_clear_to_end_of_line();
104104- for (size_t i = m_cursor; i < m_buffer.size(); ++i)
105105- fputc(m_buffer[i], stdout);
106106- vt_restore_cursor();
107107-10894 m_buffer.insert(m_cursor, ch);
9595+ ++m_chars_inserted_in_the_middle;
10996 ++m_cursor;
11097}
11198···118105 m_key_callbacks.set(ch, make<KeyCallback>(move(callback)));
119106}
120107108108+void Editor::stylize(const Span& span, const Style& style)
109109+{
110110+ auto starting_map = m_spans_starting.get(span.beginning()).value_or({});
111111+112112+ if (!starting_map.contains(span.end()))
113113+ m_refresh_needed = true;
114114+115115+ starting_map.set(span.end(), style);
116116+117117+ m_spans_starting.set(span.beginning(), starting_map);
118118+119119+ auto ending_map = m_spans_ending.get(span.end()).value_or({});
120120+121121+ if (!ending_map.contains(span.beginning()))
122122+ m_refresh_needed = true;
123123+ ending_map.set(span.beginning(), style);
124124+125125+ m_spans_ending.set(span.end(), ending_map);
126126+}
127127+121128void Editor::cut_mismatching_chars(String& completion, const String& other, size_t start_compare)
122129{
123130 size_t i = start_compare;
···174181 return;
175182 }
176183 m_buffer.remove(m_cursor);
177177- fputs("\033[3~", stdout);
178178- fflush(stdout);
179179- vt_save_cursor();
180180- vt_clear_to_end_of_line();
181181- for (size_t i = m_cursor; i < m_buffer.size(); ++i)
182182- fputc(m_buffer[i], stdout);
183183- vt_restore_cursor();
184184 };
185185-186185 for (ssize_t i = 0; i < nread; ++i) {
187186 char ch = keybuf[i];
188187 if (ch == 0)
···379378 do_backspace();
380379 continue;
381380 }
382382- if (ch == 0xc) { // ^L
381381+ if (ch == 0xc) { // ^L
383382 printf("\033[3J\033[H\033[2J"); // Clear screen.
384383 fputs(prompt.characters(), stdout);
385384 for (size_t i = 0; i < m_buffer.size(); ++i)
···422421423422 insert(ch);
424423 }
424424+ refresh_display();
425425 }
426426}
427427428428+void Editor::refresh_display()
429429+{
430430+ if (on_display_refresh)
431431+ on_display_refresh(*this);
432432+433433+ if (!m_refresh_needed && m_cursor == m_buffer.size()) {
434434+ // just write the characters out and continue
435435+ // no need to refresh the entire line
436436+ char null = 0;
437437+ m_pending_chars.append(&null, 1);
438438+ fputs((char*)m_pending_chars.data(), stdout);
439439+ m_pending_chars.clear();
440440+ fflush(stdout);
441441+ return;
442442+ }
443443+444444+ // ouch, reflow entire line
445445+ // FIXME: handle multiline stuff
446446+ vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
447447+ vt_save_cursor();
448448+ auto current_line = cursor_line();
449449+ vt_clear_lines(current_line - 1, num_lines() - current_line);
450450+ vt_move_relative(-num_lines() + 1, -offset_in_line() + m_chars_inserted_in_the_middle);
451451+ vt_clear_to_end_of_line();
452452+ HashMap<u32, Style> empty_styles {};
453453+ for (size_t i = 0; i < m_buffer.size(); ++i) {
454454+ auto ends = m_spans_ending.get(i).value_or(empty_styles);
455455+ auto starts = m_spans_starting.get(i).value_or(empty_styles);
456456+ if (ends.size()) {
457457+ // go back to defaults
458458+ vt_apply_style(find_applicable_style(i));
459459+ }
460460+ if (starts.size()) {
461461+ // set new options
462462+ vt_apply_style(starts.begin()->value); // apply some random style that starts here
463463+ }
464464+ fputc(m_buffer[i], stdout);
465465+ }
466466+ vt_apply_style({}); // don't bleed to EOL
467467+ vt_restore_cursor();
468468+ vt_move_relative(0, m_chars_inserted_in_the_middle);
469469+ fflush(stdout);
470470+ m_pending_chars.clear();
471471+ m_refresh_needed = false;
472472+ m_chars_inserted_in_the_middle = 0;
473473+}
474474+475475+void Editor::vt_move_relative(int x, int y)
476476+{
477477+ char x_op = 'A', y_op = 'D';
478478+ if (x > 0)
479479+ x_op = 'B';
480480+ else
481481+ x = -x;
482482+ if (y > 0)
483483+ y_op = 'C';
484484+ else
485485+ y = -y;
486486+487487+ if (x > 0)
488488+ printf("\033[%d%c", x, x_op);
489489+ if (y > 0)
490490+ printf("\033[%d%c", y, y_op);
491491+}
492492+493493+Style Editor::find_applicable_style(size_t offset) const
494494+{
495495+ // walk through our styles and find one that fits in the offset
496496+ for (auto& entry : m_spans_starting) {
497497+ if (entry.key > offset)
498498+ continue;
499499+ for (auto& style_value : entry.value) {
500500+ if (style_value.key <= offset)
501501+ continue;
502502+ return style_value.value;
503503+ }
504504+ }
505505+ return {};
506506+}
507507+508508+void Editor::vt_apply_style(const Style& style)
509509+{
510510+ printf(
511511+ "\033[%d;%d;%d;%d;%dm",
512512+ style.bold() ? 1 : 22,
513513+ style.underline() ? 4 : 24,
514514+ style.italic() ? 3 : 23,
515515+ (int)style.foreground() + 30,
516516+ (int)style.background() + 40);
517517+}
518518+519519+void Editor::vt_clear_lines(size_t count_above, size_t count_below)
520520+{
521521+ // go down count_below lines
522522+ if (count_below > 0)
523523+ printf("\033[%dB", (int)count_below);
524524+ // then clear lines going upwards
525525+ for (size_t i = 0; i < count_below + count_above; ++i)
526526+ fputs("\033[2K\033[A", stdout);
527527+}
528528+428529void Editor::vt_save_cursor()
429530{
430531 fputs("\033[s", stdout);
···442543 fputs("\033[K", stdout);
443544 fflush(stdout);
444545}
445445-446546}
+46-3
Libraries/LibLine/Editor.h
···2727#pragma once
28282929#include <AK/BinarySearch.h>
3030+#include <AK/ByteBuffer.h>
3031#include <AK/FileSystemPath.h>
3132#include <AK/Function.h>
3233#include <AK/HashMap.h>
···3536#include <AK/String.h>
3637#include <AK/Vector.h>
3738#include <LibCore/DirIterator.h>
3939+#include <Libraries/LibLine/Span.h>
4040+#include <Libraries/LibLine/Style.h>
3841#include <sys/stat.h>
3942#include <termios.h>
4043···76797780 void register_character_input_callback(char ch, Function<bool(Editor&)> callback);
78817979- Function<Vector<String>(const String&)> on_tab_complete_first_token = nullptr;
8080- Function<Vector<String>(const String&)> on_tab_complete_other_token = nullptr;
8282+ Function<Vector<String>(const String&)> on_tab_complete_first_token;
8383+ Function<Vector<String>(const String&)> on_tab_complete_other_token;
8484+ Function<void(Editor&)> on_display_refresh;
81858286 // FIXME: we will have to kindly ask our instantiators to set our signal handlers
8387 // since we can not do this cleanly ourselves (signal() limitation: cannot give member functions)
···9296 void insert(const String&);
9397 void insert(const char);
9498 void cut_mismatching_chars(String& completion, const String& other, size_t start_compare);
9999+ void stylize(const Span&, const Style&);
100100+ void strip_styles()
101101+ {
102102+ m_spans_starting.clear();
103103+ m_spans_ending.clear();
104104+ m_refresh_needed = true;
105105+ }
9510696107 const struct termios& termios() const { return m_termios; }
97108 const struct termios& default_termios() const { return m_default_termios; }
···100111 void vt_save_cursor();
101112 void vt_restore_cursor();
102113 void vt_clear_to_end_of_line();
114114+ void vt_clear_lines(size_t count_above, size_t count_below = 0);
115115+ void vt_move_relative(int x, int y);
116116+ void vt_apply_style(const Style&);
117117+118118+ Style find_applicable_style(size_t offset) const;
119119+120120+ void refresh_display();
121121+122122+ // FIXME: These three will report the wrong value because they do not
123123+ // take the length of the prompt into consideration, and it does not
124124+ // appear that we can figure that out easily
125125+ size_t num_lines() const
126126+ {
127127+ return (m_buffer.size() + m_num_columns) / m_num_columns;
128128+ }
129129+130130+ size_t cursor_line() const
131131+ {
132132+ return (m_cursor + m_num_columns) / m_num_columns;
133133+ }
134134+135135+ size_t offset_in_line() const
136136+ {
137137+ auto offset = m_cursor % m_num_columns;
138138+ return offset;
139139+ }
103140104141 Vector<char, 1024> m_buffer;
142142+ ByteBuffer m_pending_chars;
105143 size_t m_cursor { 0 };
144144+ size_t m_chars_inserted_in_the_middle { 0 };
106145 size_t m_times_tab_pressed { 0 };
107146 size_t m_num_columns { 0 };
108147···126165 };
127166 InputState m_state { InputState::Free };
128167129129- bool m_initialized = false;
168168+ HashMap<u32, HashMap<u32, Style>> m_spans_starting;
169169+ HashMap<u32, HashMap<u32, Style>> m_spans_ending;
170170+171171+ bool m_initialized { false };
172172+ bool m_refresh_needed { false };
130173};
131174132175}
+46
Libraries/LibLine/Span.h
···11+/*
22+ * Copyright (c) 2020, The SerenityOS developers.
33+ * All rights reserved.
44+ *
55+ * Redistribution and use in source and binary forms, with or without
66+ * modification, are permitted provided that the following conditions are met:
77+ *
88+ * 1. Redistributions of source code must retain the above copyright notice, this
99+ * list of conditions and the following disclaimer.
1010+ *
1111+ * 2. Redistributions in binary form must reproduce the above copyright notice,
1212+ * this list of conditions and the following disclaimer in the documentation
1313+ * and/or other materials provided with the distribution.
1414+ *
1515+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1616+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1717+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1818+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
1919+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2020+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2121+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2222+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2323+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2424+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525+ */
2626+2727+#pragma once
2828+#include <stdlib.h>
2929+3030+namespace Line {
3131+class Span {
3232+public:
3333+ Span(size_t start, size_t end)
3434+ : m_beginning(start)
3535+ , m_end(end)
3636+ {
3737+ }
3838+3939+ size_t beginning() const { return m_beginning; }
4040+ size_t end() const { return m_end; }
4141+4242+private:
4343+ size_t m_beginning { 0 };
4444+ size_t m_end { 0 };
4545+};
4646+}
+108
Libraries/LibLine/Style.h
···11+/*
22+ * Copyright (c) 2020, The SerenityOS developers.
33+ * All rights reserved.
44+ *
55+ * Redistribution and use in source and binary forms, with or without
66+ * modification, are permitted provided that the following conditions are met:
77+ *
88+ * 1. Redistributions of source code must retain the above copyright notice, this
99+ * list of conditions and the following disclaimer.
1010+ *
1111+ * 2. Redistributions in binary form must reproduce the above copyright notice,
1212+ * this list of conditions and the following disclaimer in the documentation
1313+ * and/or other materials provided with the distribution.
1414+ *
1515+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1616+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1717+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1818+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
1919+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2020+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2121+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2222+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2323+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2424+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525+ */
2626+2727+#pragma once
2828+#include <stdlib.h>
2929+3030+namespace Line {
3131+3232+class Style {
3333+public:
3434+ enum class Color : int {
3535+ Default = 9,
3636+ Black = 0,
3737+ Red,
3838+ Green,
3939+ Yellow,
4040+ Blue,
4141+ Magenta,
4242+ Cyan,
4343+ White,
4444+ // TODO: it appears that we do not support these SGR options
4545+ BrightBlack = 60,
4646+ BrightRed,
4747+ BrightGreen,
4848+ BrightYellow,
4949+ BrightBlue,
5050+ BrightMagenta,
5151+ BrightCyan,
5252+ BrightWhite,
5353+ };
5454+5555+ struct UnderlineTag {
5656+ };
5757+ struct BoldTag {
5858+ };
5959+ struct ItalicTag {
6060+ };
6161+ struct Background {
6262+ explicit Background(Color color)
6363+ : m_color(color)
6464+ {
6565+ }
6666+ Color m_color;
6767+ };
6868+ struct Foreground {
6969+ explicit Foreground(Color color)
7070+ : m_color(color)
7171+ {
7272+ }
7373+ Color m_color;
7474+ };
7575+7676+ static constexpr UnderlineTag Underline {};
7777+ static constexpr BoldTag Bold {};
7878+ static constexpr ItalicTag Italic {};
7979+8080+ // prepare for the horror of templates
8181+ template <typename T, typename... Rest>
8282+ Style(const T& style_arg, Rest... rest)
8383+ : Style(rest...)
8484+ {
8585+ set(style_arg);
8686+ }
8787+ Style() {}
8888+8989+ bool underline() const { return m_underline; }
9090+ bool bold() const { return m_bold; }
9191+ bool italic() const { return m_italic; }
9292+ Color background() const { return m_background; }
9393+ Color foreground() const { return m_foreground; }
9494+9595+ void set(const ItalicTag&) { m_italic = true; }
9696+ void set(const BoldTag&) { m_bold = true; }
9797+ void set(const UnderlineTag&) { m_underline = true; }
9898+ void set(const Background& bg) { m_background = bg.m_color; }
9999+ void set(const Foreground& fg) { m_foreground = fg.m_color; }
100100+101101+private:
102102+ bool m_underline { false };
103103+ bool m_bold { false };
104104+ bool m_italic { false };
105105+ Color m_background { Color::Default };
106106+ Color m_foreground { Color::Default };
107107+};
108108+}