File: text.lua

package info (click to toggle)
neovim-which-key 3.17.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 500 kB
  • sloc: sh: 21; makefile: 2
file content (164 lines) | stat: -rw-r--r-- 3,664 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
local Util = require("which-key.util")

---@class wk.Segment
---@field str string Text
---@field hl? string Extmark hl group
---@field line? number line number in a multiline segment
---@field width? number

---@class wk.Text.opts
---@field multiline? boolean
---@field indent? boolean

---@class wk.Text
---@field _lines wk.Segment[][]
---@field _col number
---@field _indents string[]
---@field _opts wk.Text.opts
local M = {}
M.__index = M

M.ns = vim.api.nvim_create_namespace("wk.text")

---@param opts? wk.Text.opts
function M.new(opts)
  local self = setmetatable({}, M)
  self._lines = {}
  self._col = 0
  self._opts = opts or {}
  self._indents = {}
  for i = 0, 100, 1 do
    self._indents[i] = (" "):rep(i)
  end
  return self
end

function M:height()
  return #self._lines
end

---@return number
function M:width()
  local width = 0
  for _, line in ipairs(self._lines) do
    local w = 0
    for _, segment in ipairs(line) do
      w = w + vim.fn.strdisplaywidth(segment.str)
    end
    width = math.max(width, w)
  end
  return width
end

---@param text string|wk.Segment[]
---@param opts? string|{hl?:string, line?:number}
function M:append(text, opts)
  opts = opts or {}
  if #self._lines == 0 then
    self:nl()
  end

  if type(text) == "table" then
    for _, s in ipairs(text) do
      s.width = s.width or #s.str
      self._col = self._col + s.width
      table.insert(self._lines[#self._lines], s)
    end
    return self
  end

  opts = type(opts) == "string" and { hl = opts } or opts

  for l, line in ipairs(vim.split(text, "\n", { plain = true })) do
    local width = #line
    self._col = self._col + width
    table.insert(self._lines[#self._lines], {
      str = line,
      width = width,
      hl = opts.hl,
      line = opts.line or l,
    })
  end
  return self
end

function M:nl()
  table.insert(self._lines, {})
  self._col = 0
  return self
end

---@param opts? {sep?:string}
function M:statusline(opts)
  local sep = opts and opts.sep or " "
  local lines = {} ---@type string[]
  for _, line in ipairs(self._lines) do
    local parts = {}
    for _, segment in ipairs(line) do
      local str = segment.str:gsub("%%", "%%%%")
      if segment.hl then
        str = ("%%#%s#%s%%*"):format(segment.hl, str)
      end
      parts[#parts + 1] = str
    end
    table.insert(lines, table.concat(parts, ""))
  end
  return table.concat(lines, sep)
end

function M:render(buf)
  local lines = {}

  for _, line in ipairs(self._lines) do
    local parts = {} ---@type string[]
    for _, segment in ipairs(line) do
      parts[#parts + 1] = segment.str
    end
    table.insert(lines, table.concat(parts, ""))
  end

  vim.bo[buf].modifiable = true
  vim.api.nvim_buf_clear_namespace(buf, M.ns, 0, -1)
  vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)

  for l, line in ipairs(self._lines) do
    local col = 0
    local row = l - 1

    for _, segment in ipairs(line) do
      local width = segment.width
      if segment.hl then
        Util.set_extmark(buf, M.ns, row, col, {
          hl_group = segment.hl,
          end_col = col + width,
        })
      end
      col = col + width
    end
  end
  vim.bo[buf].modifiable = false
end

function M:trim()
  while #self._lines > 0 and #self._lines[#self._lines] == 0 do
    table.remove(self._lines)
  end
end

function M:row()
  return #self._lines == 0 and 1 or #self._lines
end

---@param opts? {display:boolean}
function M:col(opts)
  if opts and opts.display then
    local ret = 0
    for _, segment in ipairs(self._lines[#self._lines] or {}) do
      ret = ret + vim.fn.strdisplaywidth(segment.str)
    end
    return ret
  end
  return self._col
end

return M