diff options
| -rw-r--r-- | config/essentials/vis/Makefile | 2 | ||||
| -rw-r--r-- | config/essentials/vis/commentary.lua | 251 | ||||
| -rw-r--r-- | config/essentials/vis/complete-line.lua | 141 | ||||
| -rw-r--r-- | config/essentials/vis/cursors.lua | 12 | ||||
| -rw-r--r-- | config/essentials/vis/format.lua | 131 | ||||
| -rw-r--r-- | config/essentials/vis/visrc.lua | 108 | 
6 files changed, 593 insertions, 52 deletions
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, "<C-y>", column_complete(-1), +            h "Insert the character which is above the cursor") +    vis:map(vis.modes.INSERT, "<C-e>", column_complete(1), +            h "Insert the character which is below the cursor") +    vis:map(vis.modes.INSERT, "<C-x><C-l>", 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("<Escape>") 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", ":<seq -f '%0.0f. ' 1 ", "Insert numbered list") @@ -77,20 +89,26 @@ map_keys(m.NORMAL, " nl", ":<seq -f '%0.0f. ' 1 ", "Insert numbered list")  ------------------------------------  vis.events.subscribe(vis.events.INIT, function() -    vis.options.ignorecase = true -    vis.options.autoindent = true -    vis.options.shell = "/bin/sh" -    local theme = "nord" -    vis:command("set theme " .. theme) +	vis.options.ignorecase = true +	vis.options.autoindent = true +	vis.options.shell = "/bin/sh" +	local theme = "nord" +	vis:command("set theme " .. theme)  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\"/<Enter><Escape>", -                 "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\"/<Enter><Escape>", +			"Print variable" +		) +	end + +	vis:command_register("pipe", function() +		vis:pipe(win.file, nil, "sed 's/.*/- &/'") +	end, "pipe test")  end)  | 
