浏览代码

LibLine: Support multiline editing

This commit also updates Shell, which uses actual_rendered_length.
AnotherTest 5 年之前
父节点
当前提交
a6fd969d93
共有 3 个文件被更改,包括 180 次插入86 次删除
  1. 123 68
      Libraries/LibLine/Editor.cpp
  2. 53 13
      Libraries/LibLine/Editor.h
  3. 4 5
      Shell/Shell.cpp

+ 123 - 68
Libraries/LibLine/Editor.cpp

@@ -847,12 +847,13 @@ void Editor::handle_read_event()
 
 
                 // Manually cleanup the search line.
                 // Manually cleanup the search line.
                 reposition_cursor();
                 reposition_cursor();
-                auto search_string_codepoint_length = Utf8View { search_string }.length_in_codepoints();
-                VT::clear_lines(0, (search_string_codepoint_length + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns);
+                auto search_metrics = actual_rendered_string_metrics(search_string);
+                auto metrics = actual_rendered_string_metrics(search_prompt);
+                VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns));
 
 
                 reposition_cursor();
                 reposition_cursor();
 
 
-                if (!m_reset_buffer_on_search_end || search_string_codepoint_length == 0) {
+                if (!m_reset_buffer_on_search_end || search_metrics.total_length == 0) {
                     // If the entry was empty, or we purposely quit without a newline,
                     // If the entry was empty, or we purposely quit without a newline,
                     // do not return anything; instead, just end the search.
                     // do not return anything; instead, just end the search.
                     end_search();
                     end_search();
@@ -938,8 +939,8 @@ void Editor::recalculate_origin()
     // the new size is smaller than our prompt, which would
     // the new size is smaller than our prompt, which would
     // cause said prompt to take up more space, so we should
     // cause said prompt to take up more space, so we should
     // compensate for that.
     // compensate for that.
-    if (m_cached_prompt_length >= m_num_columns) {
-        auto added_lines = (m_cached_prompt_length + 1) / m_num_columns - 1;
+    if (m_cached_prompt_metrics.max_line_length >= m_num_columns) {
+        auto added_lines = (m_cached_prompt_metrics.max_line_length + 1) / m_num_columns - 1;
         m_origin_row += added_lines;
         m_origin_row += added_lines;
     }
     }
 
 
@@ -949,11 +950,15 @@ void Editor::recalculate_origin()
 }
 }
 void Editor::cleanup()
 void Editor::cleanup()
 {
 {
-    VT::move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
+    VT::move_relative(-m_extra_forward_lines, m_pending_chars.size() - m_chars_inserted_in_the_middle);
     auto current_line = cursor_line();
     auto current_line = cursor_line();
 
 
-    VT::clear_lines(current_line - 1, num_lines() - current_line);
-    VT::move_relative(-num_lines() + 1, -offset_in_line() - m_old_prompt_length - m_pending_chars.size() + m_chars_inserted_in_the_middle);
+    // There's a newline at the top, don't clear that line.
+    if (current_prompt_metrics().line_lengths.first() == 0)
+        --current_line;
+    VT::clear_lines(current_line - 1, num_lines() - current_line + m_extra_forward_lines);
+    m_extra_forward_lines = 0;
+    reposition_cursor();
 };
 };
 
 
 void Editor::refresh_display()
 void Editor::refresh_display()
@@ -990,7 +995,7 @@ void Editor::refresh_display()
     if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
     if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
         // Probably just moving around.
         // Probably just moving around.
         reposition_cursor();
         reposition_cursor();
-        m_cached_buffer_size = m_buffer.size();
+        m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
         return;
         return;
     }
     }
     // We might be at the last line, and have more than one line;
     // We might be at the last line, and have more than one line;
@@ -1016,14 +1021,13 @@ void Editor::refresh_display()
             fputs((char*)m_pending_chars.data(), stdout);
             fputs((char*)m_pending_chars.data(), stdout);
             m_pending_chars.clear();
             m_pending_chars.clear();
             m_drawn_cursor = m_cursor;
             m_drawn_cursor = m_cursor;
-            m_cached_buffer_size = m_buffer.size();
+            m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
             fflush(stdout);
             fflush(stdout);
             return;
             return;
         }
         }
     }
     }
 
 
     // Ouch, reflow entire line.
     // Ouch, reflow entire line.
-    // FIXME: handle multiline stuff
     if (!has_cleaned_up) {
     if (!has_cleaned_up) {
         cleanup();
         cleanup();
     }
     }
@@ -1078,7 +1082,7 @@ void Editor::refresh_display()
 
 
     m_pending_chars.clear();
     m_pending_chars.clear();
     m_refresh_needed = false;
     m_refresh_needed = false;
