about summary refs log tree commit diff stats
path: root/modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua
blob: 371f5539bcd10c0a6c3fdbc10d2bc32ef9f7a58f (plain) (blame)
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
-- nixos-config - My current NixOS configuration
--
-- Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-- SPDX-License-Identifier: GPL-3.0-or-later
--
-- This file is part of my nixos-config.
--
-- You should have received a copy of the License along with this program.
-- If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.

local ls = require("luasnip")
local fmt = require("luasnip.extras.fmt").fmt

--- Get the comment string {begin,end} table
---
---@param comment_type integer 1 for `line`-comment and 2 for `block`-comment
---@return table comment_strings {["begin"]=begin_comment_string, ["end"]=end_comment_string}
local get_comment_string = function(comment_type)
  local calculate_comment_string = require("Comment.ft").calculate
  local utils = require("Comment.utils")

  -- use the `Comments.nvim` API to fetch the comment string for the region (eq. '--%s' or '--[[%s]]' for `lua`)
  local cstring =
    calculate_comment_string({ ctype = comment_type; range = utils.get_region(); })

  if cstring == nil then
    -- TODO: Use `vim.bo.commentstring` <2025-05-02>

    -- Use some useful default values.
    return { ["begin"] = "#"; ["end"] = ""; }
  end

  -- as we want only the strings themselves and not strings ready for using `format` we want to split the left and right side
  local left, right = utils.unwrap_cstr(cstring)

  -- create a `{left, right}` table for it
  return { ["begin"] = left; ["end"] = right; }
end

--- Wraps a table of snippet nodes in two comment function nodes.
---
---@param comment_type integer 1 for `line`-comment and 2 for `block`-comment
---@param nodes table The nodes that should be wrapped
---@return table wrapped_nodes The now wrapped `nodes` table.
local wrap_snippet_in_comments = function(comment_type, nodes)
  local output = {}

  table.insert(output, ls.function_node(function()
    return get_comment_string(comment_type)["begin"]
  end))


  for _, v in ipairs(nodes) do
    table.insert(output, v)
  end

  table.insert(output, ls.function_node(function()
    return get_comment_string(comment_type)["end"]
  end))

  return output
end

-- auto_pairs  {{{
local get_visual = function(_, parent)
  if #parent.snippet.env.SELECT_RAW > 0 then
    return ls.snippet_node(nil, ls.insert_node(1, parent.snippet.env.SELECT_RAW))
  else
    return ls.snippet_node(nil, ls.insert_node(1, ""))
  end
end

local function char_count_same(c1, c2)
  local line = vim.api.nvim_get_current_line()
  -- '%'-escape chars to force explicit match (gsub accepts patterns).
  -- second return value is number of substitutions.
  local _, ct1 = string.gsub(line, "%" .. c1, "")
  local _, ct2 = string.gsub(line, "%" .. c2, "")
  return ct1 == ct2
end

local function even_count(c, ...)
  local line = vim.api.nvim_get_current_line()
  local _, ct = string.gsub(line, c, "")
  return ct % 2 == 0
end

-- This makes creation of pair-type snippets easier.
local function pair(pair_begin, pair_end, file_types, condition_function)
  -- FIXME(@Soispha): This only works if file_types == nil, otherwise the snippet does not expand.
  -- It would be nice, if it would support both an empty array (`{}`) and nil <2023-08-27>
  -- file_types = file_types or {};

  return ls.snippet(
    {
      trig = pair_begin;
      wordTrig = false;
      snippetType = "autosnippet";
    },
    {
      ls.text_node({ pair_begin; });
      ls.dynamic_node(1, get_visual);
      ls.text_node({ pair_end; });
    },
    {
      condition = function()
        local filetype_check = true

        if file_types ~= nil then
          filetype_check = file_types[vim.bo.filetype] or false
        end

        return (not condition_function(pair_begin, pair_end)) and filetype_check
      end;
    }
  )
end

local auto_pairs = {
  pair("(", ")", nil, char_count_same);
  pair("{", "}", nil, char_count_same);
  pair("[", "]", nil, char_count_same);
  pair("<", ">", { ["rust"] = true; ["tex"] = true; }, char_count_same);
  pair("'", "'", nil, even_count);
  pair("\"", "\"", nil, even_count);
  pair("`", "`", nil, even_count);
}

ls.add_snippets("all", auto_pairs, { type = "snippets"; key = "auto_pairs"; })
-- }}}

