Repository: neovim
Issue: Keyword to override (replace) tree-sitter queries from user configuration directory
Labels: enhancement,treesitter

### Problem

There is a basic problem regarding being able to override tree-sitter queries from the user configuration directory. [This](https://github.com/neovim/neovim/issues/20172) issue triggered a [PR](https://github.com/neovim/neovim/pull/20105) to add the `extends` keyword in `.scm` query files.

However, the `extends` keyword is not sufficient when there is a conflict with old and extending queries, as I will show. The alternative is to [programmatically](https://github.com/nvim-treesitter/nvim-treesitter/issues/3058) override the query with another file.

My proposal to avoid having to use lua code to override the `nvim-treesitter` query files with user files in `after/query/*/*.scm` would be to add another keyword: `; replace` or `; override`, so the system could simply change the file loading precedence that happens in `/usr/share/nvim/runtime/lua/vim/treesitter/query.lua:M.get_files(lang,
query_name, is_included)`, which prefers the first file unless `; extends` is used in other files.

This is a simple example that shows the problematic behavior:

`nvim --version` is `NVIM v0.9.1-dev-54+g66a196c95`

`init.vim`
```lua
-- bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)
-- end of bootstrap

local plugins = {
  {
    'nvim-treesitter/nvim-treesitter',
    build = function()
      pcall(require('nvim-treesitter.install').update({ with_sync = true }))
    end,
  },
  'nvim-treesitter/playground',
}

local opts = {}

-- Load plugins
require("lazy").setup(plugins, opts)

-- Configuration

require'nvim-treesitter.configs'.setup {
  -- A list of parser names, or "all" (the five listed parsers should always be installed)
  ensure_installed = { "markdown" },

  -- Install parsers synchronously (only applied to `ensure_installed`)
  sync_install = false,

  -- Automatically install missing parsers when entering buffer
  -- Recommendation: set to false if you don't have `tree-sitter` CLI installed locally
  auto_install = true,

  highlight = {
    enable = true,
    additional_vim_regex_highlighting = false,
  },
  playground = {
    enable = true,
    disable = {},
    updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code
    persist_queries = false, -- Whether the query persists across vim sessions
    keybindings = {
      toggle_query_editor = 'o',
      toggle_hl_groups = 'i',
      toggle_injected_languages = 't',
      toggle_anonymous_nodes = 'a',
      toggle_language_display = 'I',
      focus_language = 'f',
      unfocus_language = 'F',
      update = 'R',
      goto_node = '<cr>',
      show_help = '?',
    },
  },
}

vim.o.termguicolors = true

vim.keymap.set('', '<F10>', '<cmd>TSHighlightCapturesUnderCursor<cr>', {desc = 'treesitter highlighting group of char under cursor'} )

require("utils").load_theme()
```

`~/.config/nvim/after/queries/markdown/highlights.scm`
```
; extends
(atx_heading (atx_h1_marker)) @TSMarkdownHeaderLevel1
(atx_heading (atx_h2_marker)) @TSMarkdownHeaderLevel2
```

`~/.config/nvim/lua/utils.lua`
```lua
local M = {}

local function override_query(lang, query_name, file)
  -- Read file into string
  local f, err = io.open(file, "r")
  if f then
    local text = f:read("*a")
    f:close()
    vim.treesitter.query.set(lang, query_name, text)
  else
    error(err)
  end
end

function M.load_theme()
  local colors = {
    head1 = '#4f97d7',
    head2 = '#2d9574',
    title = '#ff0000',
    punctuation = "#00ff00",
  }

  -- Highlight groups
  vim.api.nvim_set_hl(0, "@text.title", { fg = colors.title })
  vim.api.nvim_set_hl(0, "@punctuation.special", { fg = colors.punctuation })

  vim.api.nvim_set_hl(0, "@TSMarkdownHeaderLevel1", { fg = colors.head1, bold = true })
  vim.api.nvim_set_hl(0, "@TSMarkdownHeaderLevel2", { fg = colors.head2, bold = true })
end

function M.override_query(lang, query_name)
  override_query(lang, query_name, vim.fn.stdpath("config") .. "/after/queries/" .. lang .. "/" .. query_name .. ".scm")
  -- restart tree-sitter
  vim.treesitter.start(vim.fn.bufnr(), lang)
end

return M
```

`headers.md`
``` markdown
# Level1

## Level2
```

To reproduce, run
`nvim headers.md`

Pressing `F10` you can inspect the highlight captures, and `; extends` creates some conflicts of precedence.

To overcome this problem, you need to set the query file programmatically, like this:

`:lua require('utils').override_query("markdown", "highlights")`


### Expected behavior

I would like a new *keyword* like `; extends`, with a proper name, maybe `; replace` or `; override` that users can write at the top of their configuration `after/queries/*` files to replace any query file the system has loaded, being from Neovim's runtime or plugins like `nvim-treesitter`.

This should be quite simple to add and would allow users to create their own queries, for highlighting or other purposes, like `after/syntax` ft plugins work without the need to use lua.