/src/serenity/Userland/Libraries/LibLine/Editor.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021, the SerenityOS developers. |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #pragma once |
9 | | |
10 | | #include <AK/BinarySearch.h> |
11 | | #include <AK/ByteBuffer.h> |
12 | | #include <AK/ByteString.h> |
13 | | #include <AK/Function.h> |
14 | | #include <AK/HashMap.h> |
15 | | #include <AK/OwnPtr.h> |
16 | | #include <AK/RedBlackTree.h> |
17 | | #include <AK/Result.h> |
18 | | #include <AK/Traits.h> |
19 | | #include <AK/Utf32View.h> |
20 | | #include <AK/Utf8View.h> |
21 | | #include <AK/Vector.h> |
22 | | #include <LibCore/DirIterator.h> |
23 | | #include <LibCore/EventLoop.h> |
24 | | #include <LibCore/EventReceiver.h> |
25 | | #include <LibCore/Notifier.h> |
26 | | #include <LibLine/KeyCallbackMachine.h> |
27 | | #include <LibLine/Span.h> |
28 | | #include <LibLine/StringMetrics.h> |
29 | | #include <LibLine/Style.h> |
30 | | #include <LibLine/SuggestionDisplay.h> |
31 | | #include <LibLine/SuggestionManager.h> |
32 | | #include <LibLine/VT.h> |
33 | | #include <sys/ioctl.h> |
34 | | #include <sys/stat.h> |
35 | | #include <termios.h> |
36 | | |
37 | | namespace Line { |
38 | | |
39 | 0 | static constexpr u32 ctrl(char c) { return c & 0x3f; }Unexecuted instantiation: FuzzShell.cpp:Line::ctrl(char) Unexecuted instantiation: Parser.cpp:Line::ctrl(char) Unexecuted instantiation: AST.cpp:Line::ctrl(char) Unexecuted instantiation: ImmediateFunctions.cpp:Line::ctrl(char) Unexecuted instantiation: Formatter.cpp:Line::ctrl(char) Unexecuted instantiation: NodeVisitor.cpp:Line::ctrl(char) Unexecuted instantiation: PosixParser.cpp:Line::ctrl(char) Unexecuted instantiation: PosixLexer.cpp:Line::ctrl(char) Unexecuted instantiation: Shell.cpp:Line::ctrl(char) Unexecuted instantiation: Builtin.cpp:Line::ctrl(char) Unexecuted instantiation: Job.cpp:Line::ctrl(char) Unexecuted instantiation: URLHighlight.cpp:Line::ctrl(char) Unexecuted instantiation: Editor.cpp:Line::ctrl(char) Unexecuted instantiation: InternalFunctions.cpp:Line::ctrl(char) Unexecuted instantiation: KeyCallbackMachine.cpp:Line::ctrl(char) Unexecuted instantiation: FuzzShellPosix.cpp:Line::ctrl(char) |
40 | | |
41 | | struct KeyBinding { |
42 | | Vector<Key> keys; |
43 | | enum class Kind { |
44 | | InternalFunction, |
45 | | Insertion, |
46 | | } kind { Kind::InternalFunction }; |
47 | | ByteString binding; |
48 | | }; |
49 | | |
50 | | struct Configuration { |
51 | | enum RefreshBehavior { |
52 | | Lazy, |
53 | | Eager, |
54 | | }; |
55 | | enum OperationMode { |
56 | | Unset, |
57 | | Full, |
58 | | NoEscapeSequences, |
59 | | NonInteractive, |
60 | | }; |
61 | | enum SignalHandler { |
62 | | WithSignalHandlers, |
63 | | NoSignalHandlers, |
64 | | }; |
65 | | |
66 | | enum Flags : u32 { |
67 | | None = 0, |
68 | | BracketedPaste = 1, |
69 | | }; |
70 | | |
71 | | struct DefaultTextEditor { |
72 | | ByteString command; |
73 | | }; |
74 | | |
75 | | Configuration() |
76 | 0 | { |
77 | 0 | } |
78 | | |
79 | | template<typename Arg, typename... Rest> |
80 | | Configuration(Arg arg, Rest... rest) |
81 | 0 | : Configuration(rest...) |
82 | 0 | { |
83 | 0 | set(arg); |
84 | 0 | } Unexecuted instantiation: Line::Configuration::Configuration<Line::Configuration::RefreshBehavior, Line::Configuration::SignalHandler>(Line::Configuration::RefreshBehavior, Line::Configuration::SignalHandler) Unexecuted instantiation: Line::Configuration::Configuration<Line::Configuration::SignalHandler>(Line::Configuration::SignalHandler) |
85 | | |
86 | 0 | void set(RefreshBehavior refresh) { refresh_behavior = refresh; } |
87 | 0 | void set(OperationMode mode) { operation_mode = mode; } |
88 | 0 | void set(SignalHandler mode) { m_signal_mode = mode; } |
89 | 0 | void set(KeyBinding const& binding) { keybindings.append(binding); } |
90 | 0 | void set(DefaultTextEditor editor) { m_default_text_editor = move(editor.command); } |
91 | | void set(Flags flags) |
92 | 0 | { |
93 | 0 | enable_bracketed_paste = flags & Flags::BracketedPaste; |
94 | 0 | } |
95 | | |
96 | | static Configuration from_config(StringView libname = "line"sv); |
97 | | |
98 | | RefreshBehavior refresh_behavior { RefreshBehavior::Lazy }; |
99 | | SignalHandler m_signal_mode { SignalHandler::WithSignalHandlers }; |
100 | | OperationMode operation_mode { OperationMode::Unset }; |
101 | | Vector<KeyBinding> keybindings; |
102 | | ByteString m_default_text_editor {}; |
103 | | bool enable_bracketed_paste { false }; |
104 | | }; |
105 | | |
106 | | #define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \ |
107 | 0 | M(clear_screen) \ |
108 | 0 | M(cursor_left_character) \ |
109 | 0 | M(cursor_left_word) \ |
110 | 0 | M(cursor_left_nonspace_word) \ |
111 | 0 | M(cursor_right_character) \ |
112 | 0 | M(cursor_right_word) \ |
113 | 0 | M(cursor_right_nonspace_word) \ |
114 | 0 | M(enter_search) \ |
115 | 0 | M(search_character_backwards) \ |
116 | 0 | M(search_character_forwards) \ |
117 | 0 | M(erase_character_backwards) \ |
118 | 0 | M(erase_character_forwards) \ |
119 | 0 | M(erase_to_beginning) \ |
120 | 0 | M(erase_to_end) \ |
121 | 0 | M(erase_word_backwards) \ |
122 | 0 | M(finish_edit) \ |
123 | 0 | M(go_end) \ |
124 | 0 | M(go_home) \ |
125 | 0 | M(kill_line) \ |
126 | 0 | M(search_backwards) \ |
127 | 0 | M(search_forwards) \ |
128 | 0 | M(transpose_characters) \ |
129 | 0 | M(transpose_words) \ |
130 | 0 | M(insert_last_words) \ |
131 | 0 | M(insert_last_erased) \ |
132 | 0 | M(erase_alnum_word_backwards) \ |
133 | 0 | M(erase_alnum_word_forwards) \ |
134 | 0 | M(erase_spaces) \ |
135 | 0 | M(capitalize_word) \ |
136 | 0 | M(lowercase_word) \ |
137 | 0 | M(uppercase_word) \ |
138 | 0 | M(edit_in_external_editor) |
139 | | |
140 | | #define EDITOR_INTERNAL_FUNCTION(name) \ |
141 | 0 | [](auto& editor) { editor.name(); return false; }Unexecuted instantiation: Shell.cpp:auto Shell::Shell::setup_keybinds()::$_0::operator()(Line::Editor&) const::{lambda(auto:1&)#1}::operator()<Line::Editor>(Line::Editor&) constUnexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_0::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_1::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_2::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_3::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_4::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_5::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_6::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_7::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_8::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_9::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_10::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_11::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_12::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_13::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_14::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_15::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_16::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_17::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_18::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_19::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_20::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_21::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_22::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_23::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_24::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_25::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_26::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_27::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_28::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_29::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_30::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_31::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: Editor.cpp:auto Line::Editor::set_default_keybinds()::$_32::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_0::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_1::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_2::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_3::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_4::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_5::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_6::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_7::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_8::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_9::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_10::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_11::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_12::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_13::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_14::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_15::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_16::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_17::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_18::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_19::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_20::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_21::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_22::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_23::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_24::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_25::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_26::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_27::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_28::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_29::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_30::operator()<Line::Editor>(Line::Editor&) const Unexecuted instantiation: InternalFunctions.cpp:auto Line::Editor::find_internal_function(AK::StringView)::$_31::operator()<Line::Editor>(Line::Editor&) const |
142 | | |
143 | | class Editor : public Core::EventReceiver { |
144 | | C_OBJECT(Editor); |
145 | | |
146 | | public: |
147 | | enum class Error { |
148 | | ReadFailure, |
149 | | Empty, |
150 | | Eof, |
151 | | }; |
152 | | |
153 | | ~Editor(); |
154 | | |
155 | | Result<ByteString, Error> get_line(ByteString const& prompt); |
156 | | |
157 | | void initialize(); |
158 | | |
159 | | void refetch_default_termios(); |
160 | | |
161 | | void add_to_history(ByteString const& line); |
162 | | bool load_history(ByteString const& path); |
163 | | bool save_history(ByteString const& path); |
164 | 0 | auto const& history() const { return m_history; } |
165 | 0 | bool is_history_dirty() const { return m_history_dirty; } |
166 | | |
167 | | void register_key_input_callback(KeyBinding const&); |
168 | 0 | void register_key_input_callback(Vector<Key> keys, Function<bool(Editor&)> callback) { m_callback_machine.register_key_input_callback(move(keys), move(callback)); } |
169 | 0 | void register_key_input_callback(Key key, Function<bool(Editor&)> callback) { register_key_input_callback(Vector<Key> { key }, move(callback)); } |
170 | | |
171 | | static StringMetrics actual_rendered_string_metrics(StringView, RedBlackTree<u32, Optional<Style::Mask>> const& masks = {}, Optional<size_t> maximum_line_width = {}); |
172 | | static StringMetrics actual_rendered_string_metrics(Utf32View const&, RedBlackTree<u32, Optional<Style::Mask>> const& masks = {}, Optional<size_t> maximum_line_width = {}); |
173 | | |
174 | | Function<Vector<CompletionSuggestion>(Editor const&)> on_tab_complete; |
175 | | Function<void(Utf32View, Editor&)> on_paste; |
176 | | Function<void()> on_interrupt_handled; |
177 | | Function<void(Editor&)> on_display_refresh; |
178 | | |
179 | | static Function<bool(Editor&)> find_internal_function(StringView name); |
180 | | enum class CaseChangeOp { |
181 | | Lowercase, |
182 | | Uppercase, |
183 | | Capital, |
184 | | }; |
185 | | void case_change_word(CaseChangeOp); |
186 | | #define __ENUMERATE_EDITOR_INTERNAL_FUNCTION(name) \ |
187 | | void name(); |
188 | | |
189 | | ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(__ENUMERATE_EDITOR_INTERNAL_FUNCTION) |
190 | | |
191 | | #undef __ENUMERATE_EDITOR_INTERNAL_FUNCTION |
192 | | |
193 | | ErrorOr<void> interrupted(); |
194 | | ErrorOr<void> resized(); |
195 | | |
196 | 0 | size_t cursor() const { return m_cursor; } |
197 | | void set_cursor(size_t cursor) |
198 | 0 | { |
199 | 0 | if (cursor > m_buffer.size()) |
200 | 0 | cursor = m_buffer.size(); |
201 | 0 | m_cursor = cursor; |
202 | 0 | } |
203 | 0 | Vector<u32, 1024> const& buffer() const { return m_buffer; } |
204 | 0 | u32 buffer_at(size_t pos) const { return m_buffer.at(pos); } |
205 | 0 | ByteString line() const { return line(m_buffer.size()); } |
206 | | ByteString line(size_t up_to_index) const; |
207 | | |
208 | | // Only makes sense inside a character_input callback or on_* callback. |
209 | | void set_prompt(ByteString const& prompt) |
210 | 0 | { |
211 | 0 | if (m_cached_prompt_valid) |
212 | 0 | m_old_prompt_metrics = m_cached_prompt_metrics; |
213 | 0 | m_cached_prompt_valid = false; |
214 | 0 | m_cached_prompt_metrics = actual_rendered_string_metrics(prompt, {}); |
215 | 0 | m_new_prompt = prompt; |
216 | 0 | } |
217 | | |
218 | | void clear_line(); |
219 | | void insert(ByteString const&); |
220 | | void insert(StringView); |
221 | | void insert(Utf8View&); |
222 | | void insert(Utf32View const&); |
223 | | void insert(u32 const); |
224 | | void stylize(Span const&, Style const&); |
225 | | void strip_styles(bool strip_anchored = false); |
226 | | |
227 | | // Invariant Offset is an offset into the suggested data, hinting the editor what parts of the suggestion will not change |
228 | | // Static Offset is an offset into the token, signifying where the suggestions start |
229 | | // e.g. |
230 | | // foobar<suggestion initiated>, on_tab_complete returns "barx", "bary", "barz" |
231 | | // ^ ^ |
232 | | // +-|- static offset: the suggestions start here |
233 | | // +- invariant offset: the suggestions do not change up to here |
234 | | // |
235 | | void transform_suggestion_offsets(size_t& invariant_offset, size_t& static_offset, Span::Mode offset_mode = Span::ByteOriented) const; |
236 | | |
237 | 0 | const struct termios& termios() const { return m_termios; } |
238 | 0 | const struct termios& default_termios() const { return m_default_termios; } |
239 | | struct winsize terminal_size() const |
240 | 0 | { |
241 | 0 | winsize ws { (u16)m_num_lines, (u16)m_num_columns, 0, 0 }; |
242 | 0 | return ws; |
243 | 0 | } |
244 | | |
245 | | void finish() |
246 | 0 | { |
247 | 0 | m_finish = true; |
248 | 0 | } |
249 | | |
250 | 0 | bool is_editing() const { return m_is_editing; } |
251 | | |
252 | 0 | Utf32View const buffer_view() const { return { m_buffer.data(), m_buffer.size() }; } |
253 | | |
254 | | auto prohibit_input() |
255 | 0 | { |
256 | 0 | auto previous_value = m_prohibit_input_processing; |
257 | 0 | m_prohibit_input_processing = true; |
258 | 0 | m_have_unprocessed_read_event = false; |
259 | 0 | return ScopeGuard { |
260 | 0 | [this, previous_value] { |
261 | 0 | m_prohibit_input_processing = previous_value; |
262 | 0 | if (!m_prohibit_input_processing && m_have_unprocessed_read_event) |
263 | 0 | handle_read_event().release_value_but_fixme_should_propagate_errors(); |
264 | 0 | } |
265 | 0 | }; |
266 | 0 | } |
267 | | |
268 | | private: |
269 | | explicit Editor(Configuration configuration = Configuration::from_config()); |
270 | | |
271 | | void set_default_keybinds(); |
272 | | |
273 | | enum LoopExitCode { |
274 | | Exit = 0, |
275 | | Retry |
276 | | }; |
277 | | |
278 | | ErrorOr<void> try_update_once(); |
279 | | void handle_interrupt_event(); |
280 | | ErrorOr<void> handle_read_event(); |
281 | | ErrorOr<void> handle_resize_event(bool reset_origin); |
282 | | |
283 | | void ensure_free_lines_from_origin(size_t count); |
284 | | |
285 | | Result<Vector<size_t, 2>, Error> vt_dsr(); |
286 | | void remove_at_index(size_t); |
287 | | |
288 | | enum class ModificationKind { |
289 | | Insertion, |
290 | | Removal, |
291 | | ForcedOverlapRemoval, |
292 | | }; |
293 | | void readjust_anchored_styles(size_t hint_index, ModificationKind); |
294 | | |
295 | | Style find_applicable_style(size_t offset) const; |
296 | | |
297 | | bool search(StringView, bool allow_empty = false, bool from_beginning = true); |
298 | | inline void end_search() |
299 | 0 | { |
300 | 0 | m_is_searching = false; |
301 | 0 | m_refresh_needed = true; |
302 | 0 | m_search_offset = 0; |
303 | 0 | if (m_reset_buffer_on_search_end) { |
304 | 0 | m_buffer.clear(); |
305 | 0 | for (auto ch : m_pre_search_buffer) |
306 | 0 | m_buffer.append(ch); |
307 | 0 | m_cursor = m_pre_search_cursor; |
308 | 0 | } |
309 | 0 | m_reset_buffer_on_search_end = true; |
310 | 0 | m_search_editor = nullptr; |
311 | 0 | } |
312 | | |
313 | | void reset() |
314 | 0 | { |
315 | 0 | m_cached_buffer_metrics.reset(); |
316 | 0 | m_cached_prompt_valid = false; |
317 | 0 | m_cursor = 0; |
318 | 0 | m_drawn_cursor = 0; |
319 | 0 | m_inline_search_cursor = 0; |
320 | 0 | m_search_offset = 0; |
321 | 0 | m_search_offset_state = SearchOffsetState::Unbiased; |
322 | 0 | m_old_prompt_metrics = m_cached_prompt_metrics; |
323 | 0 | set_origin(0, 0); |
324 | 0 | m_prompt_lines_at_suggestion_initiation = 0; |
325 | 0 | m_refresh_needed = true; |
326 | 0 | m_input_error.clear(); |
327 | 0 | m_returned_line = ByteString::empty(); |
328 | 0 | m_chars_touched_in_the_middle = 0; |
329 | 0 | m_drawn_end_of_line_offset = 0; |
330 | 0 | m_drawn_spans = {}; |
331 | 0 | m_paste_buffer.clear_with_capacity(); |
332 | 0 | m_shown_lines = 0; |
333 | 0 | m_extra_forward_lines = 0; |
334 | 0 | } |
335 | | |
336 | | ErrorOr<void> refresh_display(); |
337 | | ErrorOr<void> cleanup(); |
338 | | ErrorOr<void> cleanup_suggestions(); |
339 | | ErrorOr<void> really_quit_event_loop(); |
340 | | |
341 | | void restore() |
342 | 0 | { |
343 | 0 | VERIFY(m_initialized); |
344 | 0 | tcsetattr(0, TCSANOW, &m_default_termios); |
345 | 0 | m_initialized = false; |
346 | 0 | if (m_configuration.enable_bracketed_paste) |
347 | 0 | warn("\x1b[?2004l"); |
348 | 0 | for (auto id : m_signal_handlers) |
349 | 0 | Core::EventLoop::unregister_signal(id); |
350 | 0 | } |
351 | | |
352 | | StringMetrics const& current_prompt_metrics() const |
353 | 0 | { |
354 | 0 | return m_cached_prompt_valid ? m_cached_prompt_metrics : m_old_prompt_metrics; |
355 | 0 | } |
356 | | |
357 | | size_t num_lines() const |
358 | 0 | { |
359 | 0 | return current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns); |
360 | 0 | } |
361 | | |
362 | | size_t cursor_line() const |
363 | 0 | { |
364 | 0 | auto cursor = m_drawn_cursor; |
365 | 0 | if (cursor > m_cursor) |
366 | 0 | cursor = m_cursor; |
367 | 0 | return current_prompt_metrics().lines_with_addition( |
368 | 0 | actual_rendered_string_metrics(buffer_view().substring_view(0, cursor), m_current_masks), |
369 | 0 | m_num_columns); |
370 | 0 | } |
371 | | |
372 | | size_t offset_in_line() const |
373 | 0 | { |
374 | 0 | auto cursor = m_drawn_cursor; |
375 | 0 | if (cursor > m_cursor) |
376 | 0 | cursor = m_cursor; |
377 | 0 | auto buffer_metrics = actual_rendered_string_metrics(buffer_view().substring_view(0, cursor), m_current_masks); |
378 | 0 | return current_prompt_metrics().offset_with_addition(buffer_metrics, m_num_columns); |
379 | 0 | } |
380 | | |
381 | | bool set_origin(bool quit_on_error = true) |
382 | 0 | { |
383 | 0 | auto position = vt_dsr(); |
384 | 0 | if (!position.is_error()) { |
385 | 0 | set_origin(position.value()[0], position.value()[1]); |
386 | 0 | return true; |
387 | 0 | } |
388 | 0 | if (quit_on_error && position.is_error()) { |
389 | 0 | m_input_error = position.error(); |
390 | 0 | finish(); |
391 | 0 | } |
392 | 0 | return false; |
393 | 0 | } |
394 | | |
395 | | void set_origin(int row, int col) |
396 | 0 | { |
397 | 0 | m_origin_row = row; |
398 | 0 | m_origin_column = col; |
399 | 0 | m_suggestion_display->set_origin(row, col, {}); |
400 | 0 | } |
401 | | |
402 | | void recalculate_origin(); |
403 | | ErrorOr<void> reposition_cursor(Stream&, bool to_end = false); |
404 | | |
405 | | struct CodepointRange { |
406 | | size_t start { 0 }; |
407 | | size_t end { 0 }; |
408 | | }; |
409 | | CodepointRange byte_offset_range_to_code_point_offset_range(size_t byte_start, size_t byte_end, size_t code_point_scan_offset, bool reverse = false) const; |
410 | | |
411 | | void get_terminal_size(); |
412 | | |
413 | | bool m_finish { false }; |
414 | | |
415 | | RefPtr<Editor> m_search_editor; |
416 | | bool m_is_searching { false }; |
417 | | bool m_reset_buffer_on_search_end { true }; |
418 | | size_t m_search_offset { 0 }; |
419 | | enum class SearchOffsetState { |
420 | | Unbiased, |
421 | | Backwards, |
422 | | Forwards, |
423 | | } m_search_offset_state { SearchOffsetState::Unbiased }; |
424 | | size_t m_pre_search_cursor { 0 }; |
425 | | Vector<u32, 1024> m_pre_search_buffer; |
426 | | |
427 | | Vector<u32, 1024> m_buffer; |
428 | | ByteBuffer m_pending_chars; |
429 | | Vector<char, 512> m_incomplete_data; |
430 | | Optional<Error> m_input_error; |
431 | | ByteString m_returned_line; |
432 | | |
433 | | size_t m_cursor { 0 }; |
434 | | size_t m_drawn_cursor { 0 }; |
435 | | size_t m_drawn_end_of_line_offset { 0 }; |
436 | | size_t m_inline_search_cursor { 0 }; |
437 | | size_t m_chars_touched_in_the_middle { 0 }; |
438 | | size_t m_times_tab_pressed { 0 }; |
439 | | size_t m_num_columns { 0 }; |
440 | | size_t m_num_lines { 1 }; |
441 | | size_t m_previous_num_columns { 0 }; |
442 | | size_t m_extra_forward_lines { 0 }; |
443 | | size_t m_shown_lines { 0 }; |
444 | | StringMetrics m_cached_prompt_metrics; |
445 | | StringMetrics m_old_prompt_metrics; |
446 | | StringMetrics m_cached_buffer_metrics; |
447 | | size_t m_prompt_lines_at_suggestion_initiation { 0 }; |
448 | | bool m_cached_prompt_valid { false }; |
449 | | |
450 | | // Exact position before our prompt in the terminal. |
451 | | size_t m_origin_row { 0 }; |
452 | | size_t m_origin_column { 0 }; |
453 | | bool m_expected_origin_changed { false }; |
454 | | bool m_has_origin_reset_scheduled { false }; |
455 | | |
456 | | OwnPtr<SuggestionDisplay> m_suggestion_display; |
457 | | Vector<u32, 32> m_remembered_suggestion_static_data; |
458 | | |
459 | | ByteString m_new_prompt; |
460 | | |
461 | | SuggestionManager m_suggestion_manager; |
462 | | |
463 | | bool m_always_refresh { false }; |
464 | | |
465 | | enum class TabDirection { |
466 | | Forward, |
467 | | Backward, |
468 | | }; |
469 | | TabDirection m_tab_direction { TabDirection::Forward }; |
470 | | |
471 | | KeyCallbackMachine m_callback_machine; |
472 | | |
473 | | struct termios m_termios {}; |
474 | | struct termios m_default_termios {}; |
475 | | bool m_was_interrupted { false }; |
476 | | bool m_previous_interrupt_was_handled_as_interrupt { true }; |
477 | | bool m_was_resized { false }; |
478 | | |
479 | | // FIXME: This should be something more take_first()-friendly. |
480 | | struct HistoryEntry { |
481 | | ByteString entry; |
482 | | time_t timestamp; |
483 | | }; |
484 | | Vector<HistoryEntry> m_history; |
485 | | size_t m_history_cursor { 0 }; |
486 | | size_t m_history_capacity { 1024 }; |
487 | | bool m_history_dirty { false }; |
488 | | static ErrorOr<Vector<HistoryEntry>> try_load_history(StringView path); |
489 | | |
490 | | enum class InputState { |
491 | | Free, |
492 | | Verbatim, |
493 | | Paste, |
494 | | GotEscape, |
495 | | SS3ExpectFinal, |
496 | | CSIExpectParameter, |
497 | | CSIExpectIntermediate, |
498 | | CSIExpectFinal, |
499 | | }; |
500 | | InputState m_state { InputState::Free }; |
501 | | InputState m_previous_free_state { InputState::Free }; |
502 | | |
503 | | struct Spans { |
504 | | HashMap<u32, HashMap<u32, Style>> m_spans_starting; |
505 | | HashMap<u32, HashMap<u32, Style>> m_spans_ending; |
506 | | HashMap<u32, HashMap<u32, Style>> m_anchored_spans_starting; |
507 | | HashMap<u32, HashMap<u32, Style>> m_anchored_spans_ending; |
508 | | |
509 | | bool contains_up_to_offset(Spans const& other, size_t offset) const; |
510 | | } m_drawn_spans, m_current_spans; |
511 | | |
512 | | RedBlackTree<u32, Optional<Style::Mask>> m_current_masks; |
513 | | |
514 | | RefPtr<Core::Notifier> m_notifier; |
515 | | |
516 | | Vector<u32> m_paste_buffer; |
517 | | Vector<u32> m_last_erased; |
518 | | |
519 | | bool m_initialized { false }; |
520 | | bool m_refresh_needed { false }; |
521 | | Vector<int, 2> m_signal_handlers; |
522 | | |
523 | | bool m_is_editing { false }; |
524 | | bool m_prohibit_input_processing { false }; |
525 | | bool m_have_unprocessed_read_event { false }; |
526 | | |
527 | | Configuration m_configuration; |
528 | | }; |
529 | | |
530 | | } |