aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorgi Kirilov <>2020-11-07 14:11:12 +0200
committerGeorgi Kirilov <>2023-10-04 18:27:25 +0800
commite2c92c58a8e1cdd16044bad7d583fa13e03e69e7 (patch)
tree801cc3d0c7d016c2437659586fa06c0a86db4c91
downloadvis-toggler-master.tar.gz
Squash commitsmaster
-rw-r--r--example.lua25
-rw-r--r--init.lua166
2 files changed, 191 insertions, 0 deletions
diff --git a/example.lua b/example.lua
new file mode 100644
index 0000000..cfb47a2
--- /dev/null
+++ b/example.lua
@@ -0,0 +1,25 @@
+return {
+ {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"},
+ {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"},
+ {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"},
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"},
+
+ -- string:
+ {"upper", "lower"},
+
+ -- math:
+ {"ceil", "floor"},
+ {"min", "max"},
+
+ -- io:
+ {"stderr", "stdout"},
+
+ -- bitwise:
+ {"<<", ">>"},
+
+ -- relational:
+ {"<=", ">"},
+ {">=", "<"},
+ {"==", "~="},
+ {"true", "false"},
+}
diff --git a/init.lua b/init.lua
new file mode 100644
index 0000000..6880ce5
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,166 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- © 2020 Georgi Kirilov
+
+require("vis")
+local vis = vis
+
+require("lpeg")
+local l = lpeg
+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
+
+local progname = ...
+
+local M = {config = {}}
+
+-- 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
+
+-- copied from vis.h
+local VIS_MOVE_CHAR_NEXT = 17
+
+local dec_digit = R"09"
+local hex_digit = R("09", "af", "AF")
+local zeros = P"0"^0 / function(c) return #c end
+
+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 num = S"-+"^-1 * ((P"0x" + "0X") * hex_digit^1 + dec_digit^1)
+ local patt = Cp() * 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)
+ local upper
+ 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)
+ if number then
+ local num = Ct(S"-+"^-1 * (Cg(P"0x" + "0X", "base") * zeros * C(hex_digit^1)^-1 +
+ zeros * C(dec_digit^1)^-1))
+ local groups = num:match(word)
+ local digits = groups[2] and #groups[2] or 0
+ local sign = neighbor < 0 and "-" or ""
+ local has_letters = groups[2] and groups[2]:find"%a"
+ if has_letters then
+ upper = groups[2]:find"%u"
+ elseif groups.base == "0X" then
+ upper = true
+ end
+ local hexfmt = upper and "%X" or "%x"
+ local abs = string.format(groups.base and hexfmt or "%d", math.abs(neighbor))
+ local dzero = #tostring(abs) - digits
+ local base = groups.base or ""
+ return sign .. base .. string.rep("0", groups[1] > 0 and groups[1] - dzero or 0) .. abs
+ end
+ return 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 h(msg)
+ return string.format("|@%s| %s", progname, msg)
+end
+
+local function operator_new(key, handler, object, motion, help, novisual)
+ local id = vis:operator_register(toggle(handler, motion))
+ 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, h(help))
+ if not novisual then
+ vis:map(vis.modes.VISUAL, key, binding, h(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>", increment, ord_next, nil, "Toggle/increment word or number", true)
+ operator_new("<C-x>", decrement, ord_next, nil, "Toggle/decrement word or number", true)
+ operator_new("~", case, nil, VIS_MOVE_CHAR_NEXT, "Toggle case of character or selection")
+ operator_new("g~", case, nil, nil, "Toggle-case operator")
+ operator_new("gu", string.lower, nil, nil, "Lower-case operator")
+ operator_new("gU", string.upper, nil, nil, "Upper-case operator")
+ lookup, ordinal_words = preprocess(M.config)
+ 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 M