javascript - 文本替换后匹配子字符串的光标位置

标签 javascript string jquery-terminal

<分区>

长话短说

我有替换文本、字符串和光标位置(一个数字)的函数,如果字符串的长度发生变化,我需要为使用替换函数创建的新字符串获取更正的位置(一个数字):

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: f b| text

input and cursor position:  foo b|ar text
replacement: foo -> f, bar -> b
result: f b| text

问题是我可以在原始文本上使用子字符串,但替换将无法匹配整个单词,因此需要对整个文本进行替换,但子字符串将无法匹配替换。

当原始光标位于被替换单词的中间时,光标始终位于单词末尾的解决方案也很好。

现在是我的实现,在 jQuery 终端中我有一组格式化程序函数:

$.terminal.defaults.formatters

他们接受一个字符串并且它应该返回新的字符串它工作正常除了这种情况:

当我有如果打破命令行会改变长度的格式化程序时,例如这个格式化程序:

$.terminal.defaults.formatters.push(function(string) {
   return string.replace(/:smile:/g, 'a')
                .replace(/(foo|bar|baz)/g, 'text_$1');
});

然后命令行获取新字符串时光标位置错误。

我已经尝试解决这个问题,但它没有按预期工作,终端的内部看起来像这样,

当我更改 position 时,我创建了另一个变量 formatted_position,它在命令行中用于显示光标。为了获得这个值(value),我使用了这个:

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 和 $.terminal.length 是终端格式感知的辅助函数(文本看起来像这样 [[b;#fff;]hello])如果你会写您可以使用普通文本并使用字符串方法的解决方案。

问题是当我将光标移动到被更改的单词的中间时

当文本较长时,它有点工作,但对于较短的字符串,当文本位于被替换的单词的中间时,光标会跳到右边。

我也尝试使用以下代码修复此问题:

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();
}

但我认为这会使情况变得更糟,因为当光标位于替换词之前或中间时它会发现差异,并且只有当光标位于替换词的中间时它才会发现差异。

你可以在这个codepen中看到我尝试的结果https://codepen.io/jcubic/pen/qPVMPg?editors=0110 (允许输入表情符号和 foo bar baz 被替换为 text_$1)

更新:

我已经用这段代码让它工作了:

    // ---------------------------------------------------------------------
    // :: 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;
    }

当您输入表情符号作为第一个字符并且光标位于 :smile: 单词的中间时,它不适用于一种情况。如何修复 get_formatted_position 函数以在替换后具有正确的固定位置?

更新:我提出了不同且简单的问题,并使用接受正则表达式和字符串的 trackingReplace 函数获得了解决方案,因此我更改了格式化程序的 API 以接受带有正则表达式和字符串的数组函数Correct substring position after replacement

最佳答案

所以我能够完成给定的任务,但是我无法将它实现到库中,因为我不确定如何在那里实现很多东西。

我是用 vanilla javascript 制作的,因此在库中实现时应该不会出现任何问题。该脚本主要依赖于文本区域、输入或类似元素上可用的 selectionStartselectionEnd 属性。完成所有替换后,使用 setSelectionRange 方法将新选择设置到文本区域。

// 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)
    }
}

这是整个演示:codepen .

PS:根据我的观察,格式脚本经常运行。

关于javascript - 文本替换后匹配子字符串的光标位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46592643/

相关文章:

c++ - 将 std::string 数据的深层拷贝存储到 std::vector

c++ - C - 外部结构

javascript - 如何 Hook 按键并获取终端的当前内容?

javascript - 无法在 JQuery 终端中编辑文本区域

jquery - 如何从另一个功能更改 JQuery 终端提示?

javascript - react 路线与 URL 参数不更新 View

javascript - 选择 onChange 的新窗口

javascript - 如何使用自动完成下拉列表中的按钮进一步过滤已显示的结果

php - php中的反向strpos函数

javascript - JavaScript 的 2019 年巴西利亚夏令时