From e2c92c58a8e1cdd16044bad7d583fa13e03e69e7 Mon Sep 17 00:00:00 2001 From: Georgi Kirilov <> Date: Sat, 7 Nov 2020 14:11:12 +0200 Subject: Squash commits --- example.lua | 25 +++++++++ init.lua | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 example.lua create mode 100644 init.lua 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("", increment, ord_next, nil, "Toggle/increment word or number", true) + operator_new("", 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 -- cgit