Match cursor position to substring after replacing text

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) { // if formatting make substring - (text before cursor) // shorter then subtract the difference formatted_position -= orig_len - formatted_len; } else if (orig_len < formatted_len) { // if the formatted string is longer add difference formatted_position += formatted_len - orig_len; } } if (formatted_position > len) { formatted_position = len; } else if (formatted_position < 0) { formatted_position = 0; } 

$. 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

+6
javascript string jquery-terminal
source share
1 answer

So, I was able to complete the task, but I could not implement it in the library, as I’m not sure how to implement many things there.

I did this in vanilla javascript, so there should be no hiccups in the library. The script mainly depends on the selectionStart and selectionEnd properties available for text fields, input elements, or similar elements. After the replacement is complete, the new selection is set in the text field using the setSelectionRange method.

 // sel = [selectionStart, selectionEnd] function updateSelection(sel, replaceStart, oldLength, newLength){ var orig = sel.map(a => a) var diff = newLength - oldLength var replaceEnd = replaceStart + oldLength if(replaceEnd <= sel[0]){ // Replacement occurs before selection sel[0] += diff sel[1] += diff console.log('Replacement occurs before selection', orig, sel) }else if(replaceStart <= sel[0]){ // Replacement starts before selection if(replaceEnd >= sel[1]){ // and ends after selection sel[1] += diff }else{ // and ends in selection } console.log('Replacement starts before selection', orig, sel) }else if(replaceStart <= sel[1]){ // Replacement starts in selection if(replaceEnd < sel[1]){ // and ends in seledtion }else{ // and ends after selection sel[1] += diff } console.log('Replacement starts in selection', orig, sel) } } 

Here is the whole demo: codepen .

PS: According to my observations, the script format often goes the way.

+1
source share

All Articles