1 -- SPDX-License-Identifier: GPL-3.0-or-later
2 -- © 2020 Georgi Kirilov
9 local C
, Cc
, Ct
, Cmt
, Cp
, Cg
, P
, R
, S
, V
= l
.C
, l
.Cc
, l
.Ct
, l
.Cmt
, l
.Cp
, l
.Cg
, l
.P
, l
.R
, l
.S
, l
.V
13 local M
= {config
= {}}
15 -- Inverted and unrolled config table.
16 -- With this table format, make_shift() can use the same logic for both numeric and word increment/decrement.
19 -- Pattern that matches any one of the words listed in the config table.
25 local VIS_MOVE_CHAR_NEXT
= 17
27 local dec_digit
= R
"09"
28 local hex_digit
= R("09", "af", "AF")
29 local zeros
= P
"0"^
0 / function(c
) return #c
end
31 local function at_or_after(_
, _
, pos
, start
, finish
, is_number
)
32 if pos
>= start
and pos
< finish
or is_number
and pos
< start
then
33 return true, start
, finish
37 local function ordinal(win
, pos
)
39 for s
in win
:selections_iterator() do
40 if s
.pos
== pos
then selection
= s
break end
42 local line
= win
.file
.lines
[selection
.line
]
43 local num
= S
"-+"^
-1 * ((P
"0x" + "0X") * hex_digit^
1 + dec_digit^
1)
44 local patt
= Cp() * num
* Cp() * Cc(true) + Cp() * ordinal_words
* Cp()
45 local start
, finish
= P
{Cmt(Cc(selection
.col
) * patt
, at_or_after
) + 1 * V(1)}:match(line
)
46 if not (start
and finish
) then return end
47 local line_begin
= selection
.pos
- selection
.col
+ 1
48 return line_begin
+ start
- 1, line_begin
+ finish
- 1
51 local function toggle(func
, motion
)
52 return function(file
, range
, pos
)
53 local word
= file
:content(range
)
54 local toggled
= func(word
, count
)
57 file
:insert(range
.start
, toggled
)
58 return motion
and range
.finish
or range
.start
64 local function make_shift(shift
)
66 return function(word
, delta
)
67 local number = tonumber(word
)
68 local iter
= number and {number} or lookup
[word
]
69 if not iter
then return end
70 local binary
= iter
[2] and #iter
[2] == 2
71 local neighbor
, rotate
= shift(iter
[1], iter
[2], delta
)
73 local num
= Ct(S
"-+"^
-1 * (Cg(P
"0x" + "0X", "base") * zeros
* C(hex_digit^
1)^
-1 +
74 zeros
* C(dec_digit^
1)^
-1))
75 local groups
= num
:match(word
)
76 local digits
= groups
[2] and #groups
[2] or 0
77 local sign
= neighbor
< 0 and "-" or ""
78 local has_letters
= groups
[2] and groups
[2]:find
"%a"
80 upper
= groups
[2]:find
"%u"
81 elseif groups
.base
== "0X" then
84 local hexfmt
= upper
and "%X" or "%x"
85 local abs = string.format(groups
.base
and hexfmt
or "%d", math
.abs(neighbor
))
86 local dzero
= #tostring(abs) - digits
87 local base
= groups
.base
or ""
88 return sign
.. base
.. string.rep("0", groups
[1] > 0 and groups
[1] - dzero
or 0) .. abs
90 return iter
[2][neighbor
] or binary
and iter
[2][rotate
]
94 local increment
= make_shift(function(i
, _
, delta
) return i
+ (delta
or 1), 1 end)
95 local decrement
= make_shift(function(i
, options
, delta
) return i
- (delta
or 1), options
and #options
end)
97 local function case(str
)
98 return str
:gsub("%a", function(char
)
99 local lower
= char
:lower()
100 return char
== lower
and char
:upper() or lower
104 local function h(msg
)
105 return string.format("|@%s| %s", progname
, msg
)
108 local function operator_new(key
, handler
, object
, motion
, help
, novisual
)
109 local id
= vis
:operator_register(toggle(handler
, motion
))
113 local function binding()
115 if vis
.mode
== vis
.modes
.OPERATOR_PENDING
then
119 vis
:textobject(object
)
125 vis
:map(vis
.modes
.NORMAL
, key
, binding
, h(help
))
127 vis
:map(vis
.modes
.VISUAL
, key
, binding
, h(help
))
131 local function preprocess(tbl
)
132 local cfg
, ord
= {}, P(false)
133 local longer_first
= {}
134 for _
, options
in ipairs(tbl
) do
135 for i
, key
in ipairs(options
) do
136 cfg
[key
] = {i
, options
}
137 table.insert(longer_first
, key
)
140 table.sort(longer_first
, function(f
, s
)
141 local flen
, slen
= #f
, #s
142 return flen
> slen
or flen
== slen
and f
< s
144 for _
, key
in ipairs(longer_first
) do
150 vis
.events
.subscribe(vis
.events
.INIT
, function()
151 local ord_next
= vis
:textobject_register(ordinal
)
152 operator_new("<C-a>", increment
, ord_next
, nil, "Toggle/increment word or number", true)
153 operator_new("<C-x>", decrement
, ord_next
, nil, "Toggle/decrement word or number", true)
154 operator_new("~", case
, nil, VIS_MOVE_CHAR_NEXT
, "Toggle case of character or selection")
155 operator_new("g~", case
, nil, nil, "Toggle-case operator")
156 operator_new("gu", string.lower
, nil, nil, "Lower-case operator")
157 operator_new("gU", string.upper
, nil, nil, "Upper-case operator")
158 lookup
, ordinal_words
= preprocess(M
.config
)
159 vis
:map(vis
.modes
.NORMAL
, "g~~", "g~il")
160 vis
:map(vis
.modes
.NORMAL
, "guu", "guil")
161 vis
:map(vis
.modes
.NORMAL
, "gUU", "gUil")
162 vis
:map(vis
.modes
.VISUAL
, "u", "gu")
163 vis
:map(vis
.modes
.VISUAL
, "U", "gU")