-    m_cached_buffer_size = m_buffer.size();
+    m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
     m_chars_inserted_in_the_middle = 0;
     m_chars_inserted_in_the_middle = 0;
     if (!m_cached_prompt_valid) {
     if (!m_cached_prompt_valid) {
         m_cached_prompt_valid = true;
         m_cached_prompt_valid = true;
@@ -1303,70 +1307,101 @@ void VT::clear_to_end_of_line()
     fflush(stdout);
     fflush(stdout);
 }
 }
 
 
-size_t Editor::actual_rendered_string_length(const StringView& string) const
+Editor::StringMetrics Editor::actual_rendered_string_metrics(const StringView& string) const
 {
 {
     size_t length { 0 };
     size_t length { 0 };
-    enum VTState {
-        Free = 1,
-        Escape = 3,
-        Bracket = 5,
-        BracketArgsSemi = 7,
-        Title = 9,
-    } state { Free };
+    StringMetrics metrics;
+    VTState state { Free };
     Utf8View view { string };
     Utf8View view { string };
     auto it = view.begin();
     auto it = view.begin();
 
 
-    for (size_t i = 0; i < view.length_in_codepoints(); ++i, ++it) {
+    for (; it != view.end(); ++it) {
         auto c = *it;
         auto c = *it;
-        switch (state) {
-        case Free:
-            if (c == '\x1b') { // escape
-                state = Escape;
-                continue;
-            }
-            if (c == '\r' || c == '\n') { // return or carriage return
-                // Reset length to 0, since we either overwrite, or are on a newline.
-                length = 0;
-                continue;
-            }
-            // FIXME: This will not support anything sophisticated
-            ++length;
-            break;
-        case Escape:
-            if (c == ']') {
-                ++i;
-                ++it;
-                if (*it == '0')
-                    state = Title;
-                continue;
-            }
-            if (c == '[') {
-                state = Bracket;
-                continue;
-            }
-            // FIXME: This does not support non-VT (aside from set-title) escapes
-            break;
-        case Bracket:
-            if (isdigit(c)) {
-                state = BracketArgsSemi;
-                continue;
-            }
-            break;
-        case BracketArgsSemi:
-            if (c == ';') {
-                state = Bracket;
-                continue;
-            }
-            if (!isdigit(c))
-                state = Free;
-            break;
-        case Title:
-            if (c == 7)
-                state = Free;
-            break;
+        auto it_copy = it;
+        ++it_copy;
+        auto next_c = it_copy == view.end() ? 0 : *it_copy;
+        state = actual_rendered_string_length_step(metrics, length, c, next_c, state);
+    }
+
+    metrics.line_lengths.append(length);
+
+    for (auto& line : metrics.line_lengths)
+        metrics.max_line_length = max(line, metrics.max_line_length);
+
+    return metrics;
+}
+
+Editor::StringMetrics Editor::actual_rendered_string_metrics(const Utf32View& view) const
+{
+    size_t length { 0 };
+    StringMetrics metrics;
+    VTState state { Free };
+
+    for (size_t i = 0; i < view.length(); ++i) {
+        auto c = view.codepoints()[i];
+        auto next_c = i + 1 < view.length() ? view.codepoints()[i + 1] : 0;
+        state = actual_rendered_string_length_step(metrics, length, c, next_c, state);
+    }
+
+    metrics.line_lengths.append(length);
+
+    for (auto& line : metrics.line_lengths)
+        metrics.max_line_length = max(line, metrics.max_line_length);
+
+    return metrics;
+}
+
+Editor::VTState Editor::actual_rendered_string_length_step(StringMetrics& metrics, size_t& length, u32 c, u32 next_c, VTState state) const
+{
+    switch (state) {
+    case Free:
+        if (c == '\x1b') { // escape
+            return Escape;
+        }
+        if (c == '\r') { // carriage return
+            length = 0;
+            if (!metrics.line_lengths.is_empty())
+                metrics.line_lengths.last() = 0;
+            return state;
+        }
+        if (c == '\n') { // return
+            metrics.line_lengths.append(length);
+            length = 0;
+            return state;
         }
         }
+        // FIXME: This will not support anything sophisticated
+        ++length;
+        ++metrics.total_length;
+        return state;
+    case Escape:
+        if (c == ']') {
+            if (next_c == '0')
+                state = Title;
+            return state;
+        }
+        if (c == '[') {
+            return Bracket;
+        }
+        // FIXME: This does not support non-VT (aside from set-title) escapes
+        return state;
+    case Bracket:
+        if (isdigit(c)) {
+            return BracketArgsSemi;
+        }
+        return state;
+    case BracketArgsSemi:
+        if (c == ';') {
+            return Bracket;
+        }
+        if (!isdigit(c))
+            state = Free;
+        return state;
+    case Title:
+        if (c == 7)
+            state = Free;
+        return state;
     }
     }
-    return length;
+    return state;
 }
 }
 
 
 Vector<size_t, 2> Editor::vt_dsr()
 Vector<size_t, 2> Editor::vt_dsr()
