Merge pull request #390 from TheLeoP/update-treesitter

Remove nvim-treesitter dependency
This commit is contained in:
Andy Massimino
2025-07-07 20:57:34 -04:00
committed by GitHub
48 changed files with 1435 additions and 1246 deletions

8
.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
.github
.gitignore
.gitlab-ci-yml
.luacheckrc
.projections.json
.vintrc
*Dockerfile
test/new/env

View File

@@ -3,7 +3,7 @@ name: Neovim
on:
push:
branches:
- '*'
- "*"
pull_request:
branches:
- master
@@ -13,28 +13,52 @@ jobs:
strategy:
matrix:
neovim_version:
- 'head'
- 'v0.10.1'
- "head"
- "v0.11.2"
runs-on: ubuntu-latest
env:
TESTS_ENABLE_TREESITTER: 1
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

View File

@@ -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

1
.gitignore vendored
View File

@@ -1 +1,2 @@
doc/tags
.nvim.lua

25
Makefile Normal file
View File

@@ -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

367
README.md
View File

@@ -9,7 +9,7 @@
</h1>
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,28 +91,43 @@ end)
and run `:PackerSync` or similar.
See [Tree-sitter integration](https://github.com/andymass/vim-matchup#tree-sitter-integration)
for information on how to enable tree-sitter matching with 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
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:
If you use [lazy.nvim](https://github.com/folke/lazy.nvim), add the following
to your plugins spec
```lua
{
"andymass/vim-matchup",
setup = function()
vim.g.matchup_matchparen_offscreen = { method = "popup" }
end,
},
'andymass/vim-matchup'
init = function()
-- modify your configuration vars here
vim.g.matchup_treesitter_stopline = 500
lvim.builtin.treesitter.matchup.enable = true
-- 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 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
into issues, remove the event key as a first debugging step.
You can use any other plugin manager such as
[vundle](https://github.com/gmarik/vundle),
[dein](https://github.com/Shougo/dein.vim),
@@ -131,39 +146,18 @@ 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).
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 <<EOF
require'nvim-treesitter.configs'.setup {
matchup = {
enable = true, -- mandatory, false will disable the whole extension
disable = { "c", "ruby" }, -- optional, list of language that will be disabled
-- [options]
},
}
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
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++
which are not supported in tree-sitter matching.
The treesitter related configuration options share the prefix
`g:matchup_treesitter`. You can check them on `:h g:matchup_treesitter_enabled`
Screenshot:
@@ -171,18 +165,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 +192,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 +213,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 +241,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 +339,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 +374,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
ifx | continue | endif
@@ -388,26 +393,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
ifx | 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 +428,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 +470,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 +562,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 +579,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 +627,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 +653,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 +672,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 +684,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 +697,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 +723,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 <silent> <F7> <plug>(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 +753,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 +821,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 +829,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 +847,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 +855,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 +886,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 +895,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 +912,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 +935,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 +959,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 +976,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 +989,3 @@ Please read the [contribution guidelines](CONTRIBUTING.md) before
contributing.
Contributions are welcome!

View File

@@ -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,9 @@
"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
(compound_statement
"{" @open.block
"}" @close.block) @scope.block

View File

@@ -1,32 +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" @_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
"if" @open.if) @scope.if
(#not-has-parent? @scope.if else_clause))
(#not-has-parent? @scope.if else_clause))
; if
(compound_statement
@@ -35,11 +43,31 @@
; 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)
(compound_statement
"{" @open.block
"}" @close.block) @scope.block
(argument_list
"(" @open.call
")" @close.call) @scope.call

View File

@@ -2,35 +2,57 @@
; 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)
(statement_block
"{" @open.block
"}" @close.block) @scope.block
(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" @_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
"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

View File

@@ -1,10 +1,10 @@
(if_else_expr
. "if" @open.if) @scope.if
.
"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
@@ -13,4 +13,4 @@
(case_of_expr
(case) @open.case
(case_of_branch
(arrow) @mid.case.1)) @scope.case
(arrow) @mid.case.1)) @scope.case

View File

@@ -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)

View File

@@ -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

View File

@@ -1,22 +1,42 @@
(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" @_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)
(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
"\"" @close.quote_double) @scope.quote_double
(block
"{" @open.block
"}" @close.block) @scope.block
(argument_list
"(" @open.call
")" @close.call) @scope.call

View File

@@ -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
"\case" @open.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

View File

@@ -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))

View File

@@ -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))

View File

@@ -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

View File

@@ -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
@@ -29,3 +34,12 @@
(do_statement
"do" @open.block
"end" @close.block) @scope.block
(table_constructor
"{" @open.table
"}" @close.table) @scope.table
(function_call
(arguments
"(" @open.call
")" @close.call)) @scope.call

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -10,24 +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" @_start (if_expression "if" @_end)
(#make-range! "mid.if_.2" @_start @_end))
"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)
@@ -37,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 .)

View File

@@ -1,6 +1,6 @@
; inherits: quote
(method_definition
(method_definition
".method" @open.function
".end method" @close.function) @scope.function

View File

@@ -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

View File

@@ -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

View File

@@ -1,9 +1,9 @@
; inherits: ecma
(type_arguments) @scope.typeargs
(type_arguments) "<" @open.typeargs
(type_arguments) ">" @close.typeargs
(type_arguments
"<" @open.typeargs
">" @close.typeargs) @scope.typeargs
(type_parameters) @scope.typeparams
(type_parameters) "<" @open.typeparams
(type_parameters) ">" @close.typeparams
(type_parameters
"<" @open.typeparams
">" @close.typeparams) @scope.typeparams

View File

@@ -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)

View File

@@ -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))

View File

@@ -2,20 +2,27 @@
(function_declaration
"fn" @open.function) @scope.function
(return_expression
"return" @mid.function.1)
; '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
"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)

View File

@@ -76,6 +76,13 @@ function! s:init_options()
call s:init_option('matchup_where_separator', '')
call s:init_option('matchup_matchpref', {})
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)
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)
@@ -408,17 +415,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 <buffer>
\ call v:lua.require('treesitter-matchup.third-party.query')
\.invalidate_query_file(expand('%:p'))
augroup END
" TODO: do something?
endfunction
"}}}1

View File

@@ -25,30 +25,13 @@ 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
endif
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
let b:matchup_delim_lists = s:init_delim_lists(l:no_words, l:filt_words)
@@ -58,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
@@ -93,7 +76,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

View File

@@ -1204,8 +1204,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

View File

@@ -15,33 +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#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
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')

View File

@@ -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 <<EOF
require'nvim-treesitter.configs'.setup {
matchup = {
enable = true, -- mandatory, false will disable the whole extension
disable = { "c", "ruby" }, -- optional, list of language that will be disabled
-- [options]
},
}
<
Beside enable and disable, the following options are available, all
defaulting to false:
*disable_virtual_text*
This feature is automatically enabled if you are using Neovim. And does not
require other plugins to work.
If true, 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*
If true, additionally include traditional vim regex matches for symbols.
The treesitter related configuration options share the prefix
`g:matchup_treesitter`. You can check them on `:h g:matchup_treesitter_enabled`
------------------------------------------------------------------------------
Highlighting matches~
To disable match highlighting at |startup|, use >
@@ -542,7 +528,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 +538,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*
@@ -666,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
@@ -696,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()|.
@@ -822,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)
@@ -834,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

View File

@@ -1,23 +1,124 @@
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 0|1
---@class matchup.MappingsConfig
---@field enabled 0|1
---@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 0|1
---@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 0|1
---@class matchup.OverrideConfig
---@field vimtex 1|0
---@class matchup.SurroundConfig
---@field enabled 0|1
---@class matchup.TextObjConfig
---@field enabled 0|1
---@field linewise_operators string[]
---@class matchup.TransmuteConfig
---@field enabled 0|1
---@class matchup.TreesitterConfig
---@field enabled boolean
---@field disabled string[]
---@field include_match_words boolean
---@field disable_virtual_text boolean
---@field enable_quotes boolean
---@field stopline integer
---@class matchup.Config
---@field delim matchup.DelimConfig
---@field enabled 0|1
---@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<string, unknown|table<string, unknown>>]]) 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

View File

@@ -1,21 +0,0 @@
if not pcall(require, 'nvim-treesitter') then
return {init = function() end}
end
local treesitter = require 'nvim-treesitter'
local queries = require 'nvim-treesitter.query'
local M = {}
function M.init()
treesitter.define_modules {
matchup = {
module_path = 'treesitter-matchup.internal',
is_supported = function(lang)
return queries.has_query_files(lang, 'matchup')
end
}
}
end
return M

View File

@@ -1,18 +1,10 @@
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
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 +12,177 @@ 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
local buf_enabled = vim.b[bufnr].matchup_treesitter_enabled
local lang_disabled = vim.list_contains(vim.g.matchup_treesitter_disabled, lang)
if buf_enabled == false then
return false
end
return enabled and not lang_disabled
end
---@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)
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
end
local _, err = ts.get_parser(bufnr, nil, {error = false})
if err then
return false
end
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)
---@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<string, matchup.treesitter.MatchInfoWrapper>
---@field open? table<string, matchup.treesitter.MatchInfoWrapper>
---@field mid? table<string, table<string, matchup.treesitter.MatchInfoWrapper>>
---@field close? table<string, matchup.treesitter.MatchInfoWrapper>
---@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, start_col, end_row, 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<string, table<string, matchup.treesitter.MatchInfo>>
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
-- 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
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
--- 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<string, table<string, boolean>>
M.get_scopes = function(bufnr)
local matches = M.get_matches(bufnr)
local scopes = {}
local scopes = {} ---@type table<string, table<string, boolean>>
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,39 +190,41 @@ 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<string, string>]
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<string, string>
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
@@ -130,9 +232,9 @@ M.get_active_nodes = ts_utils.memoize_by_buf_tick(function(bufnr)
if match.mid then
for key, mid_group in pairs(match.mid) do
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 +242,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 +269,36 @@ 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<string, unknown>
---@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)
---@class matchup.Delim
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 +308,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 +318,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<matchup.Side, ('open'|'mid'|'close')[]>
local side_table = {
open = {'open'},
mid = {'mid'},
@@ -215,25 +357,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 +391,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 +402,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 +421,66 @@ 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
---@param delim matchup.Delim
---@param down 1|0
---@param bufnr integer
---@return [string, integer, integer][]
function M.get_matching(delim, down, bufnr)
down = down > 0
local is_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'}
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_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 (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
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
@@ -342,45 +497,12 @@ function M.get_matching(delim, down, bufnr)
end)
-- no stop marker is found, use enclosing scope
if down and not got_close then
local row, col, _ = info.scope:end_()
if is_down and not got_close then
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
function M.detach(bufnr)
api.nvim_call_function('matchup#ts_engine#detach', {bufnr})
end
return M

View File

@@ -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<string, 1>
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,21 @@ function M.lang_skip(lnum, col)
return false
end
local node = get_node_at_pos({lnum, col - 1})
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)

View File

@@ -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

View File

@@ -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<any,any>
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

View File

@@ -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

View File

@@ -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('<sfile>: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 <<EOF
require'nvim-treesitter.configs'.setup {
matchup = {
enable = true
}
}
EOF
endif
filetype plugin indent on
syntax enable

View File

@@ -5,33 +5,16 @@ if !$TESTS_ENABLE_TREESITTER && $MODE > 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 <<EOF
require'nvim-treesitter.configs'.setup {
highlight = { enable = true },
matchup = { enable = true }
}
EOF
let g:matchup_treesitter_enabled = v:true
autocmd FileType *.rb lua vim.treesitter.start()
elseif $MODE == 2
lua <<EOF
require'nvim-treesitter.configs'.setup {
highlight = { enable = true },
matchup = {
enable = true,
additional_vim_regex_highlighting = true
}
}
EOF
let g:matchup_treesitter_enabled = v:true
elseif $MODE == 3
lua <<EOF
require'nvim-treesitter.configs'.setup {
highlight = { enable = true },
matchup = { enable = false }
}
EOF
let g:matchup_treesitter_enabled = v:false
let s:expect_ts_engine = 0
endif

View File

@@ -12,22 +12,6 @@ let &rtp = s:path.'/test/rtp,' . &rtp
" load other plugins, if necessary
" let &rtp = '~/path/to/other/plugin,' . &rtp
if $TESTS_ENABLE_TREESITTER
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 <<EOF
require'nvim-treesitter.configs'.setup {
matchup = {
enable = true
}
}
EOF
endif
if empty(globpath(&rtp, 'plugin/vader.vim'))
echoerr 'vader not found'
exit!

View File

@@ -1,14 +1,3 @@
Execute (Helper):
function! TSActive()
return exists(':TSInstall')
endfunction
if TSActive()
Log 'nvim-treesitter active'
else
Log 'nvim-treesitter not active'
endif
Given python (A python script):
def F(x, y):
if x == 1:
@@ -35,22 +24,22 @@ Before (Cursor):
Do (Move %):
%
Then (Verify line):
AssertEqual (TSActive() ? 4 : 2), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 4 : 2), line('.')
Do (Move % twice):
%%
Then (Verify line):
AssertEqual (TSActive() ? 11 : 2), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 11 : 2), line('.')
Do (Move % 3 times):
%%%
Then (Verify line):
AssertEqual (TSActive() ? 2 : 2), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 2 : 2), line('.')
Do (Move % 4 times):
%%%%
Then (Verify line):
AssertEqual (TSActive() ? 4 : 2), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 4 : 2), line('.')
# ----- inner if/elif/else -----
@@ -60,19 +49,19 @@ Before (Cursor):
Do (Inner: Move %):
%
Then (Verify line):
AssertEqual (TSActive() ? 7 : 5), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 7 : 5), line('.')
Do (Inner: % 2 times):
%%
Then (Verify line):
AssertEqual (TSActive() ? 9 : 5), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 9 : 5), line('.')
Do (Inner: % 3 times):
%%%
Then (Verify line):
AssertEqual (TSActive() ? 5 : 5), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 5 : 5), line('.')
Do (Inner: % 4 times):
%%%%
Then (Verify line):
AssertEqual (TSActive() ? 7 : 5), line('.')
AssertEqual (len(b:matchup_active_engines.delim_all) == 2 ? 7 : 5), line('.')

61
vim.Dockerfile Normal file
View File

@@ -0,0 +1,61 @@
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 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
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
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
# vim
COPY --from=vim-image /squashfs-root /vim-root
RUN ln -s /vim-root/AppRun /bin/vim
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"]