treesitter.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. local api = vim.api
  2. ---@type table<integer,vim.treesitter.LanguageTree>
  3. local parsers = setmetatable({}, { __mode = 'v' })
  4. local M = vim._defer_require('vim.treesitter', {
  5. _fold = ..., --- @module 'vim.treesitter._fold'
  6. _query_linter = ..., --- @module 'vim.treesitter._query_linter'
  7. _range = ..., --- @module 'vim.treesitter._range'
  8. dev = ..., --- @module 'vim.treesitter.dev'
  9. highlighter = ..., --- @module 'vim.treesitter.highlighter'
  10. language = ..., --- @module 'vim.treesitter.language'
  11. languagetree = ..., --- @module 'vim.treesitter.languagetree'
  12. query = ..., --- @module 'vim.treesitter.query'
  13. })
  14. local LanguageTree = M.languagetree
  15. --- @nodoc
  16. M.language_version = vim._ts_get_language_version()
  17. --- @nodoc
  18. M.minimum_language_version = vim._ts_get_minimum_language_version()
  19. --- Creates a new parser
  20. ---
  21. --- It is not recommended to use this; use |get_parser()| instead.
  22. ---
  23. ---@param bufnr integer Buffer the parser will be tied to (0 for current buffer)
  24. ---@param lang string Language of the parser
  25. ---@param opts (table|nil) Options to pass to the created language tree
  26. ---
  27. ---@return vim.treesitter.LanguageTree object to use for parsing
  28. function M._create_parser(bufnr, lang, opts)
  29. if bufnr == 0 then
  30. bufnr = vim.api.nvim_get_current_buf()
  31. end
  32. vim.fn.bufload(bufnr)
  33. local self = LanguageTree.new(bufnr, lang, opts)
  34. local function bytes_cb(_, ...)
  35. self:_on_bytes(...)
  36. end
  37. local function detach_cb(_, ...)
  38. if parsers[bufnr] == self then
  39. parsers[bufnr] = nil
  40. end
  41. self:_on_detach(...)
  42. end
  43. local function reload_cb(_)
  44. self:_on_reload()
  45. end
  46. local source = self:source() --[[@as integer]]
  47. api.nvim_buf_attach(
  48. source,
  49. false,
  50. { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
  51. )
  52. self:parse()
  53. return self
  54. end
  55. local function valid_lang(lang)
  56. return lang and lang ~= ''
  57. end
  58. --- Returns the parser for a specific buffer and attaches it to the buffer
  59. ---
  60. --- If needed, this will create the parser.
  61. ---
  62. --- If no parser can be created, an error is thrown. Set `opts.error = false` to suppress this and
  63. --- return nil (and an error message) instead. WARNING: This behavior will become default in Nvim
  64. --- 0.12 and the option will be removed.
  65. ---
  66. ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
  67. ---@param lang (string|nil) Language of this parser (default: from buffer filetype)
  68. ---@param opts (table|nil) Options to pass to the created language tree
  69. ---
  70. ---@return vim.treesitter.LanguageTree? object to use for parsing
  71. ---@return string? error message, if applicable
  72. function M.get_parser(bufnr, lang, opts)
  73. opts = opts or {}
  74. local should_error = opts.error == nil or opts.error
  75. if bufnr == nil or bufnr == 0 then
  76. bufnr = api.nvim_get_current_buf()
  77. end
  78. if not valid_lang(lang) then
  79. lang = M.language.get_lang(vim.bo[bufnr].filetype)
  80. end
  81. if not valid_lang(lang) then
  82. if not parsers[bufnr] then
  83. local err_msg =
  84. string.format('Parser not found for buffer %s: language could not be determined', bufnr)
  85. if should_error then
  86. error(err_msg)
  87. end
  88. return nil, err_msg
  89. end
  90. elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
  91. local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts)
  92. if not parser then
  93. local err_msg =
  94. string.format('Parser could not be created for buffer %s and language "%s"', bufnr, lang)
  95. if should_error then
  96. error(err_msg)
  97. end
  98. return nil, err_msg
  99. end
  100. parsers[bufnr] = parser
  101. end
  102. parsers[bufnr]:register_cbs(opts.buf_attach_cbs)
  103. return parsers[bufnr]
  104. end
  105. --- Returns a string parser
  106. ---
  107. ---@param str string Text to parse
  108. ---@param lang string Language of this string
  109. ---@param opts (table|nil) Options to pass to the created language tree
  110. ---
  111. ---@return vim.treesitter.LanguageTree object to use for parsing
  112. function M.get_string_parser(str, lang, opts)
  113. vim.validate('str', str, 'string')
  114. vim.validate('lang', lang, 'string')
  115. return LanguageTree.new(str, lang, opts)
  116. end
  117. --- Determines whether a node is the ancestor of another
  118. ---
  119. ---@param dest TSNode Possible ancestor
  120. ---@param source TSNode Possible descendant
  121. ---
  122. ---@return boolean True if {dest} is an ancestor of {source}
  123. function M.is_ancestor(dest, source)
  124. if not (dest and source) then
  125. return false
  126. end
  127. return dest:child_with_descendant(source) ~= nil
  128. end
  129. --- Returns the node's range or an unpacked range table
  130. ---
  131. ---@param node_or_range (TSNode | table) Node or table of positions
  132. ---
  133. ---@return integer start_row
  134. ---@return integer start_col
  135. ---@return integer end_row
  136. ---@return integer end_col
  137. function M.get_node_range(node_or_range)
  138. if type(node_or_range) == 'table' then
  139. return unpack(node_or_range)
  140. else
  141. return node_or_range:range(false)
  142. end
  143. end
  144. ---Get the range of a |TSNode|. Can also supply {source} and {metadata}
  145. ---to get the range with directives applied.
  146. ---@param node TSNode
  147. ---@param source integer|string|nil Buffer or string from which the {node} is extracted
  148. ---@param metadata vim.treesitter.query.TSMetadata|nil
  149. ---@return Range6
  150. function M.get_range(node, source, metadata)
  151. if metadata and metadata.range then
  152. assert(source)
  153. return M._range.add_bytes(source, metadata.range)
  154. end
  155. return { node:range(true) }
  156. end
  157. ---@param buf integer
  158. ---@param range Range
  159. ---@returns string
  160. local function buf_range_get_text(buf, range)
  161. local start_row, start_col, end_row, end_col = M._range.unpack4(range)
  162. if end_col == 0 then
  163. if start_row == end_row then
  164. start_col = -1
  165. start_row = start_row - 1
  166. end
  167. end_col = -1
  168. end_row = end_row - 1
  169. end
  170. local lines = api.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {})
  171. return table.concat(lines, '\n')
  172. end
  173. --- Gets the text corresponding to a given node
  174. ---
  175. ---@param node TSNode
  176. ---@param source (integer|string) Buffer or string from which the {node} is extracted
  177. ---@param opts (table|nil) Optional parameters.
  178. --- - metadata (table) Metadata of a specific capture. This would be
  179. --- set to `metadata[capture_id]` when using |vim.treesitter.query.add_directive()|.
  180. ---@return string
  181. function M.get_node_text(node, source, opts)
  182. opts = opts or {}
  183. local metadata = opts.metadata or {}
  184. if metadata.text then
  185. return metadata.text
  186. elseif type(source) == 'number' then
  187. local range = M.get_range(node, source, metadata)
  188. return buf_range_get_text(source, range)
  189. end
  190. ---@cast source string
  191. return source:sub(select(3, node:start()) + 1, select(3, node:end_()))
  192. end
  193. --- Determines whether (line, col) position is in node range
  194. ---
  195. ---@param node TSNode defining the range
  196. ---@param line integer Line (0-based)
  197. ---@param col integer Column (0-based)
  198. ---
  199. ---@return boolean True if the position is in node range
  200. function M.is_in_node_range(node, line, col)
  201. return M.node_contains(node, { line, col, line, col + 1 })
  202. end
  203. --- Determines if a node contains a range
  204. ---
  205. ---@param node TSNode
  206. ---@param range table
  207. ---
  208. ---@return boolean True if the {node} contains the {range}
  209. function M.node_contains(node, range)
  210. -- allow a table so nodes can be mocked
  211. vim.validate('node', node, { 'userdata', 'table' })
  212. vim.validate('range', range, M._range.validate, 'integer list with 4 or 6 elements')
  213. return M._range.contains({ node:range() }, range)
  214. end
  215. --- Returns a list of highlight captures at the given position
  216. ---
  217. --- Each capture is represented by a table containing the capture name as a string as
  218. --- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined).
  219. ---
  220. ---@param bufnr integer Buffer number (0 for current buffer)
  221. ---@param row integer Position row
  222. ---@param col integer Position column
  223. ---
  224. ---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[]
  225. function M.get_captures_at_pos(bufnr, row, col)
  226. if bufnr == 0 then
  227. bufnr = api.nvim_get_current_buf()
  228. end
  229. local buf_highlighter = M.highlighter.active[bufnr]
  230. if not buf_highlighter then
  231. return {}
  232. end
  233. local matches = {}
  234. buf_highlighter.tree:for_each_tree(function(tstree, tree)
  235. if not tstree then
  236. return
  237. end
  238. local root = tstree:root()
  239. local root_start_row, _, root_end_row, _ = root:range()
  240. -- Only worry about trees within the line range
  241. if root_start_row > row or root_end_row < row then
  242. return
  243. end
  244. local q = buf_highlighter:get_query(tree:lang())
  245. -- Some injected languages may not have highlight queries.
  246. if not q:query() then
  247. return
  248. end
  249. local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1)
  250. for capture, node, metadata in iter do
  251. if M.is_in_node_range(node, row, col) then
  252. ---@diagnostic disable-next-line: invisible
  253. local c = q._query.captures[capture] -- name of the capture in the query
  254. if c ~= nil then
  255. table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() })
  256. end
  257. end
  258. end
  259. end)
  260. return matches
  261. end
  262. --- Returns a list of highlight capture names under the cursor
  263. ---
  264. ---@param winnr (integer|nil) Window handle or 0 for current window (default)
  265. ---
  266. ---@return string[] List of capture names
  267. function M.get_captures_at_cursor(winnr)
  268. winnr = winnr or 0
  269. local bufnr = api.nvim_win_get_buf(winnr)
  270. local cursor = api.nvim_win_get_cursor(winnr)
  271. local data = M.get_captures_at_pos(bufnr, cursor[1] - 1, cursor[2])
  272. local captures = {}
  273. for _, capture in ipairs(data) do
  274. table.insert(captures, capture.capture)
  275. end
  276. return captures
  277. end
  278. --- Optional keyword arguments:
  279. --- @class vim.treesitter.get_node.Opts : vim.treesitter.LanguageTree.tree_for_range.Opts
  280. --- @inlinedoc
  281. ---
  282. --- Buffer number (nil or 0 for current buffer)
  283. --- @field bufnr integer?
  284. ---
  285. --- 0-indexed (row, col) tuple. Defaults to cursor position in the
  286. --- current window. Required if {bufnr} is not the current buffer
  287. --- @field pos [integer, integer]?
  288. ---
  289. --- Parser language. (default: from buffer filetype)
  290. --- @field lang string?
  291. ---
  292. --- Ignore injected languages (default true)
  293. --- @field ignore_injections boolean?
  294. ---
  295. --- Include anonymous nodes (default false)
  296. --- @field include_anonymous boolean?
  297. --- Returns the smallest named node at the given position
  298. ---
  299. --- NOTE: Calling this on an unparsed tree can yield an invalid node.
  300. --- If the tree is not known to be parsed by, e.g., an active highlighter,
  301. --- parse the tree first via
  302. ---
  303. --- ```lua
  304. --- vim.treesitter.get_parser(bufnr):parse(range)
  305. --- ```
  306. ---
  307. ---@param opts vim.treesitter.get_node.Opts?
  308. ---
  309. ---@return TSNode | nil Node at the given position
  310. function M.get_node(opts)
  311. opts = opts or {}
  312. local bufnr = opts.bufnr
  313. if not bufnr or bufnr == 0 then
  314. bufnr = api.nvim_get_current_buf()
  315. end
  316. local row, col --- @type integer, integer
  317. if opts.pos then
  318. assert(#opts.pos == 2, 'Position must be a (row, col) tuple')
  319. row, col = opts.pos[1], opts.pos[2]
  320. else
  321. assert(
  322. bufnr == api.nvim_get_current_buf(),
  323. 'Position must be explicitly provided when not using the current buffer'
  324. )
  325. local pos = api.nvim_win_get_cursor(0)
  326. -- Subtract one to account for 1-based row indexing in nvim_win_get_cursor
  327. row, col = pos[1] - 1, pos[2]
  328. end
  329. assert(row >= 0 and col >= 0, 'Invalid position: row and col must be non-negative')
  330. local ts_range = { row, col, row, col }
  331. local root_lang_tree = M.get_parser(bufnr, opts.lang, { error = false })
  332. if not root_lang_tree then
  333. return
  334. end
  335. if opts.include_anonymous then
  336. return root_lang_tree:node_for_range(ts_range, opts)
  337. end
  338. return root_lang_tree:named_node_for_range(ts_range, opts)
  339. end
  340. --- Starts treesitter highlighting for a buffer
  341. ---
  342. --- Can be used in an ftplugin or FileType autocommand.
  343. ---
  344. --- Note: By default, disables regex syntax highlighting, which may be required for some plugins.
  345. --- In this case, add `vim.bo.syntax = 'on'` after the call to `start`.
  346. ---
  347. --- Example:
  348. ---
  349. --- ```lua
  350. --- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
  351. --- callback = function(args)
  352. --- vim.treesitter.start(args.buf, 'latex')
  353. --- vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed
  354. --- end
  355. --- })
  356. --- ```
  357. ---
  358. ---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer)
  359. ---@param lang (string|nil) Language of the parser (default: from buffer filetype)
  360. function M.start(bufnr, lang)
  361. bufnr = bufnr or api.nvim_get_current_buf()
  362. local parser = assert(M.get_parser(bufnr, lang, { error = false }))
  363. M.highlighter.new(parser)
  364. end
  365. --- Stops treesitter highlighting for a buffer
  366. ---
  367. ---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer)
  368. function M.stop(bufnr)
  369. bufnr = (bufnr and bufnr ~= 0) and bufnr or api.nvim_get_current_buf()
  370. if M.highlighter.active[bufnr] then
  371. M.highlighter.active[bufnr]:destroy()
  372. end
  373. end
  374. --- Open a window that displays a textual representation of the nodes in the language tree.
  375. ---
  376. --- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the
  377. --- display of the source language of each node, "o" to toggle the query editor, and press
  378. --- [<Enter>] to jump to the node under the cursor in the source buffer. Folding also works
  379. --- (try |zo|, |zc|, etc.).
  380. ---
  381. --- Can also be shown with `:InspectTree`. [:InspectTree]()
  382. ---
  383. ---@since 11
  384. ---@param opts table|nil Optional options table with the following possible keys:
  385. --- - lang (string|nil): The language of the source buffer. If omitted, detect
  386. --- from the filetype of the source buffer.
  387. --- - bufnr (integer|nil): Buffer to draw the tree into. If omitted, a new
  388. --- buffer is created.
  389. --- - winid (integer|nil): Window id to display the tree buffer in. If omitted,
  390. --- a new window is created with {command}.
  391. --- - command (string|nil): Vimscript command to create the window. Default
  392. --- value is "60vnew". Only used when {winid} is nil.
  393. --- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a
  394. --- function, it accepts the buffer number of the source buffer as its only
  395. --- argument and should return a string.
  396. function M.inspect_tree(opts)
  397. ---@diagnostic disable-next-line: invisible
  398. M.dev.inspect_tree(opts)
  399. end
  400. --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr':
  401. ---
  402. --- ```lua
  403. --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
  404. --- ```
  405. ---
  406. ---@since 11
  407. ---@param lnum integer|nil Line number to calculate fold level for
  408. ---@return string
  409. function M.foldexpr(lnum)
  410. return M._fold.foldexpr(lnum)
  411. end
  412. return M