mirror of
https://github.com/nvim-treesitter/nvim-treesitter-context
synced 2024-09-16 14:14:03 +02:00
feat!: (BREAKING PLEASE READ) use queries for determining context (#198)
This plugin has been significantly rewritten to use Treesitter queries instead of patterns for determining context regions for languages. The main benefits of this change are: - it is a much simpler implementation since we can leverage core APIs. - it fits in more generally with the Treesitter eco-system. - it allows configuration of contexts to be provided from multiples sources. - it allows more sophisticated configuration of contexts since queries (with directives and predicates) are much more powerful than patterns. - the query format should be usable for other editors. The major downside of this new implementation is that it requires each language to provide it's own query as opposed to using the general purpose patterns. This means that some languages which had contexts before may not have them now. If this is the case then please raise an issue. Adding queries for a specific language is fairly simple but too much work to implement for all 170+ parsers that exist. This commits provides explicit support for: - bash - c - cpp - typescript - rust - json - lua - markdown - python - yaml - php - scala - teal - toml - vim Please see the README for instructions on how to add support for other languages. This commit also drops explicit support for Nvim 0.7. If you still need support for this version then you can use the `compat/0.7` release.
This commit is contained in:
parent
895ec44f5c
commit
6e53eecca4
24 changed files with 1142 additions and 507 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
neovim_branch: ['v0.7.0']
|
||||
neovim_branch: ['v0.8.2']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEOVIM_BRANCH: ${{ matrix.neovim_branch }}
|
||||
|
|
17
Makefile
17
Makefile
|
@ -16,26 +16,21 @@ $(NEOVIM):
|
|||
nvim-treesitter:
|
||||
git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter
|
||||
|
||||
nvim-treesitter/parser/lua.so: nvim-treesitter $(NEOVIM)
|
||||
nvim-treesitter/parser/%.so: nvim-treesitter $(NEOVIM)
|
||||
VIMRUNTIME=$(NEOVIM)/runtime $(NEOVIM)/build/bin/nvim \
|
||||
--headless \
|
||||
--clean \
|
||||
--cmd 'set rtp+=./nvim-treesitter' \
|
||||
-c "TSInstallSync lua" \
|
||||
-c "q"
|
||||
|
||||
nvim-treesitter/parser/rust.so: nvim-treesitter $(NEOVIM)
|
||||
VIMRUNTIME=$(NEOVIM)/runtime $(NEOVIM)/build/bin/nvim \
|
||||
--headless \
|
||||
--clean \
|
||||
--cmd 'set rtp+=./nvim-treesitter' \
|
||||
-c "TSInstallSync rust" \
|
||||
-c "TSInstallSync $*" \
|
||||
-c "q"
|
||||
|
||||
export VIMRUNTIME=$(PWD)/$(NEOVIM)/runtime
|
||||
|
||||
.PHONY: test
|
||||
test: $(NEOVIM) nvim-treesitter nvim-treesitter/parser/lua.so nvim-treesitter/parser/rust.so
|
||||
test: $(NEOVIM) nvim-treesitter \
|
||||
nvim-treesitter/parser/lua.so \
|
||||
nvim-treesitter/parser/rust.so \
|
||||
nvim-treesitter/parser/typescript.so
|
||||
$(NEOVIM)/.deps/usr/bin/busted \
|
||||
-v \
|
||||
--lazy \
|
||||
|
|
299
README.md
299
README.md
|
@ -5,7 +5,7 @@ implemented with [nvim-treesitter](https://github.com/nvim-treesitter/nvim-trees
|
|||
|
||||
## Requirements
|
||||
|
||||
Neovim >= v0.7.x
|
||||
Neovim >= v0.8.2
|
||||
|
||||
Note: if you need support for Neovim 0.6.x please use the tag `compat/0.6`.
|
||||
|
||||
|
@ -29,11 +29,166 @@ use 'nvim-treesitter/nvim-treesitter-context'
|
|||
|
||||
![theme](./static/demo.gif)
|
||||
|
||||
### Notes
|
||||
## Supported languages
|
||||
|
||||
This plugins uses the new neovim `WinScrolled` event when available to update its
|
||||
context window. Make sure to have a recent neovim build to get this behavior. The fallback
|
||||
behavior is to update its content on `CursorMoved`.
|
||||
- [x] `bash`
|
||||
- [x] `c`
|
||||
- [x] `cpp`
|
||||
- [x] `typescript`
|
||||
- [x] `rust`
|
||||
- [x] `json`
|
||||
- [x] `lua`
|
||||
- [x] `markdown`
|
||||
- [x] `python`
|
||||
- [x] `yaml`
|
||||
- [x] `php`
|
||||
- [x] `scala`
|
||||
- [x] `teal`
|
||||
- [x] `toml`
|
||||
- [x] `vim`
|
||||
- [ ] `ada`
|
||||
- [ ] `agda`
|
||||
- [ ] `arduino`
|
||||
- [ ] `astro`
|
||||
- [ ] `beancount`
|
||||
- [ ] `bibtex`
|
||||
- [ ] `bicep`
|
||||
- [ ] `blueprint`
|
||||
- [ ] `c_sharp`
|
||||
- [ ] `capnp`
|
||||
- [ ] `chatito`
|
||||
- [ ] `clojure`
|
||||
- [ ] `cmake`
|
||||
- [ ] `commonlisp`
|
||||
- [ ] `cooklang`
|
||||
- [ ] `cpon`
|
||||
- [ ] `css`
|
||||
- [ ] `cuda`
|
||||
- [ ] `d`
|
||||
- [ ] `dart`
|
||||
- [ ] `devicetree`
|
||||
- [ ] `dhall`
|
||||
- [ ] `dockerfile`
|
||||
- [ ] `dot`
|
||||
- [ ] `ebnf`
|
||||
- [ ] `ecma`
|
||||
- [ ] `eex`
|
||||
- [ ] `elixir`
|
||||
- [ ] `elm`
|
||||
- [ ] `elsa`
|
||||
- [ ] `elvish`
|
||||
- [ ] `embedded_template`
|
||||
- [ ] `erlang`
|
||||
- [ ] `fennel`
|
||||
- [ ] `fish`
|
||||
- [ ] `foam`
|
||||
- [ ] `fsh`
|
||||
- [ ] `func`
|
||||
- [ ] `fusion`
|
||||
- [ ] `gdscript`
|
||||
- [ ] `git_rebase`
|
||||
- [ ] `gleam`
|
||||
- [ ] `glimmer`
|
||||
- [ ] `glsl`
|
||||
- [ ] `go`
|
||||
- [ ] `godot_resource`
|
||||
- [ ] `gomod`
|
||||
- [ ] `gosum`
|
||||
- [ ] `gowork`
|
||||
- [ ] `graphql`
|
||||
- [ ] `hack`
|
||||
- [ ] `haskell`
|
||||
- [ ] `hcl`
|
||||
- [ ] `heex`
|
||||
- [ ] `hjson`
|
||||
- [ ] `hlsl`
|
||||
- [ ] `hocon`
|
||||
- [ ] `html`
|
||||
- [ ] `html_tags`
|
||||
- [ ] `htmldjango`
|
||||
- [ ] `http`
|
||||
- [ ] `ini`
|
||||
- [ ] `java`
|
||||
- [ ] `javascript`
|
||||
- [ ] `jq`
|
||||
- [ ] `jsdoc`
|
||||
- [ ] `json5`
|
||||
- [ ] `jsonc`
|
||||
- [ ] `jsonnet`
|
||||
- [ ] `jsx`
|
||||
- [ ] `julia`
|
||||
- [ ] `kdl`
|
||||
- [ ] `kotlin`
|
||||
- [ ] `lalrpop`
|
||||
- [ ] `latex`
|
||||
- [ ] `ledger`
|
||||
- [ ] `llvm`
|
||||
- [ ] `m68k`
|
||||
- [ ] `matlab`
|
||||
- [ ] `menhir`
|
||||
- [ ] `mermaid`
|
||||
- [ ] `meson`
|
||||
- [ ] `nickel`
|
||||
- [ ] `nix`
|
||||
- [ ] `ocaml`
|
||||
- [ ] `ocaml_interface`
|
||||
- [ ] `ocamllex`
|
||||
- [ ] `pascal`
|
||||
- [ ] `perl`
|
||||
- [ ] `phpdoc`
|
||||
- [ ] `pioasm`
|
||||
- [ ] `po`
|
||||
- [ ] `poe_filter`
|
||||
- [ ] `prisma`
|
||||
- [ ] `proto`
|
||||
- [ ] `prql`
|
||||
- [ ] `pug`
|
||||
- [ ] `ql`
|
||||
- [ ] `qmldir`
|
||||
- [ ] `qmljs`
|
||||
- [ ] `query`
|
||||
- [ ] `r`
|
||||
- [ ] `racket`
|
||||
- [ ] `rasi`
|
||||
- [ ] `rego`
|
||||
- [ ] `rnoweb`
|
||||
- [ ] `ron`
|
||||
- [ ] `rst`
|
||||
- [ ] `ruby`
|
||||
- [ ] `scheme`
|
||||
- [ ] `scss`
|
||||
- [ ] `slint`
|
||||
- [ ] `smali`
|
||||
- [ ] `smithy`
|
||||
- [ ] `solidity`
|
||||
- [ ] `sparql`
|
||||
- [ ] `sql`
|
||||
- [ ] `starlark`
|
||||
- [ ] `supercollider`
|
||||
- [ ] `surface`
|
||||
- [ ] `svelte`
|
||||
- [ ] `swift`
|
||||
- [ ] `sxhkdrc`
|
||||
- [ ] `t32`
|
||||
- [ ] `terraform`
|
||||
- [ ] `thrift`
|
||||
- [ ] `tiger`
|
||||
- [ ] `tlaplus`
|
||||
- [ ] `todotxt`
|
||||
- [ ] `tsx`
|
||||
- [ ] `turtle`
|
||||
- [ ] `twig`
|
||||
- [ ] `ungrammar`
|
||||
- [ ] `v`
|
||||
- [ ] `vala`
|
||||
- [ ] `verilog`
|
||||
- [ ] `vhs`
|
||||
- [ ] `vue`
|
||||
- [ ] `wgsl`
|
||||
- [ ] `wgsl_bevy`
|
||||
- [ ] `yang`
|
||||
- [ ] `yuck`
|
||||
- [ ] `zig`
|
||||
|
||||
## Configuration
|
||||
|
||||
|
@ -41,94 +196,17 @@ behavior is to update its content on `CursorMoved`.
|
|||
|
||||
```lua
|
||||
require'treesitter-context'.setup{
|
||||
enable = true, -- Enable this plugin (Can be enabled/disabled later via commands)
|
||||
max_lines = 0, -- How many lines the window should span. Values <= 0 mean no limit.
|
||||
trim_scope = 'outer', -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
|
||||
min_window_height = 0, -- Minimum editor window height to enable context. Values <= 0 mean no limit.
|
||||
patterns = { -- Match patterns for TS nodes. These get wrapped to match at word boundaries.
|
||||
-- For all filetypes
|
||||
-- Note that setting an entry here replaces all other patterns for this entry.
|
||||
-- By setting the 'default' entry below, you can control which nodes you want to
|
||||
-- appear in the context window.
|
||||
default = {
|
||||
'class',
|
||||
'function',
|
||||
'method',
|
||||
'for',
|
||||
'while',
|
||||
'if',
|
||||
'switch',
|
||||
'case',
|
||||
'interface',
|
||||
'struct',
|
||||
'enum',
|
||||
},
|
||||
-- Patterns for specific filetypes
|
||||
-- If a pattern is missing, *open a PR* so everyone can benefit.
|
||||
tex = {
|
||||
'chapter',
|
||||
'section',
|
||||
'subsection',
|
||||
'subsubsection',
|
||||
},
|
||||
haskell = {
|
||||
'adt'
|
||||
},
|
||||
rust = {
|
||||
'impl_item',
|
||||
|
||||
},
|
||||
terraform = {
|
||||
'block',
|
||||
'object_elem',
|
||||
'attribute',
|
||||
},
|
||||
scala = {
|
||||
'object_definition',
|
||||
},
|
||||
vhdl = {
|
||||
'process_statement',
|
||||
'architecture_body',
|
||||
'entity_declaration',
|
||||
},
|
||||
markdown = {
|
||||
'section',
|
||||
},
|
||||
elixir = {
|
||||
'anonymous_function',
|
||||
'arguments',
|
||||
'block',
|
||||
'do_block',
|
||||
'list',
|
||||
'map',
|
||||
'tuple',
|
||||
'quoted_content',
|
||||
},
|
||||
json = {
|
||||
'pair',
|
||||
},
|
||||
typescript = {
|
||||
'export_statement',
|
||||
},
|
||||
yaml = {
|
||||
'block_mapping_pair',
|
||||
},
|
||||
},
|
||||
exact_patterns = {
|
||||
-- Example for a specific filetype with Lua patterns
|
||||
-- Treat patterns.rust as a Lua pattern (i.e "^impl_item$" will
|
||||
-- exactly match "impl_item" only)
|
||||
-- rust = true,
|
||||
},
|
||||
|
||||
-- [!] The options below are exposed but shouldn't require your attention,
|
||||
-- you can safely ignore them.
|
||||
|
||||
zindex = 20, -- The Z-index of the context window
|
||||
mode = 'cursor', -- Line used to calculate context. Choices: 'cursor', 'topline'
|
||||
-- Separator between context and content. Should be a single character string, like '-'.
|
||||
-- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
|
||||
separator = nil,
|
||||
enable = true, -- Enable this plugin (Can be enabled/disabled later via commands)
|
||||
max_lines = 0, -- How many lines the window should span. Values <= 0 mean no limit.
|
||||
min_window_height = 0, -- Minimum editor window height to enable context. Values <= 0 mean no limit.
|
||||
line_numbers = true,
|
||||
multiline_threshold = 20, -- Maximum number of lines to collapse for a single context line
|
||||
trim_scope = 'outer', -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
|
||||
mode = 'cursor', -- Line used to calculate context. Choices: 'cursor', 'topline'
|
||||
-- Separator between context and content. Should be a single character string, like '-'.
|
||||
-- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
|
||||
separator = nil,
|
||||
zindex = 20, -- The Z-index of the context window
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -151,3 +229,38 @@ However, you can use this to create a border by applying an underline highlight,
|
|||
```vim
|
||||
hi TreesitterContextBottom gui=underline guisp=Grey
|
||||
```
|
||||
|
||||
## Adding support for other languages
|
||||
|
||||
To add support for another language, simply add a `context.scm` file under
|
||||
`queries/[LANG]`.
|
||||
|
||||
Queries specify the `@context` capture which specifies the first line of a node
|
||||
will be used for the context.
|
||||
|
||||
Here is a basic example for C:
|
||||
|
||||
```query
|
||||
(function_definition) @context
|
||||
(for_statement) @context
|
||||
(if_statement) @context
|
||||
(while_statement) @context
|
||||
(do_statement) @context
|
||||
```
|
||||
|
||||
You can easily look at a node names of a tree using `InspectTree` in Nvim 0.9.
|
||||
|
||||
Additionally an optional `@context.end` capture can also be specified. When
|
||||
provided, the text from the start of the `@context` capture to the start of
|
||||
`@context.end` capture (exclusive) will be used for the context and joined into
|
||||
a single line.
|
||||
|
||||
Here's what that looks like for C:
|
||||
|
||||
```query
|
||||
(if_statement consequence: (_ (_) @context.end)) @context
|
||||
```
|
||||
|
||||
This query specifies that everything from the `if` keyword up-to the first
|
||||
statement (exclusive) should be used for the context. This is useful when an
|
||||
if-statement spans multiple lines.
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
local api = vim.api
|
||||
local ts_utils = require'nvim-treesitter.ts_utils'
|
||||
local highlighter = vim.treesitter.highlighter
|
||||
|
||||
local parsers = require'nvim-treesitter.parsers'
|
||||
|
||||
local augroup = api.nvim_create_augroup
|
||||
local command = api.nvim_create_user_command
|
||||
|
||||
local function word_pattern(p)
|
||||
return '%f[%w]' .. p .. '%f[^%w]'
|
||||
end
|
||||
---@diagnostic disable:invisible
|
||||
|
||||
--- @class Config
|
||||
--- @field enable boolean
|
||||
--- @field max_lines integer
|
||||
--- @field min_window_height integer
|
||||
--- @field line_numbers boolean
|
||||
--- @field multiline_threshold integer
|
||||
--- @field trim_scope 'outer'|'inner'
|
||||
--- @field zindex integer
|
||||
--- @field mode 'cursor'|'topline'
|
||||
--- @field separator string?
|
||||
|
||||
--- @type Config
|
||||
local defaultConfig = {
|
||||
enable = true,
|
||||
max_lines = 0, -- no limit
|
||||
|
@ -22,275 +32,118 @@ local defaultConfig = {
|
|||
separator = nil,
|
||||
}
|
||||
|
||||
--- @type Config
|
||||
local config = {}
|
||||
|
||||
-- Constants
|
||||
|
||||
-- Tells us at which node type to stop when highlighting a multi-line
|
||||
-- node. If not specified, the highlighting stops after the first line.
|
||||
local last_nodes
|
||||
local QUERY_FIELD_NAME = 1
|
||||
local QUERY_NODE_TYPE = 2
|
||||
do
|
||||
local function f(name)
|
||||
return {
|
||||
name = name,
|
||||
kind = QUERY_FIELD_NAME,
|
||||
}
|
||||
end
|
||||
|
||||
local function t(name)
|
||||
return {
|
||||
name = name,
|
||||
kind = QUERY_NODE_TYPE,
|
||||
}
|
||||
end
|
||||
|
||||
last_nodes = {
|
||||
[word_pattern('function')] = {
|
||||
c = { f'declarator' },
|
||||
cpp = { f'declarator' },
|
||||
lua = { f'parameters' },
|
||||
teal = { f'signature' },
|
||||
python = { f'return_type', f'parameters' },
|
||||
rust = { f'return_type', f'parameters' },
|
||||
javascript = { f'parameters' },
|
||||
typescript = { f'return_type', f'parameters' },
|
||||
},
|
||||
[word_pattern('method')] = {
|
||||
lua = { f'parameters' },
|
||||
javascript = { f'parameters' },
|
||||
typescript = { f'return_type', f'parameters' },
|
||||
},
|
||||
[word_pattern('class')] = {
|
||||
cpp = { t'base_class_clause', f'name' },
|
||||
python = { f'superclasses' },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
-- Tells us which leading child node type to skip when highlighting a
|
||||
-- multi-line node.
|
||||
local skip_leading_types = {
|
||||
[word_pattern('class')] = {
|
||||
php = 'attribute_list',
|
||||
},
|
||||
[word_pattern('method')] = {
|
||||
php = 'attribute_list',
|
||||
},
|
||||
}
|
||||
|
||||
-- There are language-specific
|
||||
local DEFAULT_TYPE_PATTERNS = {
|
||||
-- These catch most generic groups, eg "function_declaration" or "function_block"
|
||||
default = {
|
||||
'class',
|
||||
'function',
|
||||
'method',
|
||||
'for',
|
||||
'while',
|
||||
'if',
|
||||
'switch',
|
||||
'case',
|
||||
'interface',
|
||||
'struct',
|
||||
'enum',
|
||||
},
|
||||
elixir = {
|
||||
'anonymous_function',
|
||||
'arguments',
|
||||
'block',
|
||||
'do_block',
|
||||
'list',
|
||||
'map',
|
||||
'tuple',
|
||||
'quoted_content',
|
||||
},
|
||||
haskell = {
|
||||
'adt'
|
||||
},
|
||||
json = {
|
||||
'pair',
|
||||
},
|
||||
markdown = {
|
||||
'section',
|
||||
},
|
||||
python = {
|
||||
'with_statement',
|
||||
},
|
||||
rust = {
|
||||
'impl_item',
|
||||
},
|
||||
scala = {
|
||||
'object_definition',
|
||||
},
|
||||
terraform = {
|
||||
'block',
|
||||
'object_elem',
|
||||
'attribute',
|
||||
},
|
||||
tex = {
|
||||
'chapter',
|
||||
'section',
|
||||
'subsection',
|
||||
'subsubsection',
|
||||
},
|
||||
typescript = {
|
||||
'export_statement',
|
||||
},
|
||||
verilog = {
|
||||
'always_construct',
|
||||
'statement_or_null',
|
||||
},
|
||||
vhdl = {
|
||||
'process_statement',
|
||||
'architecture_body',
|
||||
'entity_declaration',
|
||||
},
|
||||
yaml = {
|
||||
'block_mapping_pair',
|
||||
},
|
||||
exact_patterns = {},
|
||||
}
|
||||
|
||||
local DEFAULT_TYPE_EXCLUDE_PATTERNS = {
|
||||
default = {},
|
||||
teal = {
|
||||
'function_body',
|
||||
},
|
||||
}
|
||||
|
||||
local INDENT_PATTERN = '^%s+'
|
||||
|
||||
-- Script variables
|
||||
|
||||
local did_setup = false
|
||||
local enabled = false
|
||||
local gutter_winid, context_winid
|
||||
local gutter_bufnr, context_bufnr -- Don't access directly, use get_bufs()
|
||||
|
||||
-- Don't access directly, use get_bufs()
|
||||
--- @type integer?
|
||||
local gutter_winid
|
||||
|
||||
--- @type integer?
|
||||
local context_winid
|
||||
|
||||
--- @type integer?
|
||||
local gutter_bufnr
|
||||
|
||||
--- @type integer?
|
||||
local context_bufnr
|
||||
|
||||
local ns = api.nvim_create_namespace('nvim-treesitter-context')
|
||||
|
||||
--- @type TSNode[]?
|
||||
local previous_nodes
|
||||
|
||||
--- @return TSNode
|
||||
local function get_root_node()
|
||||
---@diagnostic disable-next-line
|
||||
local tree = parsers.get_parser():parse()[1]
|
||||
return tree:root()
|
||||
end
|
||||
|
||||
local function is_excluded(node, filetype)
|
||||
local node_type = node:type()
|
||||
for _, rgx in ipairs(config.exclude_patterns.default) do
|
||||
if node_type:find(rgx) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
local filetype_patterns = config.exclude_patterns[filetype]
|
||||
for _, rgx in ipairs(filetype_patterns or {}) do
|
||||
if node_type:find(rgx) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
--- @param node TSNode
|
||||
--- @param query Query
|
||||
--- @return Range4?
|
||||
local function is_valid(node, query)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local range --[[@type Range4]] = {node:range()}
|
||||
range[3] = range[1]
|
||||
range[4] = -1
|
||||
|
||||
local function is_valid(node, filetype)
|
||||
if is_excluded(node, filetype) then
|
||||
return false
|
||||
end
|
||||
-- Try and iterate on the parent node as iter_matches won't match on the top
|
||||
-- level node
|
||||
local iter_node = node:parent() or node
|
||||
|
||||
local node_type = node:type()
|
||||
for _, rgx in ipairs(config.patterns.default) do
|
||||
if node_type:find(rgx) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
local filetype_patterns = config.patterns[filetype]
|
||||
for _, rgx in ipairs(filetype_patterns or {}) do
|
||||
if node_type:find(rgx) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
for _, match in query:iter_matches(iter_node, bufnr, 0, -1) do
|
||||
local r = false
|
||||
|
||||
local function get_type_pattern(node, type_patterns)
|
||||
local node_type = node:type()
|
||||
for _, rgx in ipairs(type_patterns) do
|
||||
if node_type:find(rgx) then
|
||||
return rgx
|
||||
end
|
||||
end
|
||||
end
|
||||
for id, node0 in pairs(match --[[@as table<integer,TSNode>]]) do
|
||||
local srow, scol, erow, ecol = node0:range()
|
||||
|
||||
local function find_node(node, query)
|
||||
if query.kind == QUERY_FIELD_NAME then
|
||||
local fields = node:field(query.name)
|
||||
if fields and fields[1] then
|
||||
return fields[1]
|
||||
end
|
||||
elseif query.kind == QUERY_NODE_TYPE then
|
||||
local children = ts_utils.get_named_children(node)
|
||||
for _, c in ipairs(children) do
|
||||
if c:type() == query.name then
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_text_for_node(node)
|
||||
local type = get_type_pattern(node, config.patterns.default) or node:type()
|
||||
local filetype = vim.bo.filetype
|
||||
|
||||
local start_row, start_col = node:start()
|
||||
local end_row, end_col = node:end_()
|
||||
|
||||
local node_text = vim.treesitter.query.get_node_text(node, 0)
|
||||
if node_text == nil then return nil, nil end
|
||||
|
||||
local lines = vim.split(node_text, '\n')
|
||||
|
||||
if start_col ~= 0 then
|
||||
lines[1] = api.nvim_buf_get_lines(0, start_row, start_row + 1, false)[1]
|
||||
end
|
||||
start_col = 0
|
||||
|
||||
local queries = (last_nodes[type] or {})[filetype]
|
||||
|
||||
local last_position
|
||||
|
||||
if queries then
|
||||
local child
|
||||
for _, q in ipairs(queries) do
|
||||
local n = find_node(node, q)
|
||||
if n then
|
||||
child = n
|
||||
-- because iter_node != node we could match outside of node
|
||||
if srow < range[1] then
|
||||
break
|
||||
end
|
||||
|
||||
local name = query.captures[id] -- name of the capture in the query
|
||||
if not r and name == 'context' then
|
||||
r = node == node0
|
||||
elseif name == 'context.final' then
|
||||
range[3] = erow
|
||||
range[4] = ecol
|
||||
elseif name == 'context.end' then
|
||||
range[3] = srow
|
||||
range[4] = scol
|
||||
end
|
||||
end
|
||||
|
||||
if child then
|
||||
last_position = {child:end_()}
|
||||
|
||||
end_row = last_position[1]
|
||||
end_col = last_position[2]
|
||||
local last_index = end_row - start_row
|
||||
lines = vim.list_slice(lines, 1, last_index + 1)
|
||||
lines[#lines] = lines[#lines]:sub(1, end_col)
|
||||
if r then
|
||||
return range
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not last_position or #lines > config.multiline_threshold then
|
||||
--- @param range Range4
|
||||
--- @return string[]?, Range4?
|
||||
local function get_text_for_range(range)
|
||||
if range[4] == 0 then
|
||||
range[3] = range[3] - 1
|
||||
range[4] = -1
|
||||
end
|
||||
local lines = api.nvim_buf_get_text(0, range[1], 0, range[3], range[4], {})
|
||||
if lines == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local start_row = range[1]
|
||||
local end_row = range[3]
|
||||
local end_col = range[4]
|
||||
|
||||
lines = vim.list_slice(lines, 1, end_row - start_row+1)
|
||||
lines[#lines] = lines[#lines]:sub(1, end_col)
|
||||
|
||||
if #lines > config.multiline_threshold then
|
||||
lines = vim.list_slice(lines, 1, 1)
|
||||
end_row = start_row
|
||||
end_col = #lines[1]
|
||||
end
|
||||
|
||||
local range = {start_row, start_col, end_row, end_col}
|
||||
range = {start_row, 0, end_row, end_col}
|
||||
|
||||
return lines, range
|
||||
end
|
||||
|
||||
-- Merge lines, removing the indentation after 1st line
|
||||
--- @param lines string[]
|
||||
--- @return string
|
||||
local function merge_lines(lines)
|
||||
local text = { lines[1] }
|
||||
for i = 2, #lines do
|
||||
|
@ -300,8 +153,13 @@ local function merge_lines(lines)
|
|||
end
|
||||
|
||||
-- Get indentation for lines except first
|
||||
--- @param lines string[]
|
||||
--- @return integer[]
|
||||
local function get_indents(lines)
|
||||
--- @type integer[]
|
||||
--- @diagnostic disable-next-line
|
||||
local indents = vim.tbl_map(function(line)
|
||||
--- @type string?
|
||||
local indent = line:match(INDENT_PATTERN)
|
||||
return indent and #indent or 0
|
||||
end, lines)
|
||||
|
@ -310,13 +168,14 @@ local function get_indents(lines)
|
|||
return indents
|
||||
end
|
||||
|
||||
--- @return integer
|
||||
local function get_gutter_width()
|
||||
return vim.fn.getwininfo(vim.api.nvim_get_current_win())[1].textoff
|
||||
end
|
||||
|
||||
local cursor_moved_vertical
|
||||
local cursor_moved_vertical --[[@type fun(): boolean]]
|
||||
do
|
||||
local line
|
||||
local line --[[@type integer]]
|
||||
cursor_moved_vertical = function()
|
||||
local newline = vim.api.nvim_win_get_cursor(0)[1]
|
||||
if newline ~= line then
|
||||
|
@ -327,6 +186,7 @@ do
|
|||
end
|
||||
end
|
||||
|
||||
--- @return integer, integer
|
||||
local function get_bufs()
|
||||
if not context_bufnr or not api.nvim_buf_is_valid(context_bufnr) then
|
||||
context_bufnr = api.nvim_create_buf(false, true)
|
||||
|
@ -351,6 +211,14 @@ local function delete_bufs()
|
|||
gutter_bufnr = nil
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param winid integer?
|
||||
--- @param width integer
|
||||
--- @param height integer
|
||||
--- @param col integer
|
||||
--- @param ty string
|
||||
--- @param hl string
|
||||
--- @return integer
|
||||
local function display_window(bufnr, winid, width, height, col, ty, hl)
|
||||
if not winid or not api.nvim_win_is_valid(winid) then
|
||||
local sep = config.separator
|
||||
|
@ -389,6 +257,34 @@ local M = {
|
|||
config = config,
|
||||
}
|
||||
|
||||
--- @param node TSNode?
|
||||
--- @return TSNode[]
|
||||
local function get_node_parents(node)
|
||||
-- save nodes in a table to iterate from top to bottom
|
||||
--- @type TSNode[]
|
||||
local parents = {}
|
||||
while node ~= nil do
|
||||
parents[#parents+1] = node
|
||||
node = node:parent()
|
||||
end
|
||||
return parents
|
||||
end
|
||||
|
||||
--- @return integer, integer
|
||||
local function get_pos()
|
||||
--- @type integer, integer
|
||||
local lnum, col
|
||||
if config.mode == 'topline' then
|
||||
lnum, col = vim.fn.line('w0') --[[@as integer]], 0
|
||||
else -- default to 'cursor'
|
||||
lnum, col = unpack(api.nvim_win_get_cursor(0)) --[[@as integer]]
|
||||
end
|
||||
|
||||
return lnum, col
|
||||
end
|
||||
|
||||
--- @param max_lines integer
|
||||
--- @return Range4[]?
|
||||
local function get_parent_matches(max_lines)
|
||||
if max_lines == 0 then
|
||||
return
|
||||
|
@ -399,14 +295,31 @@ local function get_parent_matches(max_lines)
|
|||
end
|
||||
|
||||
local root_node = get_root_node()
|
||||
local lnum, col
|
||||
if config.mode == 'topline' then
|
||||
lnum, col = vim.fn.line('w0'), 0
|
||||
else -- default to 'cursor'
|
||||
lnum, col = unpack(api.nvim_win_get_cursor(0))
|
||||
|
||||
--- @type string
|
||||
local lang = parsers.ft_to_lang(vim.bo.filetype)
|
||||
|
||||
local ok, query = pcall(vim.treesitter.query.get_query, lang, 'context')
|
||||
|
||||
if not ok then
|
||||
vim.notify_once(
|
||||
string.format('Unable to load context query for %s:\n%s', lang, query),
|
||||
vim.log.levels.ERROR,
|
||||
{ title = 'nvim-treesitter-context' }
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
if not query then
|
||||
return
|
||||
end
|
||||
|
||||
local lnum, col = get_pos()
|
||||
|
||||
--- @type Range4[]
|
||||
local last_matches
|
||||
|
||||
--- @type Range4[]
|
||||
local parent_matches = {}
|
||||
local line_offset = 0
|
||||
|
||||
|
@ -423,25 +336,19 @@ local function get_parent_matches(max_lines)
|
|||
local topline = vim.fn.line('w0')
|
||||
|
||||
-- save nodes in a table to iterate from top to bottom
|
||||
local parents = {}
|
||||
while node ~= nil do
|
||||
parents[#parents+1] = node
|
||||
node = node:parent()
|
||||
end
|
||||
local parents = get_node_parents(node)
|
||||
|
||||
for i = #parents, 1, -1 do
|
||||
local parent = parents[i]
|
||||
local row = parent:start()
|
||||
|
||||
local height = math.min(max_lines, #parent_matches)
|
||||
if is_valid(parent, vim.bo.filetype)
|
||||
and row >= 0
|
||||
and row < (topline + height - 1) then
|
||||
|
||||
local range = is_valid(parent, query)
|
||||
if range and row >= 0 and row < (topline + height - 1) then
|
||||
if row == last_row then
|
||||
parent_matches[#parent_matches] = parent
|
||||
parent_matches[#parent_matches] = range
|
||||
else
|
||||
table.insert(parent_matches, parent)
|
||||
parent_matches[#parent_matches+1] = range
|
||||
last_row = row
|
||||
|
||||
local new_height = math.min(max_lines, #parent_matches)
|
||||
|
@ -469,6 +376,9 @@ local function get_parent_matches(max_lines)
|
|||
end
|
||||
end
|
||||
|
||||
--- @generic F: function
|
||||
--- @param fn F
|
||||
--- @return F
|
||||
local function throttle_fn(fn)
|
||||
local recalc_after_cooldown = false
|
||||
local cooling_down = false
|
||||
|
@ -495,7 +405,6 @@ local function throttle_fn(fn)
|
|||
return wrapped
|
||||
end
|
||||
|
||||
|
||||
local function close()
|
||||
previous_nodes = nil
|
||||
-- Can't close other windows when the command-line window is open
|
||||
|
@ -514,6 +423,9 @@ local function close()
|
|||
gutter_winid = nil
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param lines string[]
|
||||
--- @return boolean
|
||||
local function set_lines(bufnr, lines)
|
||||
local clines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local redraw = false
|
||||
|
@ -536,10 +448,13 @@ local function set_lines(bufnr, lines)
|
|||
return redraw
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param ctx_bufnr integer
|
||||
--- @param contexts Context[]
|
||||
local function highlight_contexts(bufnr, ctx_bufnr, contexts)
|
||||
api.nvim_buf_clear_namespace(ctx_bufnr, ns, 0, -1)
|
||||
|
||||
local buf_highlighter = highlighter.active[bufnr]
|
||||
local buf_highlighter = highlighter.active[bufnr] --[[@as TSHighlighter]]
|
||||
|
||||
if not buf_highlighter then
|
||||
-- Use standard highlighting when TS highlighting is not available
|
||||
|
@ -558,26 +473,26 @@ local function highlight_contexts(bufnr, ctx_bufnr, contexts)
|
|||
|
||||
local buf_query = buf_highlighter:get_query(parsers.ft_to_lang(vim.bo.filetype))
|
||||
|
||||
local query = buf_query:query()
|
||||
local query = assert(buf_query:query())
|
||||
local root = get_root_node()
|
||||
|
||||
for i, context in ipairs(contexts) do
|
||||
local start_row, _, end_row, end_col = unpack(context.range)
|
||||
local start_row = context.range[1]
|
||||
local end_row = context.range[3]
|
||||
local end_col = context.range[4]
|
||||
local indents = context.indents
|
||||
local lines = context.lines
|
||||
|
||||
local start_row_abs = context.node:start()
|
||||
|
||||
for capture, node in query:iter_captures(root, bufnr, start_row, context.node:end_()) do
|
||||
for capture, node in query:iter_captures(root, bufnr, start_row, end_row + 1) do
|
||||
local node_start_row, node_start_col, node_end_row, node_end_col = node:range()
|
||||
|
||||
if node_end_row > end_row or
|
||||
(node_end_row == end_row and node_end_col > end_col) then
|
||||
(node_end_row == end_row and node_end_col > end_col and end_col ~= -1) then
|
||||
break
|
||||
end
|
||||
|
||||
if node_start_row >= start_row_abs then
|
||||
local intended_start_row = node_start_row - start_row_abs
|
||||
if node_start_row >= start_row then
|
||||
local intended_start_row = node_start_row - start_row
|
||||
|
||||
-- Add 1 for each space added between lines when
|
||||
-- we replace '\n' with ' '
|
||||
|
@ -600,10 +515,15 @@ local function highlight_contexts(bufnr, ctx_bufnr, contexts)
|
|||
end
|
||||
end
|
||||
|
||||
--- @param lnum integer
|
||||
--- @param width integer
|
||||
--- @return string
|
||||
local function build_lno_str(lnum, width)
|
||||
return string.format('%'..width..'d', lnum)
|
||||
end
|
||||
|
||||
--- @param ctx_node_line_num integer
|
||||
--- @return integer
|
||||
local function get_relative_line_num(ctx_node_line_num)
|
||||
local cursor_line_num = vim.fn.line('.')
|
||||
local num_folded_lines = 0
|
||||
|
@ -635,30 +555,18 @@ local function horizontal_scroll_contexts()
|
|||
end
|
||||
end
|
||||
|
||||
local function normalize_node(node)
|
||||
local type = get_type_pattern(node, config.patterns.default) or node:type()
|
||||
local filetype = vim.bo.filetype
|
||||
--- @class Context
|
||||
--- @field indents integer[]
|
||||
--- @field lines string[]
|
||||
--- @field range Range4
|
||||
|
||||
local skip_leading_type = (skip_leading_types[type] or {})[filetype]
|
||||
if skip_leading_type then
|
||||
local children = ts_utils.get_named_children(node)
|
||||
for _, child in ipairs(children) do
|
||||
if child:type() ~= skip_leading_type then
|
||||
node = child
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
local function open(ctx_nodes)
|
||||
--- @param ctx_ranges Range4[]
|
||||
local function open(ctx_ranges)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
|
||||
local gutter_width = get_gutter_width()
|
||||
local win_width = math.max(1, api.nvim_win_get_width(0) - gutter_width)
|
||||
local win_height = math.max(1, #ctx_nodes)
|
||||
local win_height = math.max(1, #ctx_ranges)
|
||||
|
||||
local gbufnr, ctx_bufnr = get_bufs()
|
||||
|
||||
|
@ -674,19 +582,18 @@ local function open(ctx_nodes)
|
|||
|
||||
-- Set text
|
||||
|
||||
local context_text = {}
|
||||
local lno_text = {}
|
||||
local contexts = {}
|
||||
local context_text --[[@type string[] ]] = {}
|
||||
local lno_text --[[@type string[] ]] = {}
|
||||
local contexts --[[@type Context[] ]] = {}
|
||||
|
||||
for _, node in ipairs(ctx_nodes) do
|
||||
node = normalize_node(node)
|
||||
|
||||
local lines, range = get_text_for_node(node)
|
||||
if lines == nil or range == nil or range[1] == nil then return end
|
||||
for _, range0 in ipairs(ctx_ranges) do
|
||||
local lines, range = get_text_for_range(range0)
|
||||
if lines == nil or range == nil or range[1] == nil then
|
||||
return
|
||||
end
|
||||
local text = merge_lines(lines)
|
||||
|
||||
contexts[#contexts+1] = {
|
||||
node = node,
|
||||
lines = lines,
|
||||
range = range,
|
||||
indents = get_indents(lines),
|
||||
|
@ -694,7 +601,7 @@ local function open(ctx_nodes)
|
|||
|
||||
table.insert(context_text, text)
|
||||
|
||||
local line_num
|
||||
local line_num --[[@type integer]]
|
||||
local ctx_line_num = range[1] + 1
|
||||
if vim.o.relativenumber then
|
||||
line_num = get_relative_line_num(ctx_line_num)
|
||||
|
@ -710,13 +617,14 @@ local function open(ctx_nodes)
|
|||
return
|
||||
end
|
||||
|
||||
|
||||
highlight_contexts(bufnr, ctx_bufnr, contexts)
|
||||
|
||||
api.nvim_buf_set_extmark(ctx_bufnr, ns, #lno_text-1, 0, {end_line=#lno_text, hl_group='TreesitterContextBottom', hl_eol=true})
|
||||
api.nvim_buf_set_extmark(gbufnr, ns, #context_text-1, 0, {end_line=#context_text, hl_group='TreesitterContextBottom', hl_eol=true})
|
||||
end
|
||||
|
||||
--- @param config_max integer
|
||||
--- @return integer
|
||||
local function calc_max_lines(config_max)
|
||||
local max_lines = config_max
|
||||
max_lines = max_lines == 0 and -1 or max_lines
|
||||
|
@ -765,9 +673,12 @@ local update = throttle_fn(function()
|
|||
end
|
||||
end)
|
||||
|
||||
--- @param group string
|
||||
--- @return function
|
||||
local function autocmd_for_group(group)
|
||||
local gid = augroup(group, {})
|
||||
return function(event, opts)
|
||||
---@diagnostic disable:no-unknown
|
||||
if opts then
|
||||
if type(opts) == 'function' then
|
||||
opts = { callback = opts }
|
||||
|
@ -826,17 +737,7 @@ function M.setup(options)
|
|||
|
||||
local userOptions = options or {}
|
||||
|
||||
config = vim.tbl_deep_extend('force', {}, defaultConfig, userOptions)
|
||||
config.patterns = vim.tbl_deep_extend('force', {}, DEFAULT_TYPE_PATTERNS, userOptions.patterns or {})
|
||||
config.exclude_patterns = vim.tbl_deep_extend('force', {}, DEFAULT_TYPE_EXCLUDE_PATTERNS, userOptions.exclude_patterns or {})
|
||||
config.exact_patterns = vim.tbl_deep_extend('force', {}, userOptions.exact_patterns or {})
|
||||
|
||||
for filetype, patterns in pairs(config.patterns) do
|
||||
-- Map with word_pattern only if users don't need exact pattern matching
|
||||
if not config.exact_patterns[filetype] then
|
||||
config.patterns[filetype] = vim.tbl_map(word_pattern, patterns)
|
||||
end
|
||||
end
|
||||
config = vim.tbl_deep_extend('force', {}, defaultConfig, userOptions)
|
||||
|
||||
if config.enable then
|
||||
M.enable()
|
||||
|
|
6
queries/bash/context.scm
Normal file
6
queries/bash/context.scm
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
([
|
||||
(for_statement)
|
||||
(function_definition)
|
||||
(if_statement)
|
||||
] @context)
|
28
queries/c/context.scm
Normal file
28
queries/c/context.scm
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
(function_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(for_statement
|
||||
(compound_statement) @context.end
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
consequence: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(while_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(do_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(struct_specifier
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(enum_specifier
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
5
queries/cpp/context.scm
Normal file
5
queries/cpp/context.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
; inherits: c
|
||||
|
||||
(class_specifier
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
5
queries/json/context.scm
Normal file
5
queries/json/context.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
([
|
||||
(object)
|
||||
(pair)
|
||||
] @context)
|
27
queries/lua/context.scm
Normal file
27
queries/lua/context.scm
Normal file
|
@ -0,0 +1,27 @@
|
|||
(for_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(while_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(do_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(function_definition
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(table_constructor
|
||||
(_) @context.end
|
||||
) @context
|
||||
|
||||
(function_declaration
|
||||
parameters: (_) @context.final
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
consequence: (_) @context.end
|
||||
) @context
|
2
queries/markdown/context.scm
Normal file
2
queries/markdown/context.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
((section) @context)
|
28
queries/php/context.scm
Normal file
28
queries/php/context.scm
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
(function_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(while_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(do_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(foreach_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(class_declaration
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(for_statement
|
||||
(compound_statement (_) @context.end)
|
||||
) @context
|
47
queries/python/context.scm
Normal file
47
queries/python/context.scm
Normal file
|
@ -0,0 +1,47 @@
|
|||
(class_definition
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(function_definition
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(try_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(with_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
consequence: (_) @context.end
|
||||
) @context
|
||||
|
||||
(elif_clause
|
||||
consequence: (_) @context.end
|
||||
) @context
|
||||
|
||||
(case_clause
|
||||
consequence: (_) @context.end
|
||||
) @context
|
||||
|
||||
(while_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(except_clause
|
||||
(block) @context.end
|
||||
) @context
|
||||
|
||||
(match_statement
|
||||
alternative: (_) @context.end
|
||||
) @context
|
||||
|
||||
([
|
||||
(for_statement)
|
||||
(finally_clause)
|
||||
(else_clause)
|
||||
(pair)
|
||||
(expression_statement)
|
||||
] @context)
|
29
queries/rust/context.scm
Normal file
29
queries/rust/context.scm
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
(for_expression
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(if_expression
|
||||
consequence: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(function_item
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(impl_item
|
||||
type: (_) @context.final
|
||||
) @context
|
||||
|
||||
(struct_item
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
([
|
||||
(mod_item)
|
||||
(enum_item)
|
||||
(closure_expression)
|
||||
(expression_statement)
|
||||
(loop_expression)
|
||||
(match_expression)
|
||||
] @context)
|
28
queries/scala/context.scm
Normal file
28
queries/scala/context.scm
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
(function_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(class_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(object_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(case_clause
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(match_expression
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(call_expression
|
||||
arguments: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(if_expression
|
||||
consequence: (_ (_) @context.end)
|
||||
) @context
|
28
queries/teal/context.scm
Normal file
28
queries/teal/context.scm
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
(while_statement) @context
|
||||
|
||||
(generic_for_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(function_statement
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(anon_function
|
||||
body: (_) @context.end
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
condition: (_)
|
||||
(_) @context.end
|
||||
) @context
|
||||
|
||||
(elseif_block
|
||||
condition: (_)
|
||||
(_) @context.end
|
||||
) @context
|
||||
|
||||
(record_declaration
|
||||
record_body: (_) @context.end
|
||||
) @context
|
5
queries/toml/context.scm
Normal file
5
queries/toml/context.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
([
|
||||
(table)
|
||||
(pair)
|
||||
] @context)
|
23
queries/typescript/context.scm
Normal file
23
queries/typescript/context.scm
Normal file
|
@ -0,0 +1,23 @@
|
|||
(interface_declaration
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(class_declaration
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(method_definition
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(for_statement
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(function_declaration
|
||||
body: (_ (_) @context.end)
|
||||
) @context
|
||||
|
||||
(if_statement
|
||||
consequence: (_ (_) @context.end)
|
||||
) @context
|
36
queries/vim/context.scm
Normal file
36
queries/vim/context.scm
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
(if_statement
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(elseif_statement
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(else_statement
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(function_definition
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(while_loop
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(for_loop
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(try_statement
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(catch_statement
|
||||
(body) @context.end
|
||||
) @context
|
||||
|
||||
(finally_statement
|
||||
(body) @context.end
|
||||
) @context
|
5
queries/yaml/context.scm
Normal file
5
queries/yaml/context.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
([
|
||||
(block_mapping)
|
||||
(block_mapping_pair)
|
||||
(block_sequence_item)
|
||||
] @context)
|
76
test/test.c
Normal file
76
test/test.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
struct Bert {
|
||||
int *f1;
|
||||
// comment
|
||||
int *f2;
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
E1,
|
||||
E2,
|
||||
E3
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
} Myenum;
|
||||
|
||||
int main(int arg1,
|
||||
char **arg2,
|
||||
char **arg3
|
||||
)
|
||||
{
|
||||
|
||||
if (arg1 == 4
|
||||
&& arg2 == arg3) {
|
||||
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
for (int i = 0; i < arg1; i++) {
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
while (1) {
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
}
|
||||
|
||||
do {
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
|
||||
} while (1);
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
// comment
|
||||
}
|
||||
|
||||
}
|
||||
}
|
83
test/test.php
Normal file
83
test/test.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
/*
|
||||
* comment
|
||||
*/
|
||||
function foo($a, $b) {
|
||||
//loop, between low & high
|
||||
while ($a <= $b) {
|
||||
// comment
|
||||
$index = $low + floor(($high - $low) * $delta);
|
||||
// comment
|
||||
$indexValue = $a;
|
||||
if ($indexValue === $a) {
|
||||
// comment
|
||||
|
||||
|
||||
$position = $index;
|
||||
return (int) $position;
|
||||
}
|
||||
if ($indexValue < $key) {
|
||||
// comment
|
||||
|
||||
$low = $index + 1;
|
||||
}
|
||||
if ($indexValue > $key) {
|
||||
// comment
|
||||
do {
|
||||
// comment
|
||||
echo "The number is: $x <br>";
|
||||
$x++;
|
||||
|
||||
|
||||
|
||||
|
||||
} while ($x <= 5);
|
||||
|
||||
for ($x = 0; $x <= 10; $x++) {
|
||||
echo "The number is: $x <br>";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
foreach ($colors as $value) {
|
||||
echo "$value <br>";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$high = $index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//when key not found in array or array not sorted
|
||||
return null;
|
||||
}
|
||||
|
||||
class Fruit {
|
||||
|
||||
|
||||
|
||||
|
||||
// comment
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,12 +1,28 @@
|
|||
impl Foo {
|
||||
|
||||
|
||||
|
||||
|
||||
fn bar(&self) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if condition {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for i in 0..100 {
|
||||
|
||||
|
||||
|
||||
// comment
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,4 +30,12 @@ impl Foo {
|
|||
|
||||
struct Foo {
|
||||
|
||||
active: bool,
|
||||
|
||||
username: String,
|
||||
|
||||
email: String,
|
||||
|
||||
sign_in_count: u64,
|
||||
|
||||
}
|
47
test/test.ts
Normal file
47
test/test.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
interface User {
|
||||
name: string;
|
||||
|
||||
|
||||
|
||||
id: number;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class UserAccount {
|
||||
name: string;
|
||||
id: number;
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(name: string, id: number) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
console.log("hello");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function wrapInArray(obj: string | string[]) {
|
||||
if (typeof obj === "string") {
|
||||
return [obj];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
return obj;
|
||||
}
|
|
@ -3,15 +3,16 @@ local Screen = require('test.functional.ui.screen')
|
|||
|
||||
local clear = helpers.clear
|
||||
local exec_lua = helpers.exec_lua
|
||||
local eq = helpers.eq
|
||||
local cmd = helpers.command
|
||||
local feed = helpers.feed
|
||||
|
||||
describe('ts_context', function()
|
||||
local screen
|
||||
|
||||
setup(function()
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(30, 16)
|
||||
screen:attach()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {foreground = Screen.colors.Brown, background = Screen.colors.LightMagenta, bold = true};
|
||||
[2] = {background = Screen.colors.LightMagenta};
|
||||
|
@ -19,12 +20,13 @@ describe('ts_context', function()
|
|||
[4] = {bold = true, foreground = Screen.colors.Brown};
|
||||
[5] = {foreground = Screen.colors.DarkCyan};
|
||||
[6] = {bold = true, foreground = Screen.colors.Blue};
|
||||
[7] = {foreground = Screen.colors.SeaGreen, background = Screen.colors.LightMagenta, bold = true};
|
||||
[8] = {foreground = Screen.colors.Blue};
|
||||
[9] = {bold = true, foreground = Screen.colors.SeaGreen};
|
||||
[10] = {foreground = Screen.colors.Fuchsia, background = Screen.colors.LightMagenta};
|
||||
[11] = {foreground = Screen.colors.Fuchsia};
|
||||
[12] = {foreground = tonumber('0x6a0dad'), background = Screen.colors.LightMagenta};
|
||||
})
|
||||
end)
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen:attach()
|
||||
cmd [[set runtimepath+=.,./nvim-treesitter]]
|
||||
cmd [[let $XDG_CACHE_HOME='scratch/cache']]
|
||||
cmd [[set packpath=]]
|
||||
|
@ -88,99 +90,191 @@ describe('ts_context', function()
|
|||
]]}
|
||||
end)
|
||||
|
||||
it('edit a file in topline mode', function()
|
||||
exec_lua[[require'treesitter-context'.setup{
|
||||
mode = 'topline',
|
||||
max_lines = 2,
|
||||
}]]
|
||||
cmd('edit test/nested_file.rs')
|
||||
feed'L'
|
||||
feed'<C-e>'
|
||||
-- screen:snapshot_util()
|
||||
screen:expect{grid=[[
|
||||
{1:impl}{2: Foo { }|
|
||||
{4:fn} {5:bar}({7:&}{8:self}) { |
|
||||
{4:if} condition { |
|
||||
|
|
||||
|
|
||||
{4:for} i {4:in} {8:0}..{8:100} { |
|
||||
|
|
||||
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
{4:^struct} {5:Foo} { |
|
||||
|
|
||||
|
|
||||
]], attr_ids={
|
||||
[1] = {foreground = Screen.colors.Brown, background = Screen.colors.Plum1, bold = true};
|
||||
[2] = {background = Screen.colors.Plum1};
|
||||
[3] = {foreground = Screen.colors.Cyan4, background = Screen.colors.Plum1};
|
||||
[4] = {bold = true, foreground = Screen.colors.Brown};
|
||||
[5] = {foreground = Screen.colors.Cyan4};
|
||||
[6] = {bold = true, foreground = Screen.colors.Blue1};
|
||||
[7] = {bold = true, foreground = Screen.colors.SeaGreen4};
|
||||
[8] = {foreground = Screen.colors.Fuchsia};
|
||||
}}
|
||||
describe('language:', function()
|
||||
before_each(function()
|
||||
exec_lua[[require'treesitter-context'.setup{
|
||||
mode = 'topline',
|
||||
}]]
|
||||
cmd'set scrolloff=5'
|
||||
cmd'set nowrap'
|
||||
end)
|
||||
|
||||
feed'<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{2: }{1:fn}{2: }{3:bar}{2:(}{7:&}{8:self}{2:) }|
|
||||
{2: }{1:if}{2: condition { }|
|
||||
|
|
||||
|
|
||||
{4:for} i {4:in} {9:0}..{9:100} { |
|
||||
|
|
||||
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
{4:^struct} {5:Foo} { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
]], attr_ids={
|
||||
[1] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.LightMagenta};
|
||||
[2] = {background = Screen.colors.LightMagenta};
|
||||
[3] = {background = Screen.colors.LightMagenta, foreground = Screen.colors.Cyan4};
|
||||
[4] = {foreground = Screen.colors.Brown, bold = true};
|
||||
[5] = {foreground = Screen.colors.Cyan4};
|
||||
[6] = {foreground = Screen.colors.Blue1, bold = true};
|
||||
[7] = {foreground = Screen.colors.SeaGreen4, bold = true, background = Screen.colors.LightMagenta};
|
||||
[8] = {background = Screen.colors.LightMagenta, foreground = Screen.colors.Magenta1};
|
||||
[9] = {foreground = Screen.colors.Magenta1};
|
||||
}}
|
||||
it('rust', function()
|
||||
cmd('edit test/test.rs')
|
||||
feed'20<C-e>'
|
||||
|
||||
screen:expect{grid=[[
|
||||
{1:impl}{2: Foo }|
|
||||
{2: }{1:fn}{2: }{3:bar}{2:(}{7:&}{10:self}{2:) { }|
|
||||
{2: }{1:if}{2: condition { }|
|
||||
{2: }{1:for}{2: i }{1:in}{2: }{10:0}{2:..}{10:100}{2: { }|
|
||||
|
|
||||
^ } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
{4:struct} {5:Foo} { |
|
||||
|
|
||||
active: {9:bool}, |
|
||||
|
|
||||
username: {9:String}, |
|
||||
|
|
||||
]]}
|
||||
|
||||
feed'14<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{1:struct}{2: }{3:Foo}{2: { }|
|
||||
|
|
||||
email: {9:String}, |
|
||||
|
|
||||
sign_in_count: {9:u64}, |
|
||||
^ |
|
||||
} |
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
end)
|
||||
|
||||
it('c', function()
|
||||
cmd('edit test/test.c')
|
||||
feed'<C-e>'
|
||||
|
||||
-- Check the struct context
|
||||
screen:expect{grid=[[
|
||||
{7:struct}{2: Bert { }|
|
||||
{8:// comment} |
|
||||
{9:int} *f2; |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
^ {8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
}; |
|
||||
|
|
||||
{9:typedef} {9:enum} { |
|
||||
E1, |
|
||||
E2, |
|
||||
E3 |
|
||||
{8:// comment} |
|
||||
|
|
||||
]]}
|
||||
|
||||
feed'12<C-e>'
|
||||
|
||||
-- Check the enum context
|
||||
screen:expect{grid=[[
|
||||
{7:typedef}{2: }{7:enum}{2: { }|
|
||||
E3 |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
^ {8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
} Myenum; |
|
||||
|
|
||||
{9:int} main({9:int} arg1, |
|
||||
{9:char} **arg2, |
|
||||
{9:char} **arg3 |
|
||||
) |
|
||||
{ |
|
||||
|
|
||||
]]}
|
||||
|
||||
feed'40<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{7:int}{2: main(}{7:int}{2: arg1, }{7:char}{2: **arg2}|
|
||||
{2: }{1:if}{2: (arg1 == }{10:4}{2: && arg2 == arg}|
|
||||
{2: }{1:for}{2: (}{7:int}{2: i = }{10:0}{2:; i < arg1; }|
|
||||
{2: }{1:while}{2: (}{10:1}{2:) { }|
|
||||
} |
|
||||
^ |
|
||||
{4:do} { |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
{8:// comment} |
|
||||
|
|
||||
} {4:while} ({11:1}); |
|
||||
{8:// comment} |
|
||||
|
|
||||
]]}
|
||||
end)
|
||||
|
||||
it('typescript', function()
|
||||
cmd('edit test/test.ts')
|
||||
feed'<C-e>'
|
||||
|
||||
screen:expect{grid=[[
|
||||
{1:interface}{2: }{3:User}{2: }{3:{}{2: }|
|
||||
|
|
||||
|
|
||||
|
|
||||
{5:id}: {9:number}{4:;} |
|
||||
^ |
|
||||
|
|
||||
|
|
||||
|
|
||||
{5:}} |
|
||||
|
|
||||
{4:class} UserAccount {5:{} |
|
||||
{5:name}: {9:string}; |
|
||||
{5:id}: {9:number}; |
|
||||
|
|
||||
|
|
||||
]]}
|
||||
|
||||
feed'21<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{1:class}{2: UserAccount }{3:{}{2: }|
|
||||
{2: }{3:constructor}{2:(}{12:name}{2::}{12: }{7:string}{1:,}{12: id}|
|
||||
{2: }{1:for}{2: (}{3:let}{2: i = }{10:0}{1:;}{2: i < }{10:3}{1:;}{2: i++}|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
{5:}} |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
{5:}} |
|
||||
{5:}} |
|
||||
|
|
||||
|
|
||||
|
|
||||
]]}
|
||||
|
||||
feed'16<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{1:function}{2: }{3:wrapInArray}{2:(}{12:obj}{2::}{12: }{7:stri}|
|
||||
{2: }{1:if}{2: (}{3:typeof}{2: obj === }{10:"string"}{2:)}|
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
{5:}} |
|
||||
{4:return} obj; |
|
||||
{5:}} |
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
|
|
||||
]]}
|
||||
end)
|
||||
|
||||
feed'3<C-e>'
|
||||
screen:expect{grid=[[
|
||||
{2: }{1:if}{2: condition { }|
|
||||
{2: }{1:for}{2: i }{1:in}{2: }{7:0}{2:..}{7:100}{2: { }|
|
||||
|
|
||||
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
{4:^struct} {5:Foo} { |
|
||||
|
|
||||
} |
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
{6:~ }|
|
||||
|
|
||||
]], attr_ids={
|
||||
[1] = {background = Screen.colors.Plum1, bold = true, foreground = Screen.colors.Brown};
|
||||
[2] = {background = Screen.colors.Plum1};
|
||||
[3] = {background = Screen.colors.Plum1, foreground = Screen.colors.Cyan4};
|
||||
[4] = {foreground = Screen.colors.Brown, bold = true};
|
||||
[5] = {foreground = Screen.colors.Cyan4};
|
||||
[6] = {foreground = Screen.colors.Blue1, bold = true};
|
||||
[7] = {background = Screen.colors.Plum1, foreground = Screen.colors.Magenta};
|
||||
}}
|
||||
end)
|
||||
|
||||
end)
|
||||
|
|
Loading…
Reference in a new issue