@@ -1471,7 +1506,10 @@ void Editor::remove_at_index(size_t index)
 {
 {
     // See if we have any anchored styles, and reposition them if needed.
     // See if we have any anchored styles, and reposition them if needed.
     readjust_anchored_styles(index, ModificationKind::Removal);
     readjust_anchored_styles(index, ModificationKind::Removal);
+    auto cp = m_buffer[index];
     m_buffer.remove(index);
     m_buffer.remove(index);
+    if (cp == '\n')
+        ++m_extra_forward_lines;
 }
 }
 
 
 void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modification)
 void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modification)
@@ -1518,4 +1556,21 @@ void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modifi
         stylize(relocation.new_span, relocation.style);
         stylize(relocation.new_span, relocation.style);
     }
     }
 }
 }
+
+size_t Editor::StringMetrics::lines_with_addition(const StringMetrics& offset, size_t column_width) const
+{
+    size_t lines = 0;
+
+    for (size_t i = 0; i < line_lengths.size() - 1; ++i)
+        lines += (line_lengths[i] + column_width) / column_width;
+
+    auto last = line_lengths.last();
+    last += offset.line_lengths.first();
+    lines += (last + column_width) / column_width;
+
+    for (size_t i = 1; i < offset.line_lengths.size(); ++i)
+        lines += (offset.line_lengths[i] + column_width) / column_width;
+
+    return lines;
+}
 }
 }

+ 53 - 13
Libraries/LibLine/Editor.h

@@ -104,7 +104,22 @@ public:
     const Vector<String>& history() const { return m_history; }
     const Vector<String>& history() const { return m_history; }
 
 
     void register_character_input_callback(char ch, Function<bool(Editor&)> callback);
     void register_character_input_callback(char ch, Function<bool(Editor&)> callback);
-    size_t actual_rendered_string_length(const StringView& string) const;
+    struct StringMetrics {
+        Vector<size_t> line_lengths;
+        size_t total_length { 0 };
+        size_t max_line_length { 0 };
+
+        size_t lines_with_addition(const StringMetrics& offset, size_t column_width) const;
+        void reset()
+        {
+            line_lengths.clear();
+            total_length = 0;
+            max_line_length = 0;
+            line_lengths.append(0);
+        }
+    };
+    StringMetrics actual_rendered_string_metrics(const StringView&) const;
+    StringMetrics actual_rendered_string_metrics(const Utf32View&) const;
 
 
     Function<Vector<CompletionSuggestion>(const Editor&)> on_tab_complete;
     Function<Vector<CompletionSuggestion>(const Editor&)> on_tab_complete;
     Function<void()> on_interrupt_handled;
     Function<void()> on_interrupt_handled;
@@ -135,9 +150,9 @@ public:
     void set_prompt(const String& prompt)
     void set_prompt(const String& prompt)
     {
     {
         if (m_cached_prompt_valid)
         if (m_cached_prompt_valid)
-            m_old_prompt_length = m_cached_prompt_length;
+            m_old_prompt_metrics = m_cached_prompt_metrics;
         m_cached_prompt_valid = false;
         m_cached_prompt_valid = false;
-        m_cached_prompt_length = actual_rendered_string_length(prompt);
+        m_cached_prompt_metrics = actual_rendered_string_metrics(prompt);
         m_new_prompt = prompt;
         m_new_prompt = prompt;
     }
     }
 
 
@@ -168,9 +183,21 @@ public:
 
 
     bool is_editing() const { return m_is_editing; }
     bool is_editing() const { return m_is_editing; }
 
 
+    const Utf32View buffer_view() const { return { m_buffer.data(), m_buffer.size() }; }
+
 private:
 private:
     explicit Editor(Configuration configuration = {});
     explicit Editor(Configuration configuration = {});
 
 
+    enum VTState {
+        Free = 1,
+        Escape = 3,
+        Bracket = 5,
+        BracketArgsSemi = 7,
+        Title = 9,
+    };
+
+    VTState actual_rendered_string_length_step(StringMetrics&, size_t& length, u32, u32, VTState) const;
+
     // ^Core::Object
     // ^Core::Object
     virtual void save_to(JsonObject&) override;
     virtual void save_to(JsonObject&) override;
 
 
