TL; DR
I have a function that replaces the text, line and cursor position (number), and I need to get the corrected position (number) for a new line, which is created using the replace function if the length if the line changes:
input and cursor position: foo ba|r text replacement: foo -> baz_text, bar -> quux_text result: baz_text qu|ux_text text input and cursor position: foo bar| text replacement: foo -> baz_text, bar -> quux_text result: baz_text quux_text| text input and cursor position: foo bar| text replacement: foo -> f, bar -> b result: fb| text input and cursor position: foo b|ar text replacement: foo -> f, bar -> b result: fb| text
the problem is that I can use a substring in the source text, but then the replacement will not match the whole word, so it needs to be done for the whole text, but the substring will not match the replacement.
I also understand very well that the cursor is always at the end of the word when the original cursor is in the middle of the replaced word.
and now my implementation, in jQuery Terminal I have a set of formatters functions in:
$.terminal.defaults.formatters
they accept a string, and it should return a new string that works fine, except in this case:
when I have a formatter that changes length if it violates the command line, for example, this formatter:
$.terminal.defaults.formatters.push(function(string) { return string.replace(/:smile:/g, 'a') .replace(/(foo|bar|baz)/g, 'text_$1'); });
then the cursor position was wrong when a new line appeared on the command line.
I am trying to fix this, but it does not work as expected, the inside of the terminal looks like this:
when I change position I break another variable formatted_position , which is used on the command line to display the cursor. to get this value i use this:
formatted_position = position; var string = formatting(command); var len = $.terminal.length(string); var command_len = $.terminal.length(command); if (len !== command_len) { var orig_sub = $.terminal.substring(command, 0, position); var orig_len = $.terminal.length(orig_sub); var formatted = formatting(orig_sub); var formatted_len = $.terminal.length(formatted); if (orig_len > formatted_len) {
$. terminal.substring and $ .terminal.length are helper functions that know about terminal formatting (text similar to this [[b;#fff;]hello] ), if you write a solution, you can use plain text and use string methods.
the problem is that when I move the cursor in the middle of the word that was changed
this is some kind of work when the text is longer, but for a shorter line, the cursor jumps to the right when the text is in the middle of the word that was replaced.
I will try to fix this using this code:
function find_diff(callback) { var start = position === 0 ? 0 : position - 1; for (var i = start; i < command_len; ++i) { var substr = $.terminal.substring(command, 0, i); var next_substr = $.terminal.substring(command, 0, i + 1); var formatted = formatting(next_substr); var substr_len = $.terminal.length(substr); var formatted_len = $.terminal.length(formatted); var diff = Math.abs(substr_len - formatted_len); if (diff > 1) { return diff; } } return 0; } ... } else if (len < command_len) { formatted_position -= find_diff(); } else if (len > command_len) { formatted_position += find_diff(); }
but I think this is even worse because it finds diff when the cursor is before or in the middle of the replaced word, and it should find diff only when the cursor is in the middle of the replaced word.
You can see the result of my attempts in this code https://codepen.io/jcubic/pen/qPVMPg?editors=0110 (which allow you to enter emoji and foo bar baz are replaced by text_$1 )
UPDATE
I did this job with this code:
// --------------------------------------------------------------------- // :: functions used to calculate position of cursor when formatting // :: change length of output text like with emoji demo // --------------------------------------------------------------------- function split(formatted, normal) { function longer(str) { return found && length(str) > length(found) || !found; } var formatted_len = $.terminal.length(formatted); var normal_len = $.terminal.length(normal); var found; for (var i = normal_len; i > 1; i--) { var test_normal = $.terminal.substring(normal, 0, i); var formatted_normal = formatting(test_normal); for (var j = formatted_len; j > 1; j--) { var test_formatted = $.terminal.substring(formatted, 0, j); if (test_formatted === formatted_normal && longer(test_normal)) { found = test_normal; } } } return found || ''; } // --------------------------------------------------------------------- // :: return index after next word that got replaced by formatting // :: and change length of text // --------------------------------------------------------------------- function index_after_formatting(position) { var start = position === 0 ? 0 : position - 1; var command_len = $.terminal.length(command); for (var i = start; i < command_len; ++i) { var substr = $.terminal.substring(command, 0, i); var next_substr = $.terminal.substring(command, 0, i + 1); var formatted_substr = formatting(substr); var formatted_next = formatting(next_substr); var substr_len = length(formatted_substr); var next_len = length(formatted_next); var test_diff = Math.abs(next_len - substr_len); if (test_diff > 1) { return i; } } } // --------------------------------------------------------------------- // :: main function that return corrected cursor position on display // :: if cursor is in the middle of the word that is shorter the before // :: applying formatting then the corrected position is after the word // :: so it stay in place when you move real cursor in the middle // :: of the word // --------------------------------------------------------------------- function get_formatted_position(position) { var formatted_position = position; var string = formatting(command); var len = $.terminal.length(string); var command_len = $.terminal.length(command); if (len !== command_len) { var orig_sub = $.terminal.substring(command, 0, position); var orig_len = $.terminal.length(orig_sub); var sub = formatting(orig_sub); var sub_len = $.terminal.length(sub); var diff = Math.abs(orig_len - sub_len); if (false && orig_len > sub_len) { formatted_position -= diff; } else if (false && orig_len < sub_len) { formatted_position += diff; } else { var index = index_after_formatting(position); var to_end = $.terminal.substring(command, 0, index + 1); //formatted_position -= length(to_end) - orig_len; formatted_position -= orig_len - sub_len; if (orig_sub && orig_sub !== to_end) { var formatted_to_end = formatting(to_end); var common = split(formatted_to_end, orig_sub); var re = new RegExp('^' + $.terminal.escape_regex(common)); var to_end_rest = to_end.replace(re, ''); var to_end_rest_len = length(formatting(to_end_rest)); if (common orig_sub !== common) { var commnon_len = length(formatting(common)); formatted_position = commnon_len + to_end_rest_len; } } } if (formatted_position > len) { formatted_position = len; } else if (formatted_position < 0) { formatted_position = 0; } } return formatted_position; }
it does not work for one case when you enter emoji as the first character and the cursor is in the middle: smile: word. How to fix get_formatted_position function for correct fixed position after replacement?
UPDATE . I asked a different and simple question and got a solution using the trackingReplace function, which accept a regular expression and a string, so I changed the API for the formats to accept an array with a regular expression and a string along the function Correct the position of the substring after replacement