local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local ts_t = require('test.functional.treesitter.testutil')

local clear = n.clear
local dedent = t.dedent
local eq = t.eq
local insert = n.insert
local exec_lua = n.exec_lua
local feed = n.feed
local run_query = ts_t.run_query
local assert_alive = n.assert_alive

describe('treesitter parser API', function()
  before_each(function()
    clear()
    exec_lua(function()
      vim.g.__ts_debug = 1
    end)
  end)

  it('parses buffer', function()
    insert([[
      int main() {
        int x = 3;
      }]])

    exec_lua(function()
      _G.parser = vim.treesitter.get_parser(0, 'c')
      _G.tree = _G.parser:parse()[1]
      _G.root = _G.tree:root()
      _G.lang = vim.treesitter.language.inspect('c')
    end)

    eq('<tree>', exec_lua('return tostring(tree)'))
    eq('<node translation_unit>', exec_lua('return tostring(root)'))
    eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}'))

    eq(1, exec_lua('return root:child_count()'))
    exec_lua('child = root:child(0)')
    eq('<node function_definition>', exec_lua('return tostring(child)'))
    eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}'))

    eq('function_definition', exec_lua('return child:type()'))
    eq(true, exec_lua('return child:named()'))
    eq('number', type(exec_lua('return child:symbol()')))
    eq(true, exec_lua('return lang.symbols[child:type()]'))

    exec_lua('anon = root:descendant_for_range(0,8,0,9)')
    eq('(', exec_lua('return anon:type()'))
    eq(false, exec_lua('return anon:named()'))
    eq('number', type(exec_lua('return anon:symbol()')))
    eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=]))

    exec_lua('descendant = root:descendant_for_range(1,2,1,12)')
    eq('<node declaration>', exec_lua('return tostring(descendant)'))
    eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}'))
    eq(
      '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))',
      exec_lua('return descendant:sexpr()')
    )

    feed('2G7|ay')
    exec_lua(function()
      _G.tree2 = _G.parser:parse()[1]
      _G.root2 = _G.tree2:root()
      _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13)
    end)
    eq(false, exec_lua('return tree2 == tree1'))
    eq(false, exec_lua('return root2 == root'))
    eq('<node declaration>', exec_lua('return tostring(descendant2)'))
    eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}'))

    eq(true, exec_lua('return child == child'))
    -- separate lua object, but represents same node
    eq(true, exec_lua('return child == root:child(0)'))
    eq(false, exec_lua('return child == descendant2'))
    eq(false, exec_lua('return child == nil'))
    eq(false, exec_lua('return child == tree'))

    eq('string', exec_lua('return type(child:id())'))
    eq(true, exec_lua('return child:id() == child:id()'))
    -- separate lua object, but represents same node
    eq(true, exec_lua('return child:id() == root:child(0):id()'))
    eq(false, exec_lua('return child:id() == descendant2:id()'))
    eq(false, exec_lua('return child:id() == nil'))
    eq(false, exec_lua('return child:id() == tree'))

    -- unchanged buffer: return the same tree
    eq(true, exec_lua('return parser:parse()[1] == tree2'))
  end)

  it('preserves full-document included ranges', function()
    insert([[
      local _ = 1
    ]])

    eq(
      {
        { 0, 0, 0, 4294967295, 4294967295, 4294967295 },
      },
      exec_lua(function()
        local parser = vim.treesitter.get_parser(0, 'lua')
        local tree = parser:parse()[1]
        return tree:included_ranges(true)
      end)
    )
  end)

  it('respects eol settings when parsing buffer', function()
    insert([[
      int main() {
        int x = 3;
      } // :D]])

    exec_lua(function()
      vim.bo.eol = false
      vim.bo.fixeol = false
      _G.parser = vim.treesitter.get_parser(0, 'c')
      _G.tree = _G.parser:parse()[1]
      _G.root = _G.tree:root()
      _G.lang = vim.treesitter.language.inspect('c')
    end)

    eq(
      '<node translation_unit>',
      exec_lua(function()
        return tostring(_G.root)
      end)
    )
    eq(
      { 0, 0, 0, 2, 7, 33 },
      exec_lua(function()
        return { _G.root:range(true) }
      end)
    )

    -- NOTE: Changing these settings marks the buffer as `modified` but does not fire `on_bytes`,
    -- meaning this test case does not pass... is this intended?
    -- exec_lua(function()
    --   vim.bo.eol = true
    --   vim.bo.fixeol = true
    --   vim.cmd.update()
    --   _G.parser = vim.treesitter.get_parser(0, 'c')
    --   _G.tree = _G.parser:parse()[1]
    --   _G.root = _G.tree:root()
    --   _G.lang = vim.treesitter.language.inspect('c')
    -- end)
    --
    -- eq(
    --   '<node translation_unit>',
    --   exec_lua(function()
    --     return tostring(root)
    --   end)
    -- )
    -- eq(
    --   { 0, 0, 0, 3, 0, 34 },
    --   exec_lua(function()
    --     return { root:range(true) }
    --   end)
    -- )
  end)

  it('parses buffer asynchronously', function()
    insert([[
      int main() {
        int x = 3;
      }]])

    exec_lua(function()
      _G.parser = vim.treesitter.get_parser(0, 'c')
      _G.lang = vim.treesitter.language.inspect('c')
      _G.parser:parse(nil, function(_, trees)
        _G.tree = trees[1]
        _G.root = _G.tree:root()
      end)
      vim.wait(100, function() end)
    end)

    eq('<tree>', exec_lua('return tostring(tree)'))
    eq('<node translation_unit>', exec_lua('return tostring(root)'))
    eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}'))

    eq(1, exec_lua('return root:child_count()'))
    exec_lua('child = root:child(0)')
    eq('<node function_definition>', exec_lua('return tostring(child)'))
    eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}'))

    eq('function_definition', exec_lua('return child:type()'))
    eq(true, exec_lua('return child:named()'))
    eq('number', type(exec_lua('return child:symbol()')))
    eq(true, exec_lua('return lang.symbols[child:type()]'))

    exec_lua('anon = root:descendant_for_range(0,8,0,9)')
    eq('(', exec_lua('return anon:type()'))
    eq(false, exec_lua('return anon:named()'))
    eq('number', type(exec_lua('return anon:symbol()')))
    eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=]))

    exec_lua('descendant = root:descendant_for_range(1,2,1,12)')
    eq('<node declaration>', exec_lua('return tostring(descendant)'))
    eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}'))
    eq(
      '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))',
      exec_lua('return descendant:sexpr()')
    )

    feed('2G7|ay')
    exec_lua(function()
      _G.parser:parse(nil, function(_, trees)
        _G.tree2 = trees[1]
        _G.root2 = _G.tree2:root()
        _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13)
      end)
      vim.wait(100, function() end)
    end)
    eq(false, exec_lua('return tree2 == tree1'))
    eq(false, exec_lua('return root2 == root'))
    eq('<node declaration>', exec_lua('return tostring(descendant2)'))
    eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}'))

    eq(true, exec_lua('return child == child'))
    -- separate lua object, but represents same node
    eq(true, exec_lua('return child == root:child(0)'))
    eq(false, exec_lua('return child == descendant2'))
    eq(false, exec_lua('return child == nil'))
    eq(false, exec_lua('return child == tree'))

    eq('string', exec_lua('return type(child:id())'))
    eq(true, exec_lua('return child:id() == child:id()'))
    -- separate lua object, but represents same node
    eq(true, exec_lua('return child:id() == root:child(0):id()'))
    eq(false, exec_lua('return child:id() == descendant2:id()'))
    eq(false, exec_lua('return child:id() == nil'))
    eq(false, exec_lua('return child:id() == tree'))

    -- unchanged buffer: return the same tree
    eq(true, exec_lua('return parser:parse()[1] == tree2'))
  end)

  it('does not crash when editing large files', function()
    insert([[printf("%s", "some text");]])
    feed('yy49999p')

    exec_lua(function()
      _G.parser = vim.treesitter.get_parser(0, 'c')
      _G.done = false
      vim.treesitter.start(0, 'c')
      _G.parser:parse(nil, function()
        _G.done = true
      end)
      while not _G.done do
        -- Busy wait until async parsing has completed
        vim.wait(100, function() end)
      end
    end)

    eq(true, exec_lua([[return done]]))
    exec_lua(function()
      vim.api.nvim_input('Lxj')
    end)
    exec_lua(function()
      vim.api.nvim_input('xj')
    end)
    exec_lua(function()
      vim.api.nvim_input('xj')
    end)
    assert_alive()
  end)

  it('resets parsing state on tree changes', function()
    insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]])
    feed('yy1000p')

    exec_lua(function()
      vim.cmd('set ft=lua')

      vim.treesitter.start(0)
      local parser = assert(vim.treesitter.get_parser(0))

      parser:parse(true, function() end)
      vim.api.nvim_buf_set_lines(0, 1, -1, false, {})
      parser:parse(true)
    end)
  end)

  it('resets when buffer was editing during an async parse', function()
    insert([[printf("%s", "some text");]])
    feed('yy49999p')
    feed('gg4jO// Comment<Esc>')

    exec_lua(function()
      _G.parser = vim.treesitter.get_parser(0, 'c')
      _G.done = false
      vim.treesitter.start(0, 'c')
      _G.parser:parse(nil, function()
        _G.done = true
      end)
    end)

    exec_lua(function()
      vim.api.nvim_input('ggdj')
    end)

    eq(false, exec_lua([[return done]]))
    exec_lua(function()
      while not _G.done do
        -- Busy wait until async parsing finishes
        vim.wait(100, function() end)
      end
    end)
    eq(true, exec_lua([[return done]]))
    eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]]))
    eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]]))
  end)

  it('handles multiple async parse calls', function()
    insert([[printf("%s", "some text");]])
    feed('yy49999p')

    exec_lua(function()
      -- Spy on vim.schedule
      local schedule = vim.schedule
      vim.schedule = function(fn)
        _G.schedules = _G.schedules + 1
        schedule(fn)
      end
      _G.schedules = 0
      _G.parser = vim.treesitter.get_parser(0, 'c')
      for i = 1, 5 do
        _G['done' .. i] = false
        _G.parser:parse(nil, function()
          _G['done' .. i] = true
        end)
      end
      schedule(function()
        _G.schedules_snapshot = _G.schedules
      end)
    end)

    eq(2, exec_lua([[return schedules_snapshot]]))
    eq(
      { false, false, false, false, false },
      exec_lua([[return { done1, done2, done3, done4, done5 }]])
    )
    exec_lua(function()
      while not _G.done1 do
        -- Busy wait until async parsing finishes
        vim.wait(100, function() end)
      end
    end)
    eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]]))
  end)

  local test_text = [[
    void ui_refresh(void)
    {
      int width = INT_MAX, height = INT_MAX;
      bool ext_widgets[kUIExtCount];
      for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
        ext_widgets[i] = true;
      }

      bool inclusive = ui_override();
      for (size_t i = 0; i < ui_count; i++) {
        UI *ui = uis[i];
        width = MIN(ui->width, width);
        height = MIN(ui->height, height);
        foo = BAR(ui->bazaar, bazaar);
        for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
          ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
        }
      }
    }]]

  it('can iterate over nodes children', function()
    insert(test_text)

    local res = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'c')

      local func_node = parser:parse()[1]:root():child(0)

      local res = {}
      for node, field in func_node:iter_children() do
        table.insert(res, { node:type(), field })
      end
      return res
    end)

    eq({
      { 'primitive_type', 'type' },
      { 'function_declarator', 'declarator' },
      { 'compound_statement', 'body' },
    }, res)
  end)

  it('does not get parser for empty filetype', function()
    insert(test_text)

    local parser, error = exec_lua(function()
      return vim.treesitter.get_parser(0)
    end)
    eq(vim.NIL, parser)
    eq('Parser not found for buffer 1: language could not be determined', error)

    -- Must provide language for buffers with an empty filetype
    exec_lua("vim.treesitter.get_parser(0, 'c')")
  end)

  it('can get a child by field', function()
    insert(test_text)

    local res = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'c')

      _G.func_node = parser:parse()[1]:root():child(0)

      local res = {}
      for _, node in ipairs(_G.func_node:field('type')) do
        table.insert(res, { node:type(), node:range() })
      end
      return res
    end)

    eq({ { 'primitive_type', 0, 0, 0, 4 } }, res)

    local res_fail = exec_lua(function()
      vim.treesitter.get_parser(0, 'c')

      return #_G.func_node:field('foo') == 0
    end)

    assert(res_fail)
  end)

  it('can get text of multiline node', function()
    insert(test_text)
    local res = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'c')
      local tree = parser:parse()[1]
      return vim.treesitter.get_node_text(tree:root(), 0)
    end)
    eq(t.dedent(test_text), res)

    local res2 = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'c')
      local root = parser:parse()[1]:root()
      return vim.treesitter.get_node_text(root:child(0):child(0), 0)
    end)
    eq('void', res2)
  end)

  it('can get text where start of node is one past EOF', function()
    local text = [[
      def run
        a = <<~E
      end]]
    insert(text)
    eq(
      '',
      exec_lua(function()
        local fake_node = {}
        function fake_node:start()
          return 3, 0, 23
        end
        function fake_node:end_()
          return 3, 0, 23
        end
        function fake_node:range(bytes)
          if bytes then
            return 3, 0, 23, 3, 0, 23
          end
          return 3, 0, 3, 0
        end
        return vim.treesitter.get_node_text(fake_node, 0)
      end)
    )
  end)

  it('can get empty text if node range is zero-width', function()
    local text = [[
      ```lua
      {}
      ```
    ]]
    insert(text)
    local result = exec_lua(function()
      local fake_node = {}
      function fake_node:start()
        return 1, 0, 7
      end
      function fake_node:end_()
        return 1, 0, 7
      end
      function fake_node:range()
        return 1, 0, 1, 0
      end
      return vim.treesitter.get_node_text(fake_node, 0) == ''
    end)
    eq(true, result)
  end)

  it('can set simple ranges', function()
    insert(test_text)

    local res = exec_lua(function()
      _G.parser = vim.treesitter.get_parser(0, 'c')
      return { _G.parser:parse()[1]:root():range() }
    end)

    eq({ 0, 0, 19, 0 }, res)

    -- The following sets the included ranges for the current parser
    -- As stated here, this only includes the function (thus the whole buffer, without the last line)
    local res2 = exec_lua(function()
      local root = _G.parser:parse()[1]:root()
      _G.parser:set_included_regions({ { root:child(0) } })
      _G.parser:invalidate()
      return { _G.parser:parse(true)[1]:root():range() }
    end)

    eq({ 0, 0, 18, 1 }, res2)

    eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]])

    local range_tbl = exec_lua(function()
      _G.parser:set_included_regions { { { 0, 0, 17, 1 } } }
      _G.parser:parse()
      return _G.parser:included_regions()
    end)

    eq({ { { 0, 0, 0, 17, 1, 508 } } }, range_tbl)
  end)

  it('can set complex ranges', function()
    insert(test_text)

    local res = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'c')
      local query = vim.treesitter.query.parse('c', '(declaration) @decl')

      local nodes = {}
      for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
        table.insert(nodes, node)
      end

      parser:set_included_regions({ nodes })

      local root = parser:parse(true)[1]:root()

      local res = {}
      for i = 0, (root:named_child_count() - 1) do
        table.insert(res, { root:named_child(i):range() })
      end
      return res
    end)

    eq({
      { 2, 2, 2, 40 },
      { 3, 2, 3, 32 },
      { 4, 7, 4, 25 },
      { 8, 2, 8, 33 },
      { 9, 7, 9, 20 },
      { 10, 4, 10, 20 },
      { 14, 9, 14, 27 },
    }, res)
  end)

  it('can create string parsers', function()
    local ret = exec_lua(function()
      local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c')
      return { parser:parse()[1]:root():range() }
    end)

    eq({ 0, 0, 0, 13 }, ret)
  end)

  it('can run async parses with string parsers', function()
    local ret = exec_lua(function()
      local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c')
      return { parser:parse(nil, function() end)[1]:root():range() }
    end)

    eq({ 0, 0, 0, 13 }, ret)
  end)

  it('can run queries with string parsers', function()
    local txt = [[
      int foo = 42;
      int bar = 13;
    ]]

    local ret = exec_lua(function(str)
      local parser = vim.treesitter.get_string_parser(str, 'c')

      local nodes = {}
      local query = vim.treesitter.query.parse('c', '((identifier) @id (#eq? @id "foo"))')

      for _, node in query:iter_captures(parser:parse()[1]:root(), str) do
        table.insert(nodes, { node:range() })
      end

      return nodes
    end, txt)

    eq({ { 0, 10, 0, 13 } }, ret)
  end)

  describe('creating a language tree', function()
    local function get_ranges()
      return exec_lua(function()
        local result = {}
        _G.parser:for_each_tree(function(tree)
          table.insert(result, { tree:root():range() })
        end)
        return result
      end)
    end

    before_each(function()
      insert([[
        int x = INT_MAX;
        #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
        #define READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
        #define VALUE 123
        #define VALUE1 123
        #define VALUE2 123
      ]])
    end)

    describe('parsing regions independently', function()
      it('injects a language', function()
        exec_lua(function()
          _G.parser = vim.treesitter.get_parser(0, 'c', {
            injections = {
              c = (
                '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) '
                .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'
              ),
            },
          })
          _G.parser:parse(true)
        end)

        eq('table', exec_lua('return type(parser:children().c)'))
        eq(5, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 7, 0 }, -- root tree
          { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 14, 3, 17 }, -- VALUE 123
          { 4, 15, 4, 18 }, -- VALUE1 123
          { 5, 15, 5, 18 }, -- VALUE2 123
        }, get_ranges())

        n.feed('ggo<esc>')
        eq(5, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 8, 0 }, -- root tree
          { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 4, 14, 4, 17 }, -- VALUE 123
          { 5, 15, 5, 18 }, -- VALUE1 123
          { 6, 15, 6, 18 }, -- VALUE2 123
        }, get_ranges())
      end)
    end)

    describe('when parsing regions combined', function()
      it('injects a language', function()
        exec_lua(function()
          _G.parser = vim.treesitter.get_parser(0, 'c', {
            injections = {
              c = (
                '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) '
                .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))'
              ),
            },
          })
          _G.parser:parse(true)
        end)

        eq('table', exec_lua('return type(parser:children().c)'))
        eq(2, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 7, 0 }, -- root tree
          { 1, 26, 2, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 14, 5, 18 }, -- VALUE 123
          -- VALUE1 123
          -- VALUE2 123
        }, get_ranges())

        n.feed('ggo<esc>')
        eq('table', exec_lua('return type(parser:children().c)'))
        eq(2, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 8, 0 }, -- root tree
          { 2, 26, 3, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          -- VALUE 123
          { 4, 14, 6, 18 }, -- VALUE1 123
          -- VALUE2 123
        }, get_ranges())

        n.feed('7ggI//<esc>')
        exec_lua([[parser:parse(true)]])
        eq('table', exec_lua('return type(parser:children().c)'))
        eq(2, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 8, 0 }, -- root tree
          { 2, 26, 3, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          -- VALUE 123
          { 4, 14, 5, 18 }, -- VALUE1 123
        }, get_ranges())
      end)

      it('scopes injections appropriately', function()
        -- `injection.combined` are combined within a TSTree.
        -- Lua injections on lines 2-4 should be combined within their
        -- respective C injection trees, and lua injections on lines 0 and 6
        -- are separate from each other and other lua injections on lines 2-4.

        exec_lua(function()
          local lines = {
            [[func('int a = func("local a = [=[");')]],
            [[]],
            [[func('int a = func("local a = 6") + func("+ 3");')]],
            [[func('int a = func("local a = 6") + func("+ 3");')]],
            [[func('int a = func("local a = 6") + func("+ 3");')]],
            [[]],
            [[func('int a = func("]=]");')]],
          }
          vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
          _G.parser = vim.treesitter.get_parser(0, 'lua', {
            injections = {
              lua = [[
                ((function_call
                  arguments: (arguments
                    (string (string_content) @injection.content)))
                  (#set! injection.language "c"))
              ]],
              c = [[
                ((call_expression
                  arguments: (argument_list
                    (string_literal (string_content) @injection.content)))
                  (#set! injection.combined)
                  (#set! injection.language "lua"))
              ]],
            },
          })

          function _G.langtree_regions(parser)
            local result_regions = {}

            local regions = parser:included_regions()
            for region_i, region in pairs(regions) do
              local result_region = {}

              for _, range in ipairs(region) do
                table.insert(result_region, {
                  range[1],
                  range[2],
                  range[4],
                  range[5],
                })
              end

              result_regions[region_i] = result_region
            end

            return result_regions
          end
          function _G.all_regions(parser)
            local this_regions = _G.langtree_regions(parser)
            local child_regions = {}
            for lang, child in pairs(parser:children()) do
              child_regions[lang] = _G.all_regions(child)
            end
            return { regions = this_regions, children = child_regions }
          end
        end)

        local expected_regions = {
          children = {}, -- nothing is parsed
          regions = {
            {}, -- root tree's regions is the entire buffer
          },
        }
        eq(expected_regions, exec_lua('return all_regions(_G.parser)'))

        exec_lua('_G.parser:parse({ 3, 0, 3, 45 })')

        expected_regions = {
          children = {
            c = {
              children = {
                lua = {
                  children = {},
                  regions = {
                    { { 3, 20, 3, 31 }, { 3, 42, 3, 45 } },
                  },
                },
              },
              regions = {
                { { 3, 6, 3, 48 } },
              },
            },
          },
          regions = {
            {},
          },
        }
        eq(expected_regions, exec_lua('return all_regions(_G.parser)'))

        exec_lua('_G.parser:parse(true)')
        expected_regions = {
          children = {
            c = {
              children = {
                lua = {
                  children = {},
                  regions = {
                    { { 0, 20, 0, 33 } },
                    { { 2, 20, 2, 31 }, { 2, 42, 2, 45 } },
                    { { 3, 20, 3, 31 }, { 3, 42, 3, 45 } },
                    { { 4, 20, 4, 31 }, { 4, 42, 4, 45 } },
                    { { 6, 20, 6, 23 } },
                  },
                },
              },
              regions = {
                { { 0, 6, 0, 36 } },
                { { 2, 6, 2, 48 } },
                { { 3, 6, 3, 48 } },
                { { 4, 6, 4, 48 } },
                { { 6, 6, 6, 26 } },
              },
            },
          },
          regions = {
            {},
          },
        }
        eq(expected_regions, exec_lua('return all_regions(_G.parser)'))
      end)
    end)

    describe('when using injection.self', function()
      it('injects the source language', function()
        exec_lua(function()
          _G.parser = vim.treesitter.get_parser(0, 'c', {
            injections = {
              c = (
                '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) '
                .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))'
              ),
            },
          })
          _G.parser:parse(true)
        end)

        eq('table', exec_lua('return type(parser:children().c)'))
        eq(5, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 7, 0 }, -- root tree
          { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 14, 3, 17 }, -- VALUE 123
          { 4, 15, 4, 18 }, -- VALUE1 123
          { 5, 15, 5, 18 }, -- VALUE2 123
        }, get_ranges())

        n.feed('ggo<esc>')
        eq(5, exec_lua('return #parser:children().c:trees()'))
        eq({
          { 0, 0, 8, 0 }, -- root tree
          { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 4, 14, 4, 17 }, -- VALUE 123
          { 5, 15, 5, 18 }, -- VALUE1 123
          { 6, 15, 6, 18 }, -- VALUE2 123
        }, get_ranges())
      end)
    end)

    describe('when using the offset directive', function()
      it('shifts the range by the directive amount', function()
        exec_lua(function()
          _G.parser = vim.treesitter.get_parser(0, 'c', {
            injections = {
              c = (
                '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) '
                .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'
              ),
            },
          })
          _G.parser:parse(true)
        end)

        eq('table', exec_lua('return type(parser:children().c)'))
        eq({
          { 0, 0, 7, 0 }, -- root tree
          { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
          { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
          { 3, 16, 3, 16 }, -- VALUE 123
          { 4, 17, 4, 17 }, -- VALUE1 123
          { 5, 17, 5, 17 }, -- VALUE2 123
        }, get_ranges())
      end)
      it('applies offsets to quantified captures', function()
        local function get_ltree_ranges()
          return exec_lua(function()
            local result = {}
            _G.parser:for_each_tree(function(_, ltree)
              table.insert(result, ltree:included_regions())
            end)
            return result
          end)
        end

        exec_lua(function()
          _G.parser = vim.treesitter.get_parser(0, 'c', {
            injections = {
              c = '((preproc_def (preproc_arg) @injection.content)+ (#set! injection.language "c") (#offset! @injection.content 0 1 0 -1))',
            },
          })
          _G.parser:parse(true)
        end)

        eq('table', exec_lua('return type(parser:children().c)'))
        eq({
          { {} }, -- root tree
          {
            {
              { 3, 15, 163, 3, 16, 164 }, -- VALUE 123
              { 4, 16, 182, 4, 17, 183 }, -- VALUE1 123
              { 5, 16, 201, 5, 17, 202 }, -- VALUE2 123
            },
          },
        }, get_ltree_ranges())
      end)
      it('lists all directives', function()
        local res_list = exec_lua(function()
          local query = vim.treesitter.query

          local list = query.list_directives()

          table.sort(list)

          return list
        end)

        eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list)
      end)
    end)

    it('normalizes hyphens to underscores in injection.language', function()
      exec_lua(function()
        -- register "c" as the parser for "shell_session" (normalized form of "shell-session")
        vim.treesitter.language.register('c', 'shell_session')
        _G.parser = vim.treesitter.get_parser(0, 'c', {
          injections = {
            c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "shell-session"))',
          },
        })
        _G.parser:parse(true)
      end)

      -- injection.language "shell-session" should normalize to "shell_session",
      -- which resolves via the registered alias to the "c" parser
      eq('table', exec_lua('return type(parser:children().c)'))
    end)
  end)

  it('clips nested injections #34098', function()
    insert([=[
      ```lua
      vim.cmd([[
      set noswapfile
      set noswapfile
      set noswapfile
      ]])
      ```
    ]=])

    local result = exec_lua(function()
      local parser = vim.treesitter.get_parser(0, 'markdown')
      parser:parse(true)

      return parser._children.lua._children.vim:included_regions()
    end)

    local expected = {
      {
        { 1, 10, 17, 2, 0, 18 },
        { 2, 0, 18, 3, 0, 33 },
        { 3, 0, 33, 4, 0, 48 },
        { 4, 0, 48, 5, 0, 63 },
      },
    }
    eq(expected, result)
  end)

  describe('when getting the language for a range', function()
    before_each(function()
      insert([[
        int x = INT_MAX;
        #define VALUE 123456789
      ]])
    end)

    it('returns the correct language tree', function()
      local result = exec_lua(function()
        local parser = vim.treesitter.get_parser(0, 'c', {
          injections = {
            c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))',
          },
        })
        parser:parse(true)

        local sub_tree = parser:language_for_range({ 1, 18, 1, 19 })

        return sub_tree == parser:children().c
      end)

      eq(true, result)
    end)
  end)

  describe('when setting the node for an injection', function()
    before_each(function()
      insert([[
        print()
      ]])
    end)

    it('ignores optional captures #23100', function()
      local result = exec_lua(function()
        local parser = vim.treesitter.get_parser(0, 'lua', {
          injections = {
            lua = (
              '(function_call '
              .. '(arguments '
              .. '(string)? @injection.content '
              .. '(number)? @injection.content '
              .. '(#offset! @injection.content 0 1 0 -1) '
              .. '(#set! injection.language "c")))'
            ),
          },
        })
        parser:parse(true)

        return parser:is_valid()
      end)

      eq(true, result)
    end)
  end)

  describe('when getting/setting match data', function()
    describe('when setting for the whole match', function()
      it('sets/gets the data correctly', function()
        insert([[
          int x = 3;
        ]])

        local result = exec_lua(function()
          local query =
            vim.treesitter.query.parse('c', '((number_literal) @number (#set! "key" "value"))')
          local parser = vim.treesitter.get_parser(0, 'c')

          local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)()
          return metadata.key
        end)

        eq('value', result)
      end)

      describe('when setting a key on a capture', function()
        it('creates the nested table', function()
          insert([[
            int x = 3;
          ]])

          local result = exec_lua(function()
            local query = vim.treesitter.query.parse(
              'c',
              '((number_literal) @number (#set! @number "key" "value"))'
            )
            local parser = vim.treesitter.get_parser(0, 'c')

            local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)()
            local _, nested_tbl = next(metadata)
            return nested_tbl.key
          end)

          eq('value', result)
        end)

        it('does not overwrite the nested table', function()
          insert([[
            int x = 3;
          ]])

          local result = exec_lua(function()
            local query = vim.treesitter.query.parse(
              'c',
              '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))'
            )
            local parser = vim.treesitter.get_parser(0, 'c')

            local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)()
            local _, nested_tbl = next(metadata)
            return nested_tbl
          end)
          local expected = {
            ['key'] = 'value',
            ['key2'] = 'value2',
          }

          eq(expected, result)
        end)
      end)
    end)
  end)

  describe('trim! directive', function()
    it('can trim all whitespace', function()
      exec_lua(function()
        local lines = {
          '        print([[',
          '',
          '            f',
          '     helllo',
          '  there',
          '  asdf',
          '  asdfassd   ',
          '',
          '',
          '',
          '  ]])',
          '  print([[',
          '        ',
          '        ',
          '        ',
          '  ]])',
          '',
          '  print([[]])',
          '',
          '  print([[',
          '  ]])',
          '',
          '  print([[     hello 😃    ]])',
        }
        vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      end)
      exec_lua(function()
        vim.treesitter.start(0, 'lua')
      end)

      local query_text = [[
       ; query
        ((string_content) @str)
      ]]
      eq({
        { 'str', { 0, 16, 10, 2 } },
        { 'str', { 11, 10, 15, 2 } },
        { 'str', { 17, 10, 17, 10 } },
        { 'str', { 19, 10, 20, 2 } },
        { 'str', { 22, 10, 22, 29 } },
      }, run_query('lua', query_text))

      local trim_query_text = [[
        ; query
        ((string_content) @str
          (#trim! @str 1 1 1 1))
      ]]

      eq({
        { 'str', { 2, 12, 6, 10 } },
        { 'str', { 11, 10, 11, 10 } },
        { 'str', { 17, 10, 17, 10 } },
        { 'str', { 19, 10, 19, 10 } },
        { 'str', { 22, 15, 22, 25 } },
      }, run_query('lua', trim_query_text))
    end)

    it('trims only empty lines by default (backwards compatible)', function()
      insert(dedent [[
      ## Heading

      With some text

      ## And another

      With some more here]])

      local query_text = [[
        ; query
        ((section) @fold
          (#trim! @fold))
      ]]

      exec_lua(function()
        vim.treesitter.start(0, 'markdown')
      end)

      eq({
        { 'fold', { 0, 0, 2, 14 } },
        { 'fold', { 4, 0, 6, 19 } },
      }, run_query('markdown', query_text))
    end)

    it('can trim lines', function()
      insert(dedent [[
      - Fold list
        - Fold list
          - Fold list
          - Fold list
        - Fold list
      - Fold list
      ]])

      local query_text = [[
        ; query
        ((list_item
          (list)) @fold
          (#trim! @fold 1 1 1 1))
      ]]

      exec_lua(function()
        vim.treesitter.start(0, 'markdown')
      end)

      eq({
        { 'fold', { 0, 0, 4, 13 } },
        { 'fold', { 1, 2, 3, 15 } },
      }, run_query('markdown', query_text))
    end)
  end)

  it('tracks the root range properly (#22911)', function()
    insert([[
      int main() {
        int x = 3;
      }]])

    local query0 = [[
      (declaration) @declaration
      (function_definition) @function
    ]]

    exec_lua(function()
      vim.treesitter.start(0, 'c')
    end)

    eq({
      { 'function', { 0, 0, 2, 1 } },
      { 'declaration', { 1, 2, 1, 12 } },
    }, run_query('c', query0))

    n.command 'normal ggO'
    insert('int a;')

    eq({
      { 'declaration', { 0, 0, 0, 6 } },
      { 'function', { 1, 0, 3, 1 } },
      { 'declaration', { 2, 2, 2, 12 } },
    }, run_query('c', query0))
  end)

  it('handles ranges when source is a multiline string (#20419)', function()
    local source = [==[
      vim.cmd[[
        set number
        set cmdheight=2
        set lastsatus=2
      ]]

      set query = [[;; query
        ((function_call
          name: [
            (identifier) @_cdef_identifier
            (_ _ (identifier) @_cdef_identifier)
          ]
          arguments: (arguments (string content: _ @injection.content)))
          (#set! injection.language "c")
          (#eq? @_cdef_identifier "cdef"))
      ]]
    ]==]

    local r = exec_lua(function()
      local parser = vim.treesitter.get_string_parser(source, 'lua')
      parser:parse(true)
      local ranges = {}
      parser:for_each_tree(function(tstree, tree)
        ranges[tree:lang()] = { tstree:root():range(true) }
      end)
      return ranges
    end)

    eq({
      lua = { 0, 6, 6, 16, 4, 438 },
      query = { 6, 20, 113, 15, 6, 431 },
      vim = { 1, 0, 16, 4, 6, 89 },
    }, r)

    -- The above ranges are provided directly from treesitter, however query directives may mutate
    -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure
    -- add_bytes() produces the same result.

    local rb = exec_lua(function()
      local add_bytes = require('vim.treesitter._range').add_bytes
      for lang, range in pairs(r) do
        r[lang] = { range[1], range[2], range[4], range[5] }
        r[lang] = add_bytes(source, r[lang])
      end
      return r
    end)

    eq(rb, r)
  end)

  it('does not produce empty injection ranges (#23409)', function()
    insert [[
      Examples: >lua
        local a = {}
<
    ]]

    -- This is not a valid injection since (code) has children and include-children is not set
    exec_lua(function()
      _G.parser1 = require('vim.treesitter.languagetree').new(0, 'vimdoc', {
        injections = {
          vimdoc = '((codeblock (language) @injection.language (code) @injection.content))',
        },
      })
      _G.parser1:parse(true)
    end)

    eq(0, exec_lua('return #vim.tbl_keys(parser1:children())'))

    exec_lua(function()
      _G.parser2 = require('vim.treesitter.languagetree').new(0, 'vimdoc', {
        injections = {
          vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))',
        },
      })
      _G.parser2:parse(true)
    end)

    eq(1, exec_lua('return #vim.tbl_keys(parser2:children())'))
    eq({ { { 1, 0, 21, 2, 0, 42 } } }, exec_lua('return parser2:children().lua:included_regions()'))
  end)

  it('parses injections incrementally', function()
    insert(dedent [[
      >lua
        local a = {}
      <

      >lua
        local b = {}
      <

      >lua
        local c = {}
      <

      >lua
        local d = {}
      <

      >lua
        local e = {}
      <

      >lua
        local f = {}
      <

      >lua
        local g = {}
      <
    ]])

    exec_lua(function()
      _G.parser = require('vim.treesitter.languagetree').new(0, 'vimdoc', {
        injections = {
          vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))',
        },
      })
    end)

    --- Do not parse injections by default
    eq(
      0,
      exec_lua(function()
        _G.parser:parse()
        return #vim.tbl_keys(_G.parser:children())
      end)
    )

    --- Only parse injections between lines 0, 2
    eq(
      1,
      exec_lua(function()
        _G.parser:parse({ 0, 2 })
        return #_G.parser:children().lua:trees()
      end)
    )

    eq(
      1,
      exec_lua(function()
        _G.parser:parse({ 2, 6 })
        return #_G.parser:children().lua:trees()
      end)
    )

    eq(
      7,
      exec_lua(function()
        _G.parser:parse(true)
        return #_G.parser:children().lua:trees()
      end)
    )
  end)

  describe('languagetree is_valid()', function()
    before_each(function()
      insert(dedent [[
        Treesitter integration                                 *treesitter*

        Nvim integrates the `tree-sitter` library for incremental parsing of buffers:
        https://tree-sitter.github.io/tree-sitter/

      ]])

      feed(':set ft=help<cr>')

      exec_lua(function()
        vim.treesitter
          .get_parser(0, 'vimdoc', {
            injections = {
              vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))',
            },
          })
          :parse()
      end)
    end)

    it('is valid excluding, invalid including children initially', function()
      eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
      eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
    end)

    it('is fully valid after a full parse', function()
      exec_lua('vim.treesitter.get_parser():parse(true)')
      eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
      eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()'))
    end)

    it('is valid within a range on parsed tree after parsing it', function()
      exec_lua('vim.treesitter.get_parser():parse({5, 7})')
      eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
      eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})'))
    end)

    describe('when adding content with injections', function()
      before_each(function()
        feed('G')
        insert(dedent [[
          >lua
            local a = {}
          <

        ]])
      end)

      it('is fully invalid after changes', function()
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
      end)

      it('is valid excluding, invalid including children after a rangeless parse', function()
        exec_lua('vim.treesitter.get_parser():parse()')
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
      end)

      it('is valid within a range on parsed tree after parsing it', function()
        exec_lua('vim.treesitter.get_parser():parse({5, 7})')
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})'))
      end)

      it(
        'is valid excluding, invalid including children after a range parse that does not lead to parsing non-parsed injections',
        function()
          exec_lua('vim.treesitter.get_parser():parse({2, 4})')
          eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
          eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
        end
      )
    end)

    describe('when removing content with injections', function()
      before_each(function()
        feed('G')
        insert(dedent [[
          >lua
            local a = {}
          <

          >lua
            local a = {}
          <

        ]])

        exec_lua('vim.treesitter.get_parser():parse(true)')

        feed('Gd3k')
      end)

      it('is fully invalid after changes', function()
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
      end)

      it('is valid excluding, invalid including children after a rangeless parse', function()
        exec_lua('vim.treesitter.get_parser():parse()')
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
      end)

      it('is valid within a range parse that leads to parsing modified child tree', function()
        exec_lua('vim.treesitter.get_parser():parse({5, 7})')
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
        eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})'))
      end)

      it(
        'is valid excluding, invalid including children after a range parse that does not lead to parsing modified child tree',
        function()
          exec_lua('vim.treesitter.get_parser():parse({2, 4})')
          eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
          eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
        end
      )
    end)
  end)
end)
