summaryrefslogtreecommitdiff
path: root/Libraries/LibLine
diff options
context:
space:
mode:
Diffstat (limited to 'Libraries/LibLine')
-rw-r--r--Libraries/LibLine/Editor.cpp88
-rw-r--r--Libraries/LibLine/Editor.h10
-rw-r--r--Libraries/LibLine/InternalFunctions.cpp2
3 files changed, 85 insertions, 15 deletions
diff --git a/Libraries/LibLine/Editor.cpp b/Libraries/LibLine/Editor.cpp
index 7ed04b93c4..0ac1fe100c 100644
--- a/Libraries/LibLine/Editor.cpp
+++ b/Libraries/LibLine/Editor.cpp
@@ -212,13 +212,15 @@ void Editor::add_to_history(const String& line)
String histcontrol = getenv("HISTCONTROL");
auto ignoredups = histcontrol == "ignoredups" || histcontrol == "ignoreboth";
auto ignorespace = histcontrol == "ignorespace" || histcontrol == "ignoreboth";
- if (ignoredups && !m_history.is_empty() && line == m_history.last())
+ if (ignoredups && !m_history.is_empty() && line == m_history.last().entry)
return;
if (ignorespace && line.starts_with(' '))
return;
if ((m_history.size() + 1) > m_history_capacity)
m_history.take_first();
- m_history.append(line);
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ m_history.append({ line, tv.tv_sec });
}
bool Editor::load_history(const String& path)
@@ -226,22 +228,86 @@ bool Editor::load_history(const String& path)
auto history_file = Core::File::construct(path);
if (!history_file->open(Core::IODevice::ReadOnly))
return false;
- while (history_file->can_read_line()) {
- add_to_history(history_file->read_line());
+ auto data = history_file->read_all();
+ auto hist = StringView { data.data(), data.size() };
+ for (auto& str : hist.split_view("\n\n")) {
+ auto it = str.find_first_of("::").value_or(0);
+ auto time = str.substring_view(0, it).to_uint<time_t>().value_or(0);
+ auto string = str.substring_view(it == 0 ? it : it + 2);
+ m_history.append({ string, time });
}
return true;
}
+template<typename It0, typename It1, typename OutputT, typename MapperT, typename LessThan>
+static void merge(It0&& begin0, const It0& end0, It1&& begin1, const It1& end1, OutputT& output, MapperT left_mapper, LessThan less_than)
+{
+ for (;;) {
+ if (begin0 == end0 && begin1 == end1)
+ return;
+
+ if (begin0 == end0) {
+ auto&& right = *begin1;
+ if (output.last().entry != right.entry)
+ output.append(right);
+ ++begin1;
+ continue;
+ }
+
+ auto&& left = left_mapper(*begin0);
+ if (left.entry.is_whitespace()) {
+ ++begin0;
+ continue;
+ }
+ if (begin1 == end1) {
+ if (output.last().entry != left.entry)
+ output.append(left);
+ ++begin0;
+ continue;
+ }
+
+ auto&& right = *begin1;
+ if (less_than(left, right)) {
+ if (output.last().entry != left.entry)
+ output.append(left);
+ ++begin0;
+ } else {
+ if (output.last().entry != right.entry)
+ output.append(right);
+ ++begin1;
+ if (right.entry == left.entry)
+ ++begin0;
+ }
+ }
+}
+
bool Editor::save_history(const String& path)
{
+ Vector<HistoryEntry> final_history { { "", 0 } };
+ {
+ auto file_or_error = Core::File::open(path, Core::IODevice::ReadWrite, 0600);
+ if (file_or_error.is_error())
+ return false;
+ auto file = file_or_error.release_value();
+ merge(
+ file->line_begin(), file->line_end(), m_history.begin(), m_history.end(), final_history,
+ [](StringView str) {
+ auto it = str.find_first_of("::").value_or(0);
+ auto time = str.substring_view(0, it).to_uint<time_t>().value_or(0);
+ auto string = str.substring_view(it == 0 ? it : it + 2);
+ return HistoryEntry { string, time };
+ },
+ [](const HistoryEntry& left, const HistoryEntry& right) { return left.timestamp < right.timestamp; });
+ }
+
auto file_or_error = Core::File::open(path, Core::IODevice::WriteOnly, 0600);
if (file_or_error.is_error())
return false;
- auto& file = *file_or_error.value();
- for (const auto& line : m_history) {
- file.write(line);
- file.write("\n");
- }
+ auto file = file_or_error.release_value();
+ final_history.take_first();
+ for (const auto& entry : final_history)
+ file->write(String::formatted("{}::{}\n\n", entry.timestamp, entry.entry));
+
return true;
}
@@ -1002,7 +1068,7 @@ bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginn
size_t search_offset = m_search_offset;
for (size_t i = m_history_cursor; i > 0; --i) {
auto& entry = m_history[i - 1];
- auto contains = from_beginning ? entry.starts_with(phrase) : entry.contains(phrase);
+ auto contains = from_beginning ? entry.entry.starts_with(phrase) : entry.entry.contains(phrase);
if (contains) {
last_matching_offset = i - 1;
if (search_offset == 0) {
@@ -1022,7 +1088,7 @@ bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginn
if (found) {
m_buffer.clear();
m_cursor = 0;
- insert(m_history[last_matching_offset]);
+ insert(m_history[last_matching_offset].entry);
// Always needed, as we have cleared the buffer above.
m_refresh_needed = true;
}
diff --git a/Libraries/LibLine/Editor.h b/Libraries/LibLine/Editor.h
index 65655043e9..407b3fd9fa 100644
--- a/Libraries/LibLine/Editor.h
+++ b/Libraries/LibLine/Editor.h
@@ -154,7 +154,7 @@ public:
void add_to_history(const String& line);
bool load_history(const String& path);
bool save_history(const String& path);
- const Vector<String>& history() const { return m_history; }
+ const auto& history() const { return m_history; }
void register_key_input_callback(const KeyBinding&);
void register_key_input_callback(Vector<Key> keys, Function<bool(Editor&)> callback) { m_callback_machine.register_key_input_callback(move(keys), move(callback)); }
@@ -451,9 +451,13 @@ private:
bool m_was_resized { false };
// FIXME: This should be something more take_first()-friendly.
- Vector<String> m_history;
+ struct HistoryEntry {
+ String entry;
+ time_t timestamp;
+ };
+ Vector<HistoryEntry> m_history;
size_t m_history_cursor { 0 };
- size_t m_history_capacity { 100 };
+ size_t m_history_capacity { 1024 };
enum class InputState {
Free,
diff --git a/Libraries/LibLine/InternalFunctions.cpp b/Libraries/LibLine/InternalFunctions.cpp
index 09bc97a9c2..6cb80671f8 100644
--- a/Libraries/LibLine/InternalFunctions.cpp
+++ b/Libraries/LibLine/InternalFunctions.cpp
@@ -425,7 +425,7 @@ void Editor::insert_last_words()
{
if (!m_history.is_empty()) {
// FIXME: This isn't quite right: if the last arg was `"foo bar"` or `foo\ bar` (but not `foo\\ bar`), we should insert that whole arg as last token.
- if (auto last_words = m_history.last().split_view(' '); !last_words.is_empty())
+ if (auto last_words = m_history.last().entry.split_view(' '); !last_words.is_empty())
insert(last_words.last());
}
}