-- todo_comments {{{
local read_git_config = function(config_value)
  local command = string.format("git config \"%s\"", config_value)

  local handle = io.popen(command)
  if handle == nil then
    return error(string.format("Failed to call `%s`.", command))
  end

  local result = handle:read("*a")
  handle:close()

  -- stripped = string.gsub(str, '%s+', '')
  return string.gsub(result, "\n", "")
end

--- Create a @handle from a full name.
---
--- Example:
--- “Benedikt Peetz” -> “@bpeetz”
local handle_from_name = function(name)
  -- from: https://stackoverflow.com/a/7615129
  local split = function(inputstr, sep)
    local t = {}
    for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
      table.insert(t, str)
    end
    return t
  end

  -- Split on spaces
  local parts = split(name, "%s")

  local output_name = ""

  if #parts > 2 then
    -- Only use the first chars.
    --
    -- Example:
    -- “Richard Matthew Stallman” -> “rms”
    for _, val in ipairs(parts) do
      output_name = string.format("%s%s", output_name, val:sub(1, 1))
    end
  elseif #parts == 2 then
    output_name = string.format("%s%s", parts[1]:sub(1, 1), parts[2])
  elseif #parts == 1 then
    output_name = parts[1]
  elseif #parts == 0 then
    output_name = "<NoName>"
  end

  return string.format("@%s", output_name:lower())
end

--- Generate a comment snippet
---
---@param trig string The trigger
---@param name string name for the comment (ex.: {FIX, ISSUE, FIXIT, BUG})
---@param comment_type integer The comment type.
---@param mark_function function: The function used to get the marks
local todo_snippet = function(trig, name, comment_type, mark_function)
  assert(trig, "context doesn't include a `trig` key which is mandatory")
  assert(comment_type == 1 or comment_type == 2)

  local context = {}
  context.name = name .. " comment"
  context.trig = trig

  local date_node, signature_node = mark_function()

  local nodes = fmt("{} {}{}: {} {} {}", wrap_snippet_in_comments(comment_type, {
    ls.text_node(name);
    signature_node;
    ls.insert_node(1, "content");
    date_node;
  }))

  return ls.snippet(context, nodes, { ctype = comment_type; })
end

---@param trigger string: The luasnip trigger
---@param comment_type integer: The luasnip comment type
---@param name string: All aliases for a name
---@return table: All possible snippets build from the marks
local process_marks = function(trigger, name, comment_type)
  local username = function()
    return handle_from_name(read_git_config("user.name"))
  end

  local marks = {
    signature = function()
      return ls.text_node("(" .. username() .. ")"), ls.text_node("")
    end;

    date_signature = function()
      return ls.text_node("<" .. os.date("%Y-%m-%d") .. ">"), ls.text_node("(" .. username() .. ")")
    end;

    date = function()
      return ls.text_node("<" .. os.date("%Y-%m-%d") .. ">"), ls.text_node("")
    end;

    empty = function()
      return ls.text_node(""), ls.text_node("")
    end;
  }

  local output = {}
  for mark_name, mark_function in pairs(marks) do
    local trig = trigger .. "-" .. mark_name

    output[#output + 1] = todo_snippet(trig, name, comment_type, mark_function)
  end

  return output
end

local todo_snippet_specs = {
  { { trig = "todo"; };  { "TODO"; };                                     { ctype = 1; }; };
  { { trig = "fix"; };   { "FIXME"; "ISSUE"; };                           { ctype = 1; }; };
  { { trig = "hack"; };  { "HACK"; };                                     { ctype = 1; }; };
  { { trig = "warn"; };  { "WARNING"; };                                  { ctype = 1; }; };
  { { trig = "perf"; };  { "PERFORMANCE"; "OPTIMIZE"; };                  { ctype = 1; }; };
  { { trig = "note"; };  { "NOTE"; "INFO"; };                             { ctype = 1; }; };

  -- NOTE: Block commented todo-comments
  { { trig = "todob"; }; { "TODO"; };                                     { ctype = 2; }; };
  { { trig = "fixb"; };  { "FIXME"; "ISSUE"; };                           { ctype = 2; }; };
  { { trig = "hackb"; }; { "HACK"; };                                     { ctype = 2; }; };
  { { trig = "warnb"; }; { "WARNING"; };                                  { ctype = 2; }; };
  { { trig = "perfb"; }; { "PERF"; "PERFORMANCE"; "OPTIM"; "OPTIMIZE"; }; { ctype = 2; }; };
  { { trig = "noteb"; }; { "NOTE"; "INFO"; };                             { ctype = 2; }; };
}

local todo_comment_snippets = {}
for _, v in ipairs(todo_snippet_specs) do
  local snippets = process_marks(v[1].trig, v[2][1], v[3].ctype)
  for _, value in pairs(snippets) do
    table.insert(todo_comment_snippets, value)
  end
end

ls.add_snippets("all", todo_comment_snippets, { type = "snippets"; key = "todo_comments"; })
-- }}}

-- spdx snippets {{{
local generate_spdx_snippet = function(comment_type, spdx_license_expr, trigger)
  assert(trigger, "context doesn't include a `trig` key which is mandatory")
  assert(comment_type == 1 or comment_type == 2)

  local context = {}
  context.name = trigger .. " spdx snippet expr"
  context.trig = trigger


  local nodes = {
    fmt("{} SPDX-SnippetBegin {}", wrap_snippet_in_comments(comment_type, {}));

    fmt("{} SPDX-SnippetCopyrightText: {} {} <{}> {}",
        wrap_snippet_in_comments(comment_type, {
          ls.insert_node(1, "year");
          ls.insert_node(2, "author");
          ls.insert_node(3, "email");
        })
    );

    fmt("{} SPDX-License-Identifier: {} {}", wrap_snippet_in_comments(comment_type, {
      ls.text_node(spdx_license_expr);
    }));

    { ls.insert_node(4, "content"); };

    fmt("{} SPDX-SnippetEnd {}", wrap_snippet_in_comments(comment_type, {}));

    { ls.insert_node(0); };
  }

  local newline_nodes = {}
  for _, sub_nodes in ipairs(nodes) do
    for _, node in ipairs(sub_nodes) do
      table.insert(newline_nodes, node)
    end

    -- luasnip requires newlines to be encoded like this:
    table.insert(newline_nodes, ls.text_node({ ""; ""; }))
  end

  return ls.snippet(context, newline_nodes, { ctype = comment_type; })
end

local spdx = {
  { trigger = "spdx-AGPL3+"; license = "AGPL-3.0-or-later"; };
  { trigger = "spdx-GPL3+";  license = "GPL-3.0-or-later"; };
  { trigger = "spdx-MIT";    license = "MIT"; };
}

local spdx_snippets = {}
for _, value in ipairs(spdx) do
  local snippet = generate_spdx_snippet(1, value.license, value.trigger)
  table.insert(spdx_snippets, snippet)

  snippet = generate_spdx_snippet(2, value.license, value.trigger .. "-block")
  table.insert(spdx_snippets, snippet)
end

ls.add_snippets("all", spdx_snippets, { type = "snippets"; key = "spdx_snippets"; })
-- }}}