aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter.lua
blob: 3a475b8f983455cdd95d668cf1c28f07b4654abd (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
local a = vim.api
local query = require'vim.treesitter.query'
local language = require'vim.treesitter.language'

-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer.
-- Consider use weak references to release parser if all plugins are done with
-- it.
local parsers = {}

local Parser = {}
Parser.__index = Parser

--- Parses the buffer if needed and returns a tree.
--
-- Calling this will call the on_changedtree callbacks if the tree has changed.
--
-- @returns An up to date tree
-- @returns If the tree changed with this call, the changed ranges
function Parser:parse()
  if self.valid then
    return self.tree
  end
  local changes
  self.tree, changes = self._parser:parse_buf(self.bufnr)
  self.valid = true

  if not vim.tbl_isempty(changes) then
    for _, cb in ipairs(self.changedtree_cbs) do
      cb(changes)
    end
  end

  return self.tree, changes
end

function Parser:_on_bytes(bufnr, changed_tick,
                          start_row, start_col, start_byte,
                          old_row, old_col, old_byte,
                          new_row, new_col, new_byte)
  local old_end_col = old_col + ((old_row == 0) and start_col or 0)
  local new_end_col = new_col + ((new_row == 0) and start_col or 0)
  self._parser:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
                    start_row, start_col,
                    start_row+old_row, old_end_col,
                    start_row+new_row, new_end_col)
  self.valid = false

  for _, cb in ipairs(self.bytes_cbs) do
    cb(bufnr, changed_tick,
      start_row, start_col, start_byte,
      old_row, old_col, old_byte,
      new_row, new_col, new_byte)
  end
end

--- Sets the included ranges for the current parser
--
-- @param ranges A table of nodes that will be used as the ranges the parser should include.
function Parser:set_included_ranges(ranges)
  self._parser:set_included_ranges(ranges)
  -- The buffer will need to be parsed again later
  self.valid = false
end

local M = vim.tbl_extend("error", query, language)

setmetatable(M, {
  __index = function (t, k)
      if k == "TSHighlighter" then
        a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter")
        t[k] = require'vim.treesitter.highlighter'
        return t[k]
      elseif k == "highlighter" then
        t[k] = require'vim.treesitter.highlighter'
        return t[k]
      end
   end
 })

--- Creates a new parser.
--
-- It is not recommended to use this, use vim.treesitter.get_parser() instead.
--
-- @param bufnr The buffer the parser will be tied to
-- @param lang The language of the parser.
-- @param id The id the parser will have
function M._create_parser(bufnr, lang, id)
  language.require_language(lang)
  if bufnr == 0 then
    bufnr = a.nvim_get_current_buf()
  end

  vim.fn.bufload(bufnr)

  local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
  self._parser = vim._create_ts_parser(lang)
  self.changedtree_cbs = {}
  self.bytes_cbs = {}
  self:parse()
    -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
    -- using it.
  local function bytes_cb(_, ...)
    return self:_on_bytes(...)
  end
  local detach_cb = nil
  if id ~= nil then
    detach_cb = function()
      if parsers[id] == self then
        parsers[id] = nil
      end
    end
  end
  a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb})
  return self
end

--- Gets the parser for this bufnr / ft combination.
--
-- If needed this will create the parser.
-- Unconditionnally attach the provided callback
--
-- @param bufnr The buffer the parser should be tied to
-- @param ft The filetype of this parser
-- @param buf_attach_cbs An `nvim_buf_attach`-like table argument with the following keys :
--  `on_lines` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
--  `on_changedtree` : a callback that will be called everytime the tree has syntactical changes.
--      it will only be passed one argument, that is a table of the ranges (as node ranges) that
--      changed.
--
-- @returns The parser
function M.get_parser(bufnr, lang, buf_attach_cbs)
  if bufnr == nil or bufnr == 0 then
    bufnr = a.nvim_get_current_buf()
  end
  if lang == nil then
    lang = a.nvim_buf_get_option(bufnr, "filetype")
  end
  local id = tostring(bufnr)..'_'..lang

  if parsers[id] == nil then
    parsers[id] = M._create_parser(bufnr, lang, id)
  end

  if buf_attach_cbs and buf_attach_cbs.on_changedtree then
    table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree)
  end

  if buf_attach_cbs and buf_attach_cbs.on_bytes then
    table.insert(parsers[id].bytes_cbs, buf_attach_cbs.on_bytes)
  end

  return parsers[id]
end

return M