all repos

viye @ 2ee545d

my shot at reimplementing xiki
1 files changed, 137 insertions(+), 146 deletions(-)
update lua
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-07-23 12:27:11 +0300
Authored at: 2026-07-22 17:38:21 +0300
Change ID: lsqukyyvxxrwswrzqxmttmsqqxxmspro
Parent: d764ee5
M lua/viye.lua
···
        1
        1
         local M = {}

      
        2
        2
         

      
        3
        3
         local function indent(text) return #text:match "^%s*" end

      
        4
        
        -local function is_data(text) return text:find "^%s*[|:] " ~= nil end

      
        
        4
        +local function is_data(text) return text:find "^%s*[|:]" ~= nil end

      
        5
        5
         local function strip_bullet(text) return (text:gsub("^[+-] ", "", 1)) end

      
        6
        6
         

      
        7
        7
         local function build_path()

      ···
        9
        9
           local line = vim.fn.getline(lnum)

      
        10
        10
           local depth = indent(line)

      
        11
        11
           local parts = { strip_bullet(vim.trim(line)) }

      
        12
        
        -

      
        13
        12
           local i = lnum - 1

      
        14
        13
           while i >= 1 do

      
        15
        14
             local l = vim.fn.getline(i)

      ···
        24
        23
             end

      
        25
        24
             i = i - 1

      
        26
        25
           end

      
        27
        
        -

      
        28
        
        -  for idx = 1, #parts - 1 do

      
        29
        
        -    parts[idx] = parts[idx]:gsub("/+$", "")

      
        30
        
        -  end

      
        31
        
        -

      
        32
        26
           return table.concat(parts, "/")

      
        33
        27
         end

      
        34
        28
         

      
        35
        
        -local function has_children()

      
        36
        
        -  local lnum = vim.fn.line "."

      
        37
        
        -  local depth = indent(vim.fn.getline(lnum))

      
        38
        
        -  local next_line = vim.fn.getline(lnum + 1)

      
        39
        
        -  return next_line ~= "" and indent(next_line) > depth

      
        40
        
        -end

      
        
        29
        +local function exec()

      
        
        30
        +  local ok, err = pcall(function()

      
        
        31
        +    local line = vim.fn.getline "."

      
        41
        32
         

      
        42
        
        --- Collect : body lines under the current line (siblings at depth+2)

      
        43
        
        --- Returns list of stripped lines (with ": " prefix preserved)

      
        44
        
        -local function collect_body()

      
        45
        
        -  local lnum = vim.fn.line "."

      
        46
        
        -  local depth = indent(vim.fn.getline(lnum))

      
        47
        
        -  local body = {}

      
        48
        
        -  local i = lnum + 1

      
        49
        
        -  while i <= vim.fn.line "$" do

      
        50
        
        -    local l = vim.fn.getline(i)

      
        51
        
        -    if l == "" then break end

      
        52
        
        -    local d = indent(l)

      
        53
        
        -    if d <= depth then break end

      
        54
        
        -    if d == depth + 2 and l:find("^%s*: ") then

      
        55
        
        -      table.insert(body, vim.trim(l))

      
        56
        
        -    end

      
        57
        
        -    i = i + 1

      
        58
        
        -  end

      
        59
        
        -  return body

      
        60
        
        -end

      
        
        33
        +    -- Data line: | means collapse, : means re-execute with body

      
        
        34
        +    if is_data(line) then

      
        
        35
        +      local depth = indent(line)

      
        
        36
        +      local i = vim.fn.line "." - 1

      
        
        37
        +      while i >= 1 do

      
        
        38
        +        local l = vim.fn.getline(i)

      
        
        39
        +        if not is_data(l) and indent(l) < depth and l:find "%S" then

      
        
        40
        +          vim.fn.cursor(i, 1)

      
        
        41
        +          break

      
        
        42
        +        end

      
        
        43
        +        i = i - 1

      
        
        44
        +      end

      
        61
        45
         

      
        62
        
        --- Remove | output lines under the current line (siblings at depth+2)

      
        63
        
        --- Returns the line number after the removed block, or nil if nothing removed

      
        64
        
        -local function remove_output()

      
        65
        
        -  local lnum = vim.fn.line "."

      
        66
        
        -  local depth = indent(vim.fn.getline(lnum))

      
        67
        
        -  local first = nil

      
        68
        
        -  local last = nil

      
        69
        
        -  local i = lnum + 1

      
        70
        
        -  while i <= vim.fn.line "$" do

      
        71
        
        -    local l = vim.fn.getline(i)

      
        72
        
        -    if l == "" then break end

      
        73
        
        -    local d = indent(l)

      
        74
        
        -    if d <= depth then break end

      
        75
        
        -    if d == depth + 2 and l:find("^%s*| ") then

      
        76
        
        -      if first == nil then first = i end

      
        77
        
        -      last = i

      
        78
        
        -    end

      
        79
        
        -    i = i + 1

      
        80
        
        -  end

      
        81
        
        -  if first then

      
        82
        
        -    vim.cmd(string.format("%d,%ddelete _", first, last))

      
        83
        
        -    return first

      
        84
        
        -  end

      
        85
        
        -  return nil

      
        86
        
        -end

      
        
        46
        +      local lnum = vim.fn.line "."

      
        
        47
        +      local depth = indent(vim.fn.getline(lnum))

      
        
        48
        +      local first_child = lnum + 1

      
        
        49
        +      local body = {}

      
        
        50
        +      local last_child = nil

      
        
        51
        +      local i = first_child

      
        
        52
        +      while i <= vim.fn.line "$" do

      
        
        53
        +        local l = vim.fn.getline(i)

      
        
        54
        +        if l == "" then break end

      
        
        55
        +        local d = indent(l)

      
        
        56
        +        if d <= depth then break end

      
        
        57
        +        if d == depth + 2 then

      
        
        58
        +          if l:find "^%s*: " then table.insert(body, vim.trim(l)) end

      
        
        59
        +          last_child = i

      
        
        60
        +        end

      
        
        61
        +        i = i + 1

      
        
        62
        +      end

      
        87
        63
         

      
        88
        
        -local function collapse()

      
        89
        
        -  local lnum = vim.fn.line "."

      
        90
        
        -  local depth = indent(vim.fn.getline(lnum))

      
        91
        
        -  local s = lnum + 1

      
        92
        
        -  local e = s

      
        93
        
        -  while e <= vim.fn.line "$" do

      
        94
        
        -    local l = vim.fn.getline(e)

      
        95
        
        -    if l == "" or indent(l) <= depth then break end

      
        96
        
        -    e = e + 1

      
        97
        
        -  end

      
        98
        
        -  if e > s then vim.cmd(string.format("%d,%ddelete _", s, e - 1)) end

      
        99
        
        -end

      
        
        64
        +      if line:find "^%s*|" then

      
        
        65
        +        -- | line: collapse (delete children, toggle to +)

      
        
        66
        +        if last_child then vim.cmd(string.format("%d,%ddelete _", first_child, last_child)) end

      
        
        67
        +        vim.fn.setline(lnum, (vim.fn.getline(lnum):gsub("^%- ", "+ ", 1)))

      
        
        68
        +        return

      
        
        69
        +      end

      
        
        70
        +

      
        
        71
        +      -- : line: re-execute with body (phase 2)

      
        
        72
        +      local path = build_path()

      
        
        73
        +      if path == "" then return end

      
        
        74
        +

      
        
        75
        +      local cmd = "viye " .. vim.fn.shellescape(path)

      
        
        76
        +      if #body > 0 then

      
        
        77
        +        cmd = cmd .. " --"

      
        
        78
        +        for _, b in ipairs(body) do

      
        
        79
        +          cmd = cmd .. " " .. vim.fn.shellescape(b)

      
        
        80
        +        end

      
        
        81
        +      end

      
        100
        82
         

      
        101
        
        -local function toggle_bullet()

      
        102
        
        -  local lnum = vim.fn.line "."

      
        103
        
        -  local line = vim.fn.getline(lnum)

      
        104
        
        -  local toggled = line:gsub("^%+ ", "- ", 1)

      
        105
        
        -  if toggled ~= line then

      
        106
        
        -    vim.fn.setline(lnum, toggled)

      
        107
        
        -    return

      
        108
        
        -  end

      
        109
        
        -  toggled = line:gsub("^%- ", "+ ", 1)

      
        110
        
        -  if toggled ~= line then vim.fn.setline(lnum, toggled) end

      
        111
        
        -end

      
        
        83
        +      if last_child then vim.cmd(string.format("%d,%ddelete _", first_child, last_child)) end

      
        112
        84
         

      
        113
        
        -local function insert_output(output, after_lnum)

      
        114
        
        -  after_lnum = after_lnum or vim.fn.line "."

      
        115
        
        -  local pad = string.rep(" ", indent(vim.fn.getline(after_lnum)) + 2)

      
        116
        
        -  local lines = vim.split(output, "\n", { trimempty = true })

      
        117
        
        -  for i, line in ipairs(lines) do lines[i] = pad .. line end

      
        118
        
        -  vim.fn.append(after_lnum, lines)

      
        119
        
        -end

      
        
        85
        +      local out = vim.fn.system(cmd)

      
        
        86
        +      if vim.v.shell_error ~= 0 then

      
        
        87
        +        vim.notify("viye: " .. (out:gsub("\n$", "")), vim.log.levels.ERROR)

      
        
        88
        +        return

      
        
        89
        +      end

      
        120
        90
         

      
        121
        
        --- Find the last child line at depth+2 under the current line

      
        122
        
        -local function find_last_child()

      
        123
        
        -  local lnum = vim.fn.line "."

      
        124
        
        -  local depth = indent(vim.fn.getline(lnum))

      
        125
        
        -  local last = lnum

      
        126
        
        -  local i = lnum + 1

      
        127
        
        -  while i <= vim.fn.line "$" do

      
        128
        
        -    local l = vim.fn.getline(i)

      
        129
        
        -    if l == "" then break end

      
        130
        
        -    local d = indent(l)

      
        131
        
        -    if d <= depth then break end

      
        132
        
        -    if d == depth + 2 then

      
        133
        
        -      last = i

      
        
        91
        +      if out ~= "" then

      
        
        92
        +        local pad = string.rep(" ", depth + 2)

      
        
        93
        +        local lines = vim.split(out, "\n", { trimempty = true })

      
        
        94
        +        for j, l in ipairs(lines) do lines[j] = pad .. l end

      
        
        95
        +        vim.fn.append(lnum, lines)

      
        
        96
        +      end

      
        
        97
        +      return

      
        134
        98
             end

      
        135
        
        -    i = i + 1

      
        136
        
        -  end

      
        137
        
        -  return last

      
        138
        
        -end

      
        139
        99
         

      
        140
        
        -local function exec(reenter)

      
        141
        
        -  local line = vim.fn.getline "."

      
        142
        
        -  if is_data(line) then

      
        
        100
        +    -- Command line: toggle expand/collapse

      
        143
        101
             local lnum = vim.fn.line "."

      
        144
        
        -    local depth = indent(line)

      
        145
        
        -    local i = lnum - 1

      
        146
        
        -    while i >= 1 do

      
        
        102
        +    local depth = indent(vim.fn.getline(lnum))

      
        
        103
        +

      
        
        104
        +    local first_child = lnum + 1

      
        
        105
        +    local last_child = nil

      
        
        106
        +    local i = first_child

      
        
        107
        +    while i <= vim.fn.line "$" do

      
        147
        108
               local l = vim.fn.getline(i)

      
        148
        
        -      if not is_data(l) and indent(l) < depth and l:find "%S" then

      
        149
        
        -        vim.fn.cursor(i, 1)

      
        150
        
        -        exec(true)

      
        151
        
        -        return

      
        152
        
        -      end

      
        153
        
        -      i = i - 1

      
        
        109
        +      if l == "" then break end

      
        
        110
        +      local d = indent(l)

      
        
        111
        +      if d <= depth then break end

      
        
        112
        +      if d == depth + 2 then last_child = i end

      
        
        113
        +      i = i + 1

      
        154
        114
             end

      
        155
        
        -    return

      
        156
        
        -  end

      
        157
        115
         

      
        158
        
        -  if not reenter and has_children() then

      
        159
        
        -    collapse()

      
        160
        
        -    toggle_bullet()

      
        161
        
        -    return

      
        162
        
        -  end

      
        
        116
        +    if last_child then

      
        
        117
        +      -- Expanded: collapse

      
        
        118
        +      vim.cmd(string.format("%d,%ddelete _", first_child, last_child))

      
        
        119
        +      vim.fn.setline(lnum, (vim.fn.getline(lnum):gsub("^%- ", "+ ", 1)))

      
        
        120
        +      return

      
        
        121
        +    end

      
        163
        122
         

      
        164
        
        -  local path = build_path()

      
        165
        
        -  if path == "" then return end

      
        
        123
        +    -- Collapsed: run CLI and insert output

      
        
        124
        +    local path = build_path()

      
        
        125
        +    if path == "" then return end

      
        166
        126
         

      
        167
        
        -  local body = reenter and collect_body() or {}

      
        168
        
        -

      
        169
        
        -  local cmd = "viye " .. vim.fn.shellescape(path)

      
        170
        
        -  if #body > 0 then

      
        171
        
        -    cmd = cmd .. " --"

      
        172
        
        -    for _, line in ipairs(body) do

      
        173
        
        -      cmd = cmd .. " " .. vim.fn.shellescape(line)

      
        
        127
        +    local cmd = "viye " .. vim.fn.shellescape(path)

      
        
        128
        +    local out = vim.fn.system(cmd)

      
        
        129
        +    if vim.v.shell_error ~= 0 then

      
        
        130
        +      vim.notify("viye: " .. (out:gsub("\n$", "")), vim.log.levels.ERROR)

      
        
        131
        +      return

      
        174
        132
             end

      
        175
        
        -  end

      
        176
        
        -  vim.notify("viye: " .. cmd, vim.log.levels.INFO)

      
        177
        133
         

      
        178
        
        -  local out = vim.fn.system(cmd)

      
        179
        
        -  if vim.v.shell_error ~= 0 then

      
        180
        
        -    vim.notify("viye: " .. out:gsub("\n$", ""), vim.log.levels.ERROR)

      
        181
        
        -    return

      
        182
        
        -  end

      
        183
        
        -

      
        184
        
        -  if out ~= "" then

      
        185
        
        -    remove_output()

      
        186
        
        -    local target = reenter and find_last_child() or vim.fn.line "."

      
        187
        
        -    insert_output(out, target)

      
        188
        
        -    if not reenter then

      
        189
        
        -      toggle_bullet()

      
        
        134
        +    if out ~= "" then

      
        
        135
        +      local pad = string.rep(" ", depth + 2)

      
        
        136
        +      local lines = vim.split(out, "\n", { trimempty = true })

      
        
        137
        +      for j, l in ipairs(lines) do lines[j] = pad .. l end

      
        
        138
        +      vim.fn.append(lnum, lines)

      
        
        139
        +      vim.fn.setline(lnum, (vim.fn.getline(lnum):gsub("^%+ ", "- ", 1)))

      
        
        140
        +    else

      
        
        141
        +      vim.fn.setline(lnum, (vim.fn.getline(lnum):gsub("^%- ", "+ ", 1)))

      
        190
        142
             end

      
        
        143
        +  end)

      
        
        144
        +  if not ok then

      
        
        145
        +    vim.notify("viye: " .. tostring(err), vim.log.levels.ERROR)

      
        191
        146
           end

      
        192
        147
         end

      
        193
        148
         

      ···
        198
        153
             group = vim.api.nvim_create_augroup("viye_plugin", { clear = true }),

      
        199
        154
             callback = function() vim.keymap.set("n", "<CR>", exec, { buffer = true, silent = true }) end,

      
        200
        155
           })

      
        
        156
        +end

      
        
        157
        +

      
        
        158
        +if false then -- tests

      
        
        159
        +  local ok, fail = 0, 0

      
        
        160
        +  local function check(got, want, msg)

      
        
        161
        +    if got == want then

      
        
        162
        +      ok = ok + 1

      
        
        163
        +    else

      
        
        164
        +      fail = fail + 1

      
        
        165
        +      print("FAIL " .. msg .. ": got " .. vim.inspect(got) .. " want " .. vim.inspect(want))

      
        
        166
        +    end

      
        
        167
        +  end

      
        
        168
        +

      
        
        169
        +  check(indent "", 0, "indent empty")

      
        
        170
        +  check(indent "foo", 0, "indent no indent")

      
        
        171
        +  check(indent "  foo", 2, "indent 2 spaces")

      
        
        172
        +  check(indent "    foo", 4, "indent 4 spaces")

      
        
        173
        +

      
        
        174
        +  check(is_data ": hello", true, "is_data colon")

      
        
        175
        +  check(is_data "  : hello", true, "is_data colon indent")

      
        
        176
        +  check(is_data "| hello", true, "is_data pipe")

      
        
        177
        +  check(is_data "  |", true, "is_data pipe alone")

      
        
        178
        +  check(is_data "+ demo", false, "is_data not +")

      
        
        179
        +  check(is_data "- demo", false, "is_data not -")

      
        
        180
        +  check(is_data "hello", false, "is_data plain")

      
        
        181
        +

      
        
        182
        +  check(strip_bullet "+ demo", "demo", "strip +")

      
        
        183
        +  check(strip_bullet "- demo", "demo", "strip -")

      
        
        184
        +  check(strip_bullet "demo", "demo", "strip none")

      
        
        185
        +  check(strip_bullet "+ hello world", "hello world", "strip + multiword")

      
        
        186
        +

      
        
        187
        +  if fail == 0 then

      
        
        188
        +    print("viye.lua: " .. ok .. "/" .. (ok + fail) .. " tests passed")

      
        
        189
        +  else

      
        
        190
        +    print("viye.lua: " .. fail .. " failures out of " .. (ok + fail) .. " tests")

      
        
        191
        +  end

      
        201
        192
         end

      
        202
        193
         

      
        203
        194
         return M