aboutsummaryrefslogtreecommitdiffstats
path: root/toggler.lua
blob: b2a72edb3c35797f4a6350ecf1f7a9349b7d7b79 (plain) (blame)
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
-- 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

local M = {}
local lookup = {}
local count

local inner_word = 0
local char_next = 17

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("[%l%u]", 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 = {}
	for _, options in ipairs(tbl) do
		for i, key in ipairs(options) do
			cfg[key] = {i, options}
		end
	end
	return cfg
end

vis.events.subscribe(vis.events.INIT, function()
	operator_new("<C-a>", toggle(increment),    inner_word, nil,       "Toggle/increment word or selection", true)
	operator_new("<C-x>", toggle(decrement),    inner_word, nil,       "Toggle/decrement word or selection", 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 = 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