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
|
---@mod comment.ft Language/Filetype detection
---@brief [[
---This module is the core of filetype and commentstring detection and uses the
---|lua-treesitter| APIs to accurately detect filetype and gives the corresponding
---commentstring, stored inside the plugin, for the filetype/langauge.
---
---Compound (dot-separated) filetypes are also supported i.e. 'ansible.yaml',
---'ios.swift' etc. The commentstring resolution will be done from left to right.
---For example, If the filetype is 'ansible.yaml' then 'ansible' commenstring will
---be used if found otherwise it'll fallback to 'yaml'. Read `:h 'filetype'`
---@brief ]]
local A = vim.api
---Common commentstring shared b/w multiple languages
local M = {
cxx_l = '//%s',
cxx_b = '/*%s*/',
dbl_hash = '##%s',
dash = '--%s',
dash_bracket = '--[[%s]]',
handlebars = '{{!--%s--}}',
hash = '#%s',
hash_bracket = '#[[%s]]',
haskell_b = '{-%s-}',
fsharp_b = '(*%s*)',
html = '<!--%s-->',
latex = '%%s',
lisp_l = ';;%s',
lisp_b = '#|%s|#',
twig = '{#%s#}',
vim = '"%s',
}
---Lang table that contains commentstring (linewise/blockwise) for multiple filetypes
---Structure = { filetype = { linewise, blockwise } }
---@type table<string,string[]>
local L = setmetatable({
arduino = { M.cxx_l, M.cxx_b },
applescript = { M.hash },
autohotkey = { ";%s", M.cxx_b },
bash = { M.hash },
bib = { M.latex },
c = { M.cxx_l, M.cxx_b },
cabal = { M.dash },
cmake = { M.hash, M.hash_bracket },
conf = { M.hash },
conkyrc = { M.dash, M.dash_bracket },
cpp = { M.cxx_l, M.cxx_b },
cs = { M.cxx_l, M.cxx_b },
css = { M.cxx_b, M.cxx_b },
cuda = { M.cxx_l, M.cxx_b },
dart = { M.cxx_l, M.cxx_b },
dhall = { M.dash, M.haskell_b },
dosbatch = { 'REM%s' },
dot = { M.cxx_l, M.cxx_b },
editorconfig = { M.hash },
eelixir = { M.html, M.html },
elixir = { M.hash },
elm = { M.dash, M.haskell_b },
elvish = { M.hash },
faust = { M.cxx_l, M.cxx_b },
fennel = { ';%s' },
fish = { M.hash },
fsharp = { M.cxx_l, M.fsharp_b },
gdb = { M.hash },
gdscript = { M.hash },
gitignore = { M.hash },
gleam = { M.cxx_l },
glsl = { M.cxx_l, M.cxx_b },
gnuplot = { M.hash, M.hash_bracket },
go = { M.cxx_l, M.cxx_b },
graphql = { M.hash },
groovy = { M.cxx_l, M.cxx_b },
handlebars = { M.handlebars, M.handlebars },
haskell = { M.dash, M.haskell_b },
heex = { M.html, M.html },
html = { M.html, M.html },
htmldjango = { M.html, M.html },
idris = { M.dash, M.haskell_b },
ini = { M.hash },
java = { M.cxx_l, M.cxx_b },
javascript = { M.cxx_l, M.cxx_b },
javascriptreact = { M.cxx_l, M.cxx_b },
jsonc = { M.cxx_l },
jsonnet = { M.cxx_l, M.cxx_b },
julia = { M.hash, '#=%s=#' },
kotlin = { M.cxx_l, M.cxx_b },
lidris = { M.dash, M.haskell_b },
lilypond = { M.latex, '%{%s%}' },
lisp = { M.lisp_l, M.lisp_b },
lua = { M.dash, M.dash_bracket },
luau = { M.dash, M.dash_bracket },
markdown = { M.html, M.html },
make = { M.hash },
mbsyncrc = { M.dbl_hash },
meson = { M.hash },
nim = { M.hash, '#[%s]#' },
nix = { M.hash, M.cxx_b },
nu = { M.hash },
ocaml = { M.fsharp_b, M.fsharp_b },
plantuml = { "'%s", "/'%s'/" },
purescript = { M.dash, M.haskell_b },
python = { M.hash }, -- Python doesn't have block comments
php = { M.cxx_l, M.cxx_b },
prisma = { M.cxx_l },
quarto = { M.html, M.html },
r = { M.hash }, -- R doesn't have block comments
readline = { M.hash },
rego = { M.hash },
remind = { M.hash },
ruby = { M.hash },
rust = { M.cxx_l, M.cxx_b },
scala = { M.cxx_l, M.cxx_b },
scheme = { M.lisp_l, M.lisp_b },
sh = { M.hash },
solidity = { M.cxx_l, M.cxx_b },
supercollider = { M.cxx_l, M.cxx_b },
sql = { M.dash, M.cxx_b },
stata = { M.cxx_l, M.cxx_b },
svelte = { M.html, M.html },
swift = { M.cxx_l, M.cxx_b },
sxhkdrc = { M.hash },
teal = { M.dash, M.dash_bracket },
terraform = { M.hash, M.cxx_b },
tex = { M.latex },
template = { M.dbl_hash },
tmux = { M.hash },
toml = { M.hash },
twig = { M.twig, M.twig },
typescript = { M.cxx_l, M.cxx_b },
typescriptreact = { M.cxx_l, M.cxx_b },
v = { M.cxx_l, M.cxx_b },
vim = { M.vim },
vifm = { M.vim },
vue = { M.html, M.html },
xml = { M.html, M.html },
xdefaults = { '!%s' },
yaml = { M.hash },
yuck = { M.lisp_l },
zig = { M.cxx_l }, -- Zig doesn't have block comments
}, {
-- Support for compound filetype i.e. 'ios.swift', 'ansible.yaml' etc.
__index = function(this, k)
local base, fallback = string.match(k, '^(.-)%.(.*)')
if not (base or fallback) then
return nil
end
return this[base] or this[fallback]
end,
})
---Maps a filteype to a parsername for filetypes
---that don't have their own parser (yet).
---From: <https://github.com/nvim-treesitter/nvim-treesitter/blob/cda8b291ef6fc4e04036e2ea6cf0de8aa84c2656/lua/nvim-treesitter/parsers.lua#L4-L23>.
local filetype_to_parsername = {
quarto = "markdown",
}
local ft = {}
---Sets a commentstring(s) for a filetype/language
---@param lang string Filetype/Language of the buffer
---@param val string|string[]
---@return table self Returns itself
---@usage [[
---local ft = require('Comment.ft')
---
-----1. Using method signature
----- Set only line comment or both
----- You can also chain the set calls
---ft.set('yaml', '#%s').set('javascript', {'//%s', '/*%s*/'})
---
----- 2. Metatable magic
---ft.javascript = {'//%s', '/*%s*/'}
---ft.yaml = '#%s'
---
----- 3. Multiple filetypes
---ft({'go', 'rust'}, {'//%s', '/*%s*/'})
---ft({'toml', 'graphql'}, '#%s')
---@usage ]]
function ft.set(lang, val)
L[lang] = type(val) == 'string' and { val } or val --[[ @as string[] ]]
return ft
end
---Get line/block/both commentstring(s) for a given filetype
---@param lang string Filetype/Language of the buffer
---@param ctype? integer See |comment.utils.ctype|. If given `nil`, it'll
---return a copy of { line, block } commentstring.
---@return nil|string|string[] #Returns stored commentstring, if {lang} is not
---recognized then returns native |commentstring|
---@usage [[
---local ft = require('Comment.ft')
---local U = require('Comment.utils')
---
----- 1. Primary filetype
---ft.get('rust', U.ctype.linewise) -- `//%s`
---ft.get('rust') -- `{ '//%s', '/*%s*/' }`
---
----- 2. Compound filetype
----- NOTE: This will return `yaml` commenstring(s),
----- as `ansible` commentstring is not found.
---ft.get('ansible.yaml', U.ctype.linewise) -- `#%s`
---ft.get('ansible.yaml') -- { '#%s' }
---@usage ]]
function ft.get(lang, ctype)
local tuple = L[lang]
if not tuple then
return vim.bo.commentstring
end
if not ctype then
return vim.deepcopy(tuple)
end
return tuple[ctype]
end
---Get a language tree for a given range by walking the parse tree recursively.
---This uses 'lua-treesitter' API under the hood. This can be used to calculate
---language of a particular region which embedded multiple filetypes like html,
---vue, markdown etc.
---
---NOTE: This ignores `tree-sitter-comment` parser, if installed.
---@param tree userdata Parse tree to be walked
---@param range integer[] Range to check
---{start_row, start_col, end_row, end_col}
---@return userdata #Returns a |treesitter-languagetree|
---@see treesitter-languagetree
---@see lua-treesitter-core
---@usage [[
---local ok, parser = pcall(vim.treesitter.get_parser, 0)
---assert(ok, "No parser found!")
---local tree = require('Comment.ft').contains(parser, {0, 0, -1, 0})
---print('Lang:', tree:lang())
---@usage ]]
function ft.contains(tree, range)
for lang, child in pairs(tree:children()) do
if lang ~= 'comment' and child:contains(range) then
return ft.contains(child, range)
end
end
return tree
end
---Calculate commentstring with the power of treesitter
---@param ctx CommentCtx
---@return nil|string #Commentstring
---@see comment.utils.CommentCtx
function ft.calculate(ctx)
local buf = A.nvim_get_current_buf()
local filetype = vim.bo.filetype
local parsername = filetype_to_parsername[filetype] or filetype
local ok, parser = pcall(vim.treesitter.get_parser, buf, parsername)
if not ok then
return ft.get(vim.bo.filetype, ctx.ctype) --[[ @as string ]]
end
local lang = ft.contains(parser, {
ctx.range.srow - 1,
ctx.range.scol,
ctx.range.erow - 1,
ctx.range.ecol,
}):lang()
return ft.get(lang, ctx.ctype) or ft.get(vim.bo.filetype, ctx.ctype) --[[ @as string ]]
end
---@export ft
return setmetatable(ft, {
__newindex = function(this, k, v)
this.set(k, v)
end,
__call = function(this, langs, spec)
for _, lang in ipairs(langs) do
this.set(lang, spec)
end
return this
end,
})
|