all repos

viye @ aac48460ab9c49d76dc3bf15746ee66308cd50a7

my shot at reimplementing xiki

viye/lua/viye.lua (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
move away from xiki '/' separated command path to each node is it's own arg model, 2 months ago
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