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