@@ -215,12 +242,12 @@ private:
 
 
     void reset()
     void reset()
     {
     {
-        m_cached_buffer_size = 0;
+        m_cached_buffer_metrics.reset();
         m_cached_prompt_valid = false;
         m_cached_prompt_valid = false;
         m_cursor = 0;
         m_cursor = 0;
         m_drawn_cursor = 0;
         m_drawn_cursor = 0;
         m_inline_search_cursor = 0;
         m_inline_search_cursor = 0;
-        m_old_prompt_length = m_cached_prompt_length;
+        m_old_prompt_metrics = m_cached_prompt_metrics;
         set_origin(0, 0);
         set_origin(0, 0);
         m_prompt_lines_at_suggestion_initiation = 0;
         m_prompt_lines_at_suggestion_initiation = 0;
         m_refresh_needed = true;
         m_refresh_needed = true;
@@ -238,24 +265,36 @@ private:
         m_initialized = false;
         m_initialized = false;
     }
     }
 
 
-    size_t current_prompt_length() const
+    const StringMetrics& current_prompt_metrics() const
     {
     {
-        return m_cached_prompt_valid ? m_cached_prompt_length : m_old_prompt_length;
+        return m_cached_prompt_valid ? m_cached_prompt_metrics : m_old_prompt_metrics;
     }
     }
 
 
     size_t num_lines() const
     size_t num_lines() const
     {
     {
-        return (m_cached_buffer_size + m_num_columns + current_prompt_length() - 1) / m_num_columns;
+        return current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns);
     }
     }
 
 
     size_t cursor_line() const
     size_t cursor_line() const
     {
     {
-        return (m_drawn_cursor + m_num_columns + current_prompt_length()) / m_num_columns;
+        auto cursor = m_drawn_cursor;
+        if (cursor > m_cursor)
+            cursor = m_cursor;
+        return current_prompt_metrics().lines_with_addition(
+            actual_rendered_string_metrics(buffer_view().substring_view(0, cursor)),
+            m_num_columns);
     }
     }
 
 
     size_t offset_in_line() const
     size_t offset_in_line() const
     {
     {
-        return (m_drawn_cursor + current_prompt_length()) % m_num_columns;
+        auto cursor = m_drawn_cursor;
+        if (cursor > m_cursor)
+            cursor = m_cursor;
+        auto buffer_metrics = actual_rendered_string_metrics(buffer_view().substring_view(0, cursor));
+        if (buffer_metrics.line_lengths.size() > 1)
+            return buffer_metrics.line_lengths.last() % m_num_columns;
+
+        return (buffer_metrics.line_lengths.last() + current_prompt_metrics().line_lengths.last()) % m_num_columns;
     }
     }
 
 
     void set_origin()
     void set_origin()
@@ -305,9 +344,10 @@ private:
     size_t m_times_tab_pressed { 0 };
     size_t m_times_tab_pressed { 0 };
     size_t m_num_columns { 0 };
     size_t m_num_columns { 0 };
     size_t m_num_lines { 1 };
     size_t m_num_lines { 1 };
-    size_t m_cached_prompt_length { 0 };
-    size_t m_old_prompt_length { 0 };
-    size_t m_cached_buffer_size { 0 };
+    size_t m_extra_forward_lines { 0 };
+    StringMetrics m_cached_prompt_metrics;
+    StringMetrics m_old_prompt_metrics;
+    StringMetrics m_cached_buffer_metrics;
     size_t m_prompt_lines_at_suggestion_initiation { 0 };
     size_t m_prompt_lines_at_suggestion_initiation { 0 };
     bool m_cached_prompt_valid { false };
     bool m_cached_prompt_valid { false };
 
 

+ 4 - 5
Shell/Shell.cpp

@@ -126,7 +126,8 @@ String Shell::prompt() const
     };
     };
 
 
     auto the_prompt = build_prompt();
     auto the_prompt = build_prompt();
-    auto prompt_length = editor->actual_rendered_string_length(the_prompt);
+    auto prompt_metrics = editor->actual_rendered_string_metrics(the_prompt);
+    auto prompt_length = prompt_metrics.line_lengths.last();
 
 
     if (m_should_continue != ExitCodeOrContinuationRequest::Nothing) {
     if (m_should_continue != ExitCodeOrContinuationRequest::Nothing) {
         const auto format_string = "\033[34m%.*-s\033[m";
         const auto format_string = "\033[34m%.*-s\033[m";
@@ -1769,10 +1770,8 @@ bool Shell::read_single_line()
     if (line.is_empty())
     if (line.is_empty())
         return true;
         return true;
 
 
-    // FIXME: This might be a bit counter-intuitive, since we put nothing
-    //        between the two lines, even though the user has pressed enter
-    //        but since the LineEditor cannot yet handle literal newlines
-    //        inside the text, we opt to do this the wrong way (for the time being)
+    if (!m_complete_line_builder.is_empty())
+        m_complete_line_builder.append("\n");
     m_complete_line_builder.append(line);
     m_complete_line_builder.append(line);
 
 
     auto complete_or_exit_code = run_command(m_complete_line_builder.string_view());
     auto complete_or_exit_code = run_command(m_complete_line_builder.string_view());