summaryrefslogtreecommitdiff
path: root/Libraries/LibLine
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2020-04-19 23:34:58 +0430
committerAndreas Kling <kling@serenityos.org>2020-04-20 17:27:30 +0200
commit58912994ab47848d9883e30c0c0d4ece611e8637 (patch)
tree2ab60848b20d0255436ea2bc684092b2ce4463a0 /Libraries/LibLine
parentdf6696f5762a8cdd31ab225f8e71ddd12b593722 (diff)
downloadserenity-58912994ab47848d9883e30c0c0d4ece611e8637.zip
LibLine: Implement ^R searching
This commit adds searching in the editor history with ^R. It does so by instantiating...another Line::Editor inside the current Line::Editor :^)
Diffstat (limited to 'Libraries/LibLine')
-rw-r--r--Libraries/LibLine/Editor.cpp151
-rw-r--r--Libraries/LibLine/Editor.h36
2 files changed, 167 insertions, 20 deletions
diff --git a/Libraries/LibLine/Editor.cpp b/Libraries/LibLine/Editor.cpp
index b24348c991..fe97776cd0 100644
--- a/Libraries/LibLine/Editor.cpp
+++ b/Libraries/LibLine/Editor.cpp
@@ -33,8 +33,9 @@
namespace Line {
-Editor::Editor()
+Editor::Editor(bool always_refresh)
{
+ m_always_refresh = always_refresh;
m_pending_chars = ByteBuffer::create_uninitialized(0);
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
@@ -126,7 +127,17 @@ String Editor::get_line(const String& prompt)
m_history_cursor = m_history.size();
for (;;) {
+ if (m_always_refresh)
+ m_refresh_needed = true;
refresh_display();
+ if (m_finish) {
+ m_finish = false;
+ printf("\n");
+ fflush(stdout);
+ auto string = String::copy(m_buffer);
+ m_buffer.clear();
+ return string;
+ }
char keybuf[16];
ssize_t nread = read(0, keybuf, sizeof(keybuf));
// FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
@@ -138,13 +149,17 @@ String Editor::get_line(const String& prompt)
m_was_interrupted = false;
if (!m_buffer.is_empty())
printf("^C");
+
+ if (m_is_searching) {
+ end_search();
+ continue;
+ }
}
if (m_was_resized)
continue;
- m_buffer.clear();
- putchar('\n');
- return String::empty();
+ finish();
+ continue;
}
perror("read failed");
// FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
@@ -500,6 +515,9 @@ String Editor::get_line(const String& prompt)
m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
auto do_backspace = [&] {
+ if (m_is_searching) {
+ return;
+ }
if (m_cursor == 0) {
fputc('\a', stdout);
fflush(stdout);
@@ -549,11 +567,111 @@ String Editor::get_line(const String& prompt)
m_cursor = 0;
continue;
}
+ // ^R
+ if (ch == 0x12) {
+ if (m_is_searching) {
+ // how did we get here?
+ ASSERT_NOT_REACHED();
+ } else {
+ m_is_searching = true;
+ m_search_offset = 0;
+ m_pre_search_buffer = m_buffer;
+ m_pre_search_cursor = m_cursor;
+ m_search_editor = make<Editor>(true); // Has anyone seen 'Inception'?
+ m_search_editor->initialize();
+ m_search_editor->on_display_refresh = [this](Editor& search_editor) {
+ int last_matching_offset = -1;
+
+ // do not search for empty strings
+ if (search_editor.buffer().size() > 0) {
+ size_t search_offset = m_search_offset;
+ StringView search_term { search_editor.buffer().data(), search_editor.buffer().size() };
+ for (size_t i = m_history_cursor; i > 0; --i) {
+ if (m_history[i - 1].contains(search_term)) {
+ last_matching_offset = i - 1;
+ if (search_offset == 0)
+ break;
+ --search_offset;
+ }
+ }
+
+ if (last_matching_offset == -1) {
+ fputc('\a', stdout);
+ fflush(stdout);
+ return;
+ }
+ }
+
+ m_buffer.clear();
+ m_cursor = 0;
+ if (last_matching_offset >= 0)
+ insert(m_history[last_matching_offset]);
+ // always needed
+ m_refresh_needed = true;
+ refresh_display();
+ return;
+ };
+
+ // whenever the search editor gets a ^R, cycle between history entries
+ m_search_editor->register_character_input_callback(0x12, [this](Editor& search_editor) {
+ ++m_search_offset;
+ search_editor.m_refresh_needed = true;
+ return false; // Do not process this key event
+ });
+
+ // whenever the search editor gets a backspace, cycle back between history entries
+ // unless we're at the zeroth entry, in which case, allow the deletion
+ m_search_editor->register_character_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
+ if (m_search_offset > 0) {
+ --m_search_offset;
+ search_editor.m_refresh_needed = true;
+ return false; // Do not process this key event
+ }
+ return true;
+ });
+
+ // quit without clearing the current buffer
+ m_search_editor->register_character_input_callback('\t', [this](Editor& search_editor) {
+ search_editor.finish();
+ m_reset_buffer_on_search_end = false;
+ return false;
+ });
+
+ printf("\n");
+ fflush(stdout);
+
+ auto search_prompt = "\x1b[32msearch:\x1b[0m ";
+ auto search_string = m_search_editor->get_line(search_prompt);
+ m_search_editor = nullptr;
+ m_is_searching = false;
+ m_search_offset = 0;
+
+ // manually cleanup the search line
+ reposition_cursor();
+ vt_clear_lines(0, (search_string.length() + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns);
+
+ reposition_cursor();
+
+ if (!m_reset_buffer_on_search_end || search_string.length() == 0) {
+ // if the entry was empty, or we purposely quit without a newline,
+ // do not return anything
+ // instead, just end the search
+ end_search();
+ continue;
+ }
+
+ // return the string
+ finish();
+ continue;
+ }
+ continue;
+ }
// Normally ^D
if (ch == m_termios.c_cc[VEOF]) {
if (m_buffer.is_empty()) {
printf("<EOF>\n");
- exit(0);
+ if (!m_always_refresh) // this is a little off, but it'll do for now
+ exit(0);
}
continue;
}
@@ -564,11 +682,8 @@ String Editor::get_line(const String& prompt)
continue;
}
if (ch == '\n') {
- putchar('\n');
- fflush(stdout);
- auto string = String::copy(m_buffer);
- m_buffer.clear();
- return string;
+ finish();
+ continue;
}
insert(ch);
@@ -591,15 +706,17 @@ void Editor::recalculate_origin()
// but that will be calculated and applied at the next
// refresh cycle
}
-void Editor::refresh_display()
+void Editor::cleanup()
{
- auto cleanup = [&] {
- vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
- auto current_line = cursor_line();
+ vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
+ 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);
+};
- 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);
- };
+void Editor::refresh_display()
+{
auto has_cleaned_up = false;
// someone changed the window size, figure it out
// and react to it, we might need to redraw
diff --git a/Libraries/LibLine/Editor.h b/Libraries/LibLine/Editor.h
index ce7813d31a..aa735c531b 100644
--- a/Libraries/LibLine/Editor.h
+++ b/Libraries/LibLine/Editor.h
@@ -77,7 +77,7 @@ struct CompletionSuggestion {
class Editor {
public:
- Editor();
+ explicit Editor(bool always_refresh = false);
~Editor();
void initialize()
@@ -143,6 +143,11 @@ public:
const struct termios& termios() const { return m_termios; }
const struct termios& default_termios() const { return m_default_termios; }
+ void finish()
+ {
+ m_finish = true;
+ }
+
private:
void vt_save_cursor();
void vt_restore_cursor();
@@ -155,6 +160,19 @@ private:
Style find_applicable_style(size_t offset) const;
+ inline void end_search()
+ {
+ m_is_searching = false;
+ m_refresh_needed = true;
+ m_search_offset = 0;
+ if (m_reset_buffer_on_search_end) {
+ m_buffer = m_pre_search_buffer;
+ m_cursor = m_pre_search_cursor;
+ }
+ m_reset_buffer_on_search_end = false;
+ m_search_editor = nullptr;
+ }
+
void reset()
{
m_origin_x = 0;
@@ -165,6 +183,7 @@ private:
}
void refresh_display();
+ void cleanup();
size_t current_prompt_length() const
{
@@ -197,6 +216,15 @@ private:
void recalculate_origin();
void reposition_cursor();
+ bool m_finish { false };
+
+ OwnPtr<Editor> m_search_editor;
+ bool m_is_searching { false };
+ bool m_reset_buffer_on_search_end { true };
+ size_t m_search_offset { 0 };
+ size_t m_pre_search_cursor { 0 };
+ Vector<char, 1024> m_pre_search_buffer;
+
Vector<char, 1024> m_buffer;
ByteBuffer m_pending_chars;
size_t m_cursor { 0 };
@@ -225,6 +253,8 @@ private:
size_t m_next_suggestion_invariant_offset { 0 };
size_t m_largest_common_suggestion_prefix_length { 0 };
+ bool m_always_refresh { false };
+
enum class TabDirection {
Forward,
Backward,
@@ -235,8 +265,8 @@ private:
// TODO: handle signals internally
struct termios m_termios, m_default_termios;
- bool m_was_interrupted = false;
- bool m_was_resized = false;
+ bool m_was_interrupted { false };
+ bool m_was_resized { false };
// FIXME: This should be something more take_first()-friendly.
Vector<String> m_history;