-
Notifications
You must be signed in to change notification settings - Fork 0
/
elastic_tabstops.lua
208 lines (172 loc) · 5.61 KB
/
elastic_tabstops.lua
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
-- Copyright 2021 Joshua Krämer, subject to the ISC license
local M = {}
local function text_width(start_position, stop_position)
local text = buffer:text_range(start_position, stop_position)
local style = buffer.style_at[start_position]
return buffer:text_width(style, text)
end
local function scan_line(line)
local cell_count = 0
local cell_widths = {}
local position = buffer:position_from_line(line)
local cell_start_position = position
while position < buffer.line_end_position[line] do
if buffer.char_at[position] == 9 then
cell_count = cell_count + 1
cell_widths[cell_count] = text_width(cell_start_position, position)
position = buffer:position_after(position)
cell_start_position = position
else
position = buffer:position_after(position)
end
end
if cell_count > 0 then
buffer.elastic_tabstops.lines[line] = {
cell_count = cell_count,
cell_widths = cell_widths,
cell_blocks = {}
}
else
buffer.elastic_tabstops.lines[line] = false
end
return cell_count
end
local function array_remove(array, start, count)
local new_index = 1
for index = 1, #array do
if (index >= start) and (index < start + count) then
array[index] = nil
else
if index ~= new_index then
array[new_index] = array[index]
array[index] = nil
end
new_index = new_index + 1
end
end
end
local function resize_line_cache(start, count)
if count > 0 then
table.move(buffer.elastic_tabstops.lines, start + 1, #buffer.elastic_tabstops.lines, start + 1 + count)
elseif count < 0 then
array_remove(buffer.elastic_tabstops.lines, start, -count)
end
end
local function search_boundary(start_line, reverse)
local stop_line = reverse and 1 or buffer.line_count
local step = reverse and -1 or 1
local boundary_line = start_line
local max_cell_count = 0
for line = start_line + step, stop_line, step do
if not buffer.elastic_tabstops.lines[line] then
break
end
boundary_line = line
max_cell_count = math.max(max_cell_count, buffer.elastic_tabstops.lines[line].cell_count)
end
return boundary_line, max_cell_count
end
local function assign_column_blocks(start_line, stop_line, max_cell_count)
local lines = buffer.elastic_tabstops.lines
local block_widths = buffer.elastic_tabstops.block_widths
for column = 1, max_cell_count do
local start_new_block = true
for line = start_line, stop_line do
if lines[line] and lines[line].cell_count >= column then
if start_new_block then
block_index = column*2^16 + line
block_widths[block_index] = 0
start_new_block = false
end
block_widths[block_index] = math.max(block_widths[block_index], lines[line].cell_widths[column])
lines[line].cell_blocks[column] = block_index
else
start_new_block = true
end
end
end
end
local function set_tabstops(start_line, stop_line)
local lines = buffer.elastic_tabstops.lines
local block_widths = buffer.elastic_tabstops.block_widths
for line = start_line, stop_line do
if lines[line] then
buffer:clear_tab_stops(line)
buffer.elastic_tabstops.tabstops[line] = {}
local tabstop = 0
for cell, block in ipairs(lines[line].cell_blocks) do
local cell_width = math.max(buffer.elastic_tabstops.min_width, block_widths[block] + buffer.elastic_tabstops.padding)
tabstop = tabstop + cell_width
buffer:add_tab_stop(line, tabstop)
buffer.elastic_tabstops.tabstops[line][cell] = tabstop
end
end
end
end
local function restore_tabstops()
for line_number, line in pairs(buffer.elastic_tabstops.tabstops) do
for _, tabstop in pairs(line) do
buffer:add_tab_stop(line_number, tabstop)
end
end
end
events.connect(events.MODIFIED, function(position, mod, text, length)
local inserted = mod & buffer.MOD_INSERTTEXT > 0
local deleted = mod & buffer.MOD_DELETETEXT > 0
if not (inserted or deleted) then
return
end
local line = position and buffer:line_from_position(position)
local added_lines = text and select(2, text:gsub("\n", ""))
added_lines = deleted and added_lines*-1 or added_lines
if buffer.elastic_tabstops == nil then
char_width = buffer:text_width(buffer.STYLE_DEFAULT, "n")
buffer.elastic_tabstops = {
min_width = buffer.tab_width*char_width,
padding = buffer.tab_width*char_width/2,
lines = {},
block_widths = {},
tabstops = {}
}
end
-- If first/last line of modified/deleted range contained tabstops, they were possibly connected with adjacent blocks, which means adjacent lines need to be updated later
local extend_up = buffer.elastic_tabstops.lines[line] ~= nil
local extend_down = added_lines < 0 and (buffer.elastic_tabstops.lines[line - added_lines] ~= nil) or extend_up
if added_lines ~= 0 and buffer.elastic_tabstops.lines then
resize_line_cache(line, added_lines)
end
local first_line = line
local last_line = line + (added_lines > 0 and added_lines or 0)
local max_cell_count = 0
local cell_count = 0
for line = first_line, last_line do
cell_count = scan_line(line)
if cell_count > 0 then
max_cell_count = math.max(max_cell_count, cell_count)
if line == first_line then
extend_up = true
end
if line == last_line then
extend_down = true
end
end
end
if extend_up then
first_line, cell_count = search_boundary(first_line, true)
max_cell_count = math.max(max_cell_count, cell_count)
end
if extend_down then
last_line, cell_count = search_boundary(last_line, false)
max_cell_count = math.max(max_cell_count, cell_count)
end
if max_cell_count > 0 then
assign_column_blocks(first_line, last_line, max_cell_count)
set_tabstops(first_line, last_line)
end
end)
events.connect(events.BUFFER_AFTER_SWITCH, function()
if buffer.elastic_tabstops then
restore_tabstops()
end
end)
return M