Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibLine/Editor.cpp
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
#include "Editor.h"
9
#include <AK/CharacterTypes.h>
10
#include <AK/Debug.h>
11
#include <AK/GenericLexer.h>
12
#include <AK/JsonObject.h>
13
#include <AK/MemoryStream.h>
14
#include <AK/RedBlackTree.h>
15
#include <AK/ScopeGuard.h>
16
#include <AK/ScopedValueRollback.h>
17
#include <AK/StringBuilder.h>
18
#include <AK/Utf32View.h>
19
#include <AK/Utf8View.h>
20
#include <LibCore/ConfigFile.h>
21
#include <LibCore/Event.h>
22
#include <LibCore/EventLoop.h>
23
#include <LibCore/Notifier.h>
24
#include <LibFileSystem/FileSystem.h>
25
#include <LibFileSystem/TempFile.h>
26
#include <LibUnicode/Segmentation.h>
27
#include <errno.h>
28
#include <fcntl.h>
29
#include <signal.h>
30
#include <stdio.h>
31
#include <sys/ioctl.h>
32
#include <sys/select.h>
33
#include <sys/time.h>
34
#include <unistd.h>
35
36
namespace Line {
37
38
Configuration Configuration::from_config(StringView libname)
39
0
{
40
0
    Configuration configuration;
41
0
    auto config_file = Core::ConfigFile::open_for_lib(libname).release_value_but_fixme_should_propagate_errors();
42
43
    // Read behavior options.
44
0
    auto refresh = config_file->read_entry("behavior", "refresh", "lazy");
45
0
    auto operation = config_file->read_entry("behavior", "operation_mode");
46
0
    auto bracketed_paste = config_file->read_bool_entry("behavior", "bracketed_paste", true);
47
0
    auto default_text_editor = config_file->read_entry("behavior", "default_text_editor");
48
49
0
    Configuration::Flags flags { Configuration::Flags::None };
50
0
    if (bracketed_paste)
51
0
        flags = static_cast<Flags>(flags | Configuration::Flags::BracketedPaste);
52
53
0
    configuration.set(flags);
54
55
0
    if (refresh.equals_ignoring_ascii_case("lazy"sv))
56
0
        configuration.set(Configuration::Lazy);
57
0
    else if (refresh.equals_ignoring_ascii_case("eager"sv))
58
0
        configuration.set(Configuration::Eager);
59
60
0
    if (operation.equals_ignoring_ascii_case("full"sv))
61
0
        configuration.set(Configuration::OperationMode::Full);
62
0
    else if (operation.equals_ignoring_ascii_case("noescapesequences"sv))
63
0
        configuration.set(Configuration::OperationMode::NoEscapeSequences);
64
0
    else if (operation.equals_ignoring_ascii_case("noninteractive"sv))
65
0
        configuration.set(Configuration::OperationMode::NonInteractive);
66
0
    else
67
0
        configuration.set(Configuration::OperationMode::Unset);
68
69
0
    if (!default_text_editor.is_empty())
70
0
        configuration.set(DefaultTextEditor { move(default_text_editor) });
71
0
    else
72
0
        configuration.set(DefaultTextEditor { "/bin/TextEditor" });
73
74
    // Read keybinds.
75
76
0
    for (auto& binding_key : config_file->keys("keybinds")) {
77
0
        GenericLexer key_lexer(binding_key);
78
0
        auto has_ctrl = false;
79
0
        auto alt = false;
80
0
        auto escape = false;
81
0
        Vector<Key> keys;
82
83
0
        while (!key_lexer.is_eof()) {
84
0
            unsigned key;
85
0
            if (escape) {
86
0
                key = key_lexer.consume_escaped_character();
87
0
                escape = false;
88
0
            } else {
89
0
                if (key_lexer.next_is("alt+")) {
90
0
                    alt = key_lexer.consume_specific("alt+"sv);
91
0
                    continue;
92
0
                }
93
0
                if (key_lexer.next_is("^[")) {
94
0
                    alt = key_lexer.consume_specific("^["sv);
95
0
                    continue;
96
0
                }
97
0
                if (key_lexer.next_is("^")) {
98
0
                    has_ctrl = key_lexer.consume_specific("^"sv);
99
0
                    continue;
100
0
                }
101
0
                if (key_lexer.next_is("ctrl+")) {
102
0
                    has_ctrl = key_lexer.consume_specific("ctrl+"sv);
103
0
                    continue;
104
0
                }
105
0
                if (key_lexer.next_is("\\")) {
106
0
                    escape = true;
107
0
                    continue;
108
0
                }
109
                // FIXME: Support utf?
110
0
                key = key_lexer.consume();
111
0
            }
112
0
            if (has_ctrl)
113
0
                key = ctrl(key);
114
115
0
            keys.append(Key { key, alt ? Key::Alt : Key::None });
116
0
            alt = false;
117
0
            has_ctrl = false;
118
0
        }
119
120
0
        GenericLexer value_lexer { config_file->read_entry("keybinds", binding_key) };
121
0
        StringBuilder value_builder;
122
0
        while (!value_lexer.is_eof())
123
0
            value_builder.append(value_lexer.consume_escaped_character());
124
0
        auto value = value_builder.string_view();
125
0
        if (value.starts_with("internal:"sv)) {
126
0
            configuration.set(KeyBinding {
127
0
                keys,
128
0
                KeyBinding::Kind::InternalFunction,
129
0
                value.substring_view(9, value.length() - 9) });
130
0
        } else {
131
0
            configuration.set(KeyBinding {
132
0
                keys,
133
0
                KeyBinding::Kind::Insertion,
134
0
                value });
135
0
        }
136
0
    }
137
138
0
    return configuration;
139
0
}
140
141
void Editor::set_default_keybinds()
142
0
{
143
0
    register_key_input_callback(ctrl('N'), EDITOR_INTERNAL_FUNCTION(search_forwards));
144
0
    register_key_input_callback(ctrl('P'), EDITOR_INTERNAL_FUNCTION(search_backwards));
145
0
    register_key_input_callback(ctrl('A'), EDITOR_INTERNAL_FUNCTION(go_home));
146
0
    register_key_input_callback(ctrl('B'), EDITOR_INTERNAL_FUNCTION(cursor_left_character));
147
0
    register_key_input_callback(ctrl('D'), EDITOR_INTERNAL_FUNCTION(erase_character_forwards));
148
0
    register_key_input_callback(ctrl('E'), EDITOR_INTERNAL_FUNCTION(go_end));
149
0
    register_key_input_callback(ctrl('F'), EDITOR_INTERNAL_FUNCTION(cursor_right_character));
150
    // ^H: ctrl('H') == '\b'
151
0
    register_key_input_callback(ctrl('H'), EDITOR_INTERNAL_FUNCTION(erase_character_backwards));
152
    // DEL - Some terminals send this instead of ^H.
153
0
    register_key_input_callback((char)127, EDITOR_INTERNAL_FUNCTION(erase_character_backwards));
154
0
    register_key_input_callback(ctrl('K'), EDITOR_INTERNAL_FUNCTION(erase_to_end));
155
0
    register_key_input_callback(ctrl('L'), EDITOR_INTERNAL_FUNCTION(clear_screen));
156
0
    register_key_input_callback(ctrl('R'), EDITOR_INTERNAL_FUNCTION(enter_search));
157
0
    register_key_input_callback(ctrl(']'), EDITOR_INTERNAL_FUNCTION(search_character_forwards));
158
0
    register_key_input_callback(Key { ctrl(']'), Key::Alt }, EDITOR_INTERNAL_FUNCTION(search_character_backwards));
159
0
    register_key_input_callback(ctrl('T'), EDITOR_INTERNAL_FUNCTION(transpose_characters));
160
0
    register_key_input_callback('\n', EDITOR_INTERNAL_FUNCTION(finish));
161
162
    // ^X^E: Edit in external editor
163
0
    register_key_input_callback(Vector<Key> { ctrl('X'), ctrl('E') }, EDITOR_INTERNAL_FUNCTION(edit_in_external_editor));
164
165
    // ^[.: alt-.: insert last arg of previous command (similar to `!$`)
166
0
    register_key_input_callback(Key { '.', Key::Alt }, EDITOR_INTERNAL_FUNCTION(insert_last_words));
167
0
    register_key_input_callback(ctrl('Y'), EDITOR_INTERNAL_FUNCTION(insert_last_erased));
168
0
    register_key_input_callback(Key { 'b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_word));
169
0
    register_key_input_callback(Key { 'f', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_right_word));
170
0
    register_key_input_callback(Key { ctrl('B'), Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_nonspace_word));
171
0
    register_key_input_callback(Key { ctrl('F'), Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_right_nonspace_word));
172
    // ^[^H: alt-backspace: backward delete word
173
0
    register_key_input_callback(Key { '\b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(erase_alnum_word_backwards));
174
0
    register_key_input_callback(Key { 'd', Key::Alt }, EDITOR_INTERNAL_FUNCTION(erase_alnum_word_forwards));
175
0
    register_key_input_callback(Key { '\\', Key::Alt }, EDITOR_INTERNAL_FUNCTION(erase_spaces));
176
0
    register_key_input_callback(Key { 'c', Key::Alt }, EDITOR_INTERNAL_FUNCTION(capitalize_word));
177
0
    register_key_input_callback(Key { 'l', Key::Alt }, EDITOR_INTERNAL_FUNCTION(lowercase_word));
178
0
    register_key_input_callback(Key { 'u', Key::Alt }, EDITOR_INTERNAL_FUNCTION(uppercase_word));
179
0
    register_key_input_callback(Key { 't', Key::Alt }, EDITOR_INTERNAL_FUNCTION(transpose_words));
180
181
    // Register these last to all the user to override the previous key bindings
182
    // Normally ^W. `stty werase \^n` can change it to ^N (or something else).
183
0
    register_key_input_callback(m_termios.c_cc[VWERASE], EDITOR_INTERNAL_FUNCTION(erase_word_backwards));
184
    // Normally ^U. `stty kill \^n` can change it to ^N (or something else).
185
0
    register_key_input_callback(m_termios.c_cc[VKILL], EDITOR_INTERNAL_FUNCTION(kill_line));
186
0
    register_key_input_callback(m_termios.c_cc[VERASE], EDITOR_INTERNAL_FUNCTION(erase_character_backwards));
187
0
}
188
189
Editor::Editor(Configuration configuration)
190
0
    : m_configuration(move(configuration))
191
0
{
192
0
    m_always_refresh = m_configuration.refresh_behavior == Configuration::RefreshBehavior::Eager;
193
0
    m_pending_chars = {};
194
0
    get_terminal_size();
195
0
    m_suggestion_display = make<XtermSuggestionDisplay>(m_num_lines, m_num_columns);
196
0
}
197
198
Editor::~Editor()
199
0
{
200
0
    if (m_initialized)
201
0
        restore();
202
0
}
203
204
void Editor::ensure_free_lines_from_origin(size_t count)
205
0
{
206
0
    if (count > m_num_lines) {
207
        // FIXME: Implement paging
208
0
    }
209
210
0
    if (m_origin_row + count <= m_num_lines)
211
0
        return;
212
213
0
    auto diff = m_origin_row + count - m_num_lines - 1;
214
0
    out(stderr, "\x1b[{}S", diff);
215
0
    fflush(stderr);
216
0
    m_origin_row -= diff;
217
0
    m_refresh_needed = false;
218
0
    m_chars_touched_in_the_middle = 0;
219
0
}
220
221
void Editor::get_terminal_size()
222
0
{
223
0
    struct winsize ws;
224
0
    ioctl(STDERR_FILENO, TIOCGWINSZ, &ws);
225
0
    if (ws.ws_col == 0 || ws.ws_row == 0) {
226
        // LLDB uses ttys which "work" and then gives us a zero sized
227
        // terminal which is far from useful
228
0
        if (int fd = open("/dev/tty", O_RDONLY); fd != -1) {
229
0
            ioctl(fd, TIOCGWINSZ, &ws);
230
0
            close(fd);
231
0
        }
232
0
    }
233
0
    m_num_columns = ws.ws_col;
234
0
    m_num_lines = ws.ws_row;
235
0
}
236
237
void Editor::add_to_history(ByteString const& line)
238
0
{
239
0
    if (line.is_empty())
240
0
        return;
241
0
    ByteString histcontrol = getenv("HISTCONTROL");
242
0
    auto ignoredups = histcontrol == "ignoredups" || histcontrol == "ignoreboth";
243
0
    auto ignorespace = histcontrol == "ignorespace" || histcontrol == "ignoreboth";
244
0
    if (ignoredups && !m_history.is_empty() && line == m_history.last().entry)
245
0
        return;
246
0
    if (ignorespace && line.starts_with(' '))
247
0
        return;
248
0
    if ((m_history.size() + 1) > m_history_capacity)
249
0
        m_history.take_first();
250
0
    struct timeval tv;
251
0
    gettimeofday(&tv, nullptr);
252
0
    m_history.append({ line, tv.tv_sec });
253
0
    m_history_dirty = true;
254
0
}
255
256
ErrorOr<Vector<Editor::HistoryEntry>> Editor::try_load_history(StringView path)
257
0
{
258
0
    auto history_file_or_error = Core::File::open(path, Core::File::OpenMode::Read);
259
260
    // We ignore "No such file or directory" errors, as that is just equivalent to an empty history.
261
0
    if (history_file_or_error.is_error() && history_file_or_error.error().is_errno() && history_file_or_error.error().code() == ENOENT)
262
0
        return Vector<Editor::HistoryEntry> {};
263
264
0
    auto history_file = history_file_or_error.release_value();
265
0
    auto data = TRY(history_file->read_until_eof());
266
0
    auto hist = StringView { data };
267
0
    Vector<HistoryEntry> history;
268
0
    for (auto& str : hist.split_view("\n\n"sv)) {
269
0
        auto it = str.find("::"sv).value_or(0);
270
0
        auto time = str.substring_view(0, it).to_number<time_t>().value_or(0);
271
0
        auto string = str.substring_view(it == 0 ? it : it + 2);
272
0
        history.append({ string, time });
273
0
    }
274
0
    return history;
275
0
}
276
277
bool Editor::load_history(ByteString const& path)
278
0
{
279
0
    auto history_or_error = try_load_history(path);
280
0
    if (history_or_error.is_error())
281
0
        return false;
282
0
    auto maybe_error = m_history.try_extend(history_or_error.release_value());
283
0
    auto okay = !maybe_error.is_error();
284
0
    return okay;
285
0
}
286
287
template<typename It0, typename It1, typename OutputT, typename LessThan>
288
static void merge(It0&& begin0, It0 const& end0, It1&& begin1, It1 const& end1, OutputT& output, LessThan less_than)
289
0
{
290
0
    for (;;) {
291
0
        if (begin0 == end0 && begin1 == end1)
292
0
            return;
293
294
0
        if (begin0 == end0) {
295
0
            auto&& right = *begin1;
296
0
            if (output.last().entry != right.entry)
297
0
                output.append(right);
298
0
            ++begin1;
299
0
            continue;
300
0
        }
301
302
0
        auto&& left = *begin0;
303
0
        if (left.entry.is_whitespace()) {
304
0
            ++begin0;
305
0
            continue;
306
0
        }
307
0
        if (begin1 == end1) {
308
0
            if (output.last().entry != left.entry)
309
0
                output.append(left);
310
0
            ++begin0;
311
0
            continue;
312
0
        }
313
314
0
        auto&& right = *begin1;
315
0
        if (less_than(left, right)) {
316
0
            if (output.last().entry != left.entry)
317
0
                output.append(left);
318
0
            ++begin0;
319
0
        } else {
320
0
            if (output.last().entry != right.entry)
321
0
                output.append(right);
322
0
            ++begin1;
323
0
            if (right.entry == left.entry)
324
0
                ++begin0;
325
0
        }
326
0
    }
327
0
}
328
329
bool Editor::save_history(ByteString const& path)
330
0
{
331
    // Note: Use a dummy entry to simplify merging.
332
0
    Vector<HistoryEntry> final_history { { "", 0 } };
333
0
    {
334
0
        auto history_or_error = try_load_history(path);
335
0
        if (history_or_error.is_error())
336
0
            return false;
337
0
        Vector<HistoryEntry> old_history = history_or_error.release_value();
338
0
        merge(
339
0
            old_history.begin(), old_history.end(),
340
0
            m_history.begin(), m_history.end(),
341
0
            final_history,
342
0
            [](HistoryEntry const& left, HistoryEntry const& right) { return left.timestamp < right.timestamp; });
343
0
    }
344
345
0
    auto temp_or_error = FileSystem::TempFile::create_temp_file();
346
0
    if (temp_or_error.is_error())
347
0
        return false;
348
349
0
    auto temp_file = temp_or_error.release_value();
350
351
0
    {
352
0
        auto file_or_error = Core::File::open(temp_file->path(), Core::File::OpenMode::Write, 0600);
353
0
        if (file_or_error.is_error())
354
0
            return false;
355
0
        auto file = file_or_error.release_value();
356
        // Skip the dummy entry:
357
0
        for (auto iter = final_history.begin() + 1; iter != final_history.end(); ++iter) {
358
0
            auto const& entry = *iter;
359
0
            auto buffer = ByteString::formatted("{}::{}\n\n", entry.timestamp, entry.entry);
360
0
            auto maybe_error = file->write_until_depleted(buffer.bytes());
361
0
            if (maybe_error.is_error())
362
0
                return false;
363
0
        }
364
0
    }
365
366
0
    auto result = FileSystem::copy_file_or_directory(path, temp_file->path(), FileSystem::RecursionMode::Disallowed, FileSystem::LinkMode::Disallowed, FileSystem::AddDuplicateFileMarker::No);
367
0
    if (result.is_error())
368
0
        return false;
369
370
0
    m_history_dirty = false;
371
0
    return true;
372
0
}
373
374
void Editor::clear_line()
375
0
{
376
0
    for (size_t i = 0; i < m_cursor; ++i)
377
0
        fputc(0x8, stderr);
378
0
    fputs("\033[K", stderr);
379
0
    fflush(stderr);
380
0
    m_chars_touched_in_the_middle = buffer().size();
381
0
    m_buffer.clear();
382
0
    m_cursor = 0;
383
0
    m_inline_search_cursor = m_cursor;
384
0
}
385
386
void Editor::insert(Utf32View const& string)
387
0
{
388
0
    for (size_t i = 0; i < string.length(); ++i)
389
0
        insert(string.code_points()[i]);
390
0
}
391
392
void Editor::insert(ByteString const& string)
393
0
{
394
0
    for (auto ch : Utf8View { string })
395
0
        insert(ch);
396
0
}
397
398
void Editor::insert(StringView string_view)
399
0
{
400
0
    auto view = Utf8View { string_view };
401
0
    insert(view);
402
0
}
403
404
void Editor::insert(Utf8View& view)
405
0
{
406
0
    for (auto ch : view)
407
0
        insert(ch);
408
0
}
409
410
void Editor::insert(u32 const cp)
411
0
{
412
0
    StringBuilder builder;
413
0
    builder.append(Utf32View(&cp, 1));
414
0
    auto str = builder.to_byte_string();
415
0
    if (m_pending_chars.try_append(str.characters(), str.length()).is_error())
416
0
        return;
417
418
0
    readjust_anchored_styles(m_cursor, ModificationKind::Insertion);
419
420
0
    if (m_cursor == m_buffer.size()) {
421
0
        m_buffer.append(cp);
422
0
        m_cursor = m_buffer.size();
423
0
        m_inline_search_cursor = m_cursor;
424
0
        return;
425
0
    }
426
427
0
    m_buffer.insert(m_cursor, cp);
428
0
    ++m_chars_touched_in_the_middle;
429
0
    ++m_cursor;
430
0
    m_inline_search_cursor = m_cursor;
431
0
}
432
433
void Editor::register_key_input_callback(KeyBinding const& binding)
434
0
{
435
0
    if (binding.kind == KeyBinding::Kind::InternalFunction) {
436
0
        auto internal_function = find_internal_function(binding.binding);
437
0
        if (!internal_function) {
438
0
            dbgln("LibLine: Unknown internal function '{}'", binding.binding);
439
0
            return;
440
0
        }
441
0
        return register_key_input_callback(binding.keys, move(internal_function));
442
0
    }
443
444
0
    return register_key_input_callback(binding.keys, [binding = ByteString(binding.binding)](auto& editor) {
445
0
        editor.insert(binding);
446
0
        return false;
447
0
    });
448
0
}
449
450
static size_t code_point_length_in_utf8(u32 code_point)
451
0
{
452
0
    if (code_point <= 0x7f)
453
0
        return 1;
454
0
    if (code_point <= 0x07ff)
455
0
        return 2;
456
0
    if (code_point <= 0xffff)
457
0
        return 3;
458
0
    if (code_point <= 0x10ffff)
459
0
        return 4;
460
0
    return 3;
461
0
}
462
463
// buffer [ 0 1 2 3 . . . A . . . B . . . M . . . N ]
464
//                        ^       ^       ^       ^
465
//                        |       |       |       +- end of buffer
466
//                        |       |       +- scan offset = M
467
//                        |       +- range end = M - B
468
//                        +- range start = M - A
469
// This method converts a byte range defined by [start_byte_offset, end_byte_offset] to a code_point range [M - A, M - B] as shown in the diagram above.
470
// If `reverse' is true, A and B are before M, if not, A and B are after M.
471
Editor::CodepointRange Editor::byte_offset_range_to_code_point_offset_range(size_t start_byte_offset, size_t end_byte_offset, size_t scan_code_point_offset, bool reverse) const
472
0
{
473
0
    size_t byte_offset = 0;
474
0
    size_t code_point_offset = scan_code_point_offset + (reverse ? 1 : 0);
475
0
    CodepointRange range;
476
477
0
    for (;;) {
478
0
        if (!reverse) {
479
0
            if (code_point_offset >= m_buffer.size())
480
0
                break;
481
0
        } else {
482
0
            if (code_point_offset == 0)
483
0
                break;
484
0
        }
485
486
0
        if (byte_offset > end_byte_offset)
487
0
            break;
488
489
0
        if (byte_offset < start_byte_offset)
490
0
            ++range.start;
491
492
0
        if (byte_offset < end_byte_offset)
493
0
            ++range.end;
494
495
0
        byte_offset += code_point_length_in_utf8(m_buffer[reverse ? --code_point_offset : code_point_offset++]);
496
0
    }
497
498
0
    return range;
499
0
}
500
501
void Editor::stylize(Span const& span, Style const& style)
502
0
{
503
0
    if (!span.is_empty())
504
0
        return;
505
0
    if (style.is_empty())
506
0
        return;
507
508
0
    auto start = span.beginning();
509
0
    auto end = span.end();
510
511
0
    if (span.mode() == Span::ByteOriented) {
512
0
        auto offsets = byte_offset_range_to_code_point_offset_range(start, end, 0);
513
514
0
        start = offsets.start;
515
0
        end = offsets.end;
516
0
    }
517
518
0
    if (auto maybe_mask = style.mask(); maybe_mask.has_value()) {
519
0
        auto it = m_current_masks.find_smallest_not_below_iterator(span.beginning());
520
0
        Optional<Style::Mask> last_encountered_entry;
521
0
        if (!it.is_end()) {
522
            // Delete all overlapping old masks.
523
0
            while (true) {
524
0
                auto next_it = m_current_masks.find_largest_not_above_iterator(span.end());
525
0
                if (next_it.is_end())
526
0
                    break;
527
0
                if (it->has_value())
528
0
                    last_encountered_entry = *it;
529
0
                m_current_masks.remove(next_it.key());
530
0
            }
531
0
        }
532
0
        m_current_masks.insert(span.beginning(), move(maybe_mask));
533
0
        m_current_masks.insert(span.end(), {});
534
0
        if (last_encountered_entry.has_value())
535
0
            m_current_masks.insert(span.end() + 1, move(last_encountered_entry));
536
0
        style.unset_mask();
537
0
    }
538
539
0
    auto& spans_starting = style.is_anchored() ? m_current_spans.m_anchored_spans_starting : m_current_spans.m_spans_starting;
540
0
    auto& spans_ending = style.is_anchored() ? m_current_spans.m_anchored_spans_ending : m_current_spans.m_spans_ending;
541
542
0
    auto& starting_map = spans_starting.ensure(start);
543
0
    if (!starting_map.contains(end))
544
0
        m_refresh_needed = true;
545
0
    starting_map.set(end, style);
546
547
0
    auto& ending_map = spans_ending.ensure(end);
548
0
    if (!ending_map.contains(start))
549
0
        m_refresh_needed = true;
550
0
    ending_map.set(start, style);
551
0
}
552
553
void Editor::transform_suggestion_offsets(size_t& invariant_offset, size_t& static_offset, Span::Mode offset_mode) const
554
0
{
555
0
    auto internal_static_offset = static_offset;
556
0
    auto internal_invariant_offset = invariant_offset;
557
0
    if (offset_mode == Span::Mode::ByteOriented) {
558
        // FIXME: We're assuming that invariant_offset points to the end of the available data
559
        //        this is not necessarily true, but is true in most cases.
560
0
        auto offsets = byte_offset_range_to_code_point_offset_range(internal_static_offset, internal_invariant_offset + internal_static_offset, m_cursor - 1, true);
561
562
0
        internal_static_offset = offsets.start;
563
0
        internal_invariant_offset = offsets.end - offsets.start;
564
0
    }
565
0
    invariant_offset = internal_invariant_offset;
566
0
    static_offset = internal_static_offset;
567
0
}
568
569
void Editor::initialize()
570
0
{
571
0
    if (m_initialized)
572
0
        return;
573
574
0
    struct termios termios;
575
0
    tcgetattr(0, &termios);
576
0
    m_default_termios = termios; // grab a copy to restore
577
578
0
    get_terminal_size();
579
580
0
    if (m_configuration.operation_mode == Configuration::Unset) {
581
0
        auto istty = isatty(STDIN_FILENO) && isatty(STDERR_FILENO);
582
0
        if (!istty) {
583
0
            m_configuration.set(Configuration::NonInteractive);
584
0
        } else {
585
0
            auto* term = getenv("TERM");
586
0
            if ((term != NULL) && StringView { term, strlen(term) }.starts_with("xterm"sv))
587
0
                m_configuration.set(Configuration::Full);
588
0
            else
589
0
                m_configuration.set(Configuration::NoEscapeSequences);
590
0
        }
591
0
    }
592
593
    // Because we use our own line discipline which includes echoing,
594
    // we disable ICANON and ECHO.
595
0
    if (m_configuration.operation_mode == Configuration::Full) {
596
0
        termios.c_lflag &= ~(ECHO | ICANON);
597
0
        tcsetattr(0, TCSANOW, &termios);
598
0
    }
599
600
0
    m_termios = termios;
601
602
0
    set_default_keybinds();
603
0
    for (auto& keybind : m_configuration.keybindings)
604
0
        register_key_input_callback(keybind);
605
606
0
    if (m_configuration.m_signal_mode == Configuration::WithSignalHandlers) {
607
0
        m_signal_handlers.append(Core::EventLoop::register_signal(SIGINT, [this](int) {
608
0
            Core::EventLoop::current().deferred_invoke([this] { interrupted().release_value_but_fixme_should_propagate_errors(); });
609
0
        }));
610
611
0
        m_signal_handlers.append(Core::EventLoop::register_signal(SIGWINCH, [this](int) {
612
0
            Core::EventLoop::current().deferred_invoke([this] { resized().release_value_but_fixme_should_propagate_errors(); });
613
0
        }));
614
0
    }
615
616
0
    m_initialized = true;
617
0
}
618
619
void Editor::refetch_default_termios()
620
0
{
621
0
    struct termios termios;
622
0
    tcgetattr(0, &termios);
623
0
    m_default_termios = termios;
624
0
    if (m_configuration.operation_mode == Configuration::Full)
625
0
        termios.c_lflag &= ~(ECHO | ICANON);
626
0
    m_termios = termios;
627
0
}
628
629
ErrorOr<void> Editor::interrupted()
630
0
{
631
0
    if (m_is_searching)
632
0
        return m_search_editor->interrupted();
633
634
0
    if (!m_is_editing)
635
0
        return {};
636
637
0
    m_was_interrupted = true;
638
0
    handle_interrupt_event();
639
0
    if (!m_finish || !m_previous_interrupt_was_handled_as_interrupt)
640
0
        return {};
641
642
0
    m_finish = false;
643
0
    {
644
0
        auto stderr_stream = TRY(Core::File::standard_error());
645
0
        TRY(reposition_cursor(*stderr_stream, true));
646
0
        if (TRY(m_suggestion_display->cleanup())) {
647
0
            TRY(reposition_cursor(*stderr_stream, true));
648
0
            TRY(cleanup_suggestions());
649
0
        }
650
0
        TRY(stderr_stream->write_until_depleted("\r"sv.bytes()));
651
0
    }
652
0
    m_buffer.clear();
653
0
    m_chars_touched_in_the_middle = buffer().size();
654
0
    m_is_editing = false;
655
0
    restore();
656
0
    m_notifier->set_enabled(false);
657
0
    m_notifier = nullptr;
658
0
    Core::EventLoop::current().quit(Retry);
659
0
    return {};
660
0
}
661
662
ErrorOr<void> Editor::resized()
663
0
{
664
0
    m_was_resized = true;
665
0
    m_previous_num_columns = m_num_columns;
666
0
    auto old_origin_row = m_origin_row;
667
0
    auto old_origin_column = m_origin_column;
668
669
0
    get_terminal_size();
670
671
0
    if (!m_has_origin_reset_scheduled) {
672
        // Reset the origin, but make sure it doesn't blow up if we can't read it
673
0
        if (set_origin(false)) {
674
            // The origin we have right now actually points to where the cursor should be (in the middle of the buffer somewhere)
675
            // Find the "true" origin.
676
0
            auto current_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
677
0
            auto lines = m_cached_prompt_metrics.lines_with_addition(current_buffer_metrics, m_num_columns);
678
0
            auto offset = m_cached_prompt_metrics.offset_with_addition(current_buffer_metrics, m_num_columns);
679
0
            if (lines > m_origin_row)
680
0
                m_origin_row = 1;
681
0
            else
682
0
                m_origin_row -= lines - 1; // the prompt and the origin share a line.
683
684
0
            if (offset > m_origin_column)
685
0
                m_origin_column = 1;
686
0
            else
687
0
                m_origin_column -= offset;
688
689
0
            set_origin(m_origin_row, m_origin_column);
690
691
0
            TRY(handle_resize_event(false));
692
0
            if (old_origin_column != m_origin_column || old_origin_row != m_origin_row) {
693
0
                m_expected_origin_changed = true;
694
0
                deferred_invoke([this] {
695
0
                    (void)refresh_display();
696
0
                });
697
0
            }
698
0
        } else {
699
0
            deferred_invoke([this] { handle_resize_event(true).release_value_but_fixme_should_propagate_errors(); });
700
0
            m_has_origin_reset_scheduled = true;
701
0
        }
702
0
    }
703
704
0
    return {};
705
0
}
706
707
ErrorOr<void> Editor::handle_resize_event(bool reset_origin)
708
0
{
709
0
    if (!m_initialized || !m_is_editing)
710
0
        return {};
711
712
0
    m_has_origin_reset_scheduled = false;
713
0
    if (reset_origin && !set_origin(false)) {
714
0
        m_has_origin_reset_scheduled = true;
715
0
        deferred_invoke([this] { handle_resize_event(true).release_value_but_fixme_should_propagate_errors(); });
716
0
        return {};
717
0
    }
718
719
0
    set_origin(m_origin_row, 1);
720
721
0
    auto stderr_stream = TRY(Core::File::standard_error());
722
723
0
    TRY(reposition_cursor(*stderr_stream, true));
724
0
    TRY(m_suggestion_display->redisplay(m_suggestion_manager, m_num_lines, m_num_columns));
725
0
    m_origin_row = m_suggestion_display->origin_row();
726
0
    TRY(reposition_cursor(*stderr_stream));
727
728
0
    if (m_is_searching)
729
0
        TRY(m_search_editor->resized());
730
731
0
    return {};
732
0
}
733
734
ErrorOr<void> Editor::really_quit_event_loop()
735
0
{
736
0
    m_finish = false;
737
0
    {
738
0
        auto stderr_stream = TRY(Core::File::standard_error());
739
0
        TRY(reposition_cursor(*stderr_stream, true));
740
0
        TRY(stderr_stream->write_until_depleted("\n"sv.bytes()));
741
0
    }
742
0
    auto string = line();
743
0
    m_buffer.clear();
744
0
    m_chars_touched_in_the_middle = buffer().size();
745
0
    m_is_editing = false;
746
747
0
    if (m_initialized)
748
0
        restore();
749
750
0
    m_returned_line = string;
751
0
    m_notifier->set_enabled(false);
752
0
    m_notifier = nullptr;
753
0
    Core::EventLoop::current().quit(Exit);
754
0
    return {};
755
0
}
756
757
auto Editor::get_line(ByteString const& prompt) -> Result<ByteString, Editor::Error>
758
0
{
759
0
    initialize();
760
0
    m_is_editing = true;
761
762
0
    if (m_configuration.operation_mode == Configuration::NoEscapeSequences || m_configuration.operation_mode == Configuration::NonInteractive) {
763
        // Do not use escape sequences, instead, use LibC's getline.
764
0
        size_t size = 0;
765
0
        char* line = nullptr;
766
        // Show the prompt only on interactive mode (NoEscapeSequences in this case).
767
0
        if (m_configuration.operation_mode != Configuration::NonInteractive)
768
0
            fputs(prompt.characters(), stderr);
769
0
        auto line_length = getline(&line, &size, stdin);
770
        // getline() returns -1 and sets errno=0 on EOF.
771
0
        if (line_length == -1) {
772
0
            if (line)
773
0
                free(line);
774
0
            if (errno == 0)
775
0
                return Error::Eof;
776
777
0
            return Error::ReadFailure;
778
0
        }
779
0
        restore();
780
0
        if (line) {
781
0
            ByteString result { line, (size_t)line_length, Chomp };
782
0
            free(line);
783
0
            return result;
784
0
        }
785
786
0
        return Error::ReadFailure;
787
0
    }
788
789
0
    auto old_cols = m_num_columns;
790
0
    auto old_lines = m_num_lines;
791
0
    get_terminal_size();
792
793
0
    if (m_configuration.enable_bracketed_paste)
794
0
        fprintf(stderr, "\x1b[?2004h");
795
796
0
    if (m_num_columns != old_cols || m_num_lines != old_lines)
797
0
        m_refresh_needed = true;
798
799
0
    set_prompt(prompt);
800
0
    reset();
801
0
    strip_styles(true);
802
803
0
    {
804
0
        auto stderr_stream = Core::File::standard_error().release_value_but_fixme_should_propagate_errors();
805
0
        auto prompt_lines = max(current_prompt_metrics().line_metrics.size(), 1ul) - 1;
806
0
        for (size_t i = 0; i < prompt_lines; ++i)
807
0
            stderr_stream->write_until_depleted("\n"sv.bytes()).release_value_but_fixme_should_propagate_errors();
808
809
0
        VT::move_relative(-static_cast<int>(prompt_lines), 0, *stderr_stream).release_value_but_fixme_should_propagate_errors();
810
0
    }
811
812
0
    set_origin();
813
814
0
    m_history_cursor = m_history.size();
815
816
0
    if (auto refresh_result = refresh_display(); refresh_result.is_error())
817
0
        m_input_error = Error::ReadFailure;
818
819
0
    Core::EventLoop loop;
820
821
0
    m_notifier = Core::Notifier::construct(STDIN_FILENO, Core::Notifier::Type::Read);
822
823
0
    if (m_input_error.has_value())
824
0
        loop.quit(Exit);
825
826
0
    m_notifier->on_activation = [&] {
827
0
        if (try_update_once().is_error())
828
0
            loop.quit(Exit);
829
0
    };
830
831
0
    if (!m_incomplete_data.is_empty()) {
832
0
        deferred_invoke([&] {
833
0
            if (try_update_once().is_error())
834
0
                loop.quit(Exit);
835
0
        });
836
0
    }
837
838
0
    if (loop.exec() == Retry)
839
0
        return get_line(prompt);
840
841
0
    return m_input_error.has_value() ? Result<ByteString, Editor::Error> { m_input_error.value() } : Result<ByteString, Editor::Error> { m_returned_line };
842
0
}
843
844
ErrorOr<void> Editor::try_update_once()
845
0
{
846
0
    if (m_was_interrupted) {
847
0
        handle_interrupt_event();
848
0
    }
849
850
0
    TRY(handle_read_event());
851
852
0
    if (m_always_refresh)
853
0
        m_refresh_needed = true;
854
855
0
    TRY(refresh_display());
856
857
0
    if (m_finish)
858
0
        TRY(really_quit_event_loop());
859
860
0
    return {};
861
0
}
862
863
void Editor::handle_interrupt_event()
864
0
{
865
0
    if (!m_initialized || !m_is_editing)
866
0
        return;
867
868
0
    m_was_interrupted = false;
869
0
    m_previous_interrupt_was_handled_as_interrupt = false;
870
871
0
    m_callback_machine.interrupted(*this);
872
0
    if (!m_callback_machine.should_process_last_pressed_key())
873
0
        return;
874
875
0
    m_previous_interrupt_was_handled_as_interrupt = true;
876
877
0
    fprintf(stderr, "^C\n");
878
0
    fflush(stderr);
879
880
0
    if (on_interrupt_handled)
881
0
        on_interrupt_handled();
882
883
0
    m_buffer.clear();
884
0
    m_chars_touched_in_the_middle = buffer().size();
885
0
    m_cursor = 0;
886
0
    set_origin(false);
887
888
0
    finish();
889
0
}
890
891
ErrorOr<void> Editor::handle_read_event()
892
0
{
893
0
    if (m_prohibit_input_processing) {
894
0
        m_have_unprocessed_read_event = true;
895
0
        return {};
896
0
    }
897
898
0
    auto prohibit_scope = prohibit_input();
899
900
0
    char keybuf[1024];
901
0
    ssize_t nread = 0;
902
903
0
    if (!m_incomplete_data.size())
904
0
        nread = read(0, keybuf, sizeof(keybuf));
905
906
0
    if (nread < 0) {
907
0
        if (errno == EINTR) {
908
0
            if (!m_was_interrupted) {
909
0
                if (m_was_resized)
910
0
                    return {};
911
912
0
                finish();
913
0
                return {};
914
0
            }
915
916
0
            handle_interrupt_event();
917
0
            return {};
918
0
        }
919
920
0
        ScopedValueRollback errno_restorer(errno);
921
0
        perror("read failed");
922
923
0
        m_input_error = Error::ReadFailure;
924
0
        finish();
925
0
        return {};
926
0
    }
927
928
0
    m_incomplete_data.append(keybuf, nread);
929
0
    auto available_bytes = m_incomplete_data.size();
930
931
0
    if (available_bytes == 0) {
932
0
        m_input_error = Error::Empty;
933
0
        finish();
934
0
        return {};
935
0
    }
936
937
0
    auto reverse_tab = false;
938
939
    // Discard starting bytes until they make sense as utf-8.
940
0
    size_t valid_bytes = 0;
941
0
    while (available_bytes > 0) {
942
0
        Utf8View { StringView { m_incomplete_data.data(), available_bytes } }.validate(valid_bytes);
943
0
        if (valid_bytes != 0)
944
0
            break;
945
0
        m_incomplete_data.take_first();
946
0
        --available_bytes;
947
0
    }
948
949
0
    Utf8View input_view { StringView { m_incomplete_data.data(), valid_bytes } };
950
0
    size_t consumed_code_points = 0;
951
952
0
    static Vector<u8, 4> csi_parameter_bytes;
953
0
    static Vector<u8> csi_intermediate_bytes;
954
0
    Vector<unsigned, 4> csi_parameters;
955
0
    u8 csi_final;
956
0
    enum CSIMod {
957
0
        Shift = 1,
958
0
        Alt = 2,
959
0
        Ctrl = 4,
960
0
    };
961
962
0
    for (auto code_point : input_view) {
963
0
        if (m_finish)
964
0
            break;
965
966
0
        ++consumed_code_points;
967
968
0
        if (code_point == 0)
969
0
            continue;
970
971
0
        switch (m_state) {
972
0
        case InputState::GotEscape:
973
0
            switch (code_point) {
974
0
            case '[':
975
0
                m_state = InputState::CSIExpectParameter;
976
0
                continue;
977
0
            default: {
978
0
                m_callback_machine.key_pressed(*this, { code_point, Key::Alt });
979
0
                m_state = InputState::Free;
980
0
                TRY(cleanup_suggestions());
981
0
                continue;
982
0
            }
983
0
            }
984
0
        case InputState::CSIExpectParameter:
985
0
            if (code_point >= 0x30 && code_point <= 0x3f) { // '0123456789:;<=>?'
986
0
                csi_parameter_bytes.append(code_point);
987
0
                continue;
988
0
            }
989
0
            m_state = InputState::CSIExpectIntermediate;
990
0
            [[fallthrough]];
991
0
        case InputState::CSIExpectIntermediate:
992
0
            if (code_point >= 0x20 && code_point <= 0x2f) { // ' !"#$%&\'()*+,-./'
993
0
                csi_intermediate_bytes.append(code_point);
994
0
                continue;
995
0
            }
996
0
            m_state = InputState::CSIExpectFinal;
997
0
            [[fallthrough]];
998
0
        case InputState::CSIExpectFinal: {
999
0
            m_state = m_previous_free_state;
1000
0
            auto is_in_paste = m_state == InputState::Paste;
1001
0
            for (auto& parameter : ByteString::copy(csi_parameter_bytes).split(';')) {
1002
0
                if (auto value = parameter.to_number<unsigned>(); value.has_value())
1003
0
                    csi_parameters.append(value.value());
1004
0
                else
1005
0
                    csi_parameters.append(0);
1006
0
            }
1007
0
            unsigned param1 = 0, param2 = 0;
1008
0
            if (csi_parameters.size() >= 1)
1009
0
                param1 = csi_parameters[0];
1010
0
            if (csi_parameters.size() >= 2)
1011
0
                param2 = csi_parameters[1];
1012
0
            unsigned modifiers = param2 ? param2 - 1 : 0;
1013
1014
0
            if (is_in_paste && code_point != '~' && param1 != 201) {
1015
                // The only valid escape to process in paste mode is the stop-paste sequence.
1016
                // so treat everything else as part of the pasted data.
1017
0
                insert('\x1b');
1018
0
                insert('[');
1019
0
                insert(StringView { csi_parameter_bytes.data(), csi_parameter_bytes.size() });
1020
0
                insert(StringView { csi_intermediate_bytes.data(), csi_intermediate_bytes.size() });
1021
0
                insert(code_point);
1022
0
                continue;
1023
0
            }
1024
0
            if (!(code_point >= 0x40 && code_point <= 0x7f)) {
1025
0
                dbgln("LibLine: Invalid CSI: {:02x} ({:c})", code_point, code_point);
1026
0
                continue;
1027
0
            }
1028
0
            csi_final = code_point;
1029
0
            csi_parameters.clear();
1030
0
            csi_parameter_bytes.clear();
1031
0
            csi_intermediate_bytes.clear();
1032
1033
0
            if (csi_final == 'Z') {
1034
                // 'reverse tab'
1035
0
                reverse_tab = true;
1036
0
                break;
1037
0
            }
1038
0
            TRY(cleanup_suggestions());
1039
1040
0
            switch (csi_final) {
1041
0
            case 'A': // ^[[A: arrow up
1042
0
                search_backwards();
1043
0
                continue;
1044
0
            case 'B': // ^[[B: arrow down
1045
0
                search_forwards();
1046
0
                continue;
1047
0
            case 'D': // ^[[D: arrow left
1048
0
                if (modifiers == CSIMod::Alt || modifiers == CSIMod::Ctrl)
1049
0
                    cursor_left_word();
1050
0
                else
1051
0
                    cursor_left_character();
1052
0
                continue;
1053
0
            case 'C': // ^[[C: arrow right
1054
0
                if (modifiers == CSIMod::Alt || modifiers == CSIMod::Ctrl)
1055
0
                    cursor_right_word();
1056
0
                else
1057
0
                    cursor_right_character();
1058
0
                continue;
1059
0
            case 'H': // ^[[H: home
1060
0
                go_home();
1061
0
                continue;
1062
0
            case 'F': // ^[[F: end
1063
0
                go_end();
1064
0
                continue;
1065
0
            case 127:
1066
0
                if (modifiers == CSIMod::Ctrl)
1067
0
                    erase_alnum_word_backwards();
1068
0
                else
1069
0
                    erase_character_backwards();
1070
0
                continue;
1071
0
            case '~':
1072
0
                if (param1 == 3) { // ^[[3~: delete
1073
0
                    if (modifiers == CSIMod::Ctrl)
1074
0
                        erase_alnum_word_forwards();
1075
0
                    else
1076
0
                        erase_character_forwards();
1077
0
                    m_search_offset = 0;
1078
0
                    continue;
1079
0
                }
1080
0
                if (m_configuration.enable_bracketed_paste) {
1081
                    // ^[[200~: start bracketed paste
1082
                    // ^[[201~: end bracketed paste
1083
0
                    if (!is_in_paste && param1 == 200) {
1084
0
                        m_state = InputState::Paste;
1085
0
                        continue;
1086
0
                    }
1087
0
                    if (is_in_paste && param1 == 201) {
1088
0
                        m_state = InputState::Free;
1089
0
                        if (on_paste) {
1090
0
                            on_paste(Utf32View { m_paste_buffer.data(), m_paste_buffer.size() }, *this);
1091
0
                            m_paste_buffer.clear_with_capacity();
1092
0
                        }
1093
0
                        if (!m_paste_buffer.is_empty())
1094
0
                            insert(Utf32View { m_paste_buffer.data(), m_paste_buffer.size() });
1095
0
                        continue;
1096
0
                    }
1097
0
                }
1098
                // ^[[5~: page up
1099
                // ^[[6~: page down
1100
0
                dbgln("LibLine: Unhandled '~': {}", param1);
1101
0
                continue;
1102
0
            default:
1103
0
                dbgln("LibLine: Unhandled final: {:02x} ({:c})", code_point, code_point);
1104
0
                continue;
1105
0
            }
1106
0
            VERIFY_NOT_REACHED();
1107
0
        }
1108
0
        case InputState::Verbatim:
1109
0
            m_state = InputState::Free;
1110
            // Verbatim mode will bypass all mechanisms and just insert the code point.
1111
0
            insert(code_point);
1112
0
            continue;
1113
0
        case InputState::Paste:
1114
0
            if (code_point == 27) {
1115
0
                m_previous_free_state = InputState::Paste;
1116
0
                m_state = InputState::GotEscape;
1117
0
                continue;
1118
0
            }
1119
0
            if (on_paste)
1120
0
                m_paste_buffer.append(code_point);
1121
0
            else
1122
0
                insert(code_point);
1123
0
            continue;
1124
0
        case InputState::Free:
1125
0
            m_previous_free_state = InputState::Free;
1126
0
            if (code_point == 27) {
1127
0
                m_callback_machine.key_pressed(*this, code_point);
1128
                // Note that this should also deal with explicitly registered keys
1129
                // that would otherwise be interpreted as escapes.
1130
0
                if (m_callback_machine.should_process_last_pressed_key())
1131
0
                    m_state = InputState::GotEscape;
1132
0
                continue;
1133
0
            }
1134
0
            if (code_point == 22) { // ^v
1135
0
                m_callback_machine.key_pressed(*this, code_point);
1136
0
                if (m_callback_machine.should_process_last_pressed_key())
1137
0
                    m_state = InputState::Verbatim;
1138
0
                continue;
1139
0
            }
1140
0
            break;
1141
0
        }
1142
1143
        // There are no sequences past this point, so short of 'tab', we will want to cleanup the suggestions.
1144
0
        ArmedScopeGuard suggestion_cleanup { [this] { cleanup_suggestions().release_value_but_fixme_should_propagate_errors(); } };
1145
1146
        // Normally ^D. `stty eof \^n` can change it to ^N (or something else), but Serenity doesn't have `stty` yet.
1147
        // Process this here since the keybinds might override its behavior.
1148
        // This only applies when the buffer is empty. at any other time, the behavior should be configurable.
1149
0
        if (code_point == m_termios.c_cc[VEOF] && m_buffer.size() == 0) {
1150
0
            finish_edit();
1151
0
            continue;
1152
0
        }
1153
1154
0
        m_callback_machine.key_pressed(*this, code_point);
1155
0
        if (!m_callback_machine.should_process_last_pressed_key())
1156
0
            continue;
1157
1158
0
        m_search_offset = 0; // reset search offset on any key
1159
1160
0
        if (code_point == '\t' || reverse_tab) {
1161
0
            suggestion_cleanup.disarm();
1162
1163
0
            if (!on_tab_complete)
1164
0
                continue;
1165
1166
            // Reverse tab can count as regular tab here.
1167
0
            m_times_tab_pressed++;
1168
1169
0
            int token_start = m_cursor;
1170
1171
            // Ask for completions only on the first tab
1172
            // and scan for the largest common prefix to display,
1173
            // further tabs simply show the cached completions.
1174
0
            if (m_times_tab_pressed == 1) {
1175
0
                m_suggestion_manager.set_suggestions(on_tab_complete(*this));
1176
0
                m_suggestion_manager.set_start_index(0);
1177
0
                m_prompt_lines_at_suggestion_initiation = num_lines();
1178
0
                if (m_suggestion_manager.count() == 0) {
1179
                    // There are no suggestions, beep.
1180
0
                    fputc('\a', stderr);
1181
0
                    fflush(stderr);
1182
0
                }
1183
0
            }
1184
1185
            // Adjust already incremented / decremented index when switching tab direction.
1186
0
            if (reverse_tab && m_tab_direction != TabDirection::Backward) {
1187
0
                m_suggestion_manager.previous();
1188
0
                m_suggestion_manager.previous();
1189
0
                m_tab_direction = TabDirection::Backward;
1190
0
            }
1191
0
            if (!reverse_tab && m_tab_direction != TabDirection::Forward) {
1192
0
                m_suggestion_manager.next();
1193
0
                m_suggestion_manager.next();
1194
0
                m_tab_direction = TabDirection::Forward;
1195
0
            }
1196
0
            reverse_tab = false;
1197
1198
0
            SuggestionManager::CompletionMode completion_mode;
1199
0
            switch (m_times_tab_pressed) {
1200
0
            case 1:
1201
0
                completion_mode = SuggestionManager::CompletePrefix;
1202
0
                break;
1203
0
            case 2:
1204
0
                completion_mode = SuggestionManager::ShowSuggestions;
1205
0
                break;
1206
0
            default:
1207
0
                completion_mode = SuggestionManager::CycleSuggestions;
1208
0
                break;
1209
0
            }
1210
1211
0
            insert(Utf32View { m_remembered_suggestion_static_data.data(), m_remembered_suggestion_static_data.size() });
1212
0
            m_remembered_suggestion_static_data.clear_with_capacity();
1213
1214
0
            auto completion_result = m_suggestion_manager.attempt_completion(completion_mode, token_start);
1215
1216
0
            auto new_cursor = m_cursor;
1217
1218
0
            new_cursor += completion_result.new_cursor_offset;
1219
0
            for (size_t i = completion_result.offset_region_to_remove.start; i < completion_result.offset_region_to_remove.end; ++i)
1220
0
                remove_at_index(new_cursor);
1221
1222
0
            new_cursor -= completion_result.static_offset_from_cursor;
1223
0
            for (size_t i = 0; i < completion_result.static_offset_from_cursor; ++i) {
1224
0
                m_remembered_suggestion_static_data.append(m_buffer[new_cursor]);
1225
0
                remove_at_index(new_cursor);
1226
0
            }
1227
1228
0
            m_cursor = new_cursor;
1229
0
            m_inline_search_cursor = new_cursor;
1230
0
            m_refresh_needed = true;
1231
0
            m_chars_touched_in_the_middle++;
1232
1233
0
            for (auto& view : completion_result.insert)
1234
0
                insert(view);
1235
1236
0
            auto stderr_stream = TRY(Core::File::standard_error());
1237
0
            TRY(reposition_cursor(*stderr_stream));
1238
1239
0
            if (completion_result.style_to_apply.has_value()) {
1240
                // Apply the style of the last suggestion.
1241
0
                readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
1242
0
                stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, completion_result.style_to_apply.value());
1243
0
            }
1244
1245
0
            switch (completion_result.new_completion_mode) {
1246
0
            case SuggestionManager::DontComplete:
1247
0
                m_times_tab_pressed = 0;
1248
0
                m_remembered_suggestion_static_data.clear_with_capacity();
1249
0
                break;
1250
0
            case SuggestionManager::CompletePrefix:
1251
0
                break;
1252
0
            default:
1253
0
                ++m_times_tab_pressed;
1254
0
                break;
1255
0
            }
1256
1257
0
            if (m_times_tab_pressed > 1 && m_suggestion_manager.count() > 0) {
1258
0
                if (TRY(m_suggestion_display->cleanup()))
1259
0
                    TRY(reposition_cursor(*stderr_stream));
1260
1261
0
                m_suggestion_display->set_initial_prompt_lines(m_prompt_lines_at_suggestion_initiation);
1262
1263
0
                TRY(m_suggestion_display->display(m_suggestion_manager));
1264
1265
0
                m_origin_row = m_suggestion_display->origin_row();
1266
0
            }
1267
1268
0
            if (m_times_tab_pressed > 2) {
1269
0
                if (m_tab_direction == TabDirection::Forward)
1270
0
                    m_suggestion_manager.next();
1271
0
                else
1272
0
                    m_suggestion_manager.previous();
1273
0
            }
1274
1275
0
            if (m_suggestion_manager.count() < 2 && !completion_result.avoid_committing_to_single_suggestion) {
1276
                // We have none, or just one suggestion,
1277
                // we should just commit that and continue
1278
                // after it, as if it were auto-completed.
1279
0
                TRY(reposition_cursor(*stderr_stream, true));
1280
0
                TRY(cleanup_suggestions());
1281
0
                m_remembered_suggestion_static_data.clear_with_capacity();
1282
0
            }
1283
0
            continue;
1284
0
        }
1285
1286
        // If we got here, manually cleanup the suggestions and then insert the new code point.
1287
0
        m_remembered_suggestion_static_data.clear_with_capacity();
1288
0
        suggestion_cleanup.disarm();
1289
0
        TRY(cleanup_suggestions());
1290
0
        insert(code_point);
1291
0
    }
1292
1293
0
    if (consumed_code_points == valid_bytes) {
1294
0
        m_incomplete_data.clear();
1295
0
    } else {
1296
0
        auto bytes_to_drop = input_view.byte_offset_of(consumed_code_points + 1);
1297
0
        for (size_t i = 0; i < bytes_to_drop; ++i)
1298
0
            m_incomplete_data.take_first();
1299
0
    }
1300
1301
0
    if (!m_incomplete_data.is_empty() && !m_finish)
1302
0
        deferred_invoke([&] { try_update_once().release_value_but_fixme_should_propagate_errors(); });
1303
1304
0
    return {};
1305
0
}
1306
1307
ErrorOr<void> Editor::cleanup_suggestions()
1308
0
{
1309
0
    if (m_times_tab_pressed != 0) {
1310
        // Apply the style of the last suggestion.
1311
0
        readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
1312
0
        stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, m_suggestion_manager.current_suggestion().style);
1313
        // We probably have some suggestions drawn,
1314
        // let's clean them up.
1315
0
        if (TRY(m_suggestion_display->cleanup())) {
1316
0
            auto stderr_stream = TRY(Core::File::standard_error());
1317
0
            TRY(reposition_cursor(*stderr_stream));
1318
0
            m_refresh_needed = true;
1319
0
        }
1320
0
        m_suggestion_manager.reset();
1321
0
        m_suggestion_display->finish();
1322
0
    }
1323
0
    m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
1324
0
    return {};
1325
0
}
1326
1327
bool Editor::search(StringView phrase, bool allow_empty, bool from_beginning)
1328
0
{
1329
0
    int last_matching_offset = -1;
1330
0
    bool found = false;
1331
1332
    // Do not search for empty strings.
1333
0
    if (allow_empty || phrase.length() > 0) {
1334
0
        size_t search_offset = m_search_offset;
1335
0
        for (size_t i = m_history_cursor; i > 0; --i) {
1336
0
            auto& entry = m_history[i - 1];
1337
0
            auto contains = from_beginning ? entry.entry.starts_with(phrase) : entry.entry.contains(phrase);
1338
0
            if (contains) {
1339
0
                last_matching_offset = i - 1;
1340
0
                if (search_offset == 0) {
1341
0
                    found = true;
1342
0
                    break;
1343
0
                }
1344
0
                --search_offset;
1345
0
            }
1346
0
        }
1347
1348
0
        if (!found) {
1349
0
            fputc('\a', stderr);
1350
0
            fflush(stderr);
1351
0
        }
1352
0
    }
1353
1354
0
    if (found) {
1355
        // We plan to clear the buffer, so mark the entire thing touched.
1356
0
        m_chars_touched_in_the_middle = m_buffer.size();
1357
0
        m_buffer.clear();
1358
0
        m_cursor = 0;
1359
0
        insert(m_history[last_matching_offset].entry);
1360
        // Always needed, as we have cleared the buffer above.
1361
0
        m_refresh_needed = true;
1362
0
    }
1363
1364
0
    return found;
1365
0
}
1366
1367
void Editor::recalculate_origin()
1368
0
{
1369
    // Changing the columns can affect our origin if
1370
    // the new size is smaller than our prompt, which would
1371
    // cause said prompt to take up more space, so we should
1372
    // compensate for that.
1373
0
    if (m_cached_prompt_metrics.max_line_length >= m_num_columns) {
1374
0
        auto added_lines = (m_cached_prompt_metrics.max_line_length + 1) / m_num_columns - 1;
1375
0
        m_origin_row += added_lines;
1376
0
    }
1377
1378
    // We also need to recalculate our cursor position,
1379
    // but that will be calculated and applied at the next
1380
    // refresh cycle.
1381
0
}
1382
1383
ErrorOr<void> Editor::cleanup()
1384
0
{
1385
0
    auto current_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
1386
0
    auto new_lines = current_prompt_metrics().lines_with_addition(current_buffer_metrics, m_num_columns);
1387
0
    if (new_lines < m_shown_lines)
1388
0
        m_extra_forward_lines = max(m_shown_lines - new_lines, m_extra_forward_lines);
1389
1390
0
    auto stderr_stream = TRY(Core::File::standard_error());
1391
0
    TRY(reposition_cursor(*stderr_stream, true));
1392
0
    auto current_line = num_lines() - 1;
1393
0
    TRY(VT::clear_lines(current_line, m_extra_forward_lines, *stderr_stream));
1394
0
    m_extra_forward_lines = 0;
1395
0
    TRY(reposition_cursor(*stderr_stream));
1396
0
    return {};
1397
0
}
1398
1399
ErrorOr<void> Editor::refresh_display()
1400
0
{
1401
0
    AllocatingMemoryStream output_stream;
1402
0
    ScopeGuard flush_stream {
1403
0
        [&] {
1404
0
            m_shown_lines = current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns);
1405
1406
0
            if (output_stream.used_buffer_size() == 0)
1407
0
                return;
1408
1409
0
            auto buffer = output_stream.read_until_eof().release_value_but_fixme_should_propagate_errors();
1410
0
            fwrite(buffer.data(), sizeof(char), buffer.size(), stderr);
1411
0
        }
1412
0
    };
1413
1414
0
    auto has_cleaned_up = false;
1415
    // Someone changed the window size, figure it out
1416
    // and react to it, we might need to redraw.
1417
0
    if (m_was_resized) {
1418
0
        if (m_expected_origin_changed || m_previous_num_columns != m_num_columns) {
1419
            // We need to cleanup and redo everything.
1420
0
            m_expected_origin_changed = false;
1421
0
            m_cached_prompt_valid = false;
1422
0
            m_refresh_needed = true;
1423
0
            swap(m_previous_num_columns, m_num_columns);
1424
0
            recalculate_origin();
1425
0
            TRY(cleanup());
1426
0
            swap(m_previous_num_columns, m_num_columns);
1427
0
            has_cleaned_up = true;
1428
0
        }
1429
0
        m_was_resized = false;
1430
0
    }
1431
    // We might be at the last line, and have more than one line;
1432
    // Refreshing the display will cause the terminal to scroll,
1433
    // so note that fact and bring origin up, making sure to
1434
    // reserve the space for however many lines we move it up.
1435
0
    auto current_num_lines = num_lines();
1436
0
    if (m_origin_row + current_num_lines > m_num_lines) {
1437
0
        if (current_num_lines > m_num_lines) {
1438
0
            for (size_t i = 0; i < m_num_lines; ++i)
1439
0
                TRY(output_stream.write_until_depleted("\n"sv.bytes()));
1440
0
            m_origin_row = 0;
1441
0
        } else {
1442
0
            auto old_origin_row = m_origin_row;
1443
0
            m_origin_row = m_num_lines - current_num_lines + 1;
1444
0
            for (size_t i = 0; i < old_origin_row - m_origin_row; ++i)
1445
0
                TRY(output_stream.write_until_depleted("\n"sv.bytes()));
1446
0
        }
1447
0
    }
1448
    // Do not call hook on pure cursor movement.
1449
0
    if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
1450
        // Probably just moving around.
1451
0
        TRY(reposition_cursor(output_stream));
1452
0
        m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
1453
0
        m_drawn_end_of_line_offset = m_buffer.size();
1454
0
        return {};
1455
0
    }
1456
1457
0
    if (on_display_refresh)
1458
0
        on_display_refresh(*this);
1459
1460
0
    if (m_cached_prompt_valid) {
1461
0
        if (!m_refresh_needed && m_cursor == m_buffer.size()) {
1462
            // Just write the characters out and continue,
1463
            // no need to refresh the entire line.
1464
0
            TRY(output_stream.write_until_depleted(m_pending_chars));
1465
0
            m_pending_chars.clear();
1466
0
            m_drawn_cursor = m_cursor;
1467
0
            m_drawn_end_of_line_offset = m_buffer.size();
1468
0
            m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
1469
0
            m_drawn_spans = m_current_spans;
1470
0
            return {};
1471
0
        }
1472
0
    }
1473
1474
0
    auto apply_styles = [&, empty_styles = HashMap<u32, Style> {}](size_t i) -> ErrorOr<void> {
1475
0
        auto& ends = m_current_spans.m_spans_ending.get(i).value_or<>(empty_styles);
1476
0
        auto& starts = m_current_spans.m_spans_starting.get(i).value_or<>(empty_styles);
1477
1478
0
        auto& anchored_ends = m_current_spans.m_anchored_spans_ending.get(i).value_or<>(empty_styles);
1479
0
        auto& anchored_starts = m_current_spans.m_anchored_spans_starting.get(i).value_or<>(empty_styles);
1480
1481
0
        if (ends.size() || anchored_ends.size()) {
1482
0
            Style style;
1483
1484
0
            for (auto& applicable_style : ends)
1485
0
                style.unify_with(applicable_style.value);
1486
1487
0
            for (auto& applicable_style : anchored_ends)
1488
0
                style.unify_with(applicable_style.value);
1489
1490
            // Disable any style that should be turned off.
1491
0
            TRY(VT::apply_style(style, output_stream, false));
1492
1493
            // Reapply styles for overlapping spans that include this one.
1494
0
            style = find_applicable_style(i);
1495
0
            TRY(VT::apply_style(style, output_stream, true));
1496
0
        }
1497
0
        if (starts.size() || anchored_starts.size()) {
1498
0
            Style style;
1499
1500
0
            for (auto& applicable_style : starts)
1501
0
                style.unify_with(applicable_style.value);
1502
1503
0
            for (auto& applicable_style : anchored_starts)
1504
0
                style.unify_with(applicable_style.value);
1505
1506
            // Set new styles.
1507
0
            TRY(VT::apply_style(style, output_stream, true));
1508
0
        }
1509
1510
0
        return {};
1511
0
    };
1512
1513
0
    auto print_character_at = [&](size_t i) {
1514
0
        Variant<u32, Utf8View> c { Utf8View {} };
1515
0
        if (auto it = m_current_masks.find_largest_not_above_iterator(i); !it.is_end() && it->has_value()) {
1516
0
            auto offset = i - it.key();
1517
0
            if (it->value().mode == Style::Mask::Mode::ReplaceEntireSelection) {
1518
0
                auto& mask = it->value().replacement_view;
1519
0
                auto replacement = mask.begin().peek(offset);
1520
0
                if (!replacement.has_value())
1521
0
                    return;
1522
0
                c = replacement.value();
1523
0
                ++it;
1524
0
                u32 next_offset = it.is_end() ? m_drawn_end_of_line_offset : it.key();
1525
0
                if (i + 1 == next_offset)
1526
0
                    c = mask.unicode_substring_view(offset, mask.length() - offset);
1527
0
            } else {
1528
0
                c = it->value().replacement_view;
1529
0
            }
1530
0
        } else {
1531
0
            c = m_buffer[i];
1532
0
        }
1533
0
        auto print_single_character = [&](auto c) -> ErrorOr<void> {
1534
0
            StringBuilder builder;
1535
0
            bool should_print_masked = is_ascii_control(c) && c != '\n';
1536
0
            bool should_print_caret = c < 64 && should_print_masked;
1537
0
            if (should_print_caret)
1538
0
                builder.appendff("^{:c}", c + 64);
1539
0
            else if (should_print_masked)
1540
0
                builder.appendff("\\x{:0>2x}", c);
1541
0
            else
1542
0
                builder.append(Utf32View { &c, 1 });
1543
1544
0
            if (should_print_masked)
1545
0
                TRY(output_stream.write_until_depleted("\033[7m"sv.bytes()));
1546
1547
0
            TRY(output_stream.write_until_depleted(builder.string_view().bytes()));
1548
1549
0
            if (should_print_masked)
1550
0
                TRY(output_stream.write_until_depleted("\033[27m"sv.bytes()));
1551
1552
0
            return {};
1553
0
        };
1554
0
        c.visit(
1555
0
            [&](u32 c) { print_single_character(c).release_value_but_fixme_should_propagate_errors(); },
1556
0
            [&](auto& view) { for (auto c : view) print_single_character(c).release_value_but_fixme_should_propagate_errors(); });
1557
0
    };
1558
1559
    // If there have been no changes to previous sections of the line (style or text)
1560
    // just append the new text with the appropriate styles.
1561
0
    if (!m_always_refresh && m_cached_prompt_valid && m_chars_touched_in_the_middle == 0 && m_drawn_spans.contains_up_to_offset(m_current_spans, m_drawn_cursor)) {
1562
0
        auto initial_style = find_applicable_style(m_drawn_end_of_line_offset);
1563
0
        TRY(VT::apply_style(initial_style, output_stream));
1564
1565
0
        for (size_t i = m_drawn_end_of_line_offset; i < m_buffer.size(); ++i) {
1566
0
            TRY(apply_styles(i));
1567
0
            print_character_at(i);
1568
0
        }
1569
1570
0
        TRY(VT::apply_style(Style::reset_style(), output_stream));
1571
0
        m_pending_chars.clear();
1572
0
        m_refresh_needed = false;
1573
0
        m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
1574
0
        m_chars_touched_in_the_middle = 0;
1575
0
        m_drawn_cursor = m_cursor;
1576
0
        m_drawn_end_of_line_offset = m_buffer.size();
1577
1578
        // No need to reposition the cursor, the cursor is already where it needs to be.
1579
0
        return {};
1580
0
    }
1581
1582
    if constexpr (LINE_EDITOR_DEBUG) {
1583
        if (m_cached_prompt_valid && m_chars_touched_in_the_middle == 0) {
1584
            auto x = m_drawn_spans.contains_up_to_offset(m_current_spans, m_drawn_cursor);
1585
            dbgln("Contains: {} At offset: {}", x, m_drawn_cursor);
1586
            dbgln("Drawn Spans:");
1587
            for (auto& sentry : m_drawn_spans.m_spans_starting) {
1588
                for (auto& entry : sentry.value) {
1589
                    dbgln("{}-{}: {}", sentry.key, entry.key, entry.value.to_byte_string());
1590
                }
1591
            }
1592
            dbgln("==========================================================================");
1593
            dbgln("Current Spans:");
1594
            for (auto& sentry : m_current_spans.m_spans_starting) {
1595
                for (auto& entry : sentry.value) {
1596
                    dbgln("{}-{}: {}", sentry.key, entry.key, entry.value.to_byte_string());
1597
                }
1598
            }
1599
        }
1600
    }
1601
1602
    // Ouch, reflow entire line.
1603
0
    if (!has_cleaned_up) {
1604
0
        TRY(cleanup());
1605
0
    }
1606
0
    TRY(VT::move_absolute(m_origin_row, m_origin_column, output_stream));
1607
1608
0
    TRY(output_stream.write_until_depleted(m_new_prompt.bytes()));
1609
1610
0
    TRY(VT::clear_to_end_of_line(output_stream));
1611
0
    StringBuilder builder;
1612
0
    for (size_t i = 0; i < m_buffer.size(); ++i) {
1613
0
        TRY(apply_styles(i));
1614
0
        print_character_at(i);
1615
0
    }
1616
1617
0
    TRY(VT::apply_style(Style::reset_style(), output_stream)); // don't bleed to EOL
1618
1619
0
    m_pending_chars.clear();
1620
0
    m_refresh_needed = false;
1621
0
    m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view(), m_current_masks);
1622
0
    m_chars_touched_in_the_middle = 0;
1623
0
    m_drawn_spans = m_current_spans;
1624
0
    m_drawn_end_of_line_offset = m_buffer.size();
1625
0
    m_cached_prompt_valid = true;
1626
1627
0
    TRY(reposition_cursor(output_stream));
1628
0
    return {};
1629
0
}
1630
1631
void Editor::strip_styles(bool strip_anchored)
1632
0
{
1633
0
    m_current_spans.m_spans_starting.clear();
1634
0
    m_current_spans.m_spans_ending.clear();
1635
0
    m_current_masks.clear();
1636
0
    m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view(), {});
