summaryrefslogtreecommitdiff
path: root/server/src/parser/lines.lua
blob: ea41e92766198fe94345852c9e70e5a9ab9a6be8 (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
local m = require 'lpeglabel'

local function utf8_len(buf, start, finish)
    local len, pos = utf8.len(buf, start, finish)
    if len then
        return len
    end
    return 1 + utf8_len(buf, start, pos-1) + utf8_len(buf, pos+1, finish)
end

local function Line(pos, str, ...)
    local line = {...}
    local sp = 0
    local tab = 0
    for i = 1, #line do
        if line[i] == ' ' then
            sp = sp + 1
        else
            tab = tab + 1
        end
        line[i] = nil
    end
    line[1] = pos
    line[2] = sp
    line[3] = tab
    return line
end

local parser = m.P{
'Lines',
Lines   = m.Ct(m.V'Line'^0 * m.V'LastLine'),
Line    = m.Cp() * m.C(m.V'Indent' * (1 - m.V'Nl')^0 * m.V'Nl') / Line,
LastLine= m.Cp() * m.C(m.V'Indent' * (1 - m.V'Nl')^0) / Line,
Nl      = m.P'\r\n' + m.S'\r\n',
Indent  = m.C(m.S' \t')^0,
}

local mt = {}
mt.__index = mt

function mt:position(row, col, code)
    if row < 1 then
        return 1
    end
    if row > #self then
        if code == 'utf8' then
            return utf8_len(self.buf) + 1
        else
            return #self.buf + 1
        end
    end
    local line = self[row]
    local next_line = self[row+1]
    local start = line[1]
    local finish
    if next_line then
        finish = next_line[1] - 1
    else
        finish = #self.buf + 1
    end
    local pos
    if code == 'utf8' then
        pos = utf8.offset(self.buf, col, start) or finish
    else
        pos = start + col - 1
    end
    if pos < start then
        pos = start
    elseif pos > finish then
        pos = finish
    end
    return pos
end

function mt:rowcol(pos, code)
    if pos < 1 then
        return 1, 1
    end
    if pos > #self.buf + 1 then
        local start = self[#self][1]
        if code == 'utf8' then
            return #self, utf8_len(self.buf, start) + 1
        else
            return #self, #self.buf - start + 2
        end
    end
    local min = 1
    local max = #self
    for _ = 1, 100 do
        local row = (max - min) // 2 + min
        local start = self[row][1]
        if pos < start then
            max = row
        elseif pos > start then
            local next_start = self[row + 1][1]
            if pos < next_start then
                if code == 'utf8' then
                    return row, utf8_len(self.buf, start, pos)
                else
                    return row, pos - start + 1
                end
            elseif pos > next_start then
                min = row
            else
                return row + 1, 1
            end
        else
            return row, 1
        end
    end
    error('rowcol failed!')
end

return function (self, buf)
    local lines, err = parser:match(buf)
    if not lines then
        return nil, err
    end
    lines.buf = buf

    return setmetatable(lines, mt)
end