diff --git a/src/data/action/value.hpp b/src/data/action/value.hpp index f1bc8a5b..047dda24 100644 --- a/src/data/action/value.hpp +++ b/src/data/action/value.hpp @@ -61,6 +61,8 @@ class TextValueAction : public ValueAction { virtual void perform(bool to_undo); virtual bool merge(const Action& action); + inline const String& newValue() const { return new_value(); } + /// The modified selection size_t selection_start, selection_end; private: diff --git a/src/data/keyword.cpp b/src/data/keyword.cpp index 49144775..c077dab7 100644 --- a/src/data/keyword.cpp +++ b/src/data/keyword.cpp @@ -528,6 +528,8 @@ String KeywordDatabase::expand(const String& text, // note: start_u can be (uint)-1 when len_u == 0 size_t part_end = len_u > 0 ? untagged_to_index(s, start_u + len_u, true) : start; String part = s.substr(start, part_end - start); + // strip left over parameters[j/2-1]; diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index 010ab725..c9244469 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -504,6 +504,27 @@ void TextValueEditor::showCaret() { void TextValueEditor::insert(const String& text, const String& action_name) { replaceSelection(text, action_name); } + +/// compare two cursor positions, determine how much the text matches before and after +size_t match_cursor_position(size_t pos1, const String& text1, size_t pos2, const String& text2) { + size_t penalty = 0; // penalty for case mismatches + size_t before; + for (before = 0 ; before < min(pos1,pos2) ; ++before) { + Char c1 = text1.GetChar(pos1-before-1), c2 = text2.GetChar(pos2-before-1); + if (toLower(c1) != toLower(c2)) break; + else if (c1 != c2) ++penalty; + } + if (pos1 == before && pos2 == before) ++before; // bonus points for matching start of string + size_t after; + for (after = 0 ; after < min(text1.size() - pos1, text2.size() - pos2) ; ++after) { + Char c1 = text1.GetChar(pos1+after), c2 = text2.GetChar(pos2+after); + if (toLower(c1) != toLower(c2)) break; + else if (c1 != c2) ++penalty; + } + if (pos1+after == text1.size() && pos2+after == text2.size()) ++after; // bonus points for matching end of string + return 1000 * before + 2 * after - penalty; // matching 'before' is more important +} + void TextValueEditor::replaceSelection(const String& replacement, const String& name) { if (replacement.empty() && selection_start == selection_end) { // no text selected, nothing to delete @@ -514,43 +535,53 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& fixSelection(); // execute the action before adding it to the stack, // because we want to run scripts before action listeners see the action - ValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, selection_start, selection_end, replacement, name); + TextValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, selection_start, selection_end, replacement, name); if (!action) { // nothing changes, but move the selection anyway moveSelection(TYPE_CURSOR, selection_start); return; } + // what we would expect if no scripts take place + String expected_value = untag_for_cursor(action->newValue()); + size_t expected_cursor = min(selection_start, selection_end) + untag(replacement).size(); // perform the action // NOTE: this calls our onAction, invalidating the text viewer and moving the selection around the new text getSet().actions.add(action); // move cursor - if (field().move_cursor_with_sort && replacement.size() == 1) { - String val = value().value(); - Char typed = replacement.GetChar(0); - Char typedU = toUpper(typed); - Char cur = val.GetChar(selection_start_i); - // the cursor may have moved because of sorting... - // is 'replacement' just after the current cursor? - if (selection_start_i >= 0 && selection_start_i < val.size() && (cur == typed || cur == typedU)) { - // no need to move cursor in a special way - selection_end_i = selection_start_i = min(selection_end_i, selection_start_i) + 1; + { + String real_value = untag_for_cursor(value().value()); + // where real and expected value are the same, nothing has happend, so don't look there + size_t start, end_min; + for (start = 0 ; start < min(real_value.size(), expected_value.size()) ; ++start) { + if (real_value.GetChar(start) != expected_value.GetChar(start)) break; + } + for (end_min = 0 ; end_min < min(real_value.size(), expected_value.size()) ; ++end_min) { + if (real_value.GetChar(real_value.size() - end_min - 1) != + expected_value.GetChar(expected_value.size() - end_min - 1)) break; + } + // what is the best cursor position? + size_t best_cursor = expected_cursor; + if (real_value.size() < expected_value.size() + && expected_cursor < expected_value.size() + && expected_value.GetChar(expected_cursor) == _('\3') // \3 == + && real_value.GetChar(start) == _('\3') // \3 == + && real_value.size() - end_min == start) { + // exception for type-over separators + best_cursor = start + 1; } else { - // find the last occurence of 'replacement' in the value - size_t pos = val.find_last_of(typed); - if (pos == String::npos) { - // try upper case - pos = val.find_last_of(typedU); - } - if (pos != String::npos) { - selection_end_i = selection_start_i = pos + 1; - } else { - selection_end_i = selection_start_i; + // try to find the best match + size_t best_match = 0; + for (size_t i = min(start, expected_cursor) ; i <= real_value.size() - end_min ; ++i) { + size_t match = match_cursor_position(expected_cursor, expected_value, i, real_value); + if (match > best_match) { + best_match = match; + best_cursor = i; + } } } - } else { - selection_end_i = selection_start_i = min(selection_end_i, selection_start_i) + replacement.size(); + selection_end = selection_start = best_cursor; + fixSelection(TYPE_CURSOR, MOVE_RIGHT); } - fixSelection(TYPE_INDEX, MOVE_RIGHT); // scroll with next update scroll_with_cursor = true; } diff --git a/src/util/tagged_string.cpp b/src/util/tagged_string.cpp index 258bc661..eb7bfd55 100644 --- a/src/util/tagged_string.cpp +++ b/src/util/tagged_string.cpp @@ -301,6 +301,10 @@ size_t cursor_to_index(const String& str, size_t cursor, Movement dir) { return i; } } + if (starts_with(tag1, _("/sym"))) { + // we like to be inside and tags, but outside tags + start = i; + } } else { i++; } @@ -310,6 +314,28 @@ size_t cursor_to_index(const String& str, size_t cursor, Movement dir) { return dir <= 0 /*MOVE_LEFT*/ ? start : end - 1; } +String untag_for_cursor(const String& str) { + String ret; ret.reserve(str.size()); + for (size_t i = 0 ; i < str.size() ; ) { + Char c = str.GetChar(i); + if (c == _('<')) { + if (is_substr(str, i, _("... becomes a single character. +/** This string should only be used for cursor position calculations. */ +String untag_for_cursor(const String& str); + // ----------------------------------------------------------------------------- : Untagged position /// Find the tagged position corresponding to the given untagged position.