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
|
-- SPDX-License-Identifier: GPL-3.0-or-later
-- © 2020 Georgi Kirilov
local progname = ...
local lpeg = require("lpeg")
local R, C, Cmt = lpeg.R, lpeg.C, lpeg.Cmt
-- copied from vis.h
local VIS_MOVE_NOP = 64
local cont = R("\128\191")
local charpattern = R("\0\127") + R("\194\223") * cont + R("\224\239") * cont *
cont + R("\240\244") * cont * cont * cont
local function concat_keys(tbl)
local keys = {}
for k in pairs(tbl) do table.insert(keys, k) end
return table.concat(keys, "\n"), #keys
end
local function line_complete()
local file = vis.win.file
local sel = vis.win.selection
local cur_line = file.lines[sel.line]
local indent_patt = "^[ \t\v\f]+"
local prefix = cur_line:sub(1, sel.col - 1):gsub(indent_patt, "")
local candidates = {}
for l in file:lines_iterator() do
local unindented = l:gsub(indent_patt, "")
local start, finish = unindented:find(prefix, 1, true)
if start == 1 and finish < #unindented then
candidates[unindented] = true
end
end
local candidates_str, n = concat_keys(candidates)
if n < 2 then
if n == 1 then vis:insert(candidates_str:sub(#prefix + 1)) end
return
end
-- XXX: with too many candidates this command will become longer that the shell can handle:
local command = string.format("vis-menu -l %d <<'EOF'\n%s\nEOF\n",
math.min(n, math.ceil(vis.win.height / 2)),
candidates_str)
local status, output = vis:pipe(nil, nil, command)
if n > 0 and status == 0 then
vis:insert(output:sub(#prefix + 1):gsub("\n$", ""))
end
end
local function selection_by_pos(pos)
for s in vis.win:selections_iterator() do
if s.pos == pos then return s end
end
end
local function charwidth(cells_so_far, char)
if char == "\t" then
local tw = vis.tabwidth or 8
local trail = cells_so_far % tw
return tw - trail
else
return 1
end
end
local function virtcol(line, col)
local ncells = 0
local nchars = 0
local function upto(_, _, char)
if nchars < col - 1 then
ncells = ncells + charwidth(ncells, char)
nchars = nchars + 1
return true
end
end
(Cmt(C(charpattern), upto) ^ 0):match(line)
return ncells + 1
end
local function neighbor(lines, ln, col, direction)
local line = ln + direction > 0 and lines[ln + direction]
if not line then return end
local column = virtcol(lines[ln], col)
local ncells = 0
local function upto(_, _, char)
ncells = ncells + charwidth(ncells, char)
return ncells < column
end
return
(Cmt(C(charpattern), upto) ^ (-column + 1) / 0 * C(charpattern)):match(
line)
end
local function dup_neighbor(direction)
return function(file, _, pos)
local sel = selection_by_pos(pos)
local char = neighbor(file.lines, sel.line, sel.col, direction)
if not char then return pos end
file:insert(pos, char)
return pos + #char
end
end
local function dup_neighbor_feedkeys(direction)
local sel = vis.win.selection
local file = vis.win.file
local char = neighbor(file.lines, sel.line, sel.col, direction)
if not char then return end
vis:feedkeys(char)
end
local function operator(handler)
local id = vis:operator_register(handler)
return id >= 0 and function()
vis:operator(id)
vis:motion(VIS_MOVE_NOP)
end
end
vis.events.subscribe(vis.events.INIT, function()
local function h(msg) return string.format("|@%s| %s", progname, msg) end
local function column_complete(direction)
local binding = operator(dup_neighbor(direction))
return function()
if #vis.win.selections == 1 then
dup_neighbor_feedkeys(direction)
else
return binding()
end
end
end
vis:map(vis.modes.INSERT, "<C-y>", column_complete(-1),
h "Insert the character which is above the cursor")
vis:map(vis.modes.INSERT, "<C-e>", column_complete(1),
h "Insert the character which is below the cursor")
vis:map(vis.modes.INSERT, "<C-x><C-l>", line_complete,
h "Complete the current line")
end)
|