1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-09 09:34:57 +09:00

LibLine: Handle history across multiple concurrent sessions better

- Store history entries as (timestamp)::(entry)\n\n
- Merge the entries together when saving to avoid loss of history
  entries

To ideally make having two concurrently open shells
(or `js` repls or whatever) not overwrite each others' history entries.
This commit is contained in:
AnotherTest 2021-01-11 18:58:42 +03:30 committed by Andreas Kling
parent 711ced80c0
commit 510030971b
Notes: sideshowbarker 2024-07-18 23:55:36 +09:00
5 changed files with 88 additions and 18 deletions

View file

@ -269,7 +269,7 @@ int main(int argc, char** argv)
Optional<Debug::DebugSession::DebugDecision> decision;
if (command.is_empty() && !editor->history().is_empty()) {
command = editor->history().last();
command = editor->history().last().entry;
}
if (command == "cont") {
decision = Debug::DebugSession::DebugDecision::Continue;
@ -300,7 +300,7 @@ int main(int argc, char** argv)
if (success && !command.is_empty()) {
// Don't add repeated commands to history
if (editor->history().is_empty() || editor->history().last() != command)
if (editor->history().is_empty() || editor->history().last().entry != command)
editor->add_to_history(command);
}
if (!success) {

View file

@ -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;
}

View file

@ -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,

View file

@ -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());
}
}

View file

@ -463,7 +463,7 @@ int Shell::builtin_disown(int argc, const char** argv)
int Shell::builtin_history(int, const char**)
{
for (size_t i = 0; i < m_editor->history().size(); ++i) {
printf("%6zu %s\n", i, m_editor->history()[i].characters());
printf("%6zu %s\n", i, m_editor->history()[i].entry.characters());
}
return 0;
}