1637
1638
0
    if (strip_anchored) {
1639
0
        m_current_spans.m_anchored_spans_starting.clear();
1640
0
        m_current_spans.m_anchored_spans_ending.clear();
1641
0
    }
1642
1643
0
    m_refresh_needed = true;
1644
0
}
1645
1646
ErrorOr<void> Editor::reposition_cursor(Stream& stream, bool to_end)
1647
0
{
1648
0
    auto cursor = m_cursor;
1649
0
    auto saved_cursor = m_cursor;
1650
0
    if (to_end)
1651
0
        cursor = m_buffer.size();
1652
1653
0
    m_cursor = cursor;
1654
0
    m_drawn_cursor = cursor;
1655
1656
0
    auto line = cursor_line() - 1;
1657
0
    auto column = offset_in_line();
1658
1659
0
    ensure_free_lines_from_origin(line);
1660
1661
0
    VERIFY(column + m_origin_column <= m_num_columns);
1662
0
    TRY(VT::move_absolute(line + m_origin_row, column + m_origin_column, stream));
1663
1664
0
    m_cursor = saved_cursor;
1665
0
    return {};
1666
0
}
1667
1668
ErrorOr<void> VT::move_absolute(u32 row, u32 col, Stream& stream)
1669
0
{
1670
0
    return stream.write_until_depleted(ByteString::formatted("\033[{};{}H", row, col));
1671
0
}
1672
1673
ErrorOr<void> VT::move_relative(int row, int col, Stream& stream)
1674
0
{
1675
0
    char x_op = 'A', y_op = 'D';
1676
1677
0
    if (row > 0)
1678
0
        x_op = 'B';
1679
0
    else
1680
0
        row = -row;
1681
0
    if (col > 0)
1682
0
        y_op = 'C';
1683
0
    else
1684
0
        col = -col;
1685
1686
0
    if (row > 0)
1687
0
        TRY(stream.write_until_depleted(ByteString::formatted("\033[{}{}", row, x_op)));
1688
0
    if (col > 0)
1689
0
        TRY(stream.write_until_depleted(ByteString::formatted("\033[{}{}", col, y_op)));
1690
1691
0
    return {};
1692
0
}
1693
1694
Style Editor::find_applicable_style(size_t offset) const
1695
0
{
1696
    // Walk through our styles and merge all that fit in the offset.
1697
0
    auto style = Style::reset_style();
1698
0
    auto unify = [&](auto& entry) {
1699
0
        if (entry.key >= offset)
1700
0
            return;
1701
0
        for (auto& style_value : entry.value) {
1702
0
            if (style_value.key <= offset)
1703
0
                return;
1704
0
            style.unify_with(style_value.value, true);
1705
0
        }
1706
0
    };
1707
1708
0
    for (auto& entry : m_current_spans.m_spans_starting) {
1709
0
        unify(entry);
1710
0
    }
1711
1712
0
    for (auto& entry : m_current_spans.m_anchored_spans_starting) {
1713
0
        unify(entry);
1714
0
    }
1715
1716
0
    return style;
1717
0
}
1718
1719
ByteString Style::Background::to_vt_escape() const
1720
0
{
1721
0
    if (is_default())
1722
0
        return "";
1723
1724
0
    if (m_is_rgb) {
1725
0
        return ByteString::formatted("\e[48;2;{};{};{}m", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
1726
0
    } else {
1727
0
        return ByteString::formatted("\e[{}m", (u8)m_xterm_color + 40);
1728
0
    }
1729
0
}
1730
1731
ByteString Style::Foreground::to_vt_escape() const
1732
0
{
1733
0
    if (is_default())
1734
0
        return "";
1735
1736
0
    if (m_is_rgb) {
1737
0
        return ByteString::formatted("\e[38;2;{};{};{}m", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
1738
0
    } else {
1739
0
        return ByteString::formatted("\e[{}m", (u8)m_xterm_color + 30);
1740
0
    }
1741
0
}
1742
1743
ByteString Style::Hyperlink::to_vt_escape(bool starting) const
1744
0
{
1745
0
    if (is_empty())
1746
0
        return "";
1747
1748
0
    return ByteString::formatted("\e]8;;{}\e\\", starting ? m_link : ByteString::empty());
1749
0
}
1750
1751
void Style::unify_with(Style const& other, bool prefer_other)
1752
0
{
1753
    // Unify colors.
1754
0
    if (prefer_other || m_background.is_default())
1755
0
        m_background = other.background();
1756
1757
0
    if (prefer_other || m_foreground.is_default())
1758
0
        m_foreground = other.foreground();
1759
1760
    // Unify graphic renditions.
1761
0
    if (other.bold())
1762
0
        set(Bold);
1763
1764
0
    if (other.italic())
1765
0
        set(Italic);
1766
1767
0
    if (other.underline())
1768
0
        set(Underline);
1769
1770
    // Unify links.
1771
0
    if (prefer_other || m_hyperlink.is_empty())
1772
0
        m_hyperlink = other.hyperlink();
1773
1774
0
    m_is_empty &= other.m_is_empty;
1775
0
}
1776
1777
ByteString Style::to_byte_string() const
1778
0
{
1779
0
    StringBuilder builder;
1780
0
    builder.append("Style { "sv);
1781
1782
0
    if (!m_foreground.is_default()) {
1783
0
        builder.append("Foreground("sv);
1784
0
        if (m_foreground.m_is_rgb) {
1785
0
            builder.join(", "sv, m_foreground.m_rgb_color);
1786
0
        } else {
1787
0
            builder.appendff("(XtermColor) {}", (int)m_foreground.m_xterm_color);
1788
0
        }
1789
0
        builder.append("), "sv);
1790
0
    }
1791
1792
0
    if (!m_background.is_default()) {
1793
0
        builder.append("Background("sv);
1794
0
        if (m_background.m_is_rgb) {
1795
0
            builder.join(' ', m_background.m_rgb_color);
1796
0
        } else {
1797
0
            builder.appendff("(XtermColor) {}", (int)m_background.m_xterm_color);
1798
0
        }
1799
0
        builder.append("), "sv);
1800
0
    }
1801
1802
0
    if (bold())
1803
0
        builder.append("Bold, "sv);
1804
1805
0
    if (underline())
1806
0
        builder.append("Underline, "sv);
1807
1808
0
    if (italic())
1809
0
        builder.append("Italic, "sv);
1810
1811
0
    if (!m_hyperlink.is_empty())
1812
0
        builder.appendff("Hyperlink(\"{}\"), ", m_hyperlink.m_link);
1813
1814
0
    if (!m_mask.has_value()) {
1815
0
        builder.appendff("Mask(\"{}\", {}), ",
1816
0
            m_mask->replacement,
1817
0
            m_mask->mode == Mask::Mode::ReplaceEntireSelection
1818
0
                ? "ReplaceEntireSelection"
1819
0
                : "ReplaceEachCodePointInSelection");
1820
0
    }
1821
1822
0
    builder.append('}');
1823
1824
0
    return builder.to_byte_string();
1825
0
}
1826
1827
ErrorOr<void> VT::apply_style(Style const& style, Stream& stream, bool is_starting)
1828
0
{
1829
0
    if (is_starting) {
1830
0
        TRY(stream.write_until_depleted(ByteString::formatted("\033[{};{};{}m{}{}{}",
1831
0
            style.bold() ? 1 : 22,
1832
0
            style.underline() ? 4 : 24,
1833
0
            style.italic() ? 3 : 23,
1834
0
            style.background().to_vt_escape(),
1835
0
            style.foreground().to_vt_escape(),
1836
0
            style.hyperlink().to_vt_escape(true))));
1837
0
    } else {
1838
0
        TRY(stream.write_until_depleted(style.hyperlink().to_vt_escape(false)));
1839
0
    }
1840
1841
0
    return {};
1842
0
}
1843
1844
ErrorOr<void> VT::clear_lines(size_t count_above, size_t count_below, Stream& stream)
1845
0
{
1846
0
    if (count_below + count_above == 0) {
1847
0
        TRY(stream.write_until_depleted("\033[2K"sv));
1848
0
    } else {
1849
        // Go down count_below lines.
1850
0
        if (count_below > 0)
1851
0
            TRY(stream.write_until_depleted(ByteString::formatted("\033[{}B", count_below)));
1852
        // Then clear lines going upwards.
1853
0
        for (size_t i = count_below + count_above; i > 0; --i) {
1854
0
            TRY(stream.write_until_depleted("\033[2K"sv));
1855
0
            if (i != 1)
1856
0
                TRY(stream.write_until_depleted("\033[A"sv));
1857
0
        }
1858
0
    }
1859
1860
0
    return {};
1861
0
}
1862
1863
ErrorOr<void> VT::save_cursor(Stream& stream)
1864
0
{
1865
0
    return stream.write_until_depleted("\033[s"sv);
1866
0
}
1867
1868
ErrorOr<void> VT::restore_cursor(Stream& stream)
1869
0
{
1870
0
    return stream.write_until_depleted("\033[u"sv);
1871
0
}
1872
1873
ErrorOr<void> VT::clear_to_end_of_line(Stream& stream)
1874
0
{
1875
0
    return stream.write_until_depleted("\033[K"sv);
1876
0
}
1877
1878
enum VTState {
1879
    Free = 1,
1880
    Escape = 3,
1881
    Bracket = 5,
1882
    BracketArgsSemi = 7,
1883
    Title = 9,
1884
    URL = 11,
1885
};
1886
static VTState actual_rendered_string_length_step(StringMetrics& metrics, size_t index, StringMetrics::LineMetrics& current_line, u32 c, u32 next_c, VTState state, Optional<Style::Mask> const& mask, Optional<size_t> const& maximum_line_width = {}, Optional<size_t&> last_return = {});
1887
1888
enum class MaskedSelectionDecision {
1889
    Skip,
1890
    Continue,
1891
};
1892
static MaskedSelectionDecision resolve_masked_selection(Optional<Style::Mask>& mask, size_t& i, auto& mask_it, auto& view, auto& state, auto& metrics, auto& current_line)
1893
0
{
1894
0
    if (mask.has_value() && mask->mode == Style::Mask::Mode::ReplaceEntireSelection) {
1895
0
        ++mask_it;
1896
0
        auto actual_end_offset = mask_it.is_end() ? view.length() : mask_it.key();
1897
0
        auto end_offset = min(actual_end_offset, view.length());
1898
0
        size_t j = 0;
1899
0
        for (auto it = mask->replacement_view.begin(); it != mask->replacement_view.end(); ++it) {
1900
0
            auto it_copy = it;
1901
0
            ++it_copy;
1902
0
            auto next_c = it_copy == mask->replacement_view.end() ? 0 : *it_copy;
1903
0
            state = actual_rendered_string_length_step(metrics, j, current_line, *it, next_c, state, {});
1904
0
            ++j;
1905
0
            if (j <= actual_end_offset - i && j + i >= view.length())
1906
0
                break;
1907
0
        }
1908
0
        current_line.masked_chars.empend(i, end_offset - i, j);
1909
0
        i = end_offset;
1910
1911
0
        if (mask_it.is_end())
1912
0
            mask = {};
1913
0
        else
1914
0
            mask = *mask_it;
1915
0
        return MaskedSelectionDecision::Skip;
1916
0
    }
1917
0
    return MaskedSelectionDecision::Continue;
1918
0
}
1919
1920
StringMetrics Editor::actual_rendered_string_metrics(StringView string, RedBlackTree<u32, Optional<Style::Mask>> const& masks, Optional<size_t> maximum_line_width)
1921
0
{
1922
0
    Vector<u32> utf32_buffer;
1923
0
    utf32_buffer.ensure_capacity(string.length());
1924
0
    for (auto c : Utf8View { string })
1925
0
        utf32_buffer.append(c);
1926
1927
0
    return actual_rendered_string_metrics(Utf32View { utf32_buffer.data(), utf32_buffer.size() }, masks, maximum_line_width);
1928
0
}
1929
1930
StringMetrics Editor::actual_rendered_string_metrics(Utf32View const& view, RedBlackTree<u32, Optional<Style::Mask>> const& masks, Optional<size_t> maximum_line_width)
1931
0
{
1932
0
    StringMetrics metrics;
1933
0
    StringMetrics::LineMetrics current_line;
1934
0
    VTState state { Free };
1935
0
    Optional<Style::Mask> mask;
1936
0
    size_t last_return { 0 };
1937
1938
0
    auto mask_it = masks.begin();
1939
1940
0
    Vector<size_t> grapheme_breaks;
1941
0
    Unicode::for_each_grapheme_segmentation_boundary(view, [&](size_t offset) -> IterationDecision {
1942
0
        if (offset >= view.length())
1943
0
            return IterationDecision::Break;
1944
1945
0
        grapheme_breaks.append(offset);
1946
0
        return IterationDecision::Continue;
1947
0
    });
1948
1949
    // In case Unicode data isn't available, default to using code points as grapheme boundaries.
1950
0
    if (grapheme_breaks.is_empty()) {
1951
0
        for (size_t i = 0; i < view.length(); ++i)
1952
0
            grapheme_breaks.append(i);
1953
0
    }
1954
1955
0
    for (size_t break_index = 0; break_index < grapheme_breaks.size(); ++break_index) {
1956
0
        auto i = grapheme_breaks[break_index];
1957
0
        if (!mask_it.is_end() && mask_it.key() <= i)
1958
0
            mask = *mask_it;
1959
1960
0
        if (resolve_masked_selection(mask, i, mask_it, view, state, metrics, current_line) == MaskedSelectionDecision::Skip) {
1961
0
            --i;
1962
0
            binary_search(grapheme_breaks, i, &break_index);
1963
0
            continue;
1964
0
        }
1965
1966
0
        auto next_grapheme_start = break_index + 1 < grapheme_breaks.size() ? grapheme_breaks[break_index + 1] : view.length();
1967
0
        auto next_c = break_index + 1 < grapheme_breaks.size() ? view.code_points()[grapheme_breaks[break_index + 1]] : 0;
1968
0
        auto c = view[i];
1969
0
        state = actual_rendered_string_length_step(metrics, i, current_line, c, next_c, state, mask, maximum_line_width, last_return);
1970
1971
0
        for (size_t j = i + 1; j < next_grapheme_start; ++j) {
1972
            // Consume the rest of the code points in this grapheme cluster without updating the state; this is just to account for their length properly.
1973
0
            current_line.length++;
1974
0
            current_line.visible_length++;
1975
0
            metrics.total_length++;
1976
0
            if (current_line.bit_length.has_value())
1977
0
                current_line.bit_length.value() += code_point_length_in_utf8(view[j]);
1978
0
        }
1979
1980
0
        if (!mask_it.is_end() && mask_it.key() <= i) {
1981
0
            auto mask_it_peek = mask_it;
1982
0
            ++mask_it_peek;
1983
0
            if (!mask_it_peek.is_end() && mask_it_peek.key() > i)
1984
0
                mask_it = mask_it_peek;
1985
0
        }
1986
0
    }
1987
1988
0
    metrics.line_metrics.append(current_line);
1989
1990
0
    for (auto& line : metrics.line_metrics)
1991
0
        metrics.max_line_length = max(line.total_length(), metrics.max_line_length);
1992
1993
0
    metrics.grapheme_breaks = move(grapheme_breaks);
1994
1995
0
    return metrics;
1996
0
}
1997
1998
VTState actual_rendered_string_length_step(StringMetrics& metrics, size_t index, StringMetrics::LineMetrics& current_line, u32 c, u32 next_c, VTState state, Optional<Style::Mask> const& mask, Optional<size_t> const& maximum_line_width, Optional<size_t&> last_return)
1999
0
{
2000
0
    auto const save_line = [&metrics, &current_line, &last_return, &index]() {
2001
0
        if (last_return.has_value()) {
2002
0
            auto const last_index = index - 1;
2003
0
            current_line.bit_length = last_index - *last_return + 1;
2004
0
            last_return.value() = last_index + 1;
2005
0
        }
2006
0
        metrics.line_metrics.append(current_line);
2007
2008
0
        current_line.masked_chars = {};
2009
0
        current_line.length = 0;
2010
0
        current_line.visible_length = 0;
2011
0
        current_line.bit_length = {};
2012
0
    };
2013
2014
    // FIXME: current_line.visible_length can go above maximum_line_width when using masks
2015
0
    if (maximum_line_width.has_value() && current_line.visible_length >= maximum_line_width.value())
2016
0
        save_line();
2017
2018
0
    ScopeGuard bit_length_update { [&last_return, &current_line, &index]() {
2019
0
        if (last_return.has_value())
2020
0
            current_line.bit_length = index - *last_return + 1;
2021
0
    } };
2022
2023
0
    switch (state) {
2024
0
    case Free: {
2025
0
        if (c == '\x1b') { // escape
2026
0
            return Escape;
2027
0
        }
2028
0
        if (c == '\r') { // carriage return
2029
0
            current_line.masked_chars = {};
2030
0
            current_line.length = 0;
2031
0
            current_line.visible_length = 0;
2032
0
            if (!metrics.line_metrics.is_empty())
2033
0
                metrics.line_metrics.last() = { {}, 0 };
2034
0
            return state;
2035
0
        }
2036
0
        if (c == '\n') { // return
2037
0
            save_line();
2038
0
            return state;
2039
0
        }
2040
0
        if (c == '\t') {
2041
            // Tabs are a special case, because their width is variable.
2042
0
            ++current_line.length;
2043
0
            current_line.visible_length += (8 - (current_line.visible_length % 8));
2044
0
            return state;
2045
0
        }
2046
0
        auto is_control = is_ascii_control(c);
2047
0
        if (is_control) {
2048
0
            if (mask.has_value())
2049
0
                current_line.masked_chars.append({ index, 1, mask->replacement_view.length() });
2050
0
            else
2051
0
                current_line.masked_chars.append({ index, 1, c < 64 ? 2u : 4u }); // if the character cannot be represented as ^c, represent it as \xbb.
2052
0
        }
2053
        // FIXME: This will not support anything sophisticated
2054
0
        if (mask.has_value()) {
2055
0
            current_line.length += mask->replacement_view.length();
2056
0
            current_line.visible_length += mask->replacement_view.length();
2057
0
            metrics.total_length += mask->replacement_view.length();
2058
0
        } else if (is_control) {
2059
0
            current_line.length += current_line.masked_chars.last().masked_length;
2060
0
            current_line.visible_length += current_line.masked_chars.last().masked_length;
2061
0
            metrics.total_length += current_line.masked_chars.last().masked_length;
2062
0
        } else {
2063
0
            ++current_line.length;
2064
0
            ++current_line.visible_length;
2065
0
            ++metrics.total_length;
2066
0
        }
2067
0
        return state;
2068
0
    }
2069
0
    case Escape:
2070
0
        if (c == ']') {
2071
0
            if (next_c == '0')
2072
0
                state = Title;
2073
0
            if (next_c == '8')
2074
0
                state = URL;
2075
0
            return state;
2076
0
        }
2077
0
        if (c == '[') {
2078
0
            return Bracket;
2079
0
        }
2080
        // FIXME: This does not support non-VT (aside from set-title) escapes
2081
0
        return state;
2082
0
    case Bracket:
2083
0
        if (is_ascii_digit(c)) {
2084
0
            return BracketArgsSemi;
2085
0
        }
2086
0
        return state;
2087
0
    case BracketArgsSemi:
2088
0
        if (c == ';') {
2089
0
            return Bracket;
2090
0
        }
2091
0
        if (!is_ascii_digit(c))
2092
0
            state = Free;
2093
0
        return state;
2094
0
    case Title:
2095
0
        if (c == 7)
2096
0
            state = Free;
2097
0
        return state;
2098
0
    case URL:
2099
0
        if (c == '\\')
2100
0
            state = Free;
2101
0
        return state;
2102
0
    }
2103
0
    return state;
2104
0
}
2105
2106
Result<Vector<size_t, 2>, Editor::Error> Editor::vt_dsr()
2107
0
{
2108
0
    char buf[16];
2109
2110
    // Read whatever junk there is before talking to the terminal
2111
    // and insert them later when we're reading user input.
2112
0
    bool more_junk_to_read { false };
2113
0
    timeval timeout { 0, 0 };
2114
0
    fd_set readfds;
2115
0
    FD_ZERO(&readfds);
2116
0
    FD_SET(0, &readfds);
2117
2118
0
    do {
2119
0
        more_junk_to_read = false;
2120
0
        [[maybe_unused]] auto rc = select(1, &readfds, nullptr, nullptr, &timeout);
2121
0
        if (FD_ISSET(0, &readfds)) {
2122
0
            auto nread = read(0, buf, 16);
2123
0
            if (nread < 0) {
2124
0
                m_input_error = Error::ReadFailure;
2125
0
                finish();
2126
0
                break;
2127
0
            }
2128
2129
0
            if (nread == 0)
2130
0
                break;
2131
2132
0
            m_incomplete_data.append(buf, nread);
2133
0
            more_junk_to_read = true;
2134
0
        }
2135
0
    } while (more_junk_to_read);
2136
2137
0
    if (m_input_error.has_value())
2138
0
        return m_input_error.value();
2139
2140
0
    fputs("\033[6n\n", stderr);
2141
0
    fflush(stderr);
2142
2143
    // Parse the DSR response
2144
    // it should be of the form .*\e[\d+;\d+R.*
2145
    // Anything not part of the response is just added to the incomplete data.
2146
0
    enum {
2147
0
        Free,
2148
0
        SawEsc,
2149
0
        SawBracket,
2150
0
        InFirstCoordinate,
2151
0
        SawSemicolon,
2152
0
        InSecondCoordinate,
2153
0
        SawR,
2154
0
    } state { Free };
2155
0
    auto has_error = false;
2156
0
    Vector<char, 4> coordinate_buffer;
2157
0
    size_t row { 1 }, col { 1 };
2158
2159
0
    do {
2160
0
        char c;
2161
0
        auto nread = read(0, &c, 1);
2162
0
        if (nread < 0) {
2163
0
            if (errno == 0 || errno == EINTR) {
2164
                // ????
2165
0
                continue;
2166
0
            }
2167
0
            dbgln("Error while reading DSR: {}", strerror(errno));
2168
0
            return Error::ReadFailure;
2169
0
        }
2170
0
        if (nread == 0) {
2171
0
            dbgln("Terminal DSR issue; received no response");
2172
0
            return Error::Empty;
2173
0
        }
2174
2175
0
        switch (state) {
2176
0
        case Free:
2177
0
            if (c == '\x1b') {
2178
0
                state = SawEsc;
2179
0
                continue;
2180
0
            }
2181
0
            m_incomplete_data.append(c);
2182
0
            continue;
2183
0
        case SawEsc:
2184
0
            if (c == '[') {
2185
0
                state = SawBracket;
2186
0
                continue;
2187
0
            }
2188
0
            m_incomplete_data.append(c);
2189
0
            state = Free;
2190
0
            continue;
2191
0
        case SawBracket:
2192
0
            if (is_ascii_digit(c)) {
2193
0
                state = InFirstCoordinate;
2194
0
                coordinate_buffer.clear_with_capacity();
2195
0
                coordinate_buffer.append(c);
2196
0
                continue;
2197
0
            }
2198
0
            m_incomplete_data.append(c);
2199
0
            state = Free;
2200
0
            continue;
2201
0
        case InFirstCoordinate:
2202
0
            if (is_ascii_digit(c)) {
2203
0
                coordinate_buffer.append(c);
2204
0
                continue;
2205
0
            }
2206
0
            if (c == ';') {
2207
0
                auto maybe_row = StringView { coordinate_buffer.data(), coordinate_buffer.size() }.to_number<unsigned>();
2208
0
                if (!maybe_row.has_value())
2209
0
                    has_error = true;
2210
0
                row = maybe_row.value_or(1u);
2211
0
                coordinate_buffer.clear_with_capacity();
2212
0
                state = SawSemicolon;
2213
0
                continue;
2214
0
            }
2215
0
            m_incomplete_data.append(c);
2216
0
            state = Free;
2217
0
            continue;
2218
0
        case SawSemicolon:
2219
0
            if (is_ascii_digit(c)) {
2220
0
                state = InSecondCoordinate;
2221
0
                coordinate_buffer.append(c);
2222
0
                continue;
2223
0
            }
2224
0
            m_incomplete_data.append(c);
2225
0
            state = Free;
2226
0
            continue;
2227
0
        case InSecondCoordinate:
2228
0
            if (is_ascii_digit(c)) {
2229
0
                coordinate_buffer.append(c);
2230
0
                continue;
2231
0
            }
2232
0
            if (c == 'R') {
2233
0
                auto maybe_column = StringView { coordinate_buffer.data(), coordinate_buffer.size() }.to_number<unsigned>();
2234
0
                if (!maybe_column.has_value())
2235
0
                    has_error = true;
2236
0
                col = maybe_column.value_or(1u);
2237
0
                coordinate_buffer.clear_with_capacity();
2238
0
                state = SawR;
2239
0
                continue;
2240
0
            }
2241
0
            m_incomplete_data.append(c);
2242
0
            state = Free;
2243
0
            continue;
2244
0
        case SawR:
2245
0
            m_incomplete_data.append(c);
2246
0
            continue;
2247
0
        default:
2248
0
            VERIFY_NOT_REACHED();
2249
0
        }
2250
0
    } while (state != SawR);
2251
2252
0
    if (has_error)
2253
0
        dbgln("Terminal DSR issue, couldn't parse DSR response");
2254
0
    return Vector<size_t, 2> { row, col };
2255
0
}
2256
2257
ByteString Editor::line(size_t up_to_index) const
2258
0
{
2259
0
    StringBuilder builder;
2260
0
    builder.append(Utf32View { m_buffer.data(), min(m_buffer.size(), up_to_index) });
2261
0
    return builder.to_byte_string();
2262
0
}
2263
2264
void Editor::remove_at_index(size_t index)
2265
0
{
2266
    // See if we have any anchored styles, and reposition them if needed.
2267
0
    readjust_anchored_styles(index, ModificationKind::Removal);
2268
0
    auto cp = m_buffer[index];
2269
0
    m_buffer.remove(index);
2270
0
    if (cp == '\n')
2271
0
        ++m_extra_forward_lines;
2272
0
    ++m_chars_touched_in_the_middle;
2273
0
}
2274
2275
void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modification)
2276
0
{
2277
0
    struct Anchor {
2278
0
        Span old_span;
2279
0
        Span new_span;
2280
0
        Style style;
2281
0
    };
2282
0
    Vector<Anchor> anchors_to_relocate;
2283
0
    auto index_shift = modification == ModificationKind::Insertion ? 1 : -1;
2284
0
    auto forced_removal = modification == ModificationKind::ForcedOverlapRemoval;
2285
2286
0
    for (auto& start_entry : m_current_spans.m_anchored_spans_starting) {
2287
0
        for (auto& end_entry : start_entry.value) {
2288
0
            if (forced_removal) {
2289
0
                if (start_entry.key <= hint_index && end_entry.key > hint_index) {
2290
                    // Remove any overlapping regions.
2291
0
                    continue;
2292
0
                }
2293
0
            }
2294
0
            if (start_entry.key >= hint_index) {
2295
0
                if (start_entry.key == hint_index && end_entry.key == hint_index + 1 && modification == ModificationKind::Removal) {
2296
                    // Remove the anchor, as all its text was wiped.
2297
0
                    continue;
2298
0
                }
2299
                // Shift everything.
2300
0
                anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key + index_shift, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
2301
0
                continue;
2302
0
            }
2303
0
            if (end_entry.key > hint_index) {
2304
                // Shift just the end.
2305
0
                anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
2306
0
                continue;
2307
0
            }
2308
0
            anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, end_entry.value });
2309
0
        }
2310
0
    }
