aboutsummaryrefslogtreecommitdiffstats
path: root/lua/nvim-treesitter/query_predicates.lua
blob: 75f0efc04a720d3651e2dca954daba4de5cd06a1 (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 query = require "vim.treesitter.query"

local function error(str)
  vim.api.nvim_err_writeln(str)
end

local function valid_args(name, pred, count, strict_count)
  local arg_count = #pred - 1

  if strict_count then
    if arg_count ~= count then
      error(string.format("%s must have exactly %d arguments", name, count))
      return false
    end
  elseif arg_count < count then
    error(string.format("%s must have at least %d arguments", name, count))
    return false
  end

  return true
end

query.add_predicate("nth?", function(match, pattern, bufnr, pred)
  if not valid_args("nth?", pred, 2, true) then
    return
  end

  local node = match[pred[2]]
  local n = tonumber(pred[3])
  if node and node:parent() and node:parent():named_child_count() > n then
    return node:parent():named_child(n) == node
  end

  return false
end)

local function has_ancestor(match, pattern, bufnr, pred)
  if not valid_args(pred[1], pred, 2) then
    return
  end

  local node = match[pred[2]]
  local ancestor_types = { unpack(pred, 3) }
  if not node then
    return true
  end

  local just_direct_parent = pred[1]:find("has-parent", 1, true)

  node = node:parent()
  while node do
    if vim.tbl_contains(ancestor_types, node:type()) then
      return true
    end
    if just_direct_parent then
      node = nil
    else
      node = node:parent()
    end
  end
  return false
end

query.add_predicate("has-ancestor?", has_ancestor)

query.add_predicate("has-parent?", has_ancestor)

query.add_predicate("is?", function(match, pattern, bufnr, pred)
  if not valid_args("is?", pred, 2) then
    return
  end

  -- Avoid circular dependencies
  local locals = require "nvim-treesitter.locals"
  local node = match[pred[2]]
  local types = { unpack(pred, 3) }

  if not node then
    return true
  end

  local _, _, kind = locals.find_definition(node, bufnr)

  return vim.tbl_contains(types, kind)
end)

query.add_predicate("has-type?", function(match, pattern, bufnr, pred)
  if not valid_args(pred[1], pred, 2) then
    return
  end

  local node = match[pred[2]]
  local types = { unpack(pred, 3) }

  if not node then
    return true
  end

  return vim.tbl_contains(types, node:type())
end)

-- Just avoid some annoying warnings for this directive
query.add_directive("make-range!", function() end)

query.add_directive("downcase!", function(match, _, bufnr, pred, metadata)
  local text, key, value

  if #pred == 3 then
    -- (#downcase! @capture "key")
    key = pred[3]
    value = metadata[pred[2]][key]
  else
    -- (#downcase! "key")
    key = pred[2]
    value = metadata[key]
  end

  if type(value) == "string" then
    text = value
  else
    local node = match[value]
    text = query.get_node_text(node, bufnr) or ""
  end

  if #pred == 3 then
    metadata[pred[2]][key] = string.lower(text)
  else
    metadata[key] = string.lower(text)
  end
end)

query.add_directive("exclude_children!", function(match, _pattern, _bufnr, pred, metadata)
  local capture_id = pred[2]
  local node = match[capture_id]
  local start_row, start_col, end_row, end_col = node:range()
  local ranges = {}
  for i = 0, node:named_child_count() - 1 do
    local child = node:named_child(i)
    local child_start_row, child_start_col, child_end_row, child_end_col = child:range()
    if child_start_row > start_row or child_start_col > start_col then
      table.insert(ranges, {
        start_row,
        start_col,
        child_start_row,
        child_start_col,
      })
    end
    start_row = child_end_row
    start_col = child_end_col
  end
  if end_row > start_row or end_col > start_col then
    table.insert(ranges, { start_row, start_col, end_row, end_col })
  end
  metadata.content = ranges
end)