2 files changed,
132 insertions(+),
178 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-08-22 19:26:02 +0300
Authored at:
2026-08-01 20:04:35 +0300
Change ID:
uvsluvqpkrkuyznzxynwrkmnxullwvzw
Parent:
1e27f37
jump to
| D | lua/viye.lua |
| A | plugin/viye.lua |
D
lua/viye.lua
··· 1 -local M = {} 2 - 3 -local function indent(text) return #text:match("^%s*") end 4 -local function is_data(line) return line:find("^%s*[|:]") ~= nil end 5 - 6 -local function children_of(cmd_lnum) 7 - local depth = indent(vim.fn.getline(cmd_lnum)) 8 - local first = cmd_lnum + 1 9 - local last = nil 10 - local i = first 11 - while i <= vim.fn.line("$") do 12 - local l = vim.fn.getline(i) 13 - if l == "" then break end 14 - if indent(l) <= depth then break end 15 - last = i 16 - i = i + 1 17 - end 18 - return first, last 19 -end 20 - 21 -local function parent_of(lnum) 22 - local d = indent(vim.fn.getline(lnum)) 23 - while lnum >= 1 do 24 - local l = vim.fn.getline(lnum) 25 - if not is_data(l) and l:find("%S") and indent(l) < d then return lnum end 26 - lnum = lnum - 1 27 - end 28 - return nil 29 -end 30 - 31 -local function ancestors(cmd_lnum) 32 - local parts = {} 33 - local depth = math.huge 34 - local i = cmd_lnum 35 - while i >= 1 do 36 - local l = vim.fn.getline(i) 37 - if not is_data(l) and l:find("%S") then 38 - local d = indent(l) 39 - if d < depth then 40 - local text = l:match("^%s*[+-] (.*)") 41 - if not text then text = vim.trim(l) end 42 - table.insert(parts, 1, text) 43 - depth = d 44 - if d == 0 then break end 45 - end 46 - end 47 - i = i - 1 48 - end 49 - return parts 50 -end 51 - 52 -local function body_of(cmd_lnum) 53 - local depth = indent(vim.fn.getline(cmd_lnum)) 54 - local body = {} 55 - local i = cmd_lnum + 1 56 - while i <= vim.fn.line("$") do 57 - local l = vim.fn.getline(i) 58 - if l == "" then break end 59 - local d = indent(l) 60 - if d <= depth then break end 61 - if d == depth + 2 then 62 - local val = l:match("^%s*: (.*)") 63 - if val then 64 - table.insert(body, ": " .. val) 65 - end 66 - end 67 - i = i + 1 68 - end 69 - return body 70 -end 71 - 72 -local function run(cmd_lnum) 73 - local parts = ancestors(cmd_lnum) 74 - if #parts == 0 then return nil end 75 - 76 - local cmd = "viye" 77 - for _, p in ipairs(parts) do 78 - cmd = cmd .. " " .. vim.fn.shellescape(p) 79 - end 80 - local body = body_of(cmd_lnum) 81 - if #body > 0 then 82 - cmd = cmd .. " --" 83 - for _, b in ipairs(body) do 84 - cmd = cmd .. " " .. vim.fn.shellescape(b) 85 - end 86 - end 87 - 88 - vim.notify("ex'ing: " .. cmd, vim.log.levels.INFO) 89 - local out = vim.fn.system(cmd) 90 - if vim.v.shell_error ~= 0 then 91 - vim.notify("viye: " .. (vim.trim(out or "")), vim.log.levels.ERROR) 92 - return nil 93 - end 94 - return out 95 -end 96 - 97 -local function delete_range(first, last) 98 - if last then vim.cmd(string.format("%d,%ddelete _", first, last)) end 99 -end 100 - 101 -local function insert_output(cmd_lnum, out) 102 - local pad = string.rep(" ", indent(vim.fn.getline(cmd_lnum)) + 2) 103 - local lines = vim.split(out, "\n", { trimempty = true }) 104 - for i, l in ipairs(lines) do 105 - lines[i] = pad .. l 106 - end 107 - vim.fn.append(cmd_lnum, lines) 108 -end 109 - 110 -local function toggle_bullet(cmd_lnum, to) 111 - vim.fn.setline(cmd_lnum, (vim.fn.getline(cmd_lnum):gsub("^[+-] ", to .. " ", 1))) 112 -end 113 - 114 -local function exec() 115 - local lnum = vim.fn.line(".") 116 - local line = vim.fn.getline(lnum) 117 - 118 - -- find the enclosing command line 119 - local cmd_lnum 120 - if is_data(line) then 121 - cmd_lnum = parent_of(lnum) 122 - else 123 - cmd_lnum = lnum 124 - end 125 - if not cmd_lnum then return end 126 - 127 - local first, last = children_of(cmd_lnum) 128 - local on_cmd = cmd_lnum == lnum 129 - local has_bullet = line:find("^%s*[+-] ") 130 - 131 - if on_cmd and last and has_bullet then 132 - -- expanded togglable command -> collapse 133 - delete_range(first, last) 134 - toggle_bullet(cmd_lnum, "+") 135 - return 136 - end 137 - 138 - -- | output line: collapse without re-running 139 - if line:find("^%s*|") then 140 - if last then 141 - delete_range(first, last) 142 - toggle_bullet(cmd_lnum, "+") 143 - end 144 - return 145 - end 146 - 147 - -- collapsed command, or Enter on a child: run and (re-)expand 148 - local out = run(cmd_lnum) 149 - if not out then return end 150 - 151 - delete_range(first, last) 152 - if out ~= "" then 153 - insert_output(cmd_lnum, out) 154 - end 155 - toggle_bullet(cmd_lnum, "-") 156 -end 157 - 158 -function M.setup() 159 - vim.filetype.add({ extension = { viye = "viye" } }) 160 - local group = vim.api.nvim_create_augroup("viye", { clear = true }) 161 - vim.api.nvim_create_autocmd("FileType", { 162 - pattern = "viye", 163 - group = group, 164 - callback = function() 165 - vim.keymap.set("n", "<CR>", exec, { buffer = true, silent = true }) 166 - end, 167 - }) 168 - vim.api.nvim_create_autocmd("BufWinEnter", { 169 - group = group, 170 - callback = function() 171 - if vim.fn.expand("%") == "" and vim.bo.buftype == "" then 172 - vim.bo.filetype = "viye" 173 - end 174 - end, 175 - }) 176 -end 177 - 178 -return M
A
plugin/viye.lua
··· 1 +local function indent(t) return #t:match("^%s*") end 2 +local function is_data(line) return line:find("^%s*[|:]") ~= nil end 3 +local function is_node(line) return line:find("%S") and not is_data(line) end 4 + 5 +local function node_text(lnum) 6 + local l = vim.fn.getline(lnum) 7 + return l:match("^%s*[+-] (.*)") or vim.trim(l) 8 +end 9 + 10 +local function children_of(cmd_lnum) 11 + local depth = indent(vim.fn.getline(cmd_lnum)) 12 + local first, last = cmd_lnum + 1, nil 13 + for i = first, vim.fn.line("$") do 14 + local l = vim.fn.getline(i) 15 + if l == "" or indent(l) <= depth then break end 16 + last = i 17 + end 18 + return first, last 19 +end 20 + 21 +local function parent_of(lnum) 22 + local d = indent(vim.fn.getline(lnum)) 23 + for i = lnum - 1, 1, -1 do 24 + local l = vim.fn.getline(i) 25 + if is_node(l) and indent(l) < d then return i end 26 + end 27 + return nil 28 +end 29 + 30 +local function ancestors(cmd_lnum) 31 + local parts = {} 32 + local lnum = cmd_lnum 33 + while lnum do 34 + table.insert(parts, 1, node_text(lnum)) 35 + lnum = parent_of(lnum) 36 + end 37 + return parts 38 +end 39 + 40 +local function body_of(cmd_lnum) 41 + local depth = indent(vim.fn.getline(cmd_lnum)) 42 + local first, last = children_of(cmd_lnum) 43 + local body = {} 44 + if last then 45 + for i = first, last do 46 + local l = vim.fn.getline(i) 47 + local val = indent(l) == depth + 2 and l:match("^%s*: (.*)") 48 + if val then table.insert(body, ": " .. val) end 49 + end 50 + end 51 + return body 52 +end 53 + 54 +local function run(cmd_lnum) 55 + local parts = ancestors(cmd_lnum) 56 + if #parts == 0 then return nil end 57 + local args = vim.tbl_map(vim.fn.shellescape, parts) 58 + local body = body_of(cmd_lnum) 59 + if #body > 0 then 60 + table.insert(args, "--") 61 + vim.list_extend(args, vim.tbl_map(vim.fn.shellescape, body)) 62 + end 63 + local cmd = "viye " .. table.concat(args, " ") 64 + vim.notify("viye: " .. cmd, vim.log.levels.DEBUG) 65 + local out = vim.fn.system(cmd) 66 + if vim.v.shell_error ~= 0 then 67 + vim.notify("viye: " .. vim.trim(out or ""), vim.log.levels.ERROR) 68 + return nil 69 + end 70 + return out 71 +end 72 + 73 +local function delete_range(first, last) 74 + if last then vim.cmd(string.format("%d,%ddelete _", first, last)) end 75 +end 76 + 77 +local function insert_output(cmd_lnum, out) 78 + local pad = string.rep(" ", indent(vim.fn.getline(cmd_lnum)) + 2) 79 + local lines = vim.split(out, "\n", { trimempty = true }) 80 + for i, l in ipairs(lines) do 81 + lines[i] = pad .. l 82 + end 83 + vim.fn.append(cmd_lnum, lines) 84 +end 85 + 86 +local function toggle_bullet(cmd_lnum, to) 87 + vim.fn.setline(cmd_lnum, (vim.fn.getline(cmd_lnum):gsub("^[+-] ", to .. " ", 1))) 88 +end 89 + 90 +local function exec() 91 + local lnum = vim.fn.line(".") 92 + local line = vim.fn.getline(lnum) 93 + local cmd_lnum = is_data(line) and parent_of(lnum) or lnum 94 + if not cmd_lnum then return end 95 + local first, last = children_of(cmd_lnum) 96 + local on_cmd = cmd_lnum == lnum 97 + local has_bullet = line:find("^%s*[+-] ") ~= nil 98 + local collapsing = last and ((on_cmd and has_bullet) or line:find("^%s*|") ~= nil) 99 + if collapsing then 100 + delete_range(first, last) 101 + toggle_bullet(cmd_lnum, "+") 102 + return 103 + end 104 + local out = run(cmd_lnum) 105 + if not out then return end 106 + delete_range(first, last) 107 + if out ~= "" then 108 + insert_output(cmd_lnum, out) 109 + end 110 + toggle_bullet(cmd_lnum, "-") 111 +end 112 + 113 +vim.filetype.add { extension = { viye = "viye" } } 114 +local group = vim.api.nvim_create_augroup("viye", { clear = true }) 115 + 116 +vim.api.nvim_create_autocmd("BufEnter", { 117 + pattern = "*", 118 + group = group, 119 + callback = function() 120 + if vim.api.nvim_buf_get_name(0) == "" then 121 + vim.bo.filetype = "viye" 122 + end 123 + end 124 +}) 125 + 126 +vim.api.nvim_create_autocmd("FileType", { 127 + pattern = "viye", 128 + group = group, 129 + callback = function() 130 + vim.keymap.set("n", "<CR>", exec, { buffer = true, silent = true }) 131 + end 132 +})