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
|
-- Author: Georgi Kirilov
--
-- You can contact me via email to the posteo.net domain.
-- The local-part is the Z code for "Place a competent operator on this circuit."
require("vis")
local vis = vis
require("lpeg")
local l = lpeg
local Cc, Cmt, Cp, P, R, V = l.Cc, l.Cmt, l.Cp, l.P, l.R, l.V
local M = {}
-- Inverted and unrolled config table.
-- With this table format, make_shift() can use the same logic for both numeric and word increment/decrement.
local lookup = {}
-- Pattern that matches any one of the words listed in the config table.
local ordinal_words
local count
local char_next = 17
local function at_or_after(_, _, pos, start, finish, is_number)
if pos >= start and pos < finish or is_number and pos < start then
return true, start, finish
end
end
local function ordinal(win, pos)
local selection
for s in win:selections_iterator() do
if s.pos == pos then selection = s break end
end
local line = win.file.lines[selection.line]
local dec_num = P"-"^-1 * R"09"^1
local patt = Cp() * dec_num * Cp() * Cc(true) + Cp() * ordinal_words * Cp()
local start, finish = P{Cmt(Cc(selection.col) * patt, at_or_after) + 1 * V(1)}:match(line)
if not (start and finish) then return end
local line_begin = selection.pos - selection.col + 1
return line_begin + start - 1, line_begin + finish - 1
end
local function toggle(func, motion)
return function(file, range, pos)
local word = file:content(range)
local toggled = func(word, count)
if toggled then
file:delete(range)
file:insert(range.start, toggled)
return motion and range.finish or range.start
end
return pos
end
end
local function make_shift(shift)
return function(word, delta)
local number = tonumber(word)
local iter = number and {number} or lookup[word]
if not iter then return end
local binary = iter[2] and #iter[2] == 2
local neighbor, rotate = shift(iter[1], iter[2], delta)
return number and neighbor or iter[2][neighbor] or binary and iter[2][rotate]
end
end
local increment = make_shift(function(i, _, delta) return i + (delta or 1), 1 end)
local decrement = make_shift(function(i, options, delta) return i - (delta or 1), options and #options end)
local function case(str)
return str:gsub("%a", function(char)
local lower = char:lower()
return char == lower and char:upper() or lower
end)
end
local function operator_new(key, handler, object, motion, help, novisual)
local id = vis:operator_register(handler)
if id < 0 then
return false
end
local function binding()
vis:operator(id)
if vis.mode == vis.modes.OPERATOR_PENDING then
if object then
count = vis.count
vis.count = nil
vis:textobject(object)
elseif motion then
vis:motion(motion)
end
end
end
vis:map(vis.modes.NORMAL, key, binding, help)
if not novisual then
vis:map(vis.modes.VISUAL, key, binding, help)
end
end
local function preprocess(tbl)
local cfg, ord = {}, P(false)
local longer_first = {}
for _, options in ipairs(tbl) do
for i, key in ipairs(options) do
cfg[key] = {i, options}
table.insert(longer_first, key)
end
end
table.sort(longer_first, function(f, s)
local flen, slen = #f, #s
return flen > slen or flen == slen and f < s
end)
for _, key in ipairs(longer_first) do
ord = ord + key
end
return cfg, ord
end
vis.events.subscribe(vis.events.INIT, function()
local ord_next = vis:textobject_register(ordinal)
operator_new("<C-a>", toggle(increment), ord_next, nil, "Toggle/increment word or number", true)
operator_new("<C-x>", toggle(decrement), ord_next, nil, "Toggle/decrement word or number", true)
operator_new("~", toggle(case, true), nil, char_next, "Toggle case of character or selection")
operator_new("g~", toggle(case), nil, nil, "Toggle-case operator")
operator_new("gu", toggle(string.lower), nil, nil, "Lower-case operator")
operator_new("gU", toggle(string.upper), nil, nil, "Upper-case operator")
lookup, ordinal_words = preprocess(M)
vis:map(vis.modes.NORMAL, "g~~", "g~il")
vis:map(vis.modes.NORMAL, "guu", "guil")
vis:map(vis.modes.NORMAL, "gUU", "gUil")
vis:map(vis.modes.VISUAL, "u", "gu")
vis:map(vis.modes.VISUAL, "U", "gU")
end)
return function(config)
local ext_config = require(config)
M = ext_config or M
return M
end
|