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
|
require('vis')
local mod = {
tab_width = 8,
}
local function is_spaces(str)
return string.match(str, '^ *$') ~= nil
end
local function utf8_prefix_byte(byte)
local number = byte:byte()
return number < 128 or number >= 191
end
local function delete_characters(position, num_chars)
local prefixes_seen = 0
local num_bytes = 0
repeat
num_bytes = num_bytes + 1
byte = vis.win.file:content(position - num_bytes, 1)
if utf8_prefix_byte(byte) then
prefixes_seen = prefixes_seen + 1
end
until (prefixes_seen >= num_chars)
vis.win.file:delete(position - num_bytes, num_bytes)
return num_bytes
end
local function near_space_indent(selection)
local line_start = selection.pos - (selection.col - 1)
local line_prefix = vis.win.file:content(line_start, selection.col - 1)
return is_spaces(line_prefix)
end
local function modulus_big(dividend, divisor)
local quotient = dividend % divisor
if quotient == 0 then return divisor end
return quotient
end
local function constrain(num, minimum, maximum)
return math.max(minimum, math.min(num, maximum))
end
local function get_selections(win)
local selections = {}
for selection in win:selections_iterator() do
if selection.pos ~= nil and selection.pos ~= 0 then
table.insert(selections, selection)
end
end
-- handle deletions backwards so as not to invalidate indices
table.sort(selections, function(a, b) return a.pos > b.pos end)
return selections
end
local function smart_backspace()
for _, selection in ipairs(get_selections(vis.win)) do
local length = 1
if near_space_indent(selection) then
local tab_stop = modulus_big(selection.col - 1, mod.tab_width)
length = math.floor(constrain(tab_stop, 1, selection.col))
end
if length ~= nil then
local pos = selection.pos
local deleted_bytes = delete_characters(pos, length)
selection.pos = pos - deleted_bytes
end
end
-- vis doesn't seem to update the syntax highlighting in other splits of
-- the same file when we edit the file directly like this.
-- a workaround is to send vis a no-op insert command.
-- https://github.com/ingolemo/vis-smart-backspace/issues/2
vis:insert('')
end
vis.events.subscribe(vis.events.INIT, function()
vis:map(vis.modes.INSERT, '<Backspace>', smart_backspace)
end)
return mod
|