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
|