Commit ea2ae3b4 authored by twanvl's avatar twanvl

Cursor now moves correctly with script updates; tries to stay outside <sym> tags;

Closing </kw> tags no longer end up in keyword parameters
parent 467b22a7
......@@ -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:
......
......@@ -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 </kw tags
part = remove_tag(part,_("</kw-"));
if ((j % 2) == 0) {
// parameter
KeywordParam& kwp = *kw->parameters[j/2-1];
......
......@@ -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 == <sep>
&& real_value.GetChar(start) == _('\3') // \3 == <sep>
&& 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;
}
......
......@@ -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 <b> and <i> tags, but outside <sym> 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, _("<atom"))) {
i = match_close_tag_end(str, i);
ret += _('\2'); // use a random character here
} else if (is_substr(str, i, _("<sep"))) {
i = match_close_tag_end(str, i);
ret += _('\3'); // use a random character here
} else {
i = skip_tag(str, i);
}
} else {
ret += c;
++i;
}
}
return ret;
}
// ----------------------------------------------------------------------------- : Untagged position
size_t untagged_to_index(const String& str, size_t pos, bool inside) {
......
......@@ -111,6 +111,10 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& begin, size
/// Find the character index corresponding to the given cursor position
size_t cursor_to_index(const String& str, size_t cursor, Movement dir = MOVE_MID);
/// Untag a string for use with cursors, <atom>...</atom> 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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment