all repos

viye @ ec3d7c1c0090d32ce67215ffe3e9f40a9139a672

my shot at reimplementing xiki
2 files changed, 132 insertions(+), 178 deletions(-)
nvim: switch to plugin/ && set scratch buffer filetype
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
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
        +})