summaryrefslogtreecommitdiff
path: root/script/string-merger.lua
blob: b2a63f025a8258a01fac918f32a049eb6bfa36e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
---@class string.merger.diff
---@field start  integer # 替换开始的字节
---@field finish integer # 替换结束的字节
---@field text   string  # 替换的文本

---@class string.merger.info: string.merger.diff
---@field cstart  integer # 转换后的开始字节
---@field cfinish integer # 转换后的结束字节

---@alias string.merger.diffs string.merger.diff[]
---@alias string.merger.infos string.merger.info[]

-- 根据二分法找到最近的开始位置
---@param diffs  table
---@param offset any
---@return string.merger.info
local function getNearDiff(diffs, offset, key)
    local min = 1
    local max = #diffs
    while max > min do
        local middle = min + (max - min) // 2
        local diff  = diffs[middle]
        local ndiff = diffs[middle + 1]
        if diff[key] > offset then
            max = middle
            goto CONTINUE
        end
        if not ndiff then
            return diff
        end
        if ndiff[key] > offset then
            return diff
        end
        if min == middle then
            min = middle + 1
        else
            min = middle
        end
        ::CONTINUE::
    end
    return diffs[min]
end

local m = {}

---把文本与差异进行合并
---@param text  string
---@param diffs string.merger.diffs
---@return string
---@return string.merger.infos
function m.mergeDiff(text, diffs)
    local info = {}
    for i, diff in ipairs(diffs) do
        info[i] = {
            start  = diff.start,
            finish = diff.finish,
            text   = diff.text,
        }
    end
    table.sort(info, function (a, b)
        return a.start < b.start
    end)
    local cur = 1
    local buf = {}
    local delta = 0
    for _, diff in ipairs(info) do
        diff.cstart  = diff.start  + delta
        diff.cfinish = diff.cstart + #diff.text - 1
        buf[#buf+1] = text:sub(cur, diff.start - 1)
        buf[#buf+1] = diff.text
        cur = diff.finish + 1
        delta = delta  + #diff.text - (diff.finish - diff.start + 1)
    end
    buf[#buf+1] = text:sub(cur)
    return table.concat(buf), info
end

---根据转换前的位置获取转换后的位置
---@param info   string.merger.infos
---@param offset integer
---@return integer start
---@return integer finish
function m.getOffset(info, offset)
    local diff = getNearDiff(info, offset, 'start')
    if not diff then
        return offset, offset
    end
    if offset <= diff.finish then
        local start, finish
        if offset == diff.start then
            start = diff.cstart
        end
        if offset == diff.finish then
            finish = diff.cfinish
        end
        if not start or not finish then
            local soff = offset - diff.start
            local pos = math.min(diff.cstart + soff, diff.cfinish)
            start  = start or pos
            finish = finish or pos
        end
        if start > finish then
            start = finish
        end
        return start, finish
    end
    local pos = offset - diff.finish + diff.cfinish
    return pos, pos
end

---根据转换后的位置获取转换前的位置
---@param info   string.merger.infos
---@param offset integer
---@return integer start
---@return integer finish
function m.getOffsetBack(info, offset)
    local diff = getNearDiff(info, offset, 'cstart')
    if not diff then
        return offset, offset
    end
    if offset <= diff.cfinish then
        local start, finish
        if offset == diff.cstart then
            start = diff.start
        end
        if offset == diff.cfinish then
            finish = diff.finish
        end
        if not start or not finish then
            local soff = offset - diff.cstart
            local pos = math.min(diff.start + soff, diff.finish)
            start  = start or pos
            finish = finish or pos
        end
        if start > finish then
            start = finish
        end
        return start, finish
    end
    local pos = offset - diff.cfinish + diff.finish
    return pos, pos
end

return m