From 113d6c97e85286b50410ec1e0fcc3cd674a727c6 Mon Sep 17 00:00:00 2001 From: Dmytro Soltys Date: Tue, 8 Apr 2025 19:18:54 +0200 Subject: [PATCH 01/29] chore: Add docker test runners for local development --- .dockerignore | 8 ++++++++ Makefile | 25 +++++++++++++++++++++++++ test/new/Makefile | 18 ++++++++---------- vim.Dockerfile | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100644 Makefile create mode 100644 vim.Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..780d2f3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.github +.gitignore +.gitlab-ci-yml +.luacheckrc +.projections.json +.vintrc +*Dockerfile +test/new/env diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a2245ac --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +PHONY: docker_build docker_test_nvim + +RUNFOR ?= nvim +VIMCMD = $(shell if [ $(RUNFOR) = nvim ]; then echo "nvim --headless"; else echo "vim -T dumb --not-a-term -n"; fi) +VIMCMD != if [ $(RUNFOR) = nvim ]; then echo "nvim --headless"; else echo "vim -T dumb --not-a-term -n"; fi +NVIM_VERSION ?= stable +NVIM_ARCH ?= -linux-x86_64 +VIM_VERSION ?= v9.1.1287 + +docker_build: + docker build --tag 'vim-matchup-nvim-stable' \ + --file vim.Dockerfile \ + --build-arg NVIM_VERSION=${NVIM_VERSION} \ + --build-arg NVIM_ARCH=${NVIM_ARCH} \ + --build-arg VIM_VERSION=${VIM_VERSION} \ + . + +docker_test_old: docker_build + docker run --rm -it --pull=never --name nvim vim-matchup-nvim-stable -c 'VIMCMD="${VIMCMD}" test/vader/run' + +docker_test_new: docker_build + docker run --rm -it --pull=never --name nvim vim-matchup-nvim-stable -c 'cd ./test/new && make -j1 MYVIM="${VIMCMD}"' + +docker_test_shell: docker_build + docker run --rm -it --pull=never --name nvim vim-matchup-nvim-stable diff --git a/test/new/Makefile b/test/new/Makefile index 3b0283b..4b30c36 100644 --- a/test/new/Makefile +++ b/test/new/Makefile @@ -5,6 +5,7 @@ COVER = covimerage -q run --append --no-report \ --source $(CURDIR)/../../plugin MYVIM ?= nvim --headless MAKEFLAGS += --no-print-directory +VENV = . env/bin/activate; TESTS := $(wildcard test-*) @@ -19,27 +20,24 @@ sysinfo: @echo "**** SYSTEM INFORMATION ****" $(TESTS): env - @. env/bin/activate - mkdir -p cov.tmp - MYVIM="$(COVER) $(MYVIM)" $(MAKE) -C $@ + $(VENV) mkdir -p cov.tmp + $(VENV) MYVIM="$(COVER) $(MYVIM)" $(MAKE) -C $@ coverage: coverage.xml cov.tmp/coverage_covimerage: $(wildcard cov.tmp/_*) - coverage combine $^ + $(VENV) coverage combine $^ coverage.xml: env cov.tmp/coverage_covimerage - . env/bin/activate - coverage report -m - coverage html - coverage xml + $(VENV) coverage report -m + $(VENV) coverage html + $(VENV) coverage xml env: env/pyvenv.cfg env/pyvenv.cfg: python3 -m venv env - . env/bin/activate - pip install -r requirements.txt + $(VENV) pip install -r requirements.txt ifndef MAKECMDGOALS test: sysinfo diff --git a/vim.Dockerfile b/vim.Dockerfile new file mode 100644 index 0000000..8595200 --- /dev/null +++ b/vim.Dockerfile @@ -0,0 +1,45 @@ +FROM debian:latest AS neovim-image +ARG NVIM_VERSION=stable +ARG NVIM_ARCH=-linux-x86_64 +ADD --chmod=755 https://github.com/neovim/neovim/releases/download/${NVIM_VERSION}/nvim${NVIM_ARCH}.appimage /nvim-linux-x86_64.appimage +RUN /nvim-linux-x86_64.appimage --appimage-extract + +FROM debian:latest AS vim-image +ARG VIM_VERSION=v9.1.1287 +ADD --chmod=755 https://github.com/vim/vim-appimage/releases/download/${VIM_VERSION}/Vim-${VIM_VERSION}.glibc2.29-x86_64.AppImage /vim-linux-x86_64.appimage +RUN /vim-linux-x86_64.appimage --appimage-extract + +FROM python:latest AS base +RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ + --mount=target=/var/cache/apt,type=cache,sharing=locked \ + apt-get update -qq && \ + apt-get install --no-install-recommends -y \ + git \ + ca-certificates \ + make + +FROM base AS test-prep +WORKDIR /work +COPY test/new/requirements.txt test/new/requirements.txt +COPY test/new/Makefile test/new/Makefile +RUN cd test/new && make env +RUN mkdir -p test/vader/vader.vim && git clone --depth=1 https://github.com/junegunn/vader.vim.git test/vader/vader.vim + +FROM python:latest AS nvim + +# nvim +COPY --from=neovim-image /squashfs-root /nvim-root +RUN ln -s /nvim-root/AppRun /bin/nvim + +# vim +COPY --from=vim-image /squashfs-root /vim-root +RUN ln -s /vim-root/AppRun /bin/vim + +WORKDIR /work +ENV HOME=/work +ENV GIT_PAGER=cat + +COPY . . +COPY --from=test-prep /work/test test + +ENTRYPOINT ["bash"] From 490f6850a3c3f70ea6ac9b7ad76b5ba53a7a159c Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Wed, 28 May 2025 09:32:18 -0500 Subject: [PATCH 02/29] feat: add luaCATS typing for configuration and add treesitter config --- lua/match-up.lua | 109 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/lua/match-up.lua b/lua/match-up.lua index 2815582..9df36e7 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -1,23 +1,120 @@ local M = {} +---@class matchup.DelimConfig +---@field count_fail 0|1 +---@field nomids 0|1 +---@field noskips 0|1|2 +---@field start_plaintext 0|1 +---@field stopline integer + +---@class matchup.HotfixConfig +---@field enabled boolean + +---@class matchup.MappingsConfig +---@field enabled boolean + +---@class matchup.OffscreenConfig +---@field method 'status'|'status_manual'|'popup' +---@field scrolloff 0|1 +---@field fullwidth 0|1 +--@field highlight string Vim exclusive option. Intentionally excluded from type +--@field syntax_hl 0|1 Vim exclusive option. Intentionally excluded from type +---@field border 1|string|string[] + +---@class matchup.MatchparenConfig +---@field deferred 0|1 +---@field deferred_fade_time integer +---@field deferred_hide_delay integer +---@field deferred_show_delay integer +---@field enabled boolean +---@field end_sign string +---@field hi_background 0|1 +---@field hi_surround_always 0|1 +---@field insert_timeout integer +---@field nomode string +---@field offscreen matchup.OffscreenConfig +---@field pumvisible 0|1 +---@field singleton 0|1 +---@field stopline integer +---@field timeout integer + +---@class matchup.MatchprefHtmlConfig +---@field nolists 0|1 +---@field tagnameonly 0|1 + +---@class matchup.MatchprefConfig +---@field html matchup.MatchprefHtmlConfig + +---@class matchup.MotionConfig +---@field cursor_end 0|1 +---@field enabled 0|1 +---@field override_Npercent integer + +---@class matchup.MouseConfig +---@field enabled boolean + +---@class matchup.OverrideConfig +---@field vimtex 1|0 + +---@class matchup.SurroundConfig +---@field enabled boolean + +---@class matchup.TextObjConfig +---@field enabled boolean +---@field linewise_operators string[] + +---@class matchup.TransmuteConfig +---@field enabled boolean + +-- TODO: add documentation for g: vars +---@class matchup.TreesitterConfig +---@field enabled boolean + +---@class matchup.Config +---@field delim matchup.DelimConfig +---@field enabled boolean +---@field hotfix matchup.HotfixConfig +---@field mappings matchup.MappingsConfig +---@field matchparen matchup.MatchparenConfig +---@field matchpref matchup.MatchprefConfig +---@field motion matchup.MotionConfig +---@field mouse matchup.MouseConfig +---@field override matchup.OverrideConfig +---@field surround matchup.SurroundConfig +---@field text_obj matchup.TextObjConfig +---@field transmute matchup.TransmuteConfig +---@field treesitter matchup.TreesitterConfig + +---@param opts matchup.Config +---@param validate boolean local function do_setup(opts, validate) - for mod, elem in pairs(opts) do - for key, val in pairs(type(elem) == 'table' and elem or {}) do - local opt = 'matchup_'..mod..'_'..key - if validate and vim.g[opt] == nil then - error(string.format('invalid option name %s.%s', mod, key)) + for mod, elem in pairs(opts --[[@as table>]]) do + if type(elem) == 'table' then + for key, val in pairs(elem) do + local opt = 'matchup_'..mod..'_'..key + if validate and vim.g[opt] == nil then + error(('invalid option name %s.%s'):format(mod, key)) + end + vim.g[opt] = val end - vim.g[opt] = val + else + if validate and vim.g[mod] == nil then + error(('invalid option name %s'):format(mod)) + end + vim.g[mod] = elem end end end +---@param opts matchup.Config|{sync: boolean} function M.setup(opts) local sync = opts.sync if sync then vim.cmd[[runtime! plugin/matchup.vim]] end + opts.sync = nil + ---@cast opts matchup.Config do_setup(opts, sync) end From 7f274a21b027cabb6a7f1c859475a0d7fe6741a9 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Wed, 28 May 2025 10:44:52 -0500 Subject: [PATCH 03/29] docs: boolean -> 0|1 on matchup.Config types --- lua/match-up.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/match-up.lua b/lua/match-up.lua index 9df36e7..4378b04 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -8,10 +8,10 @@ local M = {} ---@field stopline integer ---@class matchup.HotfixConfig ----@field enabled boolean +---@field enabled 0|1 ---@class matchup.MappingsConfig ----@field enabled boolean +---@field enabled 0|1 ---@class matchup.OffscreenConfig ---@field method 'status'|'status_manual'|'popup' @@ -26,7 +26,7 @@ local M = {} ---@field deferred_fade_time integer ---@field deferred_hide_delay integer ---@field deferred_show_delay integer ----@field enabled boolean +---@field enabled 0|1 ---@field end_sign string ---@field hi_background 0|1 ---@field hi_surround_always 0|1 @@ -51,28 +51,30 @@ local M = {} ---@field override_Npercent integer ---@class matchup.MouseConfig ----@field enabled boolean +---@field enabled 0|1 ---@class matchup.OverrideConfig ---@field vimtex 1|0 ---@class matchup.SurroundConfig ----@field enabled boolean +---@field enabled 0|1 ---@class matchup.TextObjConfig ----@field enabled boolean +---@field enabled 0|1 ---@field linewise_operators string[] ---@class matchup.TransmuteConfig ----@field enabled boolean +---@field enabled 0|1 -- TODO: add documentation for g: vars +-- TODO: add defautls for this g: vars? ---@class matchup.TreesitterConfig ---@field enabled boolean +---@field disabled string[] ---@class matchup.Config ---@field delim matchup.DelimConfig ----@field enabled boolean +---@field enabled 0|1 ---@field hotfix matchup.HotfixConfig ---@field mappings matchup.MappingsConfig ---@field matchparen matchup.MatchparenConfig From db9fe5d95dfcf292268f24d56e5e05db20b902e5 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Thu, 29 May 2025 13:31:50 -0500 Subject: [PATCH 04/29] refactor: mostly remove nvim-treesitter dependency --- .gitignore | 1 + after/queries/c/matchup.scm | 3 +- after/queries/ecma/matchup.scm | 3 +- after/queries/elm/matchup.scm | 5 +- after/queries/go/matchup.scm | 3 +- after/queries/rust/matchup.scm | 3 +- after/queries/zig/matchup.scm | 3 +- autoload/matchup/loader.vim | 15 - autoload/matchup/ts_engine.vim | 7 - lua/match-up.lua | 6 +- lua/treesitter-matchup.lua | 9 +- lua/treesitter-matchup/internal.lua | 402 ++++++++++++------ lua/treesitter-matchup/syntax.lua | 56 +-- lua/treesitter-matchup/third-party/query.lua | 394 ----------------- .../third-party/ts-utils.lua | 28 ++ 15 files changed, 330 insertions(+), 608 deletions(-) delete mode 100644 lua/treesitter-matchup/third-party/query.lua create mode 100644 lua/treesitter-matchup/third-party/ts-utils.lua diff --git a/.gitignore b/.gitignore index 926ccaa..12604df 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ doc/tags +.nvim.lua diff --git a/after/queries/c/matchup.scm b/after/queries/c/matchup.scm index 48caa18..74668cd 100644 --- a/after/queries/c/matchup.scm +++ b/after/queries/c/matchup.scm @@ -19,8 +19,7 @@ ; 'else' and 'else if' (else_clause - "else" @_start (if_statement "if" @_end)? - (#make-range! "mid.if.1" @_start @_end)) + "else" @mid.if.1 (if_statement "if" @mid.if.1)?) ; if ((if_statement diff --git a/after/queries/ecma/matchup.scm b/after/queries/ecma/matchup.scm index 9b9f1da..3259186 100644 --- a/after/queries/ecma/matchup.scm +++ b/after/queries/ecma/matchup.scm @@ -17,8 +17,7 @@ ; 'else' and 'else if' (else_clause - "else" @_start (if_statement "if" @_end)? - (#make-range! "mid.if.1" @_start @_end)) + "else" @mid.if.1 (if_statement "if" @mid.if.1)?) ; if ((if_statement diff --git a/after/queries/elm/matchup.scm b/after/queries/elm/matchup.scm index 0c98830..1411f9e 100644 --- a/after/queries/elm/matchup.scm +++ b/after/queries/elm/matchup.scm @@ -2,9 +2,8 @@ . "if" @open.if) @scope.if (if_else_expr - "else" @_else - "if"? @_if - (#make-range! "mid.if.1" @_else @_if)) + "else" @mid.if.1 + "if"? @mid.if.1) (let_in_expr "let" @open.let diff --git a/after/queries/go/matchup.scm b/after/queries/go/matchup.scm index ed8415f..9058c18 100644 --- a/after/queries/go/matchup.scm +++ b/after/queries/go/matchup.scm @@ -6,8 +6,7 @@ ; 'else' and 'else if' (if_statement - "else" @_start (if_statement "if" @_end)? - (#make-range! "mid.if.1" @_start @_end)) + "else" @mid.if.1 (if_statement "if" @mid.if.1)?) ; if (block (if_statement "if" @open.if) @scope.if) diff --git a/after/queries/rust/matchup.scm b/after/queries/rust/matchup.scm index ebd01b4..a77ad56 100644 --- a/after/queries/rust/matchup.scm +++ b/after/queries/rust/matchup.scm @@ -16,8 +16,7 @@ (else_clause "else" @mid.if_.1 (block)) (else_clause - "else" @_start (if_expression "if" @_end) - (#make-range! "mid.if_.2" @_start @_end)) + "else" @mid.if_.2 (if_expression "if" @mid.if_.2)) ; --------------- async/await --------------- (function_item (function_modifiers "async" @open.async)) @scope.async diff --git a/after/queries/zig/matchup.scm b/after/queries/zig/matchup.scm index 5ec4bfb..5e1edd7 100644 --- a/after/queries/zig/matchup.scm +++ b/after/queries/zig/matchup.scm @@ -7,8 +7,7 @@ ; 'else' and 'else if' (else_clause - "else" @_start (if_statement "if" @_end)? - (#make-range! "mid.if.1" @_start @_end)) + "else" @mid.if.1 (if_statement "if" @mid.if.1)?) ; if ((if_statement diff --git a/autoload/matchup/loader.vim b/autoload/matchup/loader.vim index e78243b..61f99c7 100644 --- a/autoload/matchup/loader.vim +++ b/autoload/matchup/loader.vim @@ -33,21 +33,6 @@ function! matchup#loader#init_buffer() abort " {{{1 endif let l:has_ts_hl = 0 - if s:ts_may_be_supported && matchup#ts_engine#is_hl_enabled(bufnr('%')) - let l:has_ts_hl = 1 - - if matchup#ts_engine#get_option( - \ bufnr('%'), 'additional_vim_regex_highlighting') - if empty(&syntax) - set syntax=ON - else - augroup matchup_syntax - au! - autocmd VimEnter * if empty(&syntax) | set syntax=ON | endif - augroup END - endif - endif - endif " initialize lists of delimiter pairs and regular expressions " this is the data obtained from parsing b:match_words diff --git a/autoload/matchup/ts_engine.vim b/autoload/matchup/ts_engine.vim index 96f788f..4bea3b6 100644 --- a/autoload/matchup/ts_engine.vim +++ b/autoload/matchup/ts_engine.vim @@ -21,13 +21,6 @@ function! matchup#ts_engine#is_enabled(bufnr) abort return +s:forward('is_enabled', a:bufnr) endfunction -function! matchup#ts_engine#is_hl_enabled(bufnr) abort - if !has('nvim-0.5.0') - return 0 - endif - return +s:forward('is_hl_enabled', a:bufnr) -endfunction - function! matchup#ts_engine#get_option(bufnr, opt_name) abort return s:forward('get_option', a:bufnr, a:opt_name) endfunction diff --git a/lua/match-up.lua b/lua/match-up.lua index 4378b04..e394990 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -66,11 +66,15 @@ local M = {} ---@class matchup.TransmuteConfig ---@field enabled 0|1 +-- TODO: remove vim syntax related g: vars +-- TODO: modify vimscript to work without nvim-treesitter -- TODO: add documentation for g: vars --- TODO: add defautls for this g: vars? ---@class matchup.TreesitterConfig ---@field enabled boolean ---@field disabled string[] +---@field include_match_words boolean +---@field disable_virtual_text boolean +---@field enable_quotes boolean ---@class matchup.Config ---@field delim matchup.DelimConfig diff --git a/lua/treesitter-matchup.lua b/lua/treesitter-matchup.lua index cec414f..27dfcb2 100644 --- a/lua/treesitter-matchup.lua +++ b/lua/treesitter-matchup.lua @@ -1,9 +1,4 @@ -if not pcall(require, 'nvim-treesitter') then - return {init = function() end} -end - -local treesitter = require 'nvim-treesitter' -local queries = require 'nvim-treesitter.query' +-- TODO: remove module structure local M = {} @@ -12,7 +7,7 @@ function M.init() matchup = { module_path = 'treesitter-matchup.internal', is_supported = function(lang) - return queries.has_query_files(lang, 'matchup') + return vim.treesitter.query.get(lang, 'matchup') ~= nil end } } diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index 1e12e56..6305ba5 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -1,18 +1,16 @@ -if not pcall(require, 'nvim-treesitter') then - return {is_enabled = function(bufnr) return 0 end, - is_hl_enabled = function(bufnr) return 0 end} -end - local vim = vim local api = vim.api -local ts = require'treesitter-matchup.compat' -local configs = require'nvim-treesitter.configs' -local parsers = require'nvim-treesitter.parsers' -local queries = require'treesitter-matchup.third-party.query' -local ts_utils = require'nvim-treesitter.ts_utils' +local ts = vim.treesitter +local memoize = require'treesitter-matchup.third-party.ts-utils'.memoize + +vim.g.matchup_treesitter_enabled = false +vim.g.matchup_treesitter_disabled = {} +vim.g.matchup_treesitter_include_match_words = false +vim.g.matchup_treesitter_enable_quotes = true + +-- TODO: update this dependencies local lru = require'treesitter-matchup.third-party.lru' local util = require'treesitter-matchup.util' -local utils2 = require'treesitter-matchup.third-party.utils' local unpack = unpack or table.unpack @@ -20,69 +18,179 @@ local M = {} local cache = lru.new(150) + +---@param lang string +---@param bufnr integer +local function is_enabled(lang, bufnr) + local enabled = vim.g.matchup_treesitter_enabled == 1 + local buf_enabled = vim.b[bufnr].matchup_treesitter_enabled == 1 + local lang_disabled = vim.list_contains(vim.g.matchup_treesitter_disabled, lang) + + return enabled and buf_enabled and not lang_disabled +end +-- TODO: this is following the old module structure of nvim-treesitter. Change it + +---@param bufnr integer? +---@return boolean function M.is_enabled(bufnr) bufnr = bufnr or api.nvim_get_current_buf() - local lang = parsers.get_buf_lang(bufnr) - return configs.is_enabled('matchup', lang, bufnr) + local lang = ts.language.get_lang(vim.bo[bufnr].filetype) + if not lang then + return false + end + assert(lang) + return is_enabled(lang, bufnr) end -function M.is_hl_enabled(bufnr) - bufnr = bufnr or api.nvim_get_current_buf() - local lang = parsers.get_buf_lang(bufnr) - return configs.is_enabled('highlight', lang, bufnr) +-- TODO: I had to remove the `is_hl_enabled` function and the related logic. On +-- the `main` branch of nvim-treesitter it's not possible to tell wether or not +-- the hl is enabled for a given buffer and there is no +-- `additional_vim_regex_highlighting` option anymore. Now, users will have to +-- enable syntax themselves after doing `vim.treesitter.start()`. Mention this +-- as a possible workaround and possible regression in the PR. +-- +-- Technically, the undocumented `vim.treesitter.highlighter` table can be +-- accessed. But, should we rely in undocumented features? + +---@param bufnr integer +---@param root TSNode +---@param lang string +---@return string +local function buf_root_lang_hash(bufnr, root, lang) + return tostring(bufnr) .. root:id() .. '_' .. lang end -M.get_matches = ts_utils.memoize_by_buf_tick(function(bufnr) - local parser = parsers.get_parser(bufnr) - local matches = {} +---@class matchup.treesitter.MatchInfo +---@field range Range4 +---@field length integer +---@field last_node TSNode +---@field text string + +---@class matchup.treesitter.MatchInfoWrapper +---@field info matchup.treesitter.MatchInfo + +---@class matchup.treesitter.Match +---@field scope? table +---@field open? table +---@field mid? table> +---@field close? table +---@field skip? matchup.treesitter.MatchInfoWrapper + +---@param bufnr integer +---@param root TSNode +---@param lang string +---@return matchup.treesitter.Match[] +local get_memoized_matches = memoize(function(bufnr, root, lang) + local query_name = 'matchup' + local query = ts.query.get(lang, query_name) + + if not query then + return {} + end + + local out = {} ---@type matchup.treesitter.Match[] + for _, match, metadata in query:iter_matches(root, bufnr) do + local match_info = {} + for id, nodes in pairs(match) do + local first = nodes[1] + local last = nodes[#nodes] + + ---@type integer, integer, integer + local start_row, start_col , start_byte = unpack(ts.get_range(first, bufnr, metadata)) + ---@type integer, integer, integer, integer, integer, integer + local _, _, _, end_row, end_col , end_byte = unpack(ts.get_range(last, bufnr, metadata)) + local range = { start_row, start_col, end_row, end_col } + local length = end_byte - start_byte + + if end_col == 0 then + if start_row == end_row then + start_col = -1 + start_row = start_row - 1 + end + end_col = -1 + end_row = end_row - 1 + end + local lines = api.nvim_buf_get_text(bufnr, start_row, end_row, start_col, end_col, {}) + local text = table.concat(lines, '\n') + + local name = query.captures[id] + local path = vim.split(name, '.', { plain = true }) + + local current = match_info ---@type table> + for _, segment in ipairs(path) do + current[segment] = current[segment] or {} + current = current[segment] + end + current.info = { + range = range, + length = length, + last_node = last, + text = text, + } + end + table.insert(out, match_info) + end + + return out +end, buf_root_lang_hash) + +---@param bufnr integer +---@return matchup.treesitter.Match[] +M.get_matches = function(bufnr) + local parser = ts.get_parser(bufnr) + local matches = {} ---@type matchup.treesitter.Match[] if parser then + -- TODO: g:matchup_delim_stopline could be used, but this functions needs to + -- know on which window it should look for in order to get the current + -- cursor position of that window + parser:parse(nil) parser:for_each_tree(function(tree, lang_tree) if not tree or lang_tree:lang() == 'comment' then return end local lang = lang_tree:lang() - local group_results = queries.collect_group_results( - bufnr, 'matchup', tree:root(), lang) or {} + local group_results = get_memoized_matches(bufnr, tree:root(), lang) vim.list_extend(matches, group_results) end) end return matches -end) +end local function _time() - local s, u = vim.loop.gettimeofday() + local s, u = vim.uv.gettimeofday() return s * 1000 + u * 1e-3 end ---- Returns a (mostly) unique id for this node --- Also supports nvim-treesitter's range object -local function _node_id(node) - if not node then - return nil - end - if node:type() == 'nvim-treesitter-range' then - return string.format('range_%d_%d_%d_%d', node:range()) - end - return node:id() +--- Returns a (mostly) unique id for this range +---@param range Range4 +---@return string +function M.range_id(range) + return ('range_%d_%d_%d_%d'):format(unpack(range)) end +-- TODO: mention this in the PR. this is not memoized because: +-- - get_matches is already memoized +-- - this function does not have access to the treesitter root and memoizing by +-- buf_tick is unreliable (buf_tick may be out-of-sync with treesitter changes +-- because of undo, for example) +-- --- Get all nodes belonging to defined scopes (organized by key) -M.get_scopes = ts_utils.memoize_by_buf_tick(function(bufnr) +---@param bufnr integer +---@return table> +M.get_scopes = function(bufnr) local matches = M.get_matches(bufnr) - local scopes = {} + local scopes = {} ---@type table> for _, match in ipairs(matches) do if match.scope then for key, scope in pairs(match.scope) do - local id = _node_id(scope.node) - if scope.node then - if not scopes[key] then - scopes[key] = {} - end + if scope.info then + local id = M.range_id(scope.info.range) + scopes[key] = scopes[key] or {} scopes[key][id] = true end end @@ -90,49 +198,52 @@ M.get_scopes = ts_utils.memoize_by_buf_tick(function(bufnr) end return scopes -end) +end -M.get_active_nodes = ts_utils.memoize_by_buf_tick(function(bufnr) - -- TODO: why do we need to force a parse? - if not pcall(function() parsers.get_parser():parse() end) then - -- TODO workaround a crash due to tree-sitter parsing - return {{ open={}, mid={}, close={} }, {}} - end +---@class matchup.treesitter.Matches +---@field open matchup.treesitter.MatchInfo[] +---@field mid matchup.treesitter.MatchInfo[] +---@field close matchup.treesitter.MatchInfo[] +---@param bufnr integer +---@return [matchup.treesitter.Matches, table] +M.get_active_matches = function(bufnr) local matches = M.get_matches(bufnr) - local nodes = { open = {}, mid = {}, close = {} } + ---@type matchup.treesitter.Matches + local info = { open = {}, mid = {}, close = {} } + ---@type table local symbols = {} + local enable_quotes = vim.g.matchup_treesitter_enable_quotes for _, match in ipairs(matches) do if match.open then for key, open in pairs(match.open) do - local reject = key:find('quote') - and not M.get_option(bufnr, 'enable_quotes') - local id = _node_id(open.node) - if not reject and open.node and symbols[id] == nil then - table.insert(nodes.open, open.node) + local reject = key:find('quote') and not enable_quotes + local id = M.range_id(open.info.range) + if not reject and open.info and symbols[id] == nil then + table.insert(info.open, open.info) symbols[id] = key end end end if match.close then for key, close in pairs(match.close) do - local reject = key:find('quote') - and not M.get_option(bufnr, 'enable_quotes') - local id = _node_id(close.node) - if not reject and close.node and symbols[id] == nil then - table.insert(nodes.close, close.node) + local reject = key:find('quote') and not enable_quotes + local id = M.range_id(close.info.range) + if not reject and close.info and symbols[id] == nil then + table.insert(info.close, close.info) symbols[id] = key end end end if match.mid then for key, mid_group in pairs(match.mid) do + -- TODO: mid type is wrong, fix everywhere for _, mid in pairs(mid_group) do - local id = _node_id(mid.node) - if mid.node and symbols[id] == nil then - table.insert(nodes.mid, mid.node) + local id = M.range_id(mid.info.range) + if mid.info and symbols[id] == nil then + table.insert(info.mid, mid.info) symbols[id] = key end end @@ -140,19 +251,25 @@ M.get_active_nodes = ts_utils.memoize_by_buf_tick(function(bufnr) end end - return {nodes, symbols} -end) + return {info, symbols} +end -function M.containing_scope(node, bufnr, key) +---@param info matchup.treesitter.MatchInfo? +---@param bufnr integer? +---@param key string +---@return TSNode|nil +function M.containing_scope(info, bufnr, key) bufnr = bufnr or api.nvim_get_current_buf() local scopes = M.get_scopes(bufnr) - if not node or not scopes or not scopes[key] then return end + if not info or not scopes or not scopes[key] then return end - local iter_node = node + ---@type TSNode|nil + local iter_node = info.last_node while iter_node ~= nil do - if scopes[key][_node_id(iter_node)] then + ---@diagnostic disable-next-line: missing-fields LuaLS bug + if scopes[key][M.range_id({iter_node:range()})] then return iter_node end iter_node = iter_node:parent() @@ -161,27 +278,35 @@ function M.containing_scope(node, bufnr, key) return nil end -local function _node_text(node, bufnr) - local text = ts.get_node_text(node, bufnr) +---@param info matchup.treesitter.MatchInfo +---@return string +local function text_until_newline(info) + local text = info.text return text:match("([^\n]+).*") end --- Fill in a match result based on a seed node -function M.do_node_result(initial_node, bufnr, opts, side, key) +---@param info matchup.treesitter.MatchInfo +---@param bufnr integer +---@param opts table +---@param side matchup.Side? +---@param key string? +function M.do_match_result(info, bufnr, opts, side, key) if not side or not key then return nil end - local scope = M.containing_scope(initial_node, bufnr, key) + local scope = M.containing_scope(info, bufnr, key) if not scope then return nil end - local row, col, _ = initial_node:start() + ---@type integer, integer + local row, col = unpack(info.range) local result = { type = 'delim_py', - match = _node_text(initial_node, bufnr), + match = text_until_newline(info), side = side, lnum = row + 1, cnum = col + 1, @@ -191,9 +316,9 @@ function M.do_node_result(initial_node, bufnr, opts, side, key) _id = util.uuid4(), } - local info = { + local cached_info = { bufnr = bufnr, - initial_node = initial_node, + info = info, row = row, col = col, key = key, @@ -201,11 +326,36 @@ function M.do_node_result(initial_node, bufnr, opts, side, key) search_range = {scope:range()}, } - cache:set(result._id, info) + cache:set(result._id, cached_info) return result end +---@param info matchup.treesitter.MatchInfo +---@param line integer +---@param col integer +---@return boolean +local function is_in_range(info, line, col) + ---@type integer, integer, integer, integer + local r_start_row, r_start_col, r_end_row, r_end_col = unpack(info.range) + local p_start_row, p_start_col, p_end_row, p_end_col = line, col, line, col + 1 + + if p_start_row < r_start_row then + return false + elseif p_start_row == r_start_row and p_start_col < r_start_col then + return false + end + + if p_end_row > r_end_row then + return false + elseif p_end_row == r_end_row and p_end_col > r_end_col then + return false + end + + return true +end + +---@type table local side_table = { open = {'open'}, mid = {'mid'}, @@ -215,25 +365,32 @@ local side_table = { open_mid = {'mid', 'open'}, } +---@alias matchup.Side 'open'|'mid'|'close'|'both'|'both_all'|'open_mid' +---@alias matchup.Direction 'current'|'next'|'prev' +---@alias matchup.Type 'delim_text'|'delim_all'|'all' + +---@param bufnr integer +---@param opts {direction: matchup.Direction, side: matchup.Side, type: matchup.Type} function M.get_delim(bufnr, opts) if opts.direction == 'current' then -- get current by query - local active_nodes, symbols = unpack(M.get_active_nodes(bufnr)) + local active_matches, symbols = unpack(M.get_active_matches(bufnr)) local cursor = api.nvim_win_get_cursor(0) local smallest_len = 1e31 + ---@type {info: matchup.treesitter.MatchInfo, side: matchup.Side, key: string}|nil local result_info = nil for _, side in ipairs(side_table[opts.side]) do if not(side == 'mid' and vim.g.matchup_delim_nomids > 0) then - for _, node in ipairs(active_nodes[side]) do - if utils2.is_in_node_range(node, cursor[1]-1, cursor[2]) then - local len = ts_utils.node_length(node) + for _, info in ipairs(active_matches[side] --[=[@as matchup.treesitter.MatchInfo[]]=]) do + if is_in_range(info, cursor[1] - 1, cursor[2]) then + local len = info.length if len < smallest_len then smallest_len = len result_info = { - node = node, + info = info, side = side, - key = symbols[_node_id(node)] + key = symbols[M.range_id(info.range)] } end end @@ -242,7 +399,7 @@ function M.get_delim(bufnr, opts) end if result_info then - return M.do_node_result(result_info.node, bufnr, opts, + return M.do_match_result(result_info.info, bufnr, opts, result_info.side, result_info.key) end @@ -253,16 +410,17 @@ function M.get_delim(bufnr, opts) -- look forwards or backwards for an active node local max_col = 1e5 - local active_nodes, symbols = unpack(M.get_active_nodes(bufnr)) + local active_matches, symbols = unpack(M.get_active_matches(bufnr)) local cursor = api.nvim_win_get_cursor(0) local cur_pos = max_col * (cursor[1]-1) + cursor[2] - local closest_node, closest_dist = nil, 1e31 + local closest_match, closest_dist = nil, 1e31 local result_info = {} for _, side in ipairs(side_table[opts.side]) do - for _, node in ipairs(active_nodes[side]) do - local row, col, _ = node:start() + for _, info in ipairs(active_matches[side]--[=[@as matchup.treesitter.MatchInfo[]]=]) do + ---@type integer, integer + local row, col = unpack(info.range) local pos = max_col * row + col if opts.direction == 'next' and pos >= cur_pos @@ -271,61 +429,62 @@ function M.get_delim(bufnr, opts) local dist = math.abs(pos - cur_pos) if dist < closest_dist then closest_dist = dist - closest_node = node - result_info = { side=side, key=symbols[_node_id(node)] } + closest_match = info + result_info = { side=side, key=symbols[M.range_id(info.range)] } end end end end - if closest_node == nil then + if closest_match == nil then return nil end - return M.do_node_result(closest_node, bufnr, opts, + return M.do_match_result(closest_match, bufnr, opts, result_info.side, result_info.key) end function M.get_matching(delim, down, bufnr) down = down > 0 - local info = cache:get(delim._id) or {} - if info.bufnr ~= bufnr then + local cached_info = cache:get(delim._id) or {} + if cached_info.bufnr ~= bufnr then return {} end - local matches = {} + local matches = {} ---@type [string, integer, integer][] - local sides + local sides ---@type ('open'|'mid'|'close')[] if vim.g.matchup_delim_nomids > 0 then sides = down and {'close'} or {'open'} else sides = down and {'mid', 'close'} or {'mid', 'open'} end - local active_nodes, symbols = unpack(M.get_active_nodes(bufnr)) + local active_matches, symbols = unpack(M.get_active_matches(bufnr)) local got_close = false - local stop_time = _time() + vim.fn['matchup#perf#timeout']() + local stop_time = _time() + vim.fn['matchup#perf#timeout']() ---@type number for _, side in ipairs(sides) do - for _, node in ipairs(active_nodes[side]) do - local row, col, _ = node:start() + for _, info in ipairs(active_matches[side]--[=[@as matchup.treesitter.MatchInfo[]]=]) do + ---@type integer, integer + local row, col = unpack(info.range) if _time() > stop_time then return {} end - if info.initial_node ~= node and symbols[_node_id(node)] == info.key - and (down and (row > info.row or row == info.row and col > info.col) - or not down and (row < info.row or row == info.row and col < info.col)) - and (row >= info.search_range[1] - and row <= info.search_range[3]) then + if cached_info.info ~= info and symbols[M.range_id(info.range)] == cached_info.key + and (down and (row > cached_info.row or row == cached_info.row and col > cached_info.col) + or not down and (row < cached_info.row or row == cached_info.row and col < cached_info.col)) + and (row >= cached_info.search_range[1] + and row <= cached_info.search_range[3]) then - local target_scope = M.containing_scope(node, bufnr, info.key) - if info.scope == target_scope then - local text = _node_text(node, bufnr) or '' + local target_scope = M.containing_scope(info, bufnr, cached_info.key) + if cached_info.scope == target_scope then + local text = text_until_newline(info) or '' table.insert(matches, {text, row + 1, col + 1}) if side == 'close' then @@ -343,39 +502,14 @@ function M.get_matching(delim, down, bufnr) -- no stop marker is found, use enclosing scope if down and not got_close then - local row, col, _ = info.scope:end_() + local row, col, _ = cached_info.scope:end_() table.insert(matches, {'', row + 1, col + 1}) end return matches end -local function opt_tbl_for_lang(opt, lang) - local is_table = type(opt) == "table" - if opt and (not is_table or vim.tbl_contains(opt, lang)) then - return true - end - return false -end - -function M.get_option(bufnr, opt_name) - local config = configs.get_module('matchup') or {} - local lang = parsers.get_buf_lang(bufnr) - if (opt_name == 'include_match_words' - or opt_name == 'additional_vim_regex_highlighting' - or opt_name == 'disable_virtual_text' - or opt_name == 'enable_quotes') then - return opt_tbl_for_lang(config[opt_name], lang) - end - error('invalid option ' .. opt_name) -end - function M.attach(bufnr, lang) - if M.get_option(bufnr, 'additional_vim_regex_highlighting') - and api.nvim_buf_get_option(bufnr, 'syntax') == '' then - api.nvim_buf_set_option(bufnr, 'syntax', 'ON') - end - api.nvim_call_function('matchup#ts_engine#attach', {bufnr, lang}) end diff --git a/lua/treesitter-matchup/syntax.lua b/lua/treesitter-matchup/syntax.lua index 1f2f4a7..23b343f 100644 --- a/lua/treesitter-matchup/syntax.lua +++ b/lua/treesitter-matchup/syntax.lua @@ -1,60 +1,37 @@ -if not pcall(require, 'nvim-treesitter') then - return { - is_active = function() return false end, - synID = function(lnum, col, transparent) - return vim.fn.synID(lnum, col, transparent) - end - } -end - local api = vim.api +local vts = vim.treesitter local hl_info = require'treesitter-matchup.third-party.hl-info' -local queries = require'treesitter-matchup.third-party.query' -local ts_utils = require'nvim-treesitter.ts_utils' -local parsers = require'nvim-treesitter.parsers' +local internal = require'treesitter-matchup.internal' local M = {} +---@param bufnr integer? +---@return boolean function M.is_active(bufnr) bufnr = bufnr or api.nvim_get_current_buf() return (hl_info.active() - and api.nvim_buf_get_option(bufnr, 'syntax') == '') + and vim.bo[bufnr].syntax == '') end --- Get all nodes that are marked as skip +---@param bufnr integer function M.get_skips(bufnr) - local matches = queries.get_matches(bufnr, 'matchup') + local matches = internal.get_matches(bufnr) - local skips = {} + local skips = {} ---@type table for _, match in ipairs(matches) do if match.skip then - skips[match.skip.node:id()] = 1 + skips[internal.range_id(match.skip.info.range)] = 1 end end return skips end -local function get_node_at_pos(cursor) - local cursor_range = { cursor[1] - 1, cursor[2] } - - local buf = vim.api.nvim_win_get_buf(0) - local root_lang_tree = parsers.get_parser(buf) - if not root_lang_tree then - return - end - local root = ts_utils.get_root_for_position( - cursor_range[1], cursor_range[2], root_lang_tree) - - if not root then - return - end - - return root:named_descendant_for_range( - cursor_range[1], cursor_range[2], cursor_range[1], cursor_range[2]) -end - +---@param lnum integer +---@param col integer +---@return boolean function M.lang_skip(lnum, col) local bufnr = api.nvim_get_current_buf() local skips = M.get_skips(bufnr) @@ -63,17 +40,22 @@ function M.lang_skip(lnum, col) return false end - local node = get_node_at_pos({lnum, col - 1}) + -- TODO: is lnum - 1 ok? + local node = vts.get_node({pos = {lnum - 1, col - 1}}) if not node then return false end - if skips[node:id()] then + ---@diagnostic disable-next-line: missing-fields LuaLS bug + if skips[internal.range_id({node:range()})] then return true end return false end +---@param lnum integer +---@param col integer +---@param transparent 1|0 function M.synID(lnum, col, transparent) if not M.is_active() then return vim.fn.synID(lnum, col, transparent) diff --git a/lua/treesitter-matchup/third-party/query.lua b/lua/treesitter-matchup/third-party/query.lua deleted file mode 100644 index 5c8960a..0000000 --- a/lua/treesitter-matchup/third-party/query.lua +++ /dev/null @@ -1,394 +0,0 @@ --- From https://github.com/nvim-treesitter/nvim-treesitter --- Copyright 2021 --- licensed under the Apache License 2.0 --- See nvim-treesitter.LICENSE-APACHE-2.0 - -local api = vim.api -local ts = require 'treesitter-matchup.compat' -local tsrange = require "nvim-treesitter.tsrange" -local utils = require "nvim-treesitter.utils" -local parsers = require "nvim-treesitter.parsers" -local caching = require "nvim-treesitter.caching" - -local M = {} - -local EMPTY_ITER = function() end - -do - local query_cache = caching.create_buffer_cache() - - local function update_cached_matches(bufnr, changed_tick, query_group) - query_cache.set(query_group, bufnr, { - tick = changed_tick, - cache = M.collect_group_results(bufnr, query_group) or {}, - }) - end - - function M.get_matches(bufnr, query_group) - bufnr = bufnr or api.nvim_get_current_buf() - local cached_local = query_cache.get(query_group, bufnr) - if not cached_local or api.nvim_buf_get_changedtick(bufnr) > cached_local.tick then - update_cached_matches(bufnr, api.nvim_buf_get_changedtick(bufnr), query_group) - end - - return query_cache.get(query_group, bufnr).cache - end -end - -do - local mt = {} - mt.__index = function(tbl, key) - if rawget(tbl, key) == nil then - rawset(tbl, key, {}) - end - return rawget(tbl, key) - end - - -- cache will auto set the table for each lang if it is nil - local cache = setmetatable({}, mt) - - --- Same as `vim.treesitter.query` except will return cached values - ---@param lang string - ---@param query_name string - function M.get_query(lang, query_name) - if cache[lang][query_name] == nil then - cache[lang][query_name] = ts.get_query(lang, query_name) - end - - return cache[lang][query_name] - end - - --- Invalidates the query file cache. - --- If lang and query_name is both present, will reload for only the lang and query_name. - --- If only lang is present, will reload all query_names for that lang - --- If none are present, will reload everything - ---@param lang string - ---@param query_name string - function M.invalidate_query_cache(lang, query_name) - if lang and query_name then - cache[lang][query_name] = nil - elseif lang and not query_name then - for query_name0, _ in pairs(cache[lang]) do - M.invalidate_query_cache(lang, query_name0) - end - elseif not lang and not query_name then - for lang0, _ in pairs(cache) do - for query_name0, _ in pairs(cache[lang0]) do - M.invalidate_query_cache(lang0, query_name0) - end - end - else - error "Cannot have query_name by itself!" - end - end -end - ---- This function is meant for an autocommand and not to be used. Only use if file is a query file. ----@param fname string -function M.invalidate_query_file(fname) - local fnamemodify = vim.fn.fnamemodify - M.invalidate_query_cache(fnamemodify(fname, ":p:h:t"), fnamemodify(fname, ":t:r")) -end - ----@class QueryInfo ----@field root LanguageTree ----@field source integer ----@field start integer ----@field stop integer - ----@param bufnr integer ----@param query_name string ----@param root LanguageTree ----@param root_lang string|nil ----@return Query|nil, QueryInfo|nil -local function prepare_query(bufnr, query_name, root, root_lang) - local buf_lang = parsers.get_buf_lang(bufnr) - - if not buf_lang then - return - end - - local parser = parsers.get_parser(bufnr, buf_lang) - if not parser then - return - end - - if not root then - local first_tree = parser:trees()[1] - - if first_tree then - root = first_tree:root() - end - end - - if not root then - return - end - - local range = { root:range() } - - if not root_lang then - local lang_tree = parser:language_for_range(range) - - if lang_tree then - root_lang = lang_tree:lang() - end - end - - if not root_lang then - return - end - - local query = M.get_query(root_lang, query_name) - if not query then - return - end - - return query, - { - root = root, - source = bufnr, - start = range[1], - -- The end row is exclusive so we need to add 1 to it. - stop = range[3] + 1, - } -end - -local function get_byte_offset(buf, row, col) - local lines = api.nvim_buf_get_lines(buf, row, row + 1, false) - if #lines < 1 then - return - end - return api.nvim_buf_get_offset(buf, row) + vim.fn.byteidx(lines[1], col) -end - -local function TSRange_from_table(buf, range) - return setmetatable( - { - start_pos = {range[1], range[2], get_byte_offset(buf, range[1], range[2])}, - end_pos = {range[3], range[4], get_byte_offset(buf, range[3], range[4])}, - buf = buf, - [1] = range[1], - [2] = range[2], - [3] = range[3], - [4] = range[4], - }, - tsrange.TSRange) -end - ----@param query Query ----@param bufnr integer ----@param start_row integer ----@param end_row integer -function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row) - -- A function that splits a string on '.' - local function split(string) - local t = {} - for str in string.gmatch(string, "([^.]+)") do - table.insert(t, str) - end - - return t - end - -- Given a path (i.e. a List(String)) this functions inserts value at path - local function insert_to_path(object, path, value) - local curr_obj = object - - for index = 1, (#path - 1) do - if curr_obj[path[index]] == nil then - curr_obj[path[index]] = {} - end - - curr_obj = curr_obj[path[index]] - end - - curr_obj[path[#path]] = value - end - - local matches = query:iter_matches(qnode, bufnr, start_row, end_row, { all = false }) - - local function iterator() - local pattern, match, metadata = matches() - if pattern ~= nil then - local prepared_match = {} - - -- Extract capture names from each match - for id, node in pairs(match) do - local name = query.captures[id] -- name of the capture in the query - if name ~= nil then - local path = split(name .. ".node") - insert_to_path(prepared_match, path, node) - local metadata_path = split(name .. ".metadata") - insert_to_path(prepared_match, metadata_path, metadata[id]) - end - end - - -- Add some predicates for testing - local preds = query.info.patterns[pattern] - if preds then - for _, pred in pairs(preds) do - -- functions - if pred[1] == "set!" and type(pred[2]) == "string" then - insert_to_path(prepared_match, split(pred[2]), pred[3]) - end - if pred[1] == "make-range!" and #pred == 4 then - assert(type(pred[2]) == "string") - local path = pred[2] - insert_to_path( - prepared_match, - split(path .. ".node"), - tsrange.TSRange.from_nodes(bufnr, match[pred[3]], match[pred[4]]) - ) - end - if pred[1] == "offset!" then - local path = type(pred[2]) == "string" and pred[2] or query.captures[pred[2]] - - local offset_node = match[pred[2]] - local range = {offset_node:range()} - local start_row_offset = pred[3] or 0 - local start_col_offset = pred[4] or 0 - local end_row_offset = pred[5] or 0 - local end_col_offset = pred[6] or 0 - - range[1] = range[1] + start_row_offset - range[2] = range[2] + start_col_offset - range[3] = range[3] + end_row_offset - range[4] = range[4] + end_col_offset - - insert_to_path(prepared_match, split(path..'.node'), - TSRange_from_table(bufnr, range)) - end - end - end - - return prepared_match - end - end - return iterator -end - ---- Return all nodes corresponding to a specific capture path (like @definition.var, @reference.type) ----Works like M.get_references or M.get_scopes except you can choose the capture ----Can also be a nested capture like @definition.function to get all nodes defining a function. ---- ----@param bufnr integer the buffer ----@param captures string|string[] ----@param query_group string the name of query group (highlights or injections for example) ----@param root LanguageTree|nil node from where to start the search ----@param lang string|nil the language from where to get the captures. ---- Root nodes can have several languages. ----@return table|nil -function M.get_capture_matches(bufnr, captures, query_group, root, lang) - if type(captures) == "string" then - captures = { captures } - end - local strip_captures = {} - for i, capture in ipairs(captures) do - if capture:sub(1, 1) ~= "@" then - error 'Captures must start with "@"' - return - end - -- Remove leading "@". - strip_captures[i] = capture:sub(2) - end - - local matches = {} - for match in M.iter_group_results(bufnr, query_group, root, lang) do - for _, capture in ipairs(strip_captures) do - local insert = utils.get_at_path(match, capture) - if insert then - table.insert(matches, insert) - end - end - end - return matches -end - -function M.iter_captures(bufnr, query_name, root, lang) - local query, params = prepare_query(bufnr, query_name, root, lang) - if not query then - return EMPTY_ITER - end - assert(params) - - local iter = query:iter_captures(params.root, params.source, params.start, params.stop) - - local function wrapped_iter() - local id, node, metadata = iter() - if not id then - return - end - - local name = query.captures[id] - if string.sub(name, 1, 1) == "_" then - return wrapped_iter() - end - - return name, node, metadata - end - - return wrapped_iter -end - ----Iterates matches from a query file. ----@param bufnr integer the buffer ----@param query_group string the query file to use ----@param root LanguageTree the root node ----@param root_lang string|nil the root node lang, if known -function M.iter_group_results(bufnr, query_group, root, root_lang) - local query, params = prepare_query(bufnr, query_group, root, root_lang) - if not query then - return EMPTY_ITER - end - assert(params) - - return M.iter_prepared_matches(query, params.root, params.source, params.start, params.stop) -end - -function M.collect_group_results(bufnr, query_group, root, lang) - local matches = {} - - for prepared_match in M.iter_group_results(bufnr, query_group, root, lang) do - table.insert(matches, prepared_match) - end - - return matches -end - ----@alias CaptureResFn function(string, LanguageTree, LanguageTree): string, string - ---- Same as get_capture_matches except this will recursively get matches for every language in the tree. ----@param bufnr integer The bufnr ----@param capture_or_fn string|CaptureResFn The capture to get. If a function is provided then that ---- function will be used to resolve both the capture and query argument. ---- The function can return `nil` to ignore that tree. ----@param query_type string The query to get the capture from. This is ignore if a function is provided ---- for the captuer argument. -function M.get_capture_matches_recursively(bufnr, capture_or_fn, query_type) - ---@type CaptureResFn - local type_fn - if type(capture_or_fn) == "function" then - type_fn = capture_or_fn - else - type_fn = function(_, _, _) - return capture_or_fn, query_type - end - end - local parser = parsers.get_parser(bufnr) - local matches = {} - - if parser then - parser:for_each_tree(function(tree, lang_tree) - local lang = lang_tree:lang() - local capture, type_ = type_fn(lang, tree, lang_tree) - - if capture then - vim.list_extend(matches, M.get_capture_matches(bufnr, capture, type_, tree:root(), lang)) - end - end) - end - - return matches -end - -return M diff --git a/lua/treesitter-matchup/third-party/ts-utils.lua b/lua/treesitter-matchup/third-party/ts-utils.lua new file mode 100644 index 0000000..41db334 --- /dev/null +++ b/lua/treesitter-matchup/third-party/ts-utils.lua @@ -0,0 +1,28 @@ +-- From https://github.com/nvim-treesitter/nvim-treesitter +-- Copyright 2021 +-- licensed under the Apache License 2.0 +-- See nvim-treesitter.LICENSE-APACHE-2.0 + +local M = {} + +---Memoize a function using hash_fn to hash the arguments. +---@generic F: function +---@param fn F +---@param hash_fn fun(...): any +---@return F +function M.memoize(fn, hash_fn) + local cache = setmetatable({}, { __mode = 'kv' }) ---@type table + + return function(...) + local key = hash_fn(...) + if cache[key] == nil then + local v = fn(...) ---@type any + cache[key] = v ~= nil and v or vim.NIL + end + + local v = cache[key] + return v ~= vim.NIL and v or nil + end +end + +return M From bca8246d2a60b6f3e9bbc2187e4b8134f44cc5b1 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Thu, 29 May 2025 13:32:52 -0500 Subject: [PATCH 05/29] chore: format query files --- after/queries/bash/matchup.scm | 11 +- after/queries/c/matchup.scm | 49 ++++++--- after/queries/ecma/matchup.scm | 49 ++++++--- after/queries/elm/matchup.scm | 9 +- after/queries/fish/matchup.scm | 22 ++-- after/queries/glimmer/matchup.scm | 37 ++++--- after/queries/go/matchup.scm | 31 ++++-- after/queries/haskell/matchup.scm | 73 ++++++------- after/queries/html/matchup.scm | 10 +- after/queries/jsx/matchup.scm | 5 +- after/queries/julia/matchup.scm | 16 ++- after/queries/lua/matchup.scm | 9 +- after/queries/nim/matchup.scm | 150 ++++++++++++++++++++------- after/queries/nix/matchup.scm | 10 +- after/queries/perl/matchup.scm | 54 +++++++--- after/queries/python/matchup.scm | 32 ++++-- after/queries/ruby/matchup.scm | 40 +++++-- after/queries/rust/matchup.scm | 62 ++++++++--- after/queries/smali/matchup.scm | 2 +- after/queries/svelte/matchup.scm | 14 +-- after/queries/templ/matchup.scm | 28 ++--- after/queries/typescript/matchup.scm | 20 +++- after/queries/vim/matchup.scm | 12 ++- after/queries/vue/matchup.scm | 12 ++- after/queries/zig/matchup.scm | 18 +++- 25 files changed, 525 insertions(+), 250 deletions(-) diff --git a/after/queries/bash/matchup.scm b/after/queries/bash/matchup.scm index a5d0d57..aa75e89 100644 --- a/after/queries/bash/matchup.scm +++ b/after/queries/bash/matchup.scm @@ -20,8 +20,11 @@ (do_group "done" @close.loop)) @scope.loop -((word) @mid.loop.1 (#eq? @mid.loop.1 "break")) -((word) @mid.loop.2 (#eq? @mid.loop.2 "continue")) +((word) @mid.loop.1 + (#eq? @mid.loop.1 "break")) + +((word) @mid.loop.2 + (#eq? @mid.loop.2 "continue")) (case_statement "case" @open.case @@ -29,5 +32,5 @@ "esac" @close.case) @scope.case (heredoc_redirect - (heredoc_start) @open.rhrd - (heredoc_end ) @close.rhrd) @scope.rhrd + (heredoc_start) @open.rhrd + (heredoc_end) @close.rhrd) @scope.rhrd diff --git a/after/queries/c/matchup.scm b/after/queries/c/matchup.scm index 74668cd..2cb592e 100644 --- a/after/queries/c/matchup.scm +++ b/after/queries/c/matchup.scm @@ -1,31 +1,40 @@ ; inherits: quote (preproc_ifdef - ["#ifdef" "#ifndef"] @open.def + [ + "#ifdef" + "#ifndef" + ] @open.def "#endif" @close.def) @scope.def (preproc_if "#if" @open.def "#endif" @close.def) @scope.def -(preproc_elif "#elif" @mid.def.1) -(preproc_else "#else" @mid.def.2) +(preproc_elif + "#elif" @mid.def.1) + +(preproc_else + "#else" @mid.def.2) (switch_statement "switch" @open.switch body: (compound_statement - (case_statement "case" @mid.switch.1)? - (case_statement "default" @mid.switch.2)?)) @scope.switch + (case_statement + "case" @mid.switch.1)? + (case_statement + "default" @mid.switch.2)?)) @scope.switch ; 'else' and 'else if' (else_clause - "else" @mid.if.1 (if_statement "if" @mid.if.1)?) + "else" @mid.if.1 + (if_statement + "if" @mid.if.1)?) ; if ((if_statement "if" @open.if) @scope.if - (#not-has-parent? @scope.if else_clause)) - + (#not-has-parent? @scope.if else_clause)) ; if (compound_statement @@ -34,11 +43,23 @@ ; Functions (function_definition) @scope.function -(function_declarator declarator: (identifier) @open.function) -(return_statement "return" @mid.function.1) + +(function_declarator + declarator: (identifier) @open.function) + +(return_statement + "return" @mid.function.1) ; Loops -(for_statement "for" @open.loop) @scope.loop -(while_statement "while" @open.loop) @scope.loop -(do_statement "do" @open.loop "while" @close.loop) @scope.loop -(break_statement "break" @mid.loop.1) +(for_statement + "for" @open.loop) @scope.loop + +(while_statement + "while" @open.loop) @scope.loop + +(do_statement + "do" @open.loop + "while" @close.loop) @scope.loop + +(break_statement + "break" @mid.loop.1) diff --git a/after/queries/ecma/matchup.scm b/after/queries/ecma/matchup.scm index 3259186..f5a604b 100644 --- a/after/queries/ecma/matchup.scm +++ b/after/queries/ecma/matchup.scm @@ -2,34 +2,53 @@ ; functions [ - (arrow_function "=>" @open.function) - (function_expression "function" @open.function) - (function_declaration "function" @open.function) - (method_definition body: (statement_block "{" @open.function "}" @close.function)) + (arrow_function + "=>" @open.function) + (function_expression + "function" @open.function) + (function_declaration + "function" @open.function) + (method_definition + body: (statement_block + "{" @open.function + "}" @close.function)) ] @scope.function -(return_statement "return" @mid.function.1) +(return_statement + "return" @mid.function.1) ; switch case -(switch_statement "switch" @open.switch) @scope.switch -(switch_case "case" @mid.switch.1) -(switch_default "default" @mid.switch.2) +(switch_statement + "switch" @open.switch) @scope.switch + +(switch_case + "case" @mid.switch.1) + +(switch_default + "default" @mid.switch.2) ; 'else' and 'else if' (else_clause - "else" @mid.if.1 (if_statement "if" @mid.if.1)?) + "else" @mid.if.1 + (if_statement + "if" @mid.if.1)?) ; if ((if_statement "if" @open.if) @scope.if - (#not-has-parent? @scope.if else_clause)) + (#not-has-parent? @scope.if else_clause)) ; try -(try_statement "try" @open.try) @scope.try -(catch_clause "catch" @mid.try.1) -(finally_clause "finally" @mid.try.2) +(try_statement + "try" @open.try) @scope.try + +(catch_clause + "catch" @mid.try.1) + +(finally_clause + "finally" @mid.try.2) ; template strings (template_string - "`" @open.tmpl_str - "`" @close.tmpl_str) @scope.tmpl_str + "`" @open.tmpl_str + "`" @close.tmpl_str) @scope.tmpl_str diff --git a/after/queries/elm/matchup.scm b/after/queries/elm/matchup.scm index 1411f9e..43b0f27 100644 --- a/after/queries/elm/matchup.scm +++ b/after/queries/elm/matchup.scm @@ -1,9 +1,10 @@ (if_else_expr - . "if" @open.if) @scope.if + . + "if" @open.if) @scope.if (if_else_expr - "else" @mid.if.1 - "if"? @mid.if.1) + "else" @mid.if.1 + "if"? @mid.if.1) (let_in_expr "let" @open.let @@ -12,4 +13,4 @@ (case_of_expr (case) @open.case (case_of_branch - (arrow) @mid.case.1)) @scope.case + (arrow) @mid.case.1)) @scope.case diff --git a/after/queries/fish/matchup.scm b/after/queries/fish/matchup.scm index 3d98879..95a4727 100644 --- a/after/queries/fish/matchup.scm +++ b/after/queries/fish/matchup.scm @@ -1,21 +1,25 @@ (if_statement "if" @open.if - (else_if_clause ("else" "if") @mid.if.1)? - (else_clause "else" @mid.if.2)? - "end" @close.if - ) @scope.if + (else_if_clause + ("else" + "if") @mid.if.1)? + (else_clause + "else" @mid.if.2)? + "end" @close.if) @scope.if (switch_statement "switch" @open.switch - (case_clause "case" @mid.switch.1)? - "end" @close.switch - ) @scope.switch + (case_clause + "case" @mid.switch.1)? + "end" @close.switch) @scope.switch (for_statement "for" @open.loop "in" @mid.loop.1 "end" @close.loop) @scope.loop + ((break) @mid.loop.2)? + ((continue) @mid.loop.3)? (while_statement @@ -29,4 +33,6 @@ (function_definition "function" @open.func "end" @close.func) @scope.func -(return "return" @mid.func.1) + +(return + "return" @mid.func.1) diff --git a/after/queries/glimmer/matchup.scm b/after/queries/glimmer/matchup.scm index bf55ff3..7fb950a 100644 --- a/after/queries/glimmer/matchup.scm +++ b/after/queries/glimmer/matchup.scm @@ -2,33 +2,42 @@ (element_node) @scope.tag -(element_node_start (tag_name) @open.tag) +(element_node_start + (tag_name) @open.tag) + (element_node_end (tag_name) @close.tag (#offset! @close.tag 0 -1 0 0)) (block_statement (block_statement_start) @open.block - (block_statement_end) @close.block - ) @scope.block + (block_statement_end) @close.block) @scope.block + ; {{else if ...}} (mustache_statement - (helper_invocation helper: (identifier) @mid.block.1 (#lua-match? @mid.block.1 "else")) - ) + (helper_invocation + helper: (identifier) @mid.block.1 + (#lua-match? @mid.block.1 "else"))) + ; {{else}} -(mustache_statement ((identifier) @mid.block.2 (#lua-match? @mid.block.2 "else"))) +(mustache_statement + ((identifier) @mid.block.2 + (#lua-match? @mid.block.2 "else"))) (element_node_void (tag_name) @open.selftag - "/>" @close.selftag - ) @scope.selftag + "/>" @close.selftag) @scope.selftag (mustache_statement - [(helper_invocation) "{{"] @open.mustache - "}}" @close.mustache - ) @scope.mustache + [ + (helper_invocation) + "{{" + ] @open.mustache + "}}" @close.mustache) @scope.mustache (sub_expression - [(helper_invocation) "("] @open.subexpr - ")" @close.subexpr - ) @scope.subexpr + [ + (helper_invocation) + "(" + ] @open.subexpr + ")" @close.subexpr) @scope.subexpr diff --git a/after/queries/go/matchup.scm b/after/queries/go/matchup.scm index 9058c18..ddcefcd 100644 --- a/after/queries/go/matchup.scm +++ b/after/queries/go/matchup.scm @@ -1,20 +1,33 @@ -(function_declaration "func" @open.func) @scope.func -(method_declaration "func" @open.func) @scope.func -(func_literal "func" @open.func) @scope.func +(function_declaration + "func" @open.func) @scope.func -(return_statement "return" @mid.func.1) +(method_declaration + "func" @open.func) @scope.func + +(func_literal + "func" @open.func) @scope.func + +(return_statement + "return" @mid.func.1) ; 'else' and 'else if' (if_statement - "else" @mid.if.1 (if_statement "if" @mid.if.1)?) + "else" @mid.if.1 + (if_statement + "if" @mid.if.1)?) ; if -(block (if_statement "if" @open.if) @scope.if) +(block + (if_statement + "if" @open.if) @scope.if) ; switch -(expression_switch_statement "switch" @open.switch - (expression_case "case" @mid.switch.1) - (default_case "default" @mid.switch.2)) @scope.switch +(expression_switch_statement + "switch" @open.switch + (expression_case + "case" @mid.switch.1) + (default_case + "default" @mid.switch.2)) @scope.switch (_ "\"" @open.quote_double diff --git a/after/queries/haskell/matchup.scm b/after/queries/haskell/matchup.scm index bba5693..bddcdde 100644 --- a/after/queries/haskell/matchup.scm +++ b/after/queries/haskell/matchup.scm @@ -1,73 +1,67 @@ ; --------------- module/where --------------- -(header ( - ("module" @open.module (module)) - ("where" @mid.module.1) -)) @scope.module +(header + (("module" @open.module + (module)) + "where" @mid.module.1)) @scope.module ; ----------------- case/of ------------------ -(expression/case - "case" @open.case (_) +(expression/case + "case" @open.case + (_) "of" @mid.case.1 (alternatives - (alternative) @mid.case.2 - ) -) @scope.case + (alternative) @mid.case.2)) @scope.case ; --------------- lambda case ---------------- -(expression/lambda_case +(expression/lambda_case "\case" @open.case (alternatives - (alternative) @mid.case.1 - ) -) @scope.case + (alternative) @mid.case.1)) @scope.case ; -------------- if/then/else ---------------- (expression/conditional - "if" @open.if (_) - "then" @mid.if.1 (_) - "else" @mid.if.2 (_) -) @scope.if + "if" @open.if + (_) + "then" @mid.if.1 + (_) + "else" @mid.if.2 + (_)) @scope.if ;------------------ let/in ------------------- (expression/let_in - ("let" @open.let (local_binds)) - ("in" @mid.let.1 (_)) -) @scope.let + ("let" @open.let + (local_binds)) + ("in" @mid.let.1 + (_))) @scope.let ; -------- ADT data/constructors ------------- (data_type - "data" @open.adt (_) + "data" @open.adt + (_) constructors: (data_constructors (data_constructor - constructor: (_) @mid.adt.2 - )) -) @scope.adt + constructor: (_) @mid.adt.2))) @scope.adt ; --------------- ADT record ------------------ (data_type - "data" @open.rec (_) + "data" @open.rec + (_) constructors: (data_constructors constructor: (data_constructor (record fields: (fields - "{" @mid.rec.1 (_) - "}" @mid.rec.2 - ) - ) - ) - ) -) @scope.rec + "{" @mid.rec.1 + (_) + "}" @mid.rec.2))))) @scope.rec ; ------------- GADT data/where --------------- (data_type - "data" @open.gadt (_) + "data" @open.gadt + (_) "where" @mid.gadt.1 constructors: (gadt_constructors constructor: (gadt_constructor - name: (constructor) @mid.gadt.2 - ) - ) -) @scope.gadt + name: (constructor) @mid.gadt.2))) @scope.gadt ; --------------- class/where ----------------- (class @@ -75,7 +69,4 @@ "where" @mid.class.1 declarations: (class_declarations declaration: (_ - name: (variable) @mid.class.2 - ) - ) -) @scope.class + name: (variable) @mid.class.2))) @scope.class diff --git a/after/queries/html/matchup.scm b/after/queries/html/matchup.scm index 735233f..1b5b79f 100644 --- a/after/queries/html/matchup.scm +++ b/after/queries/html/matchup.scm @@ -1,12 +1,14 @@ ; inherits: quote [ - (element) - (script_element) - (style_element) + (element) + (script_element) + (style_element) ] @scope.tag -(start_tag (tag_name) @open.tag) +(start_tag + (tag_name) @open.tag) + (end_tag (tag_name) @close.tag (#offset! @close.tag 0 -1 0 0)) diff --git a/after/queries/jsx/matchup.scm b/after/queries/jsx/matchup.scm index 1005926..31c5bcc 100644 --- a/after/queries/jsx/matchup.scm +++ b/after/queries/jsx/matchup.scm @@ -1,5 +1,8 @@ (jsx_element) @scope.tag -(jsx_opening_element (identifier) @open.tag) + +(jsx_opening_element + (identifier) @open.tag) + (jsx_closing_element (identifier) @close.tag (#offset! @close.tag 0 -1 0 0)) diff --git a/after/queries/julia/matchup.scm b/after/queries/julia/matchup.scm index d997fc3..636d5ee 100644 --- a/after/queries/julia/matchup.scm +++ b/after/queries/julia/matchup.scm @@ -1,35 +1,46 @@ (function_definition "function" @open.function "end" @close.function) @scope.function + (return_statement "return" @mid.function.1) (if_statement "if" @open.if "end" @close.if) @scope.if -(else_clause "else" @mid.if.1) -(elseif_clause "elseif" @mid.if.2) + +(else_clause + "else" @mid.if.1) + +(elseif_clause + "elseif" @mid.if.2) (for_statement "for" @open.loop "end" @close.loop) @scope.loop + (while_statement "while" @open.loop "end" @close.loop) @scope.loop + (break_statement) @mid.loop.1 + (continue_statement) @mid.loop.2 (try_statement "try" @open.try "end" @close.try) @scope.try + (catch_clause "catch" @mid.try.2) + (finally_clause "finally" @mid.try.1) (compound_statement "begin" @open.block "end" @close.block) @scope.block + (do_clause "do" @open.block "end" @close.block) @scope.block @@ -37,6 +48,7 @@ (let_statement "let" @open.let "end" @close.let) @scope.let + (module_definition "module" @open.module "end" @close.module) @scope.module diff --git a/after/queries/lua/matchup.scm b/after/queries/lua/matchup.scm index b75d554..7579f98 100644 --- a/after/queries/lua/matchup.scm +++ b/after/queries/lua/matchup.scm @@ -13,12 +13,17 @@ (if_statement "if" @open.if "end" @close.if) @scope.if -(else_statement "else" @mid.if.1) -(elseif_statement "elseif" @mid.if.2) + +(else_statement + "else" @mid.if.1) + +(elseif_statement + "elseif" @mid.if.2) (function_declaration "function" @open.function "end" @close.function) @scope.function + (function_definition "function" @open.function "end" @close.function) @scope.function diff --git a/after/queries/nim/matchup.scm b/after/queries/nim/matchup.scm index 2bbb553..3082d78 100644 --- a/after/queries/nim/matchup.scm +++ b/after/queries/nim/matchup.scm @@ -1,40 +1,87 @@ -(for "for" @open.loop "in" @mid.loop.3) @scope.loop -(while "while" @open.loop) @scope.loop -(block "block" @open.loop) @scope.loop -(break_statement "break" @mid.loop.1) -(continue_statement "continue" @mid.loop.2) +(for + "for" @open.loop + "in" @mid.loop.3) @scope.loop -(if "if" @open.conditional) @scope.conditional -(when "when" @open.conditional) @scope.conditional -(elif_branch "elif" @mid.conditional.1) -(else_branch "else" @mid.conditional.2) +(while + "while" @open.loop) @scope.loop -(case "case" @open.conditional) @scope.conditional -(of_branch "of" @mid.conditional.3) +(block + "block" @open.loop) @scope.loop -(variant_declaration "case" @open.conditional) @scope.conditional -(conditional_declaration "when" @open.conditional) @scope.conditional +(break_statement + "break" @mid.loop.1) -(try "try" @open.try) @scope.try -(except_branch "except" @mid.try.1 ) -(finally_branch "finally" @mid.try.2) +(continue_statement + "continue" @mid.loop.2) -(proc_declaration "proc" @open.routine) @scope.routine -(func_declaration "func" @open.routine) @scope.routine -(method_declaration "method" @open.routine) @scope.routine -(converter_declaration "converter" @open.routine) @scope.routine -(template_declaration "template" @open.routine) @scope.routine -(macro_declaration "macro" @open.routine) @scope.routine +(if + "if" @open.conditional) @scope.conditional -(proc_expression "proc" @open.routine) @scope.routine -(func_expression "func" @open.routine) @scope.routine +(when + "when" @open.conditional) @scope.conditional -(return_statement "return" @mid.routine.1) +(elif_branch + "elif" @mid.conditional.1) -(iterator_declaration "iterator" @open.iterator) @scope.iterator -(iterator_expression "iterator" @open.iterator) @scope.iterator +(else_branch + "else" @mid.conditional.2) -(yield_statement "yield" @mid.iterator.1) +(case + "case" @open.conditional) @scope.conditional + +(of_branch + "of" @mid.conditional.3) + +(variant_declaration + "case" @open.conditional) @scope.conditional + +(conditional_declaration + "when" @open.conditional) @scope.conditional + +(try + "try" @open.try) @scope.try + +(except_branch + "except" @mid.try.1) + +(finally_branch + "finally" @mid.try.2) + +(proc_declaration + "proc" @open.routine) @scope.routine + +(func_declaration + "func" @open.routine) @scope.routine + +(method_declaration + "method" @open.routine) @scope.routine + +(converter_declaration + "converter" @open.routine) @scope.routine + +(template_declaration + "template" @open.routine) @scope.routine + +(macro_declaration + "macro" @open.routine) @scope.routine + +(proc_expression + "proc" @open.routine) @scope.routine + +(func_expression + "func" @open.routine) @scope.routine + +(return_statement + "return" @mid.routine.1) + +(iterator_declaration + "iterator" @open.iterator) @scope.iterator + +(iterator_expression + "iterator" @open.iterator) @scope.iterator + +(yield_statement + "yield" @mid.iterator.1) (import_statement "import" @open.import @@ -46,38 +93,63 @@ "import" @mid.from.1) @scope.from (char_literal - . "'" @open.char + . + "'" @open.char "'" @close.char .) @scope.char (interpreted_string_literal - . "\"" @open.string + . + "\"" @open.string "\"" @close.string .) @scope.string (raw_string_literal - . ["r\"" "R\""] @open.string + . + [ + "r\"" + "R\"" + ] @open.string "\"" @close.string .) @scope.string (long_string_literal - . ["\"\"\"" "r\"\"\"" "R\"\"\""] @open.multistring + . + [ + "\"\"\"" + "r\"\"\"" + "R\"\"\"" + ] @open.multistring "\"\"\"" @close.multistring .) @scope.multistring (generalized_string function: (_) - . ["\"" "\"\"\""] @open.multistring - ["\"" "\"\"\""] @close.multistring .) @scope.multistring + . + [ + "\"" + "\"\"\"" + ] @open.multistring + [ + "\"" + "\"\"\"" + ] @close.multistring .) @scope.multistring (accent_quoted - . "`" @open.accent + . + "`" @open.accent "`" @close.accent .) @scope.accent (block_documentation_comment - . "##[" @open.doc_comment + . + "##[" @open.doc_comment "]##" @close.doc_comment .) @scope.doc_comment (block_comment - . "#[" @open.comment + . + "#[" @open.comment "]#" @close.comment .) @scope.comment (pragma_list - . "{." @open.pragma - ["}" ".}"] @close.pragma .) @scope.pragma + . + "{." @open.pragma + [ + "}" + ".}" + ] @close.pragma .) @scope.pragma diff --git a/after/queries/nix/matchup.scm b/after/queries/nix/matchup.scm index c7cc2f5..bfa399a 100644 --- a/after/queries/nix/matchup.scm +++ b/after/queries/nix/matchup.scm @@ -1,9 +1,13 @@ ; --------------- let/in --------------- (let_expression - "let" @open.let (binding_set) - "in" @mid.let.1 (_)) @scope.let + "let" @open.let + (binding_set) + "in" @mid.let.1 + (_)) @scope.let + ; --------------- binding -------------- ; (binding (_)+ (function_exppression) ";") tend to be many lines long (binding - (attrpath) @open.binding (function_expression) + (attrpath) @open.binding + (function_expression) ";" @close.binding) @scope.binding diff --git a/after/queries/perl/matchup.scm b/after/queries/perl/matchup.scm index e95ce2d..fedae80 100644 --- a/after/queries/perl/matchup.scm +++ b/after/queries/perl/matchup.scm @@ -1,34 +1,54 @@ ; matches any conditional -> else type block -> end of final block (conditional_statement - ["if" "unless"] @open.if - "elsif"? @mid.if.1 - "else"? @mid.if.2 - (block "}" @close.if) . -) @scope.if + [ + "if" + "unless" + ] @open.if + "elsif"? @mid.if.1 + "else"? @mid.if.2 + (block + "}" @close.if) .) @scope.if ; matches any loop construct -> loop control (last, next) -> end of final block (_ - ["for" "foreach" "while" "unless"] @open.loop - (block "}" @close.loop) . - ) @scope.loop + [ + "for" + "foreach" + "while" + "unless" + ] @open.loop + (block + "}" @close.loop) .) @scope.loop + (loopex_expression) @mid.loop.1 ; matches sub -> return -> end of block (_ - "sub" @open.fun - (block "}" @close.fun) . -) @scope.fun -(return_expression "return" @mid.fun.1) + "sub" @open.fun + (block + "}" @close.fun) .) @scope.fun + +(return_expression + "return" @mid.fun.1) ; handling for all the different quote types; multi part quotes cycle through [ - (_ "'" @open.quotelike (string_content) "'" @close.quotelike) - (quoted_regexp "'" @open.quotelike "'" @close.quotelike) - (_ "'" @open.quotelike (_) "'"+ @mid.quotelike.1 (replacement) "'" @close.quotelike) + (_ + "'" @open.quotelike + (string_content) + "'" @close.quotelike) + (quoted_regexp + "'" @open.quotelike + "'" @close.quotelike) + (_ + "'" @open.quotelike + (_) + "'"+ @mid.quotelike.1 + (replacement) + "'" @close.quotelike) ] @scope.quotelike (try_statement "try" @open.try "catch"? @mid.try.1 - "finally"? @close.try - ) @scope.try + "finally"? @close.try) @scope.try diff --git a/after/queries/python/matchup.scm b/after/queries/python/matchup.scm index 16642fd..8acd92a 100644 --- a/after/queries/python/matchup.scm +++ b/after/queries/python/matchup.scm @@ -1,31 +1,43 @@ (if_statement "if" @open.if - alternative: (elif_clause "elif" @mid.if.1)? - alternative: (else_clause "else" @mid.if.2)?) @scope.if + alternative: (elif_clause + "elif" @mid.if.1)? + alternative: (else_clause + "else" @mid.if.2)?) @scope.if (function_definition "def" @open.function) @scope.function + (return_statement "return" @mid.function.1) + (yield "yield" @mid.function.2) (for_statement - ("for" @open.loop) - alternative: (else_clause "else" @mid.loop.1)?) @scope.loop + "for" @open.loop + alternative: (else_clause + "else" @mid.loop.1)?) @scope.loop + (while_statement - ("while" @open.loop) - alternative: (else_clause "else" @mid.loop.1)?) @scope.loop + "while" @open.loop + alternative: (else_clause + "else" @mid.loop.1)?) @scope.loop + (break_statement "break" @mid.loop.2) + (continue_statement "continue" @mid.loop.3) (try_statement - ("try" @open.try) - (finally_clause "finally" @mid.try.1)? - (except_clause "except" @mid.try.2)? - (else_clause "else" @mid.try.3)?) @scope.try + "try" @open.try + (finally_clause + "finally" @mid.try.1)? + (except_clause + "except" @mid.try.2)? + (else_clause + "else" @mid.try.3)?) @scope.try (string (string_start) @open.quote_all diff --git a/after/queries/ruby/matchup.scm b/after/queries/ruby/matchup.scm index eea6801..45b81e3 100644 --- a/after/queries/ruby/matchup.scm +++ b/after/queries/ruby/matchup.scm @@ -1,49 +1,65 @@ (method "def" @open.def "end" @close.def) @scope.def + (singleton_method "def" @open.def "end" @close.def) @scope.def + (return "return" @mid.def.1) + (yield "yield" @mid.def.2) + (body_statement - (rescue "rescue" @mid.def.1)) + (rescue + "rescue" @mid.def.1)) + (body_statement - (ensure "ensure" @mid.def.2)) + (ensure + "ensure" @mid.def.2)) (class "class" @open.class "end" @close.class) @scope.class + (singleton_class "class" @open.class "end" @close.class) @scope.class (if "if" @open.if - (else "else" @mid.if.2)? + (else + "else" @mid.if.2)? "end" @close.if) @scope.if -(elsif (else "else" @mid.if.2)) -(elsif "elsif" @mid.if.1) + +(elsif + (else + "else" @mid.if.2)) + +(elsif + "elsif" @mid.if.1) (unless "unless" @open.unless - (else "else" @mid.unless.2)? + (else + "else" @mid.unless.2)? "end" @close.unless) @scope.unless (while - "while" @open.loop + "while" @open.loop body: (do "end" @close.loop)) @scope.loop (for - "for" @open.loop + "for" @open.loop body: (do "end" @close.loop)) @scope.loop (next "next" @mid.loop.1)? + (break "break" @mid.loop.2)? @@ -72,12 +88,14 @@ "end" @close.do) @scope.do (if_modifier) @skip + (unless_modifier) @skip + (while_modifier) @skip + (until_modifier) @skip (block_parameters - ("|") @open.block_param + "|" @open.block_param (_) - ("|") @close.block_param -) @scope.block_param + "|" @close.block_param) @scope.block_param diff --git a/after/queries/rust/matchup.scm b/after/queries/rust/matchup.scm index a77ad56..bedd707 100644 --- a/after/queries/rust/matchup.scm +++ b/after/queries/rust/matchup.scm @@ -10,23 +10,44 @@ ">" @open.typeparams) @scope.typeparams ; --------------- if/else --------------- -(block (if_expression "if" @open.if_) @scope.if_) -(expression_statement (if_expression "if" @open.if_) @scope.if_) -(let_declaration (if_expression "if" @open.if_) @scope.if_) +(block + (if_expression + "if" @open.if_) @scope.if_) + +(expression_statement + (if_expression + "if" @open.if_) @scope.if_) + +(let_declaration + (if_expression + "if" @open.if_) @scope.if_) -(else_clause "else" @mid.if_.1 (block)) (else_clause - "else" @mid.if_.2 (if_expression "if" @mid.if_.2)) + "else" @mid.if_.1 + (block)) + +(else_clause + "else" @mid.if_.2 + (if_expression + "if" @mid.if_.2)) ; --------------- async/await --------------- -(function_item (function_modifiers "async" @open.async)) @scope.async -(async_block "async" @open.async) @scope.async -(await_expression "await" @mid.async.1) +(function_item + (function_modifiers + "async" @open.async)) @scope.async + +(async_block + "async" @open.async) @scope.async + +(await_expression + "await" @mid.async.1) ; --------------- fn/return --------------- (function_item "fn" @open.function) @scope.function + (closure_expression) @scope.function + (return_expression "return" @mid.function.1) @@ -36,10 +57,23 @@ "|" @close.closureparams) @scope.closureparams ; --------------- while/loop/for + break/continue --------------- -(for_expression . "for" @open.loop) @scope.loop -(while_expression . "while" @open.loop) @scope.loop -(loop_expression . "loop" @open.loop) @scope.loop +(for_expression + . + "for" @open.loop) @scope.loop -(break_expression "break" @mid.loop.1 .) -(break_expression "break" @mid.loop.1 .) -(continue_expression "continue" @mid.loop.1 .) +(while_expression + . + "while" @open.loop) @scope.loop + +(loop_expression + . + "loop" @open.loop) @scope.loop + +(break_expression + "break" @mid.loop.1 .) + +(break_expression + "break" @mid.loop.1 .) + +(continue_expression + "continue" @mid.loop.1 .) diff --git a/after/queries/smali/matchup.scm b/after/queries/smali/matchup.scm index 52174d0..5c9e1d5 100644 --- a/after/queries/smali/matchup.scm +++ b/after/queries/smali/matchup.scm @@ -1,6 +1,6 @@ ; inherits: quote -(method_definition +(method_definition ".method" @open.function ".end method" @close.function) @scope.function diff --git a/after/queries/svelte/matchup.scm b/after/queries/svelte/matchup.scm index 7e29e20..dac61f5 100644 --- a/after/queries/svelte/matchup.scm +++ b/after/queries/svelte/matchup.scm @@ -1,12 +1,13 @@ ; inherits: quote [ - (element) - (script_element) - (style_element) + (element) + (script_element) + (style_element) ] @scope.tag -(start_tag (tag_name) @open.tag) +(start_tag + (tag_name) @open.tag) (end_tag (tag_name) @close.tag @@ -17,7 +18,6 @@ "/>" @close.selftag) @scope.selftag ; await - (await_statement (await_start (block_start_tag @@ -36,7 +36,6 @@ (#offset! @mid.await.2 0 -1 0 0)) ; each - (each_statement (each_start (block_start_tag @@ -49,7 +48,6 @@ (#offset! @close.each 0 -1 0 0))) ; if - (if_statement (if_start (block_start_tag @@ -72,7 +70,6 @@ (#offset! @close.if 0 -1 0 0))) ; key - (key_statement (key_start (block_start_tag @@ -85,7 +82,6 @@ (#offset! @close.key 0 -1 0 0))) ; snippet - (snippet_statement (snippet_start (block_start_tag diff --git a/after/queries/templ/matchup.scm b/after/queries/templ/matchup.scm index 7414426..83efc3d 100644 --- a/after/queries/templ/matchup.scm +++ b/after/queries/templ/matchup.scm @@ -1,27 +1,32 @@ ; inherits: quote [ - (component_if_statement) - (conditional_attribute_if_statement) + (component_if_statement) + (conditional_attribute_if_statement) ] @scope.if ; 'else' -("else" @mid.if.1) +"else" @mid.if.1 ; if -("if" @open.if) +"if" @open.if ; switch -(component_switch_statement "switch" @open.switch - (component_switch_expression_case "case" @mid.switch.1) - (component_switch_default_case "default" @mid.switch.2)) @scope.switch +(component_switch_statement + "switch" @open.switch + (component_switch_expression_case + "case" @mid.switch.1) + (component_switch_default_case + "default" @mid.switch.2)) @scope.switch [ - (element) - (script_element) + (element) + (script_element) ] @scope.tag -(tag_start (element_identifier) @open.tag) +(tag_start + (element_identifier) @open.tag) + (tag_end (element_identifier) @close.tag (#offset! @close.tag 0 -1 0 0)) @@ -30,8 +35,7 @@ (style_tag_start) @open.style (#offset! @open.style 0 1 0 -1) (style_tag_end) @close.style - (#offset! @close.style 0 1 0 -1) - ) @scope.style + (#offset! @close.style 0 1 0 -1)) @scope.style (self_closing_tag (element_identifier) @open.selftag diff --git a/after/queries/typescript/matchup.scm b/after/queries/typescript/matchup.scm index 10c5d4f..85f2b06 100644 --- a/after/queries/typescript/matchup.scm +++ b/after/queries/typescript/matchup.scm @@ -1,9 +1,21 @@ ; inherits: ecma (type_arguments) @scope.typeargs -(type_arguments) "<" @open.typeargs -(type_arguments) ">" @close.typeargs + +(type_arguments) + +"<" @open.typeargs + +(type_arguments) + +">" @close.typeargs (type_parameters) @scope.typeparams -(type_parameters) "<" @open.typeparams -(type_parameters) ">" @close.typeparams + +(type_parameters) + +"<" @open.typeparams + +(type_parameters) + +">" @close.typeparams diff --git a/after/queries/vim/matchup.scm b/after/queries/vim/matchup.scm index 848d46c..10f5b06 100644 --- a/after/queries/vim/matchup.scm +++ b/after/queries/vim/matchup.scm @@ -1,22 +1,30 @@ (function_definition "function" @open.function "endfunction" @close.function) @scope.function + (return_statement "return" @mid.function.1) (if_statement "if" @open.if "endif" @close.if) @scope.if + (elseif_statement "elseif" @mid.if.1) + (else_statement "else" @mid.if.2) (for_loop "for" @open.loop "endfor" @close.loop) @scope.loop + (while_loop "while" @open.loop "endwhile" @close.loop) @scope.loop -(continue_statement "continue" @mid.loop.1) -(break_statement "break" @mid.loop.2) + +(continue_statement + "continue" @mid.loop.1) + +(break_statement + "break" @mid.loop.2) diff --git a/after/queries/vue/matchup.scm b/after/queries/vue/matchup.scm index 8f954cc..6a84f86 100644 --- a/after/queries/vue/matchup.scm +++ b/after/queries/vue/matchup.scm @@ -1,13 +1,15 @@ ; inherits: quote [ - (element) - (script_element) - (style_element) - (template_element) + (element) + (script_element) + (style_element) + (template_element) ] @scope.tag -(start_tag (tag_name) @open.tag) +(start_tag + (tag_name) @open.tag) + (end_tag (tag_name) @close.tag (#offset! @close.tag 0 -1 0 0)) diff --git a/after/queries/zig/matchup.scm b/after/queries/zig/matchup.scm index 5e1edd7..2a04c94 100644 --- a/after/queries/zig/matchup.scm +++ b/after/queries/zig/matchup.scm @@ -2,19 +2,27 @@ (function_declaration "fn" @open.function) @scope.function + (return_expression "return" @mid.function.1) ; 'else' and 'else if' (else_clause - "else" @mid.if.1 (if_statement "if" @mid.if.1)?) + "else" @mid.if.1 + (if_statement + "if" @mid.if.1)?) ; if ((if_statement "if" @open.if) @scope.if - (#not-has-parent? @scope.if else_clause)) + (#not-has-parent? @scope.if else_clause)) ; Loops -(while_statement "while" @open.loop) @scope.loop -(break_expression "break" @mid.loop.1) -(continue_expression "continue" @mid.loop.2) +(while_statement + "while" @open.loop) @scope.loop + +(break_expression + "break" @mid.loop.1) + +(continue_expression + "continue" @mid.loop.2) From 831c1615095063a14469797ccc7f70af54d16c45 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Thu, 29 May 2025 21:18:54 -0500 Subject: [PATCH 06/29] feat: fully functional treesitter engine without nvim-treesitter dependency --- autoload/matchup.vim | 17 +++++------- autoload/matchup/loader.vim | 4 +-- autoload/matchup/matchparen.vim | 3 +-- autoload/matchup/ts_engine.vim | 16 +---------- lua/treesitter-matchup.lua | 16 ----------- lua/treesitter-matchup/internal.lua | 41 +++++++++++++---------------- 6 files changed, 29 insertions(+), 68 deletions(-) delete mode 100644 lua/treesitter-matchup.lua diff --git a/autoload/matchup.vim b/autoload/matchup.vim index 5ca4fc4..df61101 100644 --- a/autoload/matchup.vim +++ b/autoload/matchup.vim @@ -76,6 +76,11 @@ function! s:init_options() call s:init_option('matchup_where_separator', '') call s:init_option('matchup_matchpref', {}) + + call s:init_option('matchup_treesitter_enabled', v:false) + call s:init_option('matchup_treesitter_disabled', {}) + call s:init_option('matchup_treesitter_include_match_words', v:false) + call s:init_option('matchup_treesitter_enable_quotes', v:true) endfunction function! s:init_option(option, default) @@ -408,17 +413,7 @@ function! s:treesitter_init_module() " {{{1 if !matchup#loader#_treesitter_may_be_supported() return endif - - lua require'treesitter-matchup'.init() - - augroup matchup_filetype_query - au! - autocmd FileType query - \ augroup MatchupTreesitter|augroup END - \|autocmd! MatchupTreesitter BufWritePost - \ call v:lua.require('treesitter-matchup.third-party.query') - \.invalidate_query_file(expand('%:p')) - augroup END + " TODO: do something? endfunction "}}}1 diff --git a/autoload/matchup/loader.vim b/autoload/matchup/loader.vim index 61f99c7..c84c47b 100644 --- a/autoload/matchup/loader.vim +++ b/autoload/matchup/loader.vim @@ -25,7 +25,7 @@ function! matchup#loader#init_buffer() abort " {{{1 let [l:no_words, l:filt_words] = [0, 0] if s:ts_may_be_supported && matchup#ts_engine#is_enabled(bufnr('%')) let l:has_ts = 1 - if matchup#ts_engine#get_option(bufnr('%'), 'include_match_words') + if g:matchup_treesitter_include_match_words let l:filt_words = 1 else let l:no_words = 1 @@ -78,7 +78,7 @@ function! matchup#loader#_treesitter_may_be_supported() abort endfunction let s:ts_may_be_supported = has('nvim-0.5.0') && exists('*luaeval') - \ && luaeval('pcall(require, "treesitter-matchup")') + \ && luaeval('pcall(require, "treesitter-matchup.internal")') " }}}1 function! matchup#loader#bufwinenter() abort " {{{1 diff --git a/autoload/matchup/matchparen.vim b/autoload/matchup/matchparen.vim index 172d732..1477d61 100644 --- a/autoload/matchup/matchparen.vim +++ b/autoload/matchup/matchparen.vim @@ -1201,8 +1201,7 @@ function! s:add_matches(corrlist, ...) " {{{1 if exists('s:ns_id') if strlen(l:corr.match) == 0 \ && matchup#loader#_treesitter_may_be_supported() - \ && !matchup#ts_engine#get_option( - \ bufnr('%'), 'disable_virtual_text') + \ && !g:matchup_treesitter_disable_virtual_text if hlexists('MatchupVirtualText') let l:group = 'MatchupVirtualText' endif diff --git a/autoload/matchup/ts_engine.vim b/autoload/matchup/ts_engine.vim index 4bea3b6..ce8c1f5 100644 --- a/autoload/matchup/ts_engine.vim +++ b/autoload/matchup/ts_engine.vim @@ -15,26 +15,12 @@ function! s:forward(fn, ...) endfunction function! matchup#ts_engine#is_enabled(bufnr) abort - if !has('nvim-0.5.0') + if !has('nvim-0.9.0') return 0 endif return +s:forward('is_enabled', a:bufnr) endfunction -function! matchup#ts_engine#get_option(bufnr, opt_name) abort - return s:forward('get_option', a:bufnr, a:opt_name) -endfunction - -let s:attached = {} - -function! matchup#ts_engine#attach(bufnr, lang) abort - let s:attached[a:bufnr] = a:lang -endfunction - -function! matchup#ts_engine#detach(bufnr) abort - unlet s:attached[a:bufnr] -endfunction - function! matchup#ts_engine#get_delim(opts) abort call matchup#perf#tic('ts_engine.get_delim') diff --git a/lua/treesitter-matchup.lua b/lua/treesitter-matchup.lua deleted file mode 100644 index 27dfcb2..0000000 --- a/lua/treesitter-matchup.lua +++ /dev/null @@ -1,16 +0,0 @@ --- TODO: remove module structure - -local M = {} - -function M.init() - treesitter.define_modules { - matchup = { - module_path = 'treesitter-matchup.internal', - is_supported = function(lang) - return vim.treesitter.query.get(lang, 'matchup') ~= nil - end - } - } -end - -return M diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index 6305ba5..850572c 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -3,12 +3,6 @@ local api = vim.api local ts = vim.treesitter local memoize = require'treesitter-matchup.third-party.ts-utils'.memoize -vim.g.matchup_treesitter_enabled = false -vim.g.matchup_treesitter_disabled = {} -vim.g.matchup_treesitter_include_match_words = false -vim.g.matchup_treesitter_enable_quotes = true - --- TODO: update this dependencies local lru = require'treesitter-matchup.third-party.lru' local util = require'treesitter-matchup.util' @@ -22,13 +16,16 @@ local cache = lru.new(150) ---@param lang string ---@param bufnr integer local function is_enabled(lang, bufnr) - local enabled = vim.g.matchup_treesitter_enabled == 1 - local buf_enabled = vim.b[bufnr].matchup_treesitter_enabled == 1 + local enabled = vim.g.matchup_treesitter_enabled + local buf_enabled = vim.b[bufnr].matchup_treesitter_enabled local lang_disabled = vim.list_contains(vim.g.matchup_treesitter_disabled, lang) - return enabled and buf_enabled and not lang_disabled + if buf_enabled == false then + return false + end + + return enabled and not lang_disabled end --- TODO: this is following the old module structure of nvim-treesitter. Change it ---@param bufnr integer? ---@return boolean @@ -38,7 +35,10 @@ function M.is_enabled(bufnr) if not lang then return false end - assert(lang) + local _, err = ts.get_parser(bufnr, nil, {error = false}) + if err then + return false + end return is_enabled(lang, bufnr) end @@ -110,7 +110,7 @@ local get_memoized_matches = memoize(function(bufnr, root, lang) end_col = -1 end_row = end_row - 1 end - local lines = api.nvim_buf_get_text(bufnr, start_row, end_row, start_col, end_col, {}) + local lines = api.nvim_buf_get_text(bufnr, start_row, start_col, end_row, end_col, {}) local text = table.concat(lines, '\n') local name = query.captures[id] @@ -304,6 +304,7 @@ function M.do_match_result(info, bufnr, opts, side, key) ---@type integer, integer local row, col = unpack(info.range) + ---@class matchup.Delim local result = { type = 'delim_py', match = text_until_newline(info), @@ -444,8 +445,12 @@ function M.get_delim(bufnr, opts) result_info.side, result_info.key) end -function M.get_matching(delim, down, bufnr) - down = down > 0 +---@param delim matchup.Delim +---@param _down 1|0 +---@param bufnr integer +---@return [string, integer, integer][] +function M.get_matching(delim, _down, bufnr) + local down = _down > 0 local cached_info = cache:get(delim._id) or {} if cached_info.bufnr ~= bufnr then @@ -509,12 +514,4 @@ function M.get_matching(delim, down, bufnr) return matches end -function M.attach(bufnr, lang) - api.nvim_call_function('matchup#ts_engine#attach', {bufnr, lang}) -end - -function M.detach(bufnr) - api.nvim_call_function('matchup#ts_engine#detach', {bufnr}) -end - return M From 1b16bca34a559da6f06679e6751eb93c54bbd3e5 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Thu, 29 May 2025 21:19:20 -0500 Subject: [PATCH 07/29] feat: add lua table constructor capture --- after/queries/lua/matchup.scm | 4 ++++ lua/match-up.lua | 2 -- lua/treesitter-matchup/internal.lua | 17 ----------------- lua/treesitter-matchup/syntax.lua | 1 - 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/after/queries/lua/matchup.scm b/after/queries/lua/matchup.scm index 7579f98..34b1f81 100644 --- a/after/queries/lua/matchup.scm +++ b/after/queries/lua/matchup.scm @@ -34,3 +34,7 @@ (do_statement "do" @open.block "end" @close.block) @scope.block + +(table_constructor + "{" @open.table + "}" @close.table) @scope.table diff --git a/lua/match-up.lua b/lua/match-up.lua index e394990..8a3cf46 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -66,8 +66,6 @@ local M = {} ---@class matchup.TransmuteConfig ---@field enabled 0|1 --- TODO: remove vim syntax related g: vars --- TODO: modify vimscript to work without nvim-treesitter -- TODO: add documentation for g: vars ---@class matchup.TreesitterConfig ---@field enabled boolean diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index 850572c..fd57941 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -42,16 +42,6 @@ function M.is_enabled(bufnr) return is_enabled(lang, bufnr) end --- TODO: I had to remove the `is_hl_enabled` function and the related logic. On --- the `main` branch of nvim-treesitter it's not possible to tell wether or not --- the hl is enabled for a given buffer and there is no --- `additional_vim_regex_highlighting` option anymore. Now, users will have to --- enable syntax themselves after doing `vim.treesitter.start()`. Mention this --- as a possible workaround and possible regression in the PR. --- --- Technically, the undocumented `vim.treesitter.highlighter` table can be --- accessed. But, should we rely in undocumented features? - ---@param bufnr integer ---@param root TSNode ---@param lang string @@ -171,12 +161,6 @@ function M.range_id(range) return ('range_%d_%d_%d_%d'):format(unpack(range)) end --- TODO: mention this in the PR. this is not memoized because: --- - get_matches is already memoized --- - this function does not have access to the treesitter root and memoizing by --- buf_tick is unreliable (buf_tick may be out-of-sync with treesitter changes --- because of undo, for example) --- --- Get all nodes belonging to defined scopes (organized by key) ---@param bufnr integer ---@return table> @@ -239,7 +223,6 @@ M.get_active_matches = function(bufnr) end if match.mid then for key, mid_group in pairs(match.mid) do - -- TODO: mid type is wrong, fix everywhere for _, mid in pairs(mid_group) do local id = M.range_id(mid.info.range) if mid.info and symbols[id] == nil then diff --git a/lua/treesitter-matchup/syntax.lua b/lua/treesitter-matchup/syntax.lua index 23b343f..42728e4 100644 --- a/lua/treesitter-matchup/syntax.lua +++ b/lua/treesitter-matchup/syntax.lua @@ -40,7 +40,6 @@ function M.lang_skip(lnum, col) return false end - -- TODO: is lnum - 1 ok? local node = vts.get_node({pos = {lnum - 1, col - 1}}) if not node then return false From b2d61bfcb79d8f118ff57f8bd973fc251e051e86 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Wed, 4 Jun 2025 23:14:07 -0500 Subject: [PATCH 08/29] fix: initialize g:matchup_treesitter_disable_virtual_text --- autoload/matchup.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/matchup.vim b/autoload/matchup.vim index df61101..2fa7130 100644 --- a/autoload/matchup.vim +++ b/autoload/matchup.vim @@ -81,6 +81,7 @@ function! s:init_options() call s:init_option('matchup_treesitter_disabled', {}) call s:init_option('matchup_treesitter_include_match_words', v:false) call s:init_option('matchup_treesitter_enable_quotes', v:true) + call s:init_option('matchup_treesitter_disable_virtual_text', v:true) endfunction function! s:init_option(option, default) From cd1f1c789417c56fce34e31baca9aebd194820d3 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 23 Jun 2025 21:07:39 -0500 Subject: [PATCH 09/29] feat(lua): add treesitter capture for function calls --- after/queries/lua/matchup.scm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/after/queries/lua/matchup.scm b/after/queries/lua/matchup.scm index 34b1f81..fecd5a3 100644 --- a/after/queries/lua/matchup.scm +++ b/after/queries/lua/matchup.scm @@ -38,3 +38,8 @@ (table_constructor "{" @open.table "}" @close.table) @scope.table + +(function_call + (arguments + "(" @open.call + ")" @close.call)) @scope.call From da04528e45f952676b753635b3c5c023a60acfe2 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 23 Jun 2025 21:08:12 -0500 Subject: [PATCH 10/29] feat!: enable treesitter integration by default --- autoload/matchup.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/matchup.vim b/autoload/matchup.vim index 2fa7130..45e7104 100644 --- a/autoload/matchup.vim +++ b/autoload/matchup.vim @@ -77,7 +77,7 @@ function! s:init_options() call s:init_option('matchup_matchpref', {}) - call s:init_option('matchup_treesitter_enabled', v:false) + call s:init_option('matchup_treesitter_enabled', v:true) call s:init_option('matchup_treesitter_disabled', {}) call s:init_option('matchup_treesitter_include_match_words', v:false) call s:init_option('matchup_treesitter_enable_quotes', v:true) From 480af131791ca35d1483f2022c82c74dcaa0b9f6 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Tue, 24 Jun 2025 11:18:29 -0500 Subject: [PATCH 11/29] fix: correctly process b:match_skip when treesitter is enabled --- autoload/matchup/loader.vim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/autoload/matchup/loader.vim b/autoload/matchup/loader.vim index c84c47b..6c28972 100644 --- a/autoload/matchup/loader.vim +++ b/autoload/matchup/loader.vim @@ -32,8 +32,6 @@ function! matchup#loader#init_buffer() abort " {{{1 endif endif - let l:has_ts_hl = 0 - " initialize lists of delimiter pairs and regular expressions " this is the data obtained from parsing b:match_words let b:matchup_delim_lists = s:init_delim_lists(l:no_words, l:filt_words) @@ -43,7 +41,7 @@ function! matchup#loader#init_buffer() abort " {{{1 let b:matchup_delim_re = s:init_delim_regexes() " process b:match_skip - if l:has_ts_hl + if l:has_ts let b:matchup_delim_skip \ = "matchup#ts_syntax#skip_expr(s:effline('.'),s:effcol('.'))" else From b9366dfa5376e87a07d6cd1c48958155f9b0bdf1 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Tue, 24 Jun 2025 21:28:14 -0500 Subject: [PATCH 12/29] fix: enable treesitter integration by default only on Neovim --- autoload/matchup.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/matchup.vim b/autoload/matchup.vim index 45e7104..06e4d29 100644 --- a/autoload/matchup.vim +++ b/autoload/matchup.vim @@ -77,7 +77,7 @@ function! s:init_options() call s:init_option('matchup_matchpref', {}) - call s:init_option('matchup_treesitter_enabled', v:true) + call s:init_option('matchup_treesitter_enabled', has('nvim') ? v:true : v:false) call s:init_option('matchup_treesitter_disabled', {}) call s:init_option('matchup_treesitter_include_match_words', v:false) call s:init_option('matchup_treesitter_enable_quotes', v:true) From bb9ff61bf2a76e52dfb1f5a7187c2850a20760e1 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 13:35:32 -0500 Subject: [PATCH 13/29] fix(treesitter): check if buffer is loaded before trying to get a parser for it --- lua/treesitter-matchup/internal.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index fd57941..ad8fec5 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -31,6 +31,9 @@ end ---@return boolean function M.is_enabled(bufnr) bufnr = bufnr or api.nvim_get_current_buf() + if not api.nvim_buf_is_loaded(bufnr) then + return false + end local lang = ts.language.get_lang(vim.bo[bufnr].filetype) if not lang then return false From 9e152f39d10d9a94e3c5beeb2c08be77d058d2c6 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 14:43:52 -0500 Subject: [PATCH 14/29] feat!(treesitter): parse range around cursor based on g:matchup_treesitter_stopline --- autoload/matchup.vim | 1 + lua/match-up.lua | 1 + lua/treesitter-matchup/internal.lua | 13 +++++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/autoload/matchup.vim b/autoload/matchup.vim index 06e4d29..f159a40 100644 --- a/autoload/matchup.vim +++ b/autoload/matchup.vim @@ -82,6 +82,7 @@ function! s:init_options() call s:init_option('matchup_treesitter_include_match_words', v:false) call s:init_option('matchup_treesitter_enable_quotes', v:true) call s:init_option('matchup_treesitter_disable_virtual_text', v:true) + call s:init_option('matchup_treesitter_stopline', 400) endfunction function! s:init_option(option, default) diff --git a/lua/match-up.lua b/lua/match-up.lua index 8a3cf46..b6887fe 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -73,6 +73,7 @@ local M = {} ---@field include_match_words boolean ---@field disable_virtual_text boolean ---@field enable_quotes boolean +---@field stopline integer ---@class matchup.Config ---@field delim matchup.DelimConfig diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index ad8fec5..ffd9e63 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -134,10 +134,15 @@ M.get_matches = function(bufnr) local matches = {} ---@type matchup.treesitter.Match[] if parser then - -- TODO: g:matchup_delim_stopline could be used, but this functions needs to - -- know on which window it should look for in order to get the current - -- cursor position of that window - parser:parse(nil) + -- NOTE: assummes that we are always parsing the current window. May cause + -- issues if that's not always the case + local win = api.nvim_get_current_win() + local cur_row = unpack(api.nvim_win_get_cursor(win)) + local stopline = vim.g.matchup_treesitter_stopline ---@type integer + local start_row = math.max(cur_row - stopline, 0) + local end_row = math.min(cur_row + stopline, api.nvim_buf_line_count(bufnr)) + + parser:parse({start_row, end_row}) parser:for_each_tree(function(tree, lang_tree) if not tree or lang_tree:lang() == 'comment' then return From 15135430df24d78bd0d808a9e84b83dcec50b0f1 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 15:04:25 -0500 Subject: [PATCH 15/29] docs: add documentation for new g:matchup_treesiter config vars --- doc/matchup.txt | 76 ++++++++++++++++++++++++++++++++++++++++++++++-- lua/match-up.lua | 1 - 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/doc/matchup.txt b/doc/matchup.txt index e0ae14a..2dc11f9 100644 --- a/doc/matchup.txt +++ b/doc/matchup.txt @@ -542,7 +542,7 @@ Options~ *g:matchup_delim_nomids* If set to 1, middle words (like `return`) are not matched to start and - end words for higlighting and motions. + end words for higlighting and motions. > let g:matchup_delim_noskips = 0 < @@ -552,10 +552,82 @@ Options~ When enabled (the default), the plugin will be loaded for all buffers, including ones without a file type set. This allows matching to be done in - new buffers and plain text files but adds a small start-up cost to vim. + new buffers and plain text files but adds a small start-up cost to vim. > + + let g:matchup_delim_start_plaintext = 0 +< Default: 1 +*g:matchup_treesitter_enabled* + + When enabled (the default on Neovim), the plugin will use the builtin + |treesitter| interface to compute matches. > + + let g:matchup_treesitter_enabled = v:true +< + + Default: v:true + + Note: expects either |v:true| or |v:false|. + +*g:matchup_treesitter_disabled* + + List of treesitter languages (not filetypes, see |treesitter-language|) for + which the treesitter engine will be disabled > + + let g:matchup_treesitter_disabled = [] +< + + Default: [] + +*g:matchup_treesitter_include_match_words* + + Additionally include traditional vim regex matches for symbols. For example, + highlights `/* */` comments in C++ which are not supported in tree-sitter + matching. > + + let g:matchup_treesitter_include_match_words = v:false +< + + Default: v:false + + Note: expects either |v:true| or |v:false|. + +*g:matchup_treesitter_disable_virtual_text* + + Do not use virtual text to highlight the virtual end of a block, for + languages without explicit end markers (e.g., Python). > + + let g:matchup_treesitter_disable_virtual_text = v:true +< + + Default: v:true + + Note: expects either |v:true| or |v:false|. + +*g:matchup_treesitter_enable_quotes* + + Include quotes as possible matches. > + + let g:matchup_treesitter_enable_quotes = v:true +< + + Default: v:true + + Note: expects either |v:true| or |v:false|. + +*g:matchup_treesitter_stopline* + + The number of lines to parse using treesitter in either direction while + highlighting matches, using motions and textobjects. Set this + conservatively since high values may cause performance issues. > + + let g:matchup_treesitter_stopline = 400 +< + + Default: 400 + Variables~ *b:match_words* diff --git a/lua/match-up.lua b/lua/match-up.lua index b6887fe..4fedc5c 100644 --- a/lua/match-up.lua +++ b/lua/match-up.lua @@ -66,7 +66,6 @@ local M = {} ---@class matchup.TransmuteConfig ---@field enabled 0|1 --- TODO: add documentation for g: vars ---@class matchup.TreesitterConfig ---@field enabled boolean ---@field disabled string[] From e832b01e940f07b0d48369f0decbab899967b4c2 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 15:18:35 -0500 Subject: [PATCH 16/29] docs: add lazy.nvim installation to README --- README.md | 330 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 213 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 611cd4c..32df4ce 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ match-up is a plugin that lets you highlight, navigate, and operate on -sets of matching text. It extends vim's `%` key to language-specific +sets of matching text. It extends vim's `%` key to language-specific words instead of just single characters. ## Screenshot @@ -18,20 +18,20 @@ words instead of just single characters. ## Table of contents - * [Overview](#overview) - * [Installation](#installation) - * [Features](#features) - * [Options](#options) - * [FAQ](#faq) - * [Interoperability](#interoperability) - * [Acknowledgments](#acknowledgments) - * [Development](#development) +- [Overview](#overview) +- [Installation](#installation) +- [Features](#features) +- [Options](#options) +- [FAQ](#faq) +- [Interoperability](#interoperability) +- [Acknowledgments](#acknowledgments) +- [Development](#development) ## Overview match-up can be used as a drop-in replacement for the classic plugin [matchit.vim]. match-up aims to enhance all of matchit's features, fix a number of its -deficiencies and bugs, and add a few totally new features. It also +deficiencies and bugs, and add a few totally new features. It also replaces the standard plugin [matchparen], allowing all of matchit's words to be highlighted along with the `matchpairs` (`(){}[]`). @@ -39,9 +39,9 @@ to be highlighted along with the `matchpairs` (`(){}[]`). [matchparen]: http://ftp.vim.org/pub/vim/runtime/doc/pi_paren.txt See [detailed feature documentation](#detailed-feature-documentation) for -more information. This plugin: +more information. This plugin: -- Extends vim's `%` motion to language-specific words. The following vim +- Extends vim's `%` motion to language-specific words. The following vim file type plugins currently provide special support for match-up: > abaqus, ada, aspvbs, bash, c, cpp, chicken, clojure, cmake, cobol, @@ -60,7 +60,7 @@ more information. This plugin: - Adds motions `g%`, `[%`, `]%`, and `z%`. - Combines these motions into convenient text objects `i%` and `a%`. - Highlights symbols and words under the cursor which `%` can work on, - and highlights matching symbols and words. Now you can easily tell + and highlights matching symbols and words. Now you can easily tell where `%` will jump to. ## Installation @@ -91,12 +91,41 @@ end) and run `:PackerSync` or similar. +If you use [lazy.nvim](https://github.com/folke/lazy.nvim), add the following +to your plugins spec + +```lua +{ + 'andymass/vim-matchup' + init = function() + -- modify your configuration vars here + vim.g.matchup_treesitter_stopline = 500 + + -- or call the setup function provided as a helper. It defines the + -- configuration vars for you + require('match-up').setup({ + treesitter = { + stopline = 500 + } + }) + end, + -- or use the `opts` mechanism built into `lazy.nvim`. It calls + -- `require('match-up').setup` under the hood + ---@type matchup.Config + opts = { + treesitter = { + stopline = 500, + } + } +} +``` + See [Tree-sitter integration](https://github.com/andymass/vim-matchup#tree-sitter-integration) -for information on how to enable tree-sitter matching with neovim. +for information on how the tree-sitter integration works on Neovim. Note: I do not recommend using alternative loading strategies such as `event = 'VimEnter'` or `event = 'CursorMoved'` as match-up already -loads a minimal amount of code on start-up. It may work, but if you run +loads a minimal amount of code on start-up. It may work, but if you run into issues, remove the event key as a first debugging step. With [LunarVim](https://www.lunarvim.org/), tree-sitter integration can be @@ -131,10 +160,10 @@ together with other plugins. ### Tree-sitter integration -_Note: Currently this feature is possible in neovim only. Only the latest -version of neovim is supported._ +_Note: Currently this feature is possible in Neovim only. Only the latest +stable version of Neovim is supported._ -match-up has support for language syntax provided by tree-sitter. The +match-up has support for language syntax provided by tree-sitter. The list of supported languages is available [here](https://github.com/andymass/vim-matchup/tree/master/after/queries). @@ -158,11 +187,11 @@ EOF Beside `enable` and `disable`, the following options are available, all defaulting to disabled: - - `disable_virtual_text`: do not use virtual text to highlight the +- `disable_virtual_text`: do not use virtual text to highlight the virtual end of a block, for languages without explicit end markers (e.g., Python). - - `include_match_words`: additionally include traditional vim regex - matches for symbols. For example, highlights `/* */` comments in C++ +- `include_match_words`: additionally include traditional vim regex + matches for symbols. For example, highlights `/* */` comments in C++ which are not supported in tree-sitter matching. Screenshot: @@ -171,18 +200,18 @@ Screenshot: ## Features -| | feature | __match-up__ | matchit | matchparen | -| ------- | -------------------------------- | -------------- | ------------- | ------------- | -| ([a.1]) | jump between matching words | :thumbsup: | :thumbsup: | :x: | -| ([a.2]) | jump to open & close words | :thumbsup: | :thumbsup: | :x: | -| ([a.3]) | jump inside (`z%`) | :thumbsup: | :x: | :x: | -| ([b.1]) | full set of text objects | :thumbsup: | :question: | :x: | -| ([b.2]) | delete surrounding matched words | :thumbsup: | :x: | :x: | -| ([c.1]) | highlight `()`, `[]`, & `{}` | :thumbsup: | :x: | :thumbsup: | -| ([c.2]) | highlight _all_ matching words | :thumbsup: | :x: | :x: | -| ([c.3]) | display matches off-screen | :thumbsup: | :x: | :x: | -| ([c.4]) | show where you are (breadcrumbs) | :thumbsup: | :x: | :x: | -| ([d.1]) | (neovim) tree-sitter integration | :thumbsup: | :x: | :x: | +| | feature | **match-up** | matchit | matchparen | +| ------- | -------------------------------- | ------------ | ---------- | ---------- | +| ([a.1]) | jump between matching words | :thumbsup: | :thumbsup: | :x: | +| ([a.2]) | jump to open & close words | :thumbsup: | :thumbsup: | :x: | +| ([a.3]) | jump inside (`z%`) | :thumbsup: | :x: | :x: | +| ([b.1]) | full set of text objects | :thumbsup: | :question: | :x: | +| ([b.2]) | delete surrounding matched words | :thumbsup: | :x: | :x: | +| ([c.1]) | highlight `()`, `[]`, & `{}` | :thumbsup: | :x: | :thumbsup: | +| ([c.2]) | highlight _all_ matching words | :thumbsup: | :x: | :x: | +| ([c.3]) | display matches off-screen | :thumbsup: | :x: | :x: | +| ([c.4]) | show where you are (breadcrumbs) | :thumbsup: | :x: | :x: | +| ([d.1]) | (Neovim) tree-sitter integration | :thumbsup: | :x: | :x: | [a.1]: #a1-jump-between-matching-words [a.2]: #a2-jump-to-open-and-close-words @@ -198,12 +227,12 @@ Screenshot: [exclusive]: #inclusive-and-exclusive-motions Legend: :thumbsup: supported. -:question: poorly implemented, broken, or uncertain. :x: not possible. +:question: poorly implemented, broken, or uncertain. :x: not possible. ### Detailed feature documentation -What do we mean by open, close, mid? This depends on the specific file -type and is configured through the variable `b:match_words`. Here are a +What do we mean by open, close, mid? This depends on the specific file +type and is configured through the variable `b:match_words`. Here are a couple examples: #### vim-script @@ -219,13 +248,14 @@ endif ``` For the vim-script language, match-up understands the words `if`, -`else`, `elseif`, `endif` and that they form a sequential construct. The +`else`, `elseif`, `endif` and that they form a sequential construct. The "open" word is `if`, the "close" word is `endif`, and the "mid" -words are `else` and `elseif`. The `if`/`endif` pair is called an +words are `else` and `elseif`. The `if`/`endif` pair is called an "open-to-close" block and the `if`/`else`, `else`/`elsif`, and `elseif`/`endif` are called "any" blocks. #### C, C++ + ```c #if 0 #else @@ -246,57 +276,67 @@ Since in C and C++, blocks are delimited using braces (`{` & `}`), match-up will recognize `{` as the open word and `}` as the close word. It will ignore the `if` and `else if` because they are not defined in vim's default C file type plugin. -(Note: In neovim, this is optionally supported via +(Note: In Neovim, this is optionally supported via [Tree-sitter](#tree-sitter-integration)) On the other hand, match-up will recognize the `#if`, `#else`, `#endif` preprocessor directives. #### (a.1) jump between matching words - - `%` go forwards to next matching word. If at a close word, + +- `%` go forwards to next matching word. If at a close word, cycle back to the corresponding open word. - - `{count}%` forwards `{count}` times. Requires - `{count} <= g:matchup_motion_override_Npercent`. For larger +- `{count}%` forwards `{count}` times. Requires + `{count} <= g:matchup_motion_override_Npercent`. For larger `{count}`, `{count}%` goes to the `{count}` percentage in the file. - - `g%` go backwards to `[count]`th previous matching word. If at an +- `g%` go backwards to `[count]`th previous matching word. If at an open word, cycle around to the corresponding close word. #### (a.2) jump to open and close words -- `[%` go to `[count]`th previous outer open word. Allows navigation -to the start of blocks surrounding the cursor. This is similar to vim's -built-in `[(` and `[{` and is an [exclusive] motion. -- `]%` go to `[count]`th next surrounding close word. This is an -[exclusive] motion. + +- `[%` go to `[count]`th previous outer open word. Allows navigation + to the start of blocks surrounding the cursor. This is similar to vim's + built-in `[(` and `[{` and is an [exclusive] motion. +- `]%` go to `[count]`th next surrounding close word. This is an + [exclusive] motion. #### (a.3) jump inside -- `z%` go to inside `[count]`th nearest inner contained block. This + +- `z%` go to inside `[count]`th nearest inner contained block. This is an [exclusive] motion when used with operators, except it eats - whitespace. For example, where `█` is the cursor position, + whitespace. For example, where `█` is the cursor position, ```vim █ call somefunction(param1, param2) ``` + `dz%` produces + ```vim param1, param2) ``` + but in + ```vim █ call somefunction( param1, param2) ``` + `dz%` also produces + ```vim param1, param2) ``` #### (b.1) full set of text objects + - `i%` the inside of an any block - `1i%` the inside of an open-to-close block - `{count}i%` If count is greater than 1, the inside of the `{count}`th surrounding open-to-close block - `a%` an any block. -- `1a%` an open-to-close block. Includes mids but does not include open +- `1a%` an open-to-close block. Includes mids but does not include open and close words. - `{count}a%` if `{count}` is greater than 1, the `{count}`th surrounding open-to-close block. @@ -334,7 +374,7 @@ If both the open and close match are off-screen, the close match is preferred. See the option `g:matchup_matchparen_offscreen` for more details. -For popup style (supported in recent vim and neovim versions): +For popup style (supported in recent vim and Neovim versions): ```vim let g:matchup_matchparen_offscreen = {'method': 'popup'} @@ -369,14 +409,14 @@ print out. ### Inclusive and exclusive motions In vim, character motions following operators (such as `d` for delete -and `c` for change) are either [inclusive] or [exclusive]. This means -they either include the ending position or not. Here, "ending position" +and `c` for change) are either [inclusive] or [exclusive]. This means +they either include the ending position or not. Here, "ending position" means the line and column closest to the end of the buffer of the region -swept over by the motion. match-up is designed so that `d]%` inside a set +swept over by the motion. match-up is designed so that `d]%` inside a set of parenthesis behaves exactly like `d])`, except generalized to words. Put differently, _forward_ exclusive motions will not include the close -word. In this example, where `█` is the cursor position, +word. In this example, where `█` is the cursor position, ```vim if █x | continue | endif @@ -388,26 +428,28 @@ pressing `d]%` will produce (cursor on the `e`) if endif ``` -To include the close word, use either `dv]%` or `v]%d`. This is also +To include the close word, use either `dv]%` or `v]%d`. This is also compatible with vim's `d])` and `d]}`. Operators over _backward_ exclusive motions will instead exclude the -position the cursor was on before the operator was invoked. For example, +position the cursor was on before the operator was invoked. For example, in ```vim if █x | continue | endif ``` + pressing `d[%` will produce ```vim █x | continue | endif ``` + This is compatible with vim's `d[(` and `d[{`. -Unlike `]%`, `%` is an [inclusive] motion. As a special case for the +Unlike `]%`, `%` is an [inclusive] motion. As a special case for the `d` (delete) operator, if `d%` leaves behind lines white-space, they will -be deleted also. In effect, it will be operating line-wise. As an +be deleted also. In effect, it will be operating line-wise. As an example, pressing `d%` will leave behind nothing. ```text @@ -421,35 +463,41 @@ This is vim compatible with the built-in `d%` on `matchpairs`. ### Line-wise operator/text-object combinations -Normally, the text objects `i%` and `a%` work character-wise. However, -there are some special cases. For certain operators combined with `i%`, +Normally, the text objects `i%` and `a%` work character-wise. However, +there are some special cases. For certain operators combined with `i%`, under certain conditions, match-up will effectively operate line-wise -instead. For example, in +instead. For example, in + ```vim if condition █call one() call two() endif ``` + pressing `di%` will produce + ```vim if condition endif ``` -even though deleting ` condition` would be suggested by the object `i%`. -The intention is to make operators more useful in some cases. The -following rules apply: -1. The operator must be listed in `g:matchup_text_obj_linewise_operators`. - By default this is `d` and `y` (e.g., `di%` and `ya%`). -2. The outer block must span multiple lines. -3. The open and close delimiters must be more than one character long. In - particular, `di%` involving a `(`...`)` block will not be subject to - these special rules. -To prevent this behavior for a particular operation, use `vi%d`. Note that +even though deleting ` condition` would be suggested by the object `i%`. +The intention is to make operators more useful in some cases. The +following rules apply: + +1. The operator must be listed in `g:matchup_text_obj_linewise_operators`. + By default this is `d` and `y` (e.g., `di%` and `ya%`). +2. The outer block must span multiple lines. +3. The open and close delimiters must be more than one character long. In + particular, `di%` involving a `(`...`)` block will not be subject to + these special rules. + +To prevent this behavior for a particular operation, use `vi%d`. Note that special cases involving indentation still apply (like with |i)| etc). To disable this entirely, remove the operator from the following variable, + ```vim let g:matchup_text_obj_linewise_operators = [ 'y' ] ``` @@ -457,71 +505,86 @@ let g:matchup_text_obj_linewise_operators = [ 'y' ] Note: unlike vim's built-in `i)`, `ab`, etc., `i%` does not make an existing visual mode character-wise. -A second special case involves `da%`. In this example, +A second special case involves `da%`. In this example, + ```vim if condition █call one() call two() endif ``` -pressing `da%` will delete all four lines and leave no white-space. This + +pressing `da%` will delete all four lines and leave no white-space. This is vim compatible with `da(`, `dab`, etc. ## Options To disable the plugin entirely, + ```vim let g:matchup_enabled = 0 ``` + default: 1 To disable a particular module, + ```vim let g:matchup_matchparen_enabled = 0 let g:matchup_motion_enabled = 0 let g:matchup_text_obj_enabled = 0 ``` + defaults: 1 To enable the delete surrounding (`ds%`) and change surrounding (`cs%`) maps, + ```vim let g:matchup_surround_enabled = 1 ``` + default: 0 To enable the experimental [transmute](https://github.com/andymass/vim-matchup/blob/5a1978e46a0e721b5c5d113379c685ff7ec339e7/doc/matchup.txt#L311) module, + ```vim let g:matchup_transmute_enabled = 1 ``` + default: 0 To configure the number of lines to search in either direction while using -motions and text objects. Does not apply to match highlighting +motions and text objects. Does not apply to match highlighting (see `g:matchup_matchparen_stopline` instead). + ```vim let g:matchup_delim_stopline = 1500 ``` + default: 1500 To disable matching within strings and comments, + ```vim let g:matchup_delim_noskips = 1 " recognize symbols within comments let g:matchup_delim_noskips = 2 " don't recognize anything in comments ``` + default: 0 (matching is enabled within strings and comments) ### Variables match-up understands the following variables from matchit. + - `b:match_words` - `b:match_skip` - `b:match_ignorecase` -These are set in the respective ftplugin files. They may not exist for -every file type. To support a new file type, create a file +These are set in the respective ftplugin files. They may not exist for +every file type. To support a new file type, create a file `after/ftplugin/{filetype}.vim` which sets them appropriately. ### Module matchparen @@ -534,13 +597,15 @@ The variable `g:loaded_matchparen` has no effect on match-up. #### Customizing the highlighting colors match-up uses the `MatchParen` highlighting group by default, which can be -configured. For example, +configured. For example, + ```vim :hi MatchParen ctermbg=blue guibg=lightblue cterm=italic gui=italic ``` You may want to put this inside a `ColorScheme` `autocmd` so it is preserved after colorscheme changes: + ```vim augroup matchup_matchparen_highlight autocmd! @@ -549,39 +614,45 @@ augroup END ``` You can also highlight words differently than parentheses using the -`MatchWord` highlighting group. You might do this if you find the +`MatchWord` highlighting group. You might do this if you find the `MatchParen` style distracting for large blocks. + ```vim :hi MatchWord ctermfg=red guifg=blue cterm=underline gui=underline ``` There are also `MatchParenCur` and `MatchWordCur` which allow you to configure the highlight separately for the match under the cursor. + ```vim :hi MatchParenCur cterm=underline gui=underline :hi MatchWordCur cterm=underline gui=underline ``` The matchparen module can be disabled on a per-buffer basis (there is -no command for this). By default, when disabling highlighting for a +no command for this). By default, when disabling highlighting for a particular buffer, the standard plugin matchparen will still be used for that buffer. ```vim let b:matchup_matchparen_enabled = 0 ``` + default: 1 If this module is disabled on a particular buffer, match-up will still fall-back to the vim standard plugin matchparen, which will highlight -`matchpairs` such as `()`, `[]`, & `{}`. To disable this, +`matchpairs` such as `()`, `[]`, & `{}`. To disable this, + ```vim let b:matchup_matchparen_fallback = 0 ``` + default: 1 A common usage of these options is to automatically disable matchparen for particular file types; + ```vim augroup matchup_matchparen_disable_ft autocmd! @@ -591,19 +662,22 @@ augroup END ``` Whether to highlight known words even if there is no match: + ```vim let g:matchup_matchparen_singleton = 1 ``` + default: 0 Dictionary controlling the behavior with off-screen matches. + ```vim let g:matchup_matchparen_offscreen = { ... } ``` default: `{'method': 'status'}` -If empty, this feature is disabled. Else, it should contain the +If empty, this feature is disabled. Else, it should contain the following optional keys: - `method`: @@ -614,15 +688,15 @@ following optional keys: If a match is off of the screen, the line belonging to that match will be displayed syntax-highlighted in the status line along with the line number - (if line numbers are enabled). If the match is above the screen border, + (if line numbers are enabled). If the match is above the screen border, an additional Δ symbol will be shown to indicate that the matching line is really above the cursor line. `'popup'`: Show off-screen matches in a popup (vim) or - floating (neovim) window. + floating (Neovim) window. `'status_manual'`: Compute the string which would be displayed in the - status-line or popup, but do not display it. The function + status-line or popup, but do not display it. The function `MatchupStatusOffscreen()` can be used to get the text. - `scrolloff`: @@ -633,8 +707,9 @@ following optional keys: default: 0. The number of lines to search in either direction while highlighting -matches. Set this conservatively since high values may cause performance +matches. Set this conservatively since high values may cause performance issues. + ```vim let g:matchup_matchparen_stopline = 400 " for match highlighting only ``` @@ -644,10 +719,12 @@ default: 400 #### highlighting timeouts Adjust timeouts in milliseconds for matchparen highlighting: + ```vim let g:matchup_matchparen_timeout = 300 let g:matchup_matchparen_insert_timeout = 60 ``` + default: 300, 60 #### deferred highlighting @@ -655,19 +732,23 @@ default: 300, 60 Deferred highlighting improves cursor movement performance (for example, when using `hjkl`) by delaying highlighting for a short time and waiting to see if the cursor continues moving; + ```vim let g:matchup_matchparen_deferred = 1 ``` + default: 0 (disabled) Note: this feature is only available if your vim version has `timers` and the function `timer_pause`, version 7.4.2180 and after. Adjust delays in milliseconds for deferred highlighting: + ```vim let g:matchup_matchparen_deferred_show_delay = 50 let g:matchup_matchparen_deferred_hide_delay = 700 ``` + default: 50, 700 Note: these delays cannot be changed dynamically and should be configured @@ -677,19 +758,24 @@ before the plugin loads (e.g., in your vimrc). To highlight the surrounding delimiters until the cursor moves, use a map such as the following + ```vim nmap (matchup-hi-surround) ``` + There is no default map for this feature. You can also highlight surrounding delimiters always as the cursor moves. + ```vim let g:matchup_matchparen_deferred = 1 let g:matchup_matchparen_hi_surround_always = 1 ``` + default: 0 (off) This can be set on a per-buffer basis: + ```vim autocmd FileType tex let b:matchup_matchparen_hi_surround_always = 1 ``` @@ -702,43 +788,53 @@ enabled. In vim, `{count}%` goes to the `{count}` percentage in the file. match-up overrides this motion for small `{count}` (by default, anything -less than 7). To allow `{count}%` for `{count}` less than 12, +less than 7). To allow `{count}%` for `{count}` less than 12, + ```vim g:matchup_motion_override_Npercent = 11 ``` + To disable this feature, and restore vim's default `{count}%`, + ```vim g:matchup_motion_override_Npercent = 0 ``` + To always enable this feature, use any value greater than 99, + ```vim g:matchup_motion_override_Npercent = 100 ``` + default: 6 If enabled, cursor will land on the end of mid and close words while -moving downwards (`%`/`]%`). While moving upwards (`g%`, `[%`) the cursor -will land on the beginning. To disable, +moving downwards (`%`/`]%`). While moving upwards (`g%`, `[%`) the cursor +will land on the beginning. To disable, + ```vim let g:matchup_motion_cursor_end = 0 ``` + default: 1 ### Module text_obj Modify the set of operators which may operate [line-wise](#line-wise-operatortext-object-combinations) + ```vim let g:matchup_text_obj_linewise_operators = ['d', 'y'] ``` + default: `['d', 'y']` ## FAQ - match-up doesn't work - This plugin requires at least vim 7.4. It should work in vim 7.4.898 - but at least vim 7.4.1689 is better. I recommend using the most recent + This plugin requires at least vim 7.4. It should work in vim 7.4.898 + but at least vim 7.4.1689 is better. I recommend using the most recent version of vim if possible. If you have issues, please tell me your vim version and error messages. @@ -760,7 +856,7 @@ default: `['d', 'y']` - Highlighting is not correct for construct X match-up uses matchit's filetype-specific data, which may not give - enough information to create proper highlights. To fix this, you may + enough information to create proper highlights. To fix this, you may need to modify `b:match_words` in your configuration. For more help, please open a new issue and be as specific as possible. @@ -768,17 +864,17 @@ default: `['d', 'y']` - I'm having performance problems match-up aims to be as fast as possible, but highlighting matching words - can be intensive and may be slow on less powerful machines. There are a + can be intensive and may be slow on less powerful machines. There are a few things you can try to improve performance: - 1. Update to a recent version of vim. Newer versions are faster, more - extensively tested, and better supported by match-up. + 1. Update to a recent version of vim. Newer versions are faster, more + extensively tested, and better supported by match-up. 2. Try [deferred highlighting](#deferred-highlighting), which delays - highlighting until the cursor is stationary to improve cursor movement - performance. - 3. Lower the [highlighting timeouts](#highlighting-timeouts). Note that - if highlighting takes longer than the timeout, highlighting will not be - attempted again until the cursor moves. + highlighting until the cursor is stationary to improve cursor movement + performance. + 3. Lower the [highlighting timeouts](#highlighting-timeouts). Note that + if highlighting takes longer than the timeout, highlighting will not be + attempted again until the cursor moves. If are having any other performance issues, please open a new issue and report the output of `:MatchupShowTimes`. @@ -786,7 +882,7 @@ default: `['d', 'y']` - Why is there a weird entry on the status line? This is a feature which helps you see matches that are outside of the - vim screen, similar to some IDEs. If you wish to disable it, use + vim screen, similar to some IDEs. If you wish to disable it, use ```vim let g:matchup_matchparen_offscreen = {} @@ -794,13 +890,14 @@ default: `['d', 'y']` - Matching does not work when lines are too far apart. - The number of search lines is limited for performance reasons. You may + The number of search lines is limited for performance reasons. You may increase the limits with the following options: ```vim let g:matchup_delim_stopline = 1500 " generally let g:matchup_matchparen_stopline = 400 " for match highlighting only ``` + - The maps `1i%` and `1a%` are difficult to press. You may use the following maps `I%` and `A%` for convenience: @@ -824,7 +921,7 @@ default: `['d', 'y']` - How can I contribute? Read the [contribution guidelines](CONTRIBUTING.md) and [issue - template](.github/ISSUE_TEMPLATE/bug_report.md). Be as precise and + template](.github/ISSUE_TEMPLATE/bug_report.md). Be as precise and detailed as possible when submitting issues and pull requests. @@ -833,15 +930,15 @@ default: `['d', 'y']` ### vimtex, for LaTeX documents By default, match-up will be disabled automatically for tex files when -[vimtex] is detected. To enable match-up for tex files, use +[vimtex] is detected. To enable match-up for tex files, use ```vim let g:matchup_override_vimtex = 1 ``` match-up's matching engine is more advanced than vimtex's and supports -middle delimiters such as `\middle|` and `\else`. The exact set of -delimiters recognized may differ between the two plugins. For example, +middle delimiters such as `\middle|` and `\else`. The exact set of +delimiters recognized may differ between the two plugins. For example, the mappings `da%` and `dad` will not always match, particularly if you have customized vimtex's delimiters. @@ -850,7 +947,7 @@ have customized vimtex's delimiters. match-up provides built-in support for [vim-surround]-style `ds%` and `cs%` operations (`let g:matchup_surround_enabled = 1`). If vim-surround is installed, you can use vim-surround -replacements such as `cs%)`. `%` cannot be used as a replacement. +replacements such as `cs%)`. `%` cannot be used as a replacement. An alternative plugin is [vim-sandwich], which allows more complex surround replacement rules but is not currently supported. @@ -873,23 +970,23 @@ See for instance one of the following plugins for this; match-up tries to work around matchit.vim in all cases, but if you experience problems, read the following: -- For vim, matchit.vim should not be loaded. If it is loaded, it should +- For vim, matchit.vim should not be loaded. If it is loaded, it should be loaded after match-up (in this case, matchit.vim will be disabled). Note that some plugins, such as [vim-sensible](https://github.com/tpope/vim-sensible), load matchit.vim so these should also be initialized after match-up. -- For neovim, matchit.vim is loaded by default. This should not cause any +- For Neovim, matchit.vim is loaded by default. This should not cause any problems, but you may see a very slight start-up time improvement by setting `let g:loaded_matchit = 1` in your `init.vim`. ### Matchparen emulation -match-up loads [matchparen] if it is not already loaded. Ordinarily, match-up +match-up loads [matchparen] if it is not already loaded. Ordinarily, match-up disables matchparen's highlighting and emulates it to highlight the symbol -contained in the 'matchpairs' option (by default `()`, `[]`, and `{}`). If match-up +contained in the 'matchpairs' option (by default `()`, `[]`, and `{}`). If match-up is disabled per-buffer using `b:matchup_matchparen_enabled`, match-up will use -matchparen instead of its own highlighting. See `b:matchup_matchparen_fallback` +matchparen instead of its own highlighting. See `b:matchup_matchparen_fallback` for more information. ## Acknowledgments @@ -897,7 +994,7 @@ for more information. ### Origins match-up was originally based on [@lervag](https://github.com/lervag)'s -[vimtex]. The concept and style of this plugin and its development are +[vimtex]. The concept and style of this plugin and its development are heavily influenced by vimtex. :beers: [vimtex]: https://github.com/lervag/vimtex @@ -914,8 +1011,8 @@ heavily influenced by vimtex. :beers: ### Reporting problems -Thorough issue reports are encouraged. Please read the [issue -template](.github/ISSUE_TEMPLATE/bug_report.md) first. Be as precise and +Thorough issue reports are encouraged. Please read the [issue +template](.github/ISSUE_TEMPLATE/bug_report.md) first. Be as precise and detailed as possible when submitting issues. @@ -927,4 +1024,3 @@ Please read the [contribution guidelines](CONTRIBUTING.md) before contributing. Contributions are welcome! - From 5e292db288be0b67bfec0934af9246b95ddbd9e2 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 15:19:26 -0500 Subject: [PATCH 17/29] docs: remove LunarVim specific instructions from README --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index 32df4ce..74ed72a 100644 --- a/README.md +++ b/README.md @@ -128,20 +128,6 @@ Note: I do not recommend using alternative loading strategies such as loads a minimal amount of code on start-up. It may work, but if you run into issues, remove the event key as a first debugging step. -With [LunarVim](https://www.lunarvim.org/), tree-sitter integration can be -enabled as follows: - -```lua -{ - "andymass/vim-matchup", - setup = function() - vim.g.matchup_matchparen_offscreen = { method = "popup" } - end, -}, - -lvim.builtin.treesitter.matchup.enable = true -``` - You can use any other plugin manager such as [vundle](https://github.com/gmarik/vundle), [dein](https://github.com/Shougo/dein.vim), From 1fd5e6db7a1cd1298d884f54c56aa0dbbc4d0eb4 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Sun, 29 Jun 2025 15:24:08 -0500 Subject: [PATCH 18/29] docs: update treesitter integration information on README --- README.md | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 74ed72a..10e9027 100644 --- a/README.md +++ b/README.md @@ -153,32 +153,11 @@ match-up has support for language syntax provided by tree-sitter. The list of supported languages is available [here](https://github.com/andymass/vim-matchup/tree/master/after/queries). -This feature requires manual opt-in in your init.vim and requires -[nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) to -be installed. +This feature is automatically enabled if you are using Neovim. And does not +require other plugins to work. -```vim -Plug 'nvim-treesitter/nvim-treesitter' -lua < Date: Mon, 30 Jun 2025 11:48:01 -0500 Subject: [PATCH 19/29] feat(ecma): add block query --- after/queries/ecma/matchup.scm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/after/queries/ecma/matchup.scm b/after/queries/ecma/matchup.scm index f5a604b..4cc899b 100644 --- a/after/queries/ecma/matchup.scm +++ b/after/queries/ecma/matchup.scm @@ -14,6 +14,10 @@ "}" @close.function)) ] @scope.function +(statement_block + "{" @open.block + "}" @close.block) @scope.block + (return_statement "return" @mid.function.1) From b92d25749b5068bf96580385ed9a0fe45c6d17b1 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 30 Jun 2025 11:49:59 -0500 Subject: [PATCH 20/29] fix(typescript): correctly define captures with anonymous nodes --- after/queries/typescript/matchup.scm | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/after/queries/typescript/matchup.scm b/after/queries/typescript/matchup.scm index 85f2b06..3311460 100644 --- a/after/queries/typescript/matchup.scm +++ b/after/queries/typescript/matchup.scm @@ -1,21 +1,9 @@ ; inherits: ecma -(type_arguments) @scope.typeargs +(type_arguments + "<" @open.typeargs + ">" @close.typeargs) @scope.typeargs -(type_arguments) - -"<" @open.typeargs - -(type_arguments) - -">" @close.typeargs - -(type_parameters) @scope.typeparams - -(type_parameters) - -"<" @open.typeparams - -(type_parameters) - -">" @close.typeparams +(type_parameters + "<" @open.typeparams + ">" @close.typeparams) @scope.typeparams From e4aae30b3f3401492c8cab262b2760ff230aa69e Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 30 Jun 2025 11:52:55 -0500 Subject: [PATCH 21/29] fix(haskell): properly escape backslash in lambda case query --- after/queries/haskell/matchup.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/after/queries/haskell/matchup.scm b/after/queries/haskell/matchup.scm index bddcdde..ffcbca5 100644 --- a/after/queries/haskell/matchup.scm +++ b/after/queries/haskell/matchup.scm @@ -14,7 +14,7 @@ ; --------------- lambda case ---------------- (expression/lambda_case - "\case" @open.case + "\\case" @open.case (alternatives (alternative) @mid.case.1)) @scope.case From ac432f135207660e3273b72dc79e29652778a2a3 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 30 Jun 2025 11:58:26 -0500 Subject: [PATCH 22/29] feat: add block and call queries for go, c and bash --- after/queries/bash/matchup.scm | 4 ++++ after/queries/c/matchup.scm | 8 ++++++++ after/queries/go/matchup.scm | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/after/queries/bash/matchup.scm b/after/queries/bash/matchup.scm index aa75e89..a55f65b 100644 --- a/after/queries/bash/matchup.scm +++ b/after/queries/bash/matchup.scm @@ -34,3 +34,7 @@ (heredoc_redirect (heredoc_start) @open.rhrd (heredoc_end) @close.rhrd) @scope.rhrd + +(compound_statement + "{" @open.block + "}" @close.block) @scope.block diff --git a/after/queries/c/matchup.scm b/after/queries/c/matchup.scm index 2cb592e..5afeabb 100644 --- a/after/queries/c/matchup.scm +++ b/after/queries/c/matchup.scm @@ -63,3 +63,11 @@ (break_statement "break" @mid.loop.1) + +(compound_statement + "{" @open.block + "}" @close.block) @scope.block + +(argument_list + "(" @open.call + ")" @close.call) @scope.call diff --git a/after/queries/go/matchup.scm b/after/queries/go/matchup.scm index ddcefcd..16478a2 100644 --- a/after/queries/go/matchup.scm +++ b/after/queries/go/matchup.scm @@ -32,3 +32,11 @@ (_ "\"" @open.quote_double "\"" @close.quote_double) @scope.quote_double + +(block + "{" @open.block + "}" @close.block) @scope.block + +(argument_list + "(" @open.call + ")" @close.call) @scope.call From bf56cac29d47f90a2b9c102d2026c5c65a4592b9 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 14:46:29 -0500 Subject: [PATCH 23/29] chore: make luacheck happy --- lua/treesitter-matchup/internal.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index ffd9e63..3f66988 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -437,11 +437,11 @@ function M.get_delim(bufnr, opts) end ---@param delim matchup.Delim ----@param _down 1|0 +---@param down 1|0 ---@param bufnr integer ---@return [string, integer, integer][] -function M.get_matching(delim, _down, bufnr) - local down = _down > 0 +function M.get_matching(delim, down, bufnr) + local is_down = down > 0 local cached_info = cache:get(delim._id) or {} if cached_info.bufnr ~= bufnr then @@ -452,9 +452,9 @@ function M.get_matching(delim, _down, bufnr) local sides ---@type ('open'|'mid'|'close')[] if vim.g.matchup_delim_nomids > 0 then - sides = down and {'close'} or {'open'} + sides = is_down and {'close'} or {'open'} else - sides = down and {'mid', 'close'} or {'mid', 'open'} + sides = is_down and {'mid', 'close'} or {'mid', 'open'} end local active_matches, symbols = unpack(M.get_active_matches(bufnr)) @@ -473,8 +473,8 @@ function M.get_matching(delim, _down, bufnr) end if cached_info.info ~= info and symbols[M.range_id(info.range)] == cached_info.key - and (down and (row > cached_info.row or row == cached_info.row and col > cached_info.col) - or not down and (row < cached_info.row or row == cached_info.row and col < cached_info.col)) + and (is_down and (row > cached_info.row or row == cached_info.row and col > cached_info.col) + or not is_down and (row < cached_info.row or row == cached_info.row and col < cached_info.col)) and (row >= cached_info.search_range[1] and row <= cached_info.search_range[3]) then @@ -497,7 +497,7 @@ function M.get_matching(delim, _down, bufnr) end) -- no stop marker is found, use enclosing scope - if down and not got_close then + if is_down and not got_close then local row, col, _ = cached_info.scope:end_() table.insert(matches, {'', row + 1, col + 1}) end From 321045f7274488cda68fd24b411f28ba366e8f0b Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 17:19:52 -0500 Subject: [PATCH 24/29] docs: update matchup.txt with the content of README --- doc/matchup.txt | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/doc/matchup.txt b/doc/matchup.txt index 2dc11f9..aa8909f 100644 --- a/doc/matchup.txt +++ b/doc/matchup.txt @@ -272,35 +272,21 @@ important special cases. *matchup-treesitter* Tree-sitter integration~ -Note: Currently this feature is possible in neovim only. Only the latest -version of neovim and nvim-treesitter is supported. +Note: Currently this feature is possible in Neovim only. Only the latest +stable version of Neovim is supported. match-up has support for language syntax provided by tree-sitter. The list -of supported languages is available here. This feature requires manual -opt-in in your init.vim and requires nvim-treesitter to be installed. > +of supported languages is available here. - Plug 'nvim-treesitter/nvim-treesitter' - lua < @@ -738,7 +724,7 @@ Module matchparen~ `MatchupStatusOffscreen()` can be used to get the text. `'popup'`: Use a popup window (requires at least vim 8.1.1406) or - a floating window (in neovim) to show the off-screen match. + a floating window (in Neovim) to show the off-screen match. scrolloff~ When enabled, off-screen matches will not be shown in the statusline while @@ -768,7 +754,7 @@ Module matchparen~ Default: 0 border~ - For floating window on neovim only: set to add a border. If the value + For floating window on Neovim only: set to add a border. If the value is the integer 1, default borders are enabled. A list or string can be specified as described in |nvim_open_win()|. @@ -894,7 +880,7 @@ the function |timer_pause|, version 7.4.2180 and after. *g:matchup_matchparen_end_sign* - (neovim only) Configure the virtual symbol shown for closeless matches in languages like + (Neovim only) Configure the virtual symbol shown for closeless matches in languages like C++ and python. if (true) @@ -906,7 +892,7 @@ the function |timer_pause|, version 7.4.2180 and after. *MatchupVirtualText* - (neovim only) You can also configure the color and style of the virtual text. + (Neovim only) You can also configure the color and style of the virtual text. :hi MatchupVirtualText ctermbg=blue guibg=lightblue gui=italic From 3de6be029dd512f69de9c71d92f5926f613b3062 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 17:22:44 -0500 Subject: [PATCH 25/29] fix: install treesitter parsers into docker image Co-authored-by: Dmytro Soltys --- vim.Dockerfile | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/vim.Dockerfile b/vim.Dockerfile index 8595200..5bfa336 100644 --- a/vim.Dockerfile +++ b/vim.Dockerfile @@ -9,14 +9,24 @@ ARG VIM_VERSION=v9.1.1287 ADD --chmod=755 https://github.com/vim/vim-appimage/releases/download/${VIM_VERSION}/Vim-${VIM_VERSION}.glibc2.29-x86_64.AppImage /vim-linux-x86_64.appimage RUN /vim-linux-x86_64.appimage --appimage-extract +FROM rust:latest AS tree-sitter +WORKDIR /work +RUN cargo install tree-sitter-cli --root / +ADD https://github.com/tree-sitter/tree-sitter-ruby.git /work/tree-sitter-ruby +RUN cd tree-sitter-ruby \ + && tree-sitter build -o /work/ruby.so +ADD https://github.com/tree-sitter/tree-sitter-python.git /work/tree-sitter-python +RUN cd tree-sitter-python \ + && tree-sitter build -o /work/python.so + FROM python:latest AS base RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ --mount=target=/var/cache/apt,type=cache,sharing=locked \ apt-get update -qq && \ apt-get install --no-install-recommends -y \ - git \ - ca-certificates \ - make + git \ + ca-certificates \ + make FROM base AS test-prep WORKDIR /work @@ -27,6 +37,11 @@ RUN mkdir -p test/vader/vader.vim && git clone --depth=1 https://github.com/june FROM python:latest AS nvim +WORKDIR /work +ENV HOME=/work +ENV GIT_PAGER=cat +ENV TESTS_ENABLE_TREESITTER=1 + # nvim COPY --from=neovim-image /squashfs-root /nvim-root RUN ln -s /nvim-root/AppRun /bin/nvim @@ -35,11 +50,12 @@ RUN ln -s /nvim-root/AppRun /bin/nvim COPY --from=vim-image /squashfs-root /vim-root RUN ln -s /vim-root/AppRun /bin/vim -WORKDIR /work -ENV HOME=/work -ENV GIT_PAGER=cat - COPY . . COPY --from=test-prep /work/test test +# Treesitter +RUN mkdir -p /work/.local/share/nvim/site/parser/ +COPY --from=tree-sitter /work/ruby.so /work/.local/share/nvim/site/parser/ruby.so +COPY --from=tree-sitter /work/python.so /work/.local/share/nvim/site/parser/python.so + ENTRYPOINT ["bash"] From 182fa8bc121f3fc4d7527bfb5a7b89b3833e0573 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 17:36:03 -0500 Subject: [PATCH 26/29] tests: remove references to nvim-treesitter --- test/new/common/bootstrap.vim | 17 ----------------- test/new/test-syn/test.vim | 29 ++++++----------------------- test/vader/minvimrc | 16 ---------------- test/vader/ts_py_motion.vader | 27 ++++++++------------------- 4 files changed, 14 insertions(+), 75 deletions(-) diff --git a/test/new/common/bootstrap.vim b/test/new/common/bootstrap.vim index 49c773e..3987022 100644 --- a/test/new/common/bootstrap.vim +++ b/test/new/common/bootstrap.vim @@ -3,23 +3,6 @@ set packpath-=~/.config/nvim packpath-=~/.config/nvim/after let &rtp = '../../..,' . &rtp let &rtp = &rtp . ',../../../after' -if $TESTS_ENABLE_TREESITTER - let s:path = simplify(expand(':h').'/../../..') - let &rtp = s:path.'/test/vader/plugged/nvim-treesitter,' . &rtp - let &rtp .= ','.s:path.'/test/vader/plugged/nvim-treesitter/after' - - runtime! plugin/nvim-treesitter.vim - runtime! plugin/nvim-treesitter.lua - - lua < 0 call matchup#test#finished() endif -let s:expect_ts_engine = +$TESTS_ENABLE_TREESITTER +let g:matchup_treesitter_enabled = v:false +let s:expect_ts_engine = $MODE == 0 ? 0 : +$TESTS_ENABLE_TREESITTER if $MODE == 1 - lua < Date: Mon, 7 Jul 2025 17:39:34 -0500 Subject: [PATCH 27/29] ci: install treesitter parsers without nvim-treesitter Co-authored-by: Dmytro Soltys --- .github/workflows/neovim.yml | 44 +++++++++++++++------ .github/workflows/neovim_treesitter.yml | 51 ------------------------- 2 files changed, 33 insertions(+), 62 deletions(-) delete mode 100644 .github/workflows/neovim_treesitter.yml diff --git a/.github/workflows/neovim.yml b/.github/workflows/neovim.yml index 13b8847..ef941a9 100644 --- a/.github/workflows/neovim.yml +++ b/.github/workflows/neovim.yml @@ -3,7 +3,7 @@ name: Neovim on: push: branches: - - '*' + - "*" pull_request: branches: - master @@ -13,28 +13,50 @@ jobs: strategy: matrix: neovim_version: - - 'head' - - 'v0.10.1' + - "head" + - "v0.10.1" runs-on: ubuntu-latest steps: - - uses: 'actions/checkout@v2' + - uses: "actions/checkout@v2" + - uses: tree-sitter/setup-action@v2 + with: + install-lib: false - name: Install vader.vim run: git clone --depth=1 https://github.com/junegunn/vader.vim.git test/vader/vader.vim - - name: 'setup Neovim' - uses: 'thinca/action-setup-vim@v2' + - name: "setup Neovim" + uses: "thinca/action-setup-vim@v2" with: - vim_version: '${{ matrix.neovim_version }}' - vim_type: 'Neovim' + vim_version: "${{ matrix.neovim_version }}" + vim_type: "Neovim" - - name: 'Show version' + - name: "Show version" run: nvim --version - - name: 'Run test' + - name: Clone tree-sitter-python + run: git clone --depth=1 https://github.com/tree-sitter/tree-sitter-python.git + working-directory: /tmp + + - name: Clone tree-sitter-ruby + run: git clone --depth=1 https://github.com/tree-sitter/tree-sitter-ruby.git + working-directory: /tmp + + - name: Create default nvim runtime parser directory + run: mkdir -p $HOME/.local/share/nvim/site/parser + + - name: Build tree-sitter-python + run: tree-sitter build -o $HOME/.local/share/nvim/site/parser/python.so + working-directory: /tmp/tree-sitter-python + + - name: Build tree-sitter-ruby + run: tree-sitter build -o $HOME/.local/share/nvim/site/parser/ruby.so + working-directory: /tmp/tree-sitter-ruby + + - name: "Run test" run: | bash -c 'VIMCMD=nvim test/vader/run' - - name: 'Run new tests' + - name: "Run new tests" run: | cd ./test/new && make -j1 && make -j1 coverage diff --git a/.github/workflows/neovim_treesitter.yml b/.github/workflows/neovim_treesitter.yml deleted file mode 100644 index 52ea1fa..0000000 --- a/.github/workflows/neovim_treesitter.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Neovim with Tree-sitter - -on: - push: - branches: - - '*' - pull_request: - branches: - - master - -jobs: - build: - strategy: - matrix: - neovim_version: - - 'head' - - 'v0.10.1' - runs-on: ubuntu-latest - env: - TESTS_ENABLE_TREESITTER: 1 - steps: - - uses: 'actions/checkout@v2' - - - name: Install vader.vim - run: git clone --depth=1 https://github.com/junegunn/vader.vim.git test/vader/vader.vim - - - name: 'setup Neovim' - uses: 'thinca/action-setup-vim@v2' - with: - vim_version: '${{ matrix.neovim_version }}' - vim_type: 'Neovim' - - - name: Install nvim-treesitter - run: git clone --depth=1 https://github.com/nvim-treesitter/nvim-treesitter.git test/vader/plugged/nvim-treesitter - - - name: Install python treesitter module - run: nvim --headless -Nu test/vader/minvimrc -c 'TSInstallSync python' -c 'q' - - - name: 'Show version' - run: nvim --version - - - name: 'Run test' - run: | - bash -c 'VIMCMD=nvim test/vader/run' - - - name: Install ruby treesitter module - run: nvim --headless -Nu test/vader/minvimrc -c 'TSInstallSync ruby' -c 'q' - - - name: 'Run new tests' - run: | - cd ./test/new && make -j1 && make -j1 coverage From ef27fce14ec92c8c4527116e7b9cc2be505e6e7f Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 18:01:58 -0500 Subject: [PATCH 28/29] ci: add TESTS_ENABLE_TREESITTER env var --- .github/workflows/neovim.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/neovim.yml b/.github/workflows/neovim.yml index ef941a9..b3bc777 100644 --- a/.github/workflows/neovim.yml +++ b/.github/workflows/neovim.yml @@ -16,6 +16,8 @@ jobs: - "head" - "v0.10.1" runs-on: ubuntu-latest + env: + TESTS_ENABLE_TREESITTER: 1 steps: - uses: "actions/checkout@v2" - uses: tree-sitter/setup-action@v2 From 427e9eb27aca960b74ecb98a0002708c53dd4285 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Mon, 7 Jul 2025 18:02:55 -0500 Subject: [PATCH 29/29] ci: run tests on Neovim 0.11.2 --- .github/workflows/neovim.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/neovim.yml b/.github/workflows/neovim.yml index b3bc777..64e31f3 100644 --- a/.github/workflows/neovim.yml +++ b/.github/workflows/neovim.yml @@ -14,7 +14,7 @@ jobs: matrix: neovim_version: - "head" - - "v0.10.1" + - "v0.11.2" runs-on: ubuntu-latest env: TESTS_ENABLE_TREESITTER: 1