treesitter.lua 14 KB

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