From a0f45aece21a6280633023ec84f60eb0d5e0849a Mon Sep 17 00:00:00 2001 From: Raymaekers Luca Date: Thu, 20 Jun 2024 23:46:00 +0200 Subject: checkpoint --- config/essentials/vis/Makefile | 2 +- config/essentials/vis/commentary.lua | 251 ++++++++++++++++++++++++++++++++ config/essentials/vis/complete-line.lua | 141 ++++++++++++++++++ config/essentials/vis/cursors.lua | 12 +- config/essentials/vis/format.lua | 131 +++++++++++++++++ config/essentials/vis/visrc.lua | 108 ++++++++------ 6 files changed, 593 insertions(+), 52 deletions(-) create mode 100644 config/essentials/vis/commentary.lua create mode 100644 config/essentials/vis/complete-line.lua create mode 100644 config/essentials/vis/format.lua diff --git a/config/essentials/vis/Makefile b/config/essentials/vis/Makefile index be230e3..1599b58 100644 --- a/config/essentials/vis/Makefile +++ b/config/essentials/vis/Makefile @@ -2,7 +2,7 @@ LUA_FILES := $(shell find . -name "*.lua") -all: check format +all: format check check: luacheck --no-color --globals=vis -- $(LUA_FILES) diff --git a/config/essentials/vis/commentary.lua b/config/essentials/vis/commentary.lua new file mode 100644 index 0000000..26d06b5 --- /dev/null +++ b/config/essentials/vis/commentary.lua @@ -0,0 +1,251 @@ +-- +-- vis-commentary +-- +local comment_string = { + actionscript = '//', + ada = '--', + ansi_c = '/*|*/', + antlr = '//', + apdl = '!', + apl = '#', + applescript = '--', + asp = '\'', + autoit = ';', + awk = '#', + b_lang = '//', + bash = '#', + batch = ':', + bibtex = '%', + boo = '#', + chuck = '//', + cmake = '#', + coffeescript = '#', + context = '%', + cpp = '//', + crystal = '#', + csharp = '//', + css = '/*|*/', + cuda = '//', + dart = '//', + desktop = '#', + django = '{#|#}', + dmd = '//', + dockerfile = '#', + dot = '//', + eiffel = '--', + elixir = '#', + erlang = '%', + faust = '//', + fennel = ';;', + fish = '#', + forth = '|\\', + fortran = '!', + fsharp = '//', + gap = '#', + gettext = '#', + gherkin = '#', + glsl = '//', + gnuplot = '#', + go = '//', + groovy = '//', + gtkrc = '#', + haskell = '--', + html = '', + icon = '#', + idl = '//', + inform = '!', + ini = '#', + Io = '#', + java = '//', + javascript = '//', + json = '/*|*/', + jsp = '//', + latex = '%', + ledger = '#', + less = '//', + lilypond = '%', + lisp = ';', + logtalk = '%', + lua = '--', + makefile = '#', + markdown = '', + matlab = '#', + moonscript = '--', + myrddin = '//', + nemerle = '//', + nsis = '#', + objective_c = '//', + pascal = '//', + perl = '#', + php = '//', + pico8 = '//', + pike = '//', + pkgbuild = '#', + prolog = '%', + props = '#', + protobuf = '//', + ps = '%', + pure = '//', + python = '#', + rails = '#', + rc = '#', + rebol = ';', + rest = '.. ', + rexx = '--', + rhtml = '', + rstats = '#', + ruby = '#', + rust = '//', + sass = '//', + scala = '//', + scheme = ';', + smalltalk = '"|"', + sml = '(*)', + snobol4 = '#', + sql = '#', + tcl = '#', + tex = '%', + text = '', + toml = '#', + vala = '//', + vb = '\'', + vbscript = '\'', + verilog = '//', + vhdl = '--', + wsf = '', + xml = '', + yaml = '#', + zig = '//', + nim = '#', + julia = '#', + rpmspec = '#' +} + +-- escape all magic characters with a '%' +local function esc(str) + if not str then return "" end + return (str:gsub('%%', '%%%%'):gsub('^%^', '%%^'):gsub('%$$', '%%$'):gsub( + '%(', '%%('):gsub('%)', '%%)'):gsub('%.', '%%.') + :gsub('%[', '%%['):gsub('%]', '%%]'):gsub('%*', '%%*') + :gsub('%+', '%%+'):gsub('%-', '%%-'):gsub('%?', '%%?')) +end + +-- escape '%' +local function pesc(str) + if not str then return "" end + return str:gsub('%%', '%%%%') +end + +local function rtrim(s) + local n = #s + while n > 0 and s:find("^%s", n) do n = n - 1 end + return s:sub(1, n) +end + +local function comment_line(lines, lnum, prefix, suffix) + if suffix ~= "" then suffix = " " .. suffix end + lines[lnum] = string.gsub(lines[lnum], "(%s*)(.*)", + "%1" .. pesc(prefix) .. " %2" .. pesc(suffix)) +end + +local function uncomment_line(lines, lnum, prefix, suffix) + local match_str = "^(%s*)" .. esc(prefix) .. "%s?(.*)" .. esc(suffix) + local m = table.pack(lines[lnum]:match(match_str)) + lines[lnum] = m[1] .. rtrim(m[2]) +end + +local function is_comment(line, prefix) + return (line:match("^%s*(.+)"):sub(0, #prefix) == prefix) +end + +local function toggle_line_comment(lines, lnum, prefix, suffix) + if not lines or not lines[lnum] then return end + if not lines[lnum]:match("^%s*(.+)") then return end -- ignore empty lines + if is_comment(lines[lnum], prefix) then + uncomment_line(lines, lnum, prefix, suffix) + else + comment_line(lines, lnum, prefix, suffix) + end +end + +-- if one line inside the block is not a comment, comment the block. +-- only uncomment, if every single line is comment. +local function block_comment(lines, a, b, prefix, suffix) + local uncomment = true + for i = a, b do + if lines[i]:match("^%s*(.+)") and not is_comment(lines[i], prefix) then + uncomment = false + end + end + + if uncomment then + for i = a, b do + if lines[i]:match("^%s*(.+)") then + uncomment_line(lines, i, prefix, suffix) + end + end + else + for i = a, b do + if lines[i]:match("^%s*(.+)") then + comment_line(lines, i, prefix, suffix) + end + end + end +end + +vis:map(vis.modes.NORMAL, "gcc", function() + local win = vis.win + local lines = win.file.lines + local comment = comment_string[win.syntax] + if not comment then return end + local prefix, suffix = comment:match('^([^|]+)|?([^|]*)$') + if not prefix then return end + + for sel in win:selections_iterator() do + local lnum = sel.line + local col = sel.col + + toggle_line_comment(lines, lnum, prefix, suffix) + sel:to(lnum, col) -- restore cursor position + end + + win:draw() +end, "Toggle comment on a the current line") + +local function visual_f(i) + return function() + local win = vis.win + local lines = win.file.lines + + local comment = comment_string[win.syntax] + if not comment then return end + + local prefix, suffix = comment:match('^([^|]+)|?([^|]*)$') + if not prefix then return end + + for sel in win:selections_iterator() do + local r = sel.range + local lnum = sel.line -- line number of cursor + local col = sel.col -- column of cursor + + if sel.anchored and r then + sel.pos = r.start + local a = sel.line + sel.pos = r.finish + local b = sel.line - i + + block_comment(lines, a, b, prefix, suffix) + + sel:to(lnum, col) -- restore cursor position + end + end + + win:draw() + vis.mode = vis.modes.NORMAL -- go to normal mode + end +end + +vis:map(vis.modes.VISUAL_LINE, "gc", visual_f(1), + "Toggle comment on the selected lines") +vis:map(vis.modes.VISUAL, "gc", visual_f(0), + "Toggle comment on the selected lines") diff --git a/config/essentials/vis/complete-line.lua b/config/essentials/vis/complete-line.lua new file mode 100644 index 0000000..93728a3 --- /dev/null +++ b/config/essentials/vis/complete-line.lua @@ -0,0 +1,141 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +-- © 2020 Georgi Kirilov +local progname = ... + +local lpeg = require("lpeg") +local R, C, Cmt = lpeg.R, lpeg.C, lpeg.Cmt + +-- copied from vis.h +local VIS_MOVE_NOP = 64 + +local cont = R("\128\191") + +local charpattern = R("\0\127") + R("\194\223") * cont + R("\224\239") * cont * + cont + R("\240\244") * cont * cont * cont + +local function concat_keys(tbl) + local keys = {} + for k in pairs(tbl) do table.insert(keys, k) end + return table.concat(keys, "\n"), #keys +end + +local function line_complete() + local file = vis.win.file + local sel = vis.win.selection + local cur_line = file.lines[sel.line] + local indent_patt = "^[ \t\v\f]+" + local prefix = cur_line:sub(1, sel.col - 1):gsub(indent_patt, "") + local candidates = {} + for l in file:lines_iterator() do + local unindented = l:gsub(indent_patt, "") + local start, finish = unindented:find(prefix, 1, true) + if start == 1 and finish < #unindented then + candidates[unindented] = true + end + end + local candidates_str, n = concat_keys(candidates) + if n < 2 then + if n == 1 then vis:insert(candidates_str:sub(#prefix + 1)) end + return + end + -- XXX: with too many candidates this command will become longer that the shell can handle: + local command = string.format("vis-menu -l %d <<'EOF'\n%s\nEOF\n", + math.min(n, math.ceil(vis.win.height / 2)), + candidates_str) + local status, output = vis:pipe(nil, nil, command) + if n > 0 and status == 0 then + vis:insert(output:sub(#prefix + 1):gsub("\n$", "")) + end +end + +local function selection_by_pos(pos) + for s in vis.win:selections_iterator() do + if s.pos == pos then return s end + end +end + +local function charwidth(cells_so_far, char) + if char == "\t" then + local tw = vis.tabwidth or 8 + local trail = cells_so_far % tw + return tw - trail + else + return 1 + end +end + +local function virtcol(line, col) + local ncells = 0 + local nchars = 0 + local function upto(_, _, char) + if nchars < col - 1 then + ncells = ncells + charwidth(ncells, char) + nchars = nchars + 1 + return true + end + end + (Cmt(C(charpattern), upto) ^ 0):match(line) + return ncells + 1 +end + +local function neighbor(lines, ln, col, direction) + local line = ln + direction > 0 and lines[ln + direction] + if not line then return end + local column = virtcol(lines[ln], col) + local ncells = 0 + local function upto(_, _, char) + ncells = ncells + charwidth(ncells, char) + return ncells < column + end + return + (Cmt(C(charpattern), upto) ^ (-column + 1) / 0 * C(charpattern)):match( + line) +end + +local function dup_neighbor(direction) + return function(file, _, pos) + local sel = selection_by_pos(pos) + local char = neighbor(file.lines, sel.line, sel.col, direction) + if not char then return pos end + file:insert(pos, char) + return pos + #char + end +end + +local function dup_neighbor_feedkeys(direction) + local sel = vis.win.selection + local file = vis.win.file + local char = neighbor(file.lines, sel.line, sel.col, direction) + if not char then return end + vis:feedkeys(char) +end + +local function operator(handler) + local id = vis:operator_register(handler) + return id >= 0 and function() + vis:operator(id) + vis:motion(VIS_MOVE_NOP) + end +end + +vis.events.subscribe(vis.events.INIT, function() + local function h(msg) return string.format("|@%s| %s", progname, msg) end + + local function column_complete(direction) + local binding = operator(dup_neighbor(direction)) + return function() + if #vis.win.selections == 1 then + dup_neighbor_feedkeys(direction) + else + return binding() + end + end + end + + vis:map(vis.modes.INSERT, "", column_complete(-1), + h "Insert the character which is above the cursor") + vis:map(vis.modes.INSERT, "", column_complete(1), + h "Insert the character which is below the cursor") + vis:map(vis.modes.INSERT, "", line_complete, + h "Complete the current line") +end) diff --git a/config/essentials/vis/cursors.lua b/config/essentials/vis/cursors.lua index 37165b6..5b3d43b 100644 --- a/config/essentials/vis/cursors.lua +++ b/config/essentials/vis/cursors.lua @@ -6,7 +6,7 @@ local files = {} M.maxsize = 1000 -- get the default system cache directory -local get_default_cache_path = function() +local function get_default_cache_path() local HOME = os.getenv('HOME') local XDG_CACHE_HOME = os.getenv('XDG_CACHE_HOME') local BASE = XDG_CACHE_HOME or HOME @@ -16,7 +16,7 @@ end -- default save path M.path = get_default_cache_path() -local read_files = function() +local function read_files() -- read file local file = io.open(M.path) @@ -36,10 +36,10 @@ local read_files = function() end -- read cursors from file on init -local on_init = function() read_files() end +local function on_init() read_files() end -- apply cursor pos on win open -local on_win_open = function(win) +local function on_win_open(win) if win.file == nil or win.file.path == nil then return end @@ -58,7 +58,7 @@ local on_win_open = function(win) end -- set cursor pos on close -local on_win_close = function(win) +local function on_win_close(win) if win.file == nil or win.file.path == nil then return end @@ -81,7 +81,7 @@ local on_win_close = function(win) end -- write cursors to file on quit -local on_quit = function() +local function on_quit() local file = io.open(M.path, 'w+') if file == nil then return end diff --git a/config/essentials/vis/format.lua b/config/essentials/vis/format.lua new file mode 100644 index 0000000..15488dd --- /dev/null +++ b/config/essentials/vis/format.lua @@ -0,0 +1,131 @@ +local global_options = {check_same = true} + +local function stdio_formatter(cmd, options) + local function apply(win, range, pos) + local size = win.file.size + local all = {start = 0, finish = size} + if range == nil then range = all end + local command = type(cmd) == 'function' and cmd(win, range, pos) or cmd + local check_same = (options and options.check_same ~= nil) and + options.check_same or global_options.check_same + local check = check_same == true or + (type(check_same) == 'number' and check_same >= size) + local status, out, err = vis:pipe(win.file, all, command) + if status ~= 0 then + vis:message(err) + elseif out == nil or out == '' then + vis:info('No output from formatter') + elseif not check or win.file:content(all) ~= out then + local start, finish = range.start, range.finish + win.file:delete(range) + win.file:insert(start, + out:sub(start + 1, finish + (out:len() - size))) + end + return pos + end + return { + apply = apply, + options = options or {ranged = type(cmd) == 'function'} + } +end + +local function with_filename(win, option) + if win.file.path then + return option .. "'" .. win.file.path:gsub("'", "\\'") .. "'" + else + return '' + end +end + +local formatters = {} +formatters = { + bash = stdio_formatter(function(win) + return 'shfmt ' .. with_filename(win, '--filename ') .. ' -' + end), + csharp = stdio_formatter('dotnet csharpier'), + go = stdio_formatter('gofmt'), + lua = { + pick = function(win) + local _, out = vis:pipe(win.file, + {start = 0, finish = win.file.size}, + 'test -e .lua-format && echo luaformatter || echo stylua') + return formatters[out:gsub('\n$', '')] + end + }, + luaformatter = stdio_formatter('lua-format'), + markdown = stdio_formatter(function(win) + if win.options and win.options.colorcolumn ~= 0 then + return 'prettier --parser markdown --prose-wrap always ' .. + ('--print-width ' .. (win.options.colorcolumn - 1) .. ' ') .. + with_filename(win, '--stdin-filepath ') + else + return 'prettier --parser markdown ' .. + with_filename(win, '--stdin-filepath ') + end + end, {ranged = false}), + powershell = stdio_formatter([[ + "$( (command -v powershell.exe || command -v pwsh) 2>/dev/null )" -c ' + Invoke-Formatter -ScriptDefinition ` + ([IO.StreamReader]::new([Console]::OpenStandardInput()).ReadToEnd()) + ' | sed -e :a -e '/^[\r\n]*$/{$d;N;};/\n$/ba' + ]]), + rust = stdio_formatter('rustfmt'), + stylua = stdio_formatter(function(win, range) + if range and (range.start ~= 0 or range.finish ~= win.file.size) then + return + 'stylua -s --range-start ' .. range.start .. ' --range-end ' .. + range.finish .. with_filename(win, ' --stdin-filepath ') .. + ' -' + else + return 'stylua -s ' .. with_filename(win, '--stdin-filepath ') .. + ' -' + end + end), + text = stdio_formatter(function(win) + if win.options and win.options.colorcolumn ~= 0 then + return 'fmt -w ' .. (win.options.colorcolumn - 1) + else + return 'fmt' + end + end, {ranged = false}) +} + +local function getwinforfile(file) + for win in vis:windows() do + if win and win.file and win.file.path == file.path then + return win + end + end +end + +local function apply(file_or_keys, range, pos) + local win = + type(file_or_keys) ~= 'string' and getwinforfile(file_or_keys) or + vis.win + local ret = type(file_or_keys) ~= 'string' and function() return pos end or + function() return 0 end + pos = pos or win.selection.pos + local formatter = formatters[win.syntax] + if formatter and formatter.pick then formatter = formatter.pick(win) end + if formatter == nil then + vis:info('No formatter for ' .. win.syntax) + return ret() + end + if range ~= nil and not formatter.options.ranged and range.start ~= 0 and + range.finish ~= win.file.size then + vis:info('Formatter for ' .. win.syntax .. ' does not support ranges') + return ret() + end + pos = formatter.apply(win, range, pos) or pos + vis:insert('') -- redraw and friends don't work + win.selection.pos = pos + return ret() +end + +return { + formatters = formatters, + options = global_options, + apply = apply, + stdio_formatter = stdio_formatter, + with_filename = with_filename +} diff --git a/config/essentials/vis/visrc.lua b/config/essentials/vis/visrc.lua index 3a9ff1d..60162bc 100644 --- a/config/essentials/vis/visrc.lua +++ b/config/essentials/vis/visrc.lua @@ -1,71 +1,83 @@ ------------------------------------ --- LIBRARIES ------------------------------------ -require('vis') +require("vis") -- plugins require("build") require("backup") require("cursors") require("title") +require("commentary") +require("complete-line") +local format = require("format") -vis:command_register("make", function() vis:communicate() end, "make") +-- todo: +-- c-scope +-- c-tags ------------------------------------ ---- FUNCTIONS +--- VARIABLES ------------------------------------ -local map_cmd = function(mode, map, command, help) - vis:map(mode, map, function() vis:command(command) end, help) -end - -local map_cmd_restore = function(mode, map, command, help) - vis:map(mode, map, function() - if (mode == vis.modes.INSERT) then vis:feedkeys("") end +local m = vis.modes - vis:feedkeys("m") - vis:command(command) - vis:feedkeys("M") +------------------------------------ +--- FUNCTIONS +------------------------------------ - if (mode == vis.modes.INSERT) then vis:feedkeys("i") end - end, help) +local function map_cmd(mode, map, command, help) + vis:map(mode, map, function() + vis:command(command) + end, help) end -local map_keys = function(mode, map, keys, help) - vis:map(mode, map, function() vis:feedkeys(keys) end, help) +-- TOOD: use window selection to restore position +local function wrap_restore(f, ...) + local pos = vis.win.selection.pos + f(...) + vis.win.selection.pos = pos end ------------------------------------- ---- VARIABLES ------------------------------------- - -local m = vis.modes +local function map_keys(mode, map, keys, help) + vis:map(mode, map, function() + vis:feedkeys(keys) + end, help) +end ------------------------------------ --- COMMANDS ----------------------------------- -vis:command_register("Q", function() vis:command("qa!") end, "Quit all") -vis:command_register("delws", - function() vis:command("x/[ \t]+$|^[ \t]+$/d") end, - "Remove trailing whitespace") +vis:command_register("make", function() + vis:command("!make && head -n 1") +end, "make") +vis:command_register("Q", function() + vis:command("qa!") +end, "Quit all") +vis:command_register("delws", function() + vis:command("x/[ \t]+$|^[ \t]+$/d") +end, "Remove trailing whitespace") ------------------------------------- --- MAPPINGS ------------------------------------- -map_cmd_restore(m.NORMAL, " r", "e $vis_filepath", "Reload active file") +vis:map(m.NORMAL, " r", function() + wrap_restore(vis.command, vis, "e $vis_filepath") +end, "Reload active file") + +vis:map(m.NORMAL, "=", format.apply, "Format active file") map_cmd(m.NORMAL, " c", "e ~/.config/vis/visrc.lua", "Edit config file") map_cmd(m.NORMAL, " q", "q!", "Quit (force)") map_cmd(m.NORMAL, " s", "!doas vis $vis_filepath", "Edit as superuser") map_cmd(m.NORMAL, " w", "w", "Write") -map_cmd(m.NORMAL, " x", "!chmod u+x $vis_filepath", - "Make active file executable") +map_cmd(m.NORMAL, " x", "!chmod u+x $vis_filepath", "Make active file executable") vis:map(m.NORMAL, " eh", function() - vis:command("!lowdown $vis_filepath > ${vis_filepath%.md}.html") - vis:info("exported.") + vis:command("!lowdown $vis_filepath > ${vis_filepath%.md}.html") + vis:info("exported.") end, "Export markdown to html") map_keys(m.NORMAL, " nl", ":\\&2 printf '\\2: %s\\\\n' \"$\\2\"/", - "Print variable") - end +vis.events.subscribe(vis.events.WIN_OPEN, function(win) -- luacheck: no unused args + win.options.relativenumbers = true + + if win.syntax == "bash" then + map_keys( + m.NORMAL, + " v", + "V:x/^(\\s*)(.+)$/ c/\\1>\\&2 printf '\\2: %s\\\\n' \"$\\2\"/", + "Print variable" + ) + end + + vis:command_register("pipe", function() + vis:pipe(win.file, nil, "sed 's/.*/- &/'") + end, "pipe test") end) -- cgit v1.2.3