aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/tshighlighter.lua
blob: 1544ecbf49a411bd963d157509454d1551168b77 (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
local a = vim.api

-- support reload for quick experimentation
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
TSHighlighter.__index = TSHighlighter

-- These are conventions defined by tree-sitter, though it
-- needs to be user extensible also.
-- TODO(bfredl): this is very much incomplete, we will need to
-- go through a few tree-sitter provided queries and decide
-- on translations that makes the most sense.
TSHighlighter.hl_map = {
    keyword="Keyword",
    string="String",
    type="Type",
    comment="Comment",
    constant="Constant",
    operator="Operator",
    number="Number",
    label="Label",
    ["function"]="Function",
    ["function.special"]="Function",
}

function TSHighlighter.new(query, bufnr, ft)
  local self = setmetatable({}, TSHighlighter)
  self.parser = vim.treesitter.get_parser(bufnr, ft, function(...) self:on_change(...) end)
  self.buf = self.parser.bufnr
  -- TODO(bfredl): perhaps on_start should be called uncondionally, instead for only on mod?
  local tree = self.parser:parse()
  self.root = tree:root()
  self:set_query(query)
  self.edit_count = 0
  self.redraw_count = 0
  self.line_count = {}
  a.nvim_buf_set_option(self.buf, "syntax", "")
  a.nvim__buf_set_luahl(self.buf, {
    on_start=function(...) return self:on_start(...) end,
    on_window=function(...) return self:on_window(...) end,
    on_line=function(...) return self:on_line(...) end,
  })

  -- Tricky: if syntax hasn't been enabled, we need to reload color scheme
  -- but use synload.vim rather than syntax.vim to not enable
  -- syntax FileType autocmds. Later on we should integrate with the
  -- `:syntax` and `set syntax=...` machinery properly.
  if vim.g.syntax_on ~= 1 then
    vim.api.nvim_command("runtime! syntax/synload.vim")
  end
  return self
end

function TSHighlighter:set_query(query)
  if type(query) == "string" then
    query = vim.treesitter.parse_query(self.parser.lang, query)
  end
  self.query = query

  self.id_map = {}
  for i, capture in ipairs(self.query.captures) do
    local hl = 0
    local firstc = string.sub(capture, 1, 1)
    local hl_group = self.hl_map[capture]
    if firstc ~= string.lower(firstc) then
      hl_group = vim.split(capture, '.', true)[1]
    end
    if hl_group then
      hl = a.nvim_get_hl_id_by_name(hl_group)
    end
    self.id_map[i] = hl
  end
end

function TSHighlighter:on_change(changes)
  for _, ch in ipairs(changes or {}) do
    a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1)
  end
  self.edit_count = self.edit_count + 1
end

function TSHighlighter:on_start(_, _buf, _tick)
  local tree = self.parser:parse()
  self.root = tree:root()
end

function TSHighlighter:on_window(_, _win, _buf, _topline, botline)
  self.iter = nil
  self.active_nodes = {}
  self.nextrow = 0
  self.botline = botline
  self.redraw_count = self.redraw_count + 1
end

function TSHighlighter:on_line(_, _win, buf, line)
  if self.iter == nil then
    self.iter = self.query:iter_captures(self.root,buf,line,self.botline)
  end
  while line >= self.nextrow do
    local capture, node, match = self.iter()
    local active = true
    if capture == nil then
      break
    end
    if match ~= nil then
      active = self:run_pred(match)
      match.active = active
    end
    local start_row, start_col, end_row, end_col = node:range()
    local hl = self.id_map[capture]
    if hl > 0 and active then
      if start_row == line and end_row == line then
        a.nvim__put_attr(hl, start_col, end_col)
      elseif end_row >= line then
        -- TODO(bfredl): this is quite messy. Togheter with multiline bufhl we should support
        -- luahl generating multiline highlights (and other kinds of annotations)
        self.active_nodes[{hl=hl, start_row=start_row, start_col=start_col, end_row=end_row, end_col=end_col}] = true
      end
    end
    if start_row > line then
      self.nextrow = start_row
    end
  end
  for node,_ in pairs(self.active_nodes) do
    if node.start_row <= line and node.end_row >= line then
      local start_col, end_col = node.start_col, node.end_col
      if node.start_row < line then
        start_col = 0
      end
      if node.end_row > line then
        end_col = 9000
      end
      a.nvim__put_attr(node.hl, start_col, end_col)
    end
    if node.end_row <= line then
      self.active_nodes[node] = nil
    end
  end
  self.line_count[line] = (self.line_count[line] or 0) + 1
  --return tostring(self.line_count[line])
end

return TSHighlighter