all repos

viye @ 0da3343

my shot at reimplementing xiki

viye/lua/viye.lua (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
files: support ./ paths; add gobin; add json, 2 months ago
1
local M = {}
2
3
local function indent(text) return #text:match "^%s*" end
4
local function is_data(text) return text:find "^%s*[|:] " ~= nil end
5
local function strip_bullet(text) return (text:gsub("^[+-] ", "", 1)) end
6
7
local function build_path()
8
  local lnum = vim.fn.line "."
9
  local line = vim.fn.getline(lnum)
10
  local depth = indent(line)
11
  local parts = { strip_bullet(vim.trim(line)) }
12
13
  local i = lnum - 1
14
  while i >= 1 do
15
    local l = vim.fn.getline(i)
16
    local d = indent(l)
17
    if d == 0 and l:find "%S" and depth > 0 then
18
      table.insert(parts, 1, strip_bullet(vim.trim(l)))
19
      break
20
    end
21
    if d < depth and l:find "%S" then
22
      table.insert(parts, 1, strip_bullet(vim.trim(l)))
23
      depth = d
24
    end
25
    i = i - 1
26
  end
27
28
  for idx = 1, #parts - 1 do
29
    parts[idx] = parts[idx]:gsub("/+$", "")
30
  end
31
32
  return table.concat(parts, "/")
33
end
34
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
41
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
61
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
87
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
100
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
112
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
120
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
134
    end
135
    i = i + 1
136
  end
137
  return last
138
end
139
140
local function exec(reenter)
141
  local line = vim.fn.getline "."
142
  if is_data(line) then
143
    local lnum = vim.fn.line "."
144
    local depth = indent(line)
145
    local i = lnum - 1
146
    while i >= 1 do
147
      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
154
    end
155
    return
156
  end
157
158
  if not reenter and has_children() then
159
    collapse()
160
    toggle_bullet()
161
    return
162
  end
163
164
  local path = build_path()
165
  if path == "" then return end
166
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)
174
    end
175
  end
176
  vim.notify("viye: " .. cmd, vim.log.levels.INFO)
177
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()
190
    end
191
  end
192
end
193
194
function M.setup()
195
  vim.filetype.add { extension = { viye = "viye" } }
196
  vim.api.nvim_create_autocmd("FileType", {
197
    pattern = "viye",
198
    group = vim.api.nvim_create_augroup("viye_plugin", { clear = true }),
199
    callback = function() vim.keymap.set("n", "<CR>", exec, { buffer = true, silent = true }) end,
200
  })
201
end
202
203
return M