2311
2312
0
    m_current_spans.m_anchored_spans_ending.clear();
2313
0
    m_current_spans.m_anchored_spans_starting.clear();
2314
    // Pass over the relocations and update the stale entries.
2315
0
    for (auto& relocation : anchors_to_relocate) {
2316
0
        stylize(relocation.new_span, relocation.style);
2317
0
    }
2318
0
}
2319
2320
size_t StringMetrics::lines_with_addition(StringMetrics const& offset, size_t column_width) const
2321
0
{
2322
0
    size_t lines = 0;
2323
2324
0
    if (!line_metrics.is_empty()) {
2325
0
        for (size_t i = 0; i < line_metrics.size() - 1; ++i)
2326
0
            lines += (line_metrics[i].total_length() + column_width) / column_width;
2327
2328
0
        auto last = line_metrics.last().total_length();
2329
0
        last += offset.line_metrics.first().total_length();
2330
0
        lines += (last + column_width) / column_width;
2331
0
    }
2332
2333
0
    for (size_t i = 1; i < offset.line_metrics.size(); ++i)
2334
0
        lines += (offset.line_metrics[i].total_length() + column_width) / column_width;
2335
2336
0
    return lines;
2337
0
}
2338
2339
size_t StringMetrics::offset_with_addition(StringMetrics const& offset, size_t column_width) const
2340
0
{
2341
0
    if (offset.line_metrics.size() > 1)
2342
0
        return offset.line_metrics.last().total_length() % column_width;
2343
2344
0
    if (!line_metrics.is_empty()) {
2345
0
        auto last = line_metrics.last().total_length();
2346
0
        last += offset.line_metrics.first().total_length();
2347
0
        return last % column_width;
2348
0
    }
2349
2350
0
    if (offset.line_metrics.is_empty())
2351
0
        return 0;
2352
2353
0
    return offset.line_metrics.first().total_length() % column_width;
2354
0
}
2355
2356
bool Editor::Spans::contains_up_to_offset(Spans const& other, size_t offset) const
2357
0
{
2358
0
    auto compare = [&]<typename K, typename V>(HashMap<K, HashMap<K, V>> const& left, HashMap<K, HashMap<K, V>> const& right) -> bool {
2359
0
        for (auto& entry : right) {
2360
0
            if (entry.key > offset + 1)
2361
0
                continue;
2362
2363
0
            auto left_map_it = left.find(entry.key);
2364
0
            if (left_map_it == left.end())
2365
0
                return false;
2366
2367
0
            for (auto& left_entry : left_map_it->value) {
2368
0
                auto value_it = entry.value.find(left_entry.key);
2369
0
                if (value_it == entry.value.end()) {
2370
                    // Might have the same thing with a longer span
2371
0
                    bool found = false;
2372
0
                    for (auto& possibly_longer_span_entry : entry.value) {
2373
0
                        if (possibly_longer_span_entry.key > left_entry.key && possibly_longer_span_entry.key > offset && left_entry.value == possibly_longer_span_entry.value) {
2374
0
                            found = true;
2375
0
                            break;
2376
0
                        }
2377
0
                    }
2378
0
                    if (found)
2379
0
                        continue;
2380
                    if constexpr (LINE_EDITOR_DEBUG) {
2381
                        dbgln("Compare for {}-{} failed, no entry", entry.key, left_entry.key);
2382
                        for (auto& x : entry.value)
2383
                            dbgln("Have: {}-{} = {}", entry.key, x.key, x.value.to_byte_string());
2384
                    }
2385
0
                    return false;
2386
0
                } else if (value_it->value != left_entry.value) {
2387
0
                    dbgln_if(LINE_EDITOR_DEBUG, "Compare for {}-{} failed, different values: {} != {}", entry.key, left_entry.key, value_it->value.to_byte_string(), left_entry.value.to_byte_string());
2388
0
                    return false;
2389
0
                }
2390
0
            }
2391
0
        }
2392
2393
0
        return true;
2394
0
    };
2395
2396
0
    return compare(m_spans_starting, other.m_spans_starting)
2397
0
        && compare(m_anchored_spans_starting, other.m_anchored_spans_starting);
2398
0
}
2399
2400
}