mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-29 06:13:20 +00:00
reader: make undo history private to editable_line_t
reader handles way too much state itself. Let's move the undo handling to editable_line_t entirely. No functional change.
This commit is contained in:
parent
2b2f64c045
commit
8b4b24428c
3 changed files with 70 additions and 75 deletions
|
@ -3880,11 +3880,11 @@ static void test_undo() {
|
|||
do_test(!line.undo()); // nothing to undo
|
||||
do_test(line.text().empty());
|
||||
do_test(line.position() == 0);
|
||||
line.push_edit(edit_t(0, 0, L"a b c"));
|
||||
line.push_edit(edit_t(0, 0, L"a b c"), true);
|
||||
do_test(line.text() == L"a b c");
|
||||
do_test(line.position() == 5);
|
||||
line.set_position(2);
|
||||
line.push_edit(edit_t(2, 1, L"B")); // replacement right of cursor
|
||||
line.push_edit(edit_t(2, 1, L"B"), true); // replacement right of cursor
|
||||
do_test(line.text() == L"a B c");
|
||||
line.undo();
|
||||
do_test(line.text() == L"a b c");
|
||||
|
@ -3895,7 +3895,7 @@ static void test_undo() {
|
|||
|
||||
do_test(!line.redo()); // nothing to redo
|
||||
|
||||
line.push_edit(edit_t(0, 2, L"")); // deletion left of cursor
|
||||
line.push_edit(edit_t(0, 2, L""), true); // deletion left of cursor
|
||||
do_test(line.text() == L"B c");
|
||||
do_test(line.position() == 1);
|
||||
line.undo();
|
||||
|
@ -3905,27 +3905,23 @@ static void test_undo() {
|
|||
do_test(line.text() == L"B c");
|
||||
do_test(line.position() == 1);
|
||||
|
||||
line.push_edit(edit_t(0, line.size(), L"a b c")); // replacement left and right of cursor
|
||||
line.push_edit(edit_t(0, line.size(), L"a b c"), true); // replacement left and right of cursor
|
||||
do_test(line.text() == L"a b c");
|
||||
do_test(line.position() == 5);
|
||||
|
||||
say(L"Testing undoing coalesced edits.");
|
||||
line.clear();
|
||||
line.push_edit(edit_t(line.position(), 0, L"a"));
|
||||
line.insert_coalesce(L"b");
|
||||
line.insert_coalesce(L"c");
|
||||
do_test(line.undo_history.edits.size() == 1);
|
||||
line.push_edit(edit_t(line.position(), 0, L" "));
|
||||
do_test(line.undo_history.edits.size() == 2);
|
||||
line.push_edit(edit_t(line.position(), 0, L"a"), true);
|
||||
line.push_edit(edit_t(line.position(), 0, L"b"), true);
|
||||
line.push_edit(edit_t(line.position(), 0, L"c"), true);
|
||||
line.push_edit(edit_t(line.position(), 0, L" "), true);
|
||||
line.undo();
|
||||
line.undo();
|
||||
line.redo();
|
||||
do_test(line.text() == L"abc");
|
||||
do_test(line.undo_history.edits.size() == 2);
|
||||
// This removes the space insertion from the history, but does not coalesce with the first edit.
|
||||
line.push_edit(edit_t(line.position(), 0, L"d"));
|
||||
do_test(line.undo_history.edits.size() == 2);
|
||||
line.insert_coalesce(L"e");
|
||||
line.push_edit(edit_t(line.position(), 0, L"d"), true);
|
||||
line.push_edit(edit_t(line.position(), 0, L"e"), true);
|
||||
do_test(line.text() == L"abcde");
|
||||
line.undo();
|
||||
do_test(line.text() == L"abc");
|
||||
|
|
107
src/reader.cpp
107
src/reader.cpp
|
@ -223,34 +223,17 @@ static size_t cursor_position_after_edit(const edit_t &edit) {
|
|||
return cursor > removed ? cursor - removed : 0;
|
||||
}
|
||||
|
||||
/// Whether we want to append this string to the previous edit.
|
||||
static bool want_to_coalesce_insertion_of(const editable_line_t &el, const wcstring &str) {
|
||||
// The previous edit must support coalescing.
|
||||
if (!el.undo_history.may_coalesce) return false;
|
||||
// Only consolidate single character inserts.
|
||||
if (str.size() != 1) return false;
|
||||
// Make an undo group after every space.
|
||||
if (str.at(0) == L' ' && !el.undo_history.try_coalesce) return false;
|
||||
assert(!el.undo_history.edits.empty());
|
||||
const edit_t &last_edit = el.undo_history.edits.back();
|
||||
// Don't add to the last edit if it deleted something.
|
||||
if (last_edit.length != 0) return false;
|
||||
// Must not have moved the cursor!
|
||||
if (cursor_position_after_edit(last_edit) != el.position()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editable_line_t::undo() {
|
||||
bool did_undo = false;
|
||||
maybe_t<int> last_group_id{-1};
|
||||
while (undo_history.edits_applied != 0) {
|
||||
const edit_t &edit = undo_history.edits.at(undo_history.edits_applied - 1);
|
||||
while (undo_history_.edits_applied != 0) {
|
||||
const edit_t &edit = undo_history_.edits.at(undo_history_.edits_applied - 1);
|
||||
if (did_undo && (!edit.group_id.has_value() || edit.group_id != last_group_id)) {
|
||||
// We've restored all the edits in this logical undo group
|
||||
break;
|
||||
}
|
||||
last_group_id = edit.group_id;
|
||||
undo_history.edits_applied--;
|
||||
undo_history_.edits_applied--;
|
||||
edit_t inverse = edit_t(edit.offset, edit.replacement.size(), L"");
|
||||
inverse.replacement = edit.old;
|
||||
size_t old_position = edit.cursor_position_before_edit;
|
||||
|
@ -260,18 +243,31 @@ bool editable_line_t::undo() {
|
|||
}
|
||||
|
||||
end_edit_group();
|
||||
undo_history.may_coalesce = false;
|
||||
undo_history_.may_coalesce = false;
|
||||
return did_undo;
|
||||
}
|
||||
|
||||
void editable_line_t::clear() {
|
||||
undo_history.clear();
|
||||
undo_history_.clear();
|
||||
if (empty()) return;
|
||||
set_text_bypassing_undo_history(L"");
|
||||
set_position(0);
|
||||
}
|
||||
|
||||
void editable_line_t::push_edit(edit_t &&edit) {
|
||||
void editable_line_t::push_edit(edit_t &&edit, bool allow_coalesce) {
|
||||
bool is_insertion = edit.length == 0;
|
||||
/// Coalescing insertion does not create a new undo entry but adds to the last insertion.
|
||||
if (allow_coalesce && is_insertion && want_to_coalesce_insertion_of(edit.replacement)) {
|
||||
assert(edit.offset == position());
|
||||
edit_t &last_edit = undo_history_.edits.back();
|
||||
last_edit.replacement.append(edit.replacement);
|
||||
apply_edit(&text_, edit);
|
||||
set_position(position() + edit.replacement.size());
|
||||
|
||||
assert(undo_history_.may_coalesce);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign a new group id or propagate the old one if we're in a logical grouping of edits
|
||||
if (edit_group_level_ != -1) {
|
||||
edit.group_id = edit_group_id_;
|
||||
|
@ -279,42 +275,37 @@ void editable_line_t::push_edit(edit_t &&edit) {
|
|||
|
||||
bool edit_does_nothing = edit.length == 0 && edit.replacement.empty();
|
||||
if (edit_does_nothing) return;
|
||||
if (undo_history.edits_applied != undo_history.edits.size()) {
|
||||
if (undo_history_.edits_applied != undo_history_.edits.size()) {
|
||||
// After undoing some edits, the user is making a new edit;
|
||||
// we are about to create a new edit branch.
|
||||
// Discard all edits that were undone because we only support
|
||||
// linear undo/redo, they will be unreachable.
|
||||
undo_history.edits.erase(undo_history.edits.begin() + undo_history.edits_applied,
|
||||
undo_history.edits.end());
|
||||
undo_history_.edits.erase(undo_history_.edits.begin() + undo_history_.edits_applied,
|
||||
undo_history_.edits.end());
|
||||
}
|
||||
edit.cursor_position_before_edit = position();
|
||||
edit.old = text_.substr(edit.offset, edit.length);
|
||||
apply_edit(&text_, edit);
|
||||
set_position(cursor_position_after_edit(edit));
|
||||
assert(undo_history.edits_applied == undo_history.edits.size());
|
||||
undo_history.edits_applied++;
|
||||
undo_history.edits.emplace_back(edit);
|
||||
}
|
||||
|
||||
void editable_line_t::insert_coalesce(const wcstring &str) {
|
||||
edit_t &edit = undo_history.edits.back();
|
||||
edit.replacement.append(str);
|
||||
apply_edit(&text_, edit_t(position(), 0, str));
|
||||
set_position(position() + str.size());
|
||||
assert(undo_history_.edits_applied == undo_history_.edits.size());
|
||||
undo_history_.may_coalesce =
|
||||
is_insertion && (undo_history_.try_coalesce || edit.replacement.size() == 1);
|
||||
undo_history_.edits_applied++;
|
||||
undo_history_.edits.emplace_back(std::move(edit));
|
||||
}
|
||||
|
||||
bool editable_line_t::redo() {
|
||||
bool did_redo = false;
|
||||
|
||||
maybe_t<int> last_group_id{-1};
|
||||
while (undo_history.edits_applied < undo_history.edits.size()) {
|
||||
const edit_t &edit = undo_history.edits.at(undo_history.edits_applied);
|
||||
while (undo_history_.edits_applied < undo_history_.edits.size()) {
|
||||
const edit_t &edit = undo_history_.edits.at(undo_history_.edits_applied);
|
||||
if (did_redo && (!edit.group_id.has_value() || edit.group_id != last_group_id)) {
|
||||
// We've restored all the edits in this logical undo group
|
||||
break;
|
||||
}
|
||||
last_group_id = edit.group_id;
|
||||
undo_history.edits_applied++;
|
||||
undo_history_.edits_applied++;
|
||||
apply_edit(&text_, edit);
|
||||
set_position(cursor_position_after_edit(edit));
|
||||
did_redo = true;
|
||||
|
@ -327,9 +318,9 @@ bool editable_line_t::redo() {
|
|||
void editable_line_t::begin_edit_group() {
|
||||
if (++edit_group_level_ == 0) {
|
||||
// Indicate that the next change must trigger the creation of a new history item
|
||||
undo_history.may_coalesce = false;
|
||||
undo_history_.may_coalesce = false;
|
||||
// Indicate that future changes should be coalesced into the same edit if possible.
|
||||
undo_history.try_coalesce = true;
|
||||
undo_history_.try_coalesce = true;
|
||||
// Assign a logical edit group id to future edits in this group
|
||||
edit_group_id_ += 1;
|
||||
}
|
||||
|
@ -343,11 +334,28 @@ void editable_line_t::end_edit_group() {
|
|||
}
|
||||
|
||||
if (--edit_group_level_ == -1) {
|
||||
undo_history.try_coalesce = false;
|
||||
undo_history.may_coalesce = false;
|
||||
undo_history_.try_coalesce = false;
|
||||
undo_history_.may_coalesce = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether we want to append this string to the previous edit.
|
||||
bool editable_line_t::want_to_coalesce_insertion_of(const wcstring &str) const {
|
||||
// The previous edit must support coalescing.
|
||||
if (!undo_history_.may_coalesce) return false;
|
||||
// Only consolidate single character inserts.
|
||||
if (str.size() != 1) return false;
|
||||
// Make an undo group after every space.
|
||||
if (str.at(0) == L' ' && !undo_history_.try_coalesce) return false;
|
||||
assert(!undo_history_.edits.empty());
|
||||
const edit_t &last_edit = undo_history_.edits.back();
|
||||
// Don't add to the last edit if it deleted something.
|
||||
if (last_edit.length != 0) return false;
|
||||
// Must not have moved the cursor!
|
||||
if (cursor_position_after_edit(last_edit) != position()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make the search case-insensitive unless we have an uppercase character.
|
||||
static history_search_flags_t smartcase_flags(const wcstring &query) {
|
||||
return query == wcstolower(query) ? history_search_ignore_case : 0;
|
||||
|
@ -1696,13 +1704,7 @@ void reader_data_t::delete_char(bool backward) {
|
|||
/// Returns true if the string changed.
|
||||
void reader_data_t::insert_string(editable_line_t *el, const wcstring &str) {
|
||||
if (!str.empty()) {
|
||||
if (!history_search.active() && want_to_coalesce_insertion_of(*el, str)) {
|
||||
el->insert_coalesce(str);
|
||||
assert(el->undo_history.may_coalesce);
|
||||
} else {
|
||||
el->push_edit(edit_t(el->position(), 0, str));
|
||||
el->undo_history.may_coalesce = el->undo_history.try_coalesce || (str.size() == 1);
|
||||
}
|
||||
el->push_edit(edit_t(el->position(), 0, str), !history_search.active());
|
||||
}
|
||||
|
||||
if (el == &command_line) {
|
||||
|
@ -1713,8 +1715,7 @@ void reader_data_t::insert_string(editable_line_t *el, const wcstring &str) {
|
|||
}
|
||||
|
||||
void reader_data_t::push_edit(editable_line_t *el, edit_t &&edit) {
|
||||
el->push_edit(std::move(edit));
|
||||
el->undo_history.may_coalesce = false;
|
||||
el->push_edit(std::move(edit), false);
|
||||
maybe_refilter_pager(el);
|
||||
}
|
||||
|
||||
|
@ -3048,7 +3049,7 @@ void reader_data_t::compute_and_apply_completions(readline_cmd_t c, readline_loo
|
|||
rls.complete_did_insert = false;
|
||||
size_t tok_off = static_cast<size_t>(token_begin - buff);
|
||||
size_t tok_len = static_cast<size_t>(token_end - token_begin);
|
||||
el->push_edit(edit_t{tok_off, tok_len, std::move(wc_expanded)});
|
||||
push_edit(el, edit_t{tok_off, tok_len, std::move(wc_expanded)});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
14
src/reader.h
14
src/reader.h
|
@ -80,8 +80,6 @@ struct undo_history_t {
|
|||
/// Helper class for storing a command line.
|
||||
class editable_line_t {
|
||||
public:
|
||||
undo_history_t undo_history;
|
||||
|
||||
const wcstring &text() const { return text_; }
|
||||
/// Set the text directly without maintaining undo invariants. Use with caution.
|
||||
void set_text_bypassing_undo_history(wcstring &&text) { text_ = text; }
|
||||
|
@ -100,12 +98,7 @@ class editable_line_t {
|
|||
|
||||
/// Modify the commandline according to @edit. Most modifications to the
|
||||
/// text should pass through this function.
|
||||
void push_edit(edit_t &&edit);
|
||||
|
||||
/// Modify the commandline by inserting a string at the cursor.
|
||||
/// Does not create a new undo point, but adds to the last edit which
|
||||
/// must be an insertion, too.
|
||||
void insert_coalesce(const wcstring &str);
|
||||
void push_edit(edit_t &&edit, bool allow_coalesce);
|
||||
|
||||
/// Undo the most recent edit that was not yet undone. Returns true on success.
|
||||
bool undo();
|
||||
|
@ -119,11 +112,16 @@ class editable_line_t {
|
|||
void end_edit_group();
|
||||
|
||||
private:
|
||||
/// Whether we want to append this string to the previous edit.
|
||||
bool want_to_coalesce_insertion_of(const wcstring &str) const;
|
||||
|
||||
/// The command line.
|
||||
wcstring text_;
|
||||
/// The current position of the cursor in the command line.
|
||||
size_t position_ = 0;
|
||||
|
||||
/// The history of all edits.
|
||||
undo_history_t undo_history_;
|
||||
/// The nesting level for atomic edits, so that recursive invocations of start_edit_group()
|
||||
/// are not ended by one end_edit_group() call.
|
||||
int32_t edit_group_level_ = -1;
|
||||
|
|
Loading…
Reference in a new issue