From 9fbb0932c08c36aa8361be64d80d6b073a54dd1d Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 17 Mar 2021 15:20:27 +1100 Subject: [PATCH 1/4] tree-sitter: ignore matches in inner scopes Previously, placing the cursor on line 1 would highlight every elif and else in the whole block of code. % would jump to line 4, then % from there would cycle between lines 2, 4 and 6. 1 if noice: 2 if yeah: 3 pass 4 elif no: 5 pass 6 else: 7 pass 8 elif blah: 9 pass 10 else: 11 pass With the change, which I think the code had contemplated given there was already an unused `M.containing_scope(node, bufnr, info.key)` call, % will only move between ifs and elses that are in the same `@scope.if_`, and not to any inner scopes. Hence the example will have two mutually exclusive %-cycles: [1, 8, 10] and [2, 4, 6]. --- lua/treesitter-matchup/internal.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/treesitter-matchup/internal.lua b/lua/treesitter-matchup/internal.lua index 2f89605..37a0263 100644 --- a/lua/treesitter-matchup/internal.lua +++ b/lua/treesitter-matchup/internal.lua @@ -248,13 +248,14 @@ function M.get_matching(delim, down, bufnr) and (row >= info.search_range[1] and row <= info.search_range[3]) then - local scope = M.containing_scope(node, bufnr, info.key) + local target_scope = M.containing_scope(node, bufnr, info.key) + if info.scope == target_scope then + local text = ts_utils.get_node_text(node, bufnr)[1] + table.insert(matches, {text, row + 1, col + 1}) - local text = ts_utils.get_node_text(node, bufnr)[1] - table.insert(matches, {text, row + 1, col + 1}) - - if side == 'close' then - got_close = true + if side == 'close' then + got_close = true + end end end end From 72f36ee10f4357b3903d846cdf29aa0fe738cc03 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 17 Mar 2021 16:21:07 +1100 Subject: [PATCH 2/4] add test for nested ifs in tree-sitter python --- test/vader/ts_py_motion.vader | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/test/vader/ts_py_motion.vader b/test/vader/ts_py_motion.vader index 57ea1c5..5b07158 100644 --- a/test/vader/ts_py_motion.vader +++ b/test/vader/ts_py_motion.vader @@ -10,11 +10,16 @@ Execute (Helper): endif Given python (A python script): - def F(x): + def F(x, y): if x == 1: return 1 elif x == 2: - pass + if y == 5: + pass + elif y == 7: + return 9 + else: + return x else: return 3 return 2 @@ -22,6 +27,8 @@ Given python (A python script): Execute (Logs): Log b:matchup_active_engines +# ----- outer if/elif/else ----- + Before (Cursor): normal! 2gg^ @@ -33,7 +40,7 @@ Then (Verify line): Do (Move % twice): %% Then (Verify line): - Assert line('.') == (TSActive() ? 6 : 2) + Assert line('.') == (TSActive() ? 11 : 2) Do (Move % 3 times): %%% @@ -44,3 +51,28 @@ Do (Move % 4 times): %%%% Then (Verify line): Assert line('.') == (TSActive() ? 4 : 2) + +# ----- inner if/elif/else ----- + +Before (Cursor): + normal! 5gg^ + +Do (Inner: Move %): + % +Then (Verify line): + Assert line('.') == (TSActive() ? 7 : 5) + +Do (Inner: % 2 times): + %% +Then (Verify line): + Assert line('.') == (TSActive() ? 9 : 5) + +Do (Inner: % 3 times): + %%% +Then (Verify line): + Assert line('.') == (TSActive() ? 5 : 5) + +Do (Inner: % 4 times): + %%%% +Then (Verify line): + Assert line('.') == (TSActive() ? 7 : 5) From 232d6605969a6ebfe360800247e491f1d7ec84f2 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 17 Mar 2021 17:04:56 +1100 Subject: [PATCH 3/4] add rust tree-sitter queries --- after/queries/rust/matchup.scm | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 after/queries/rust/matchup.scm diff --git a/after/queries/rust/matchup.scm b/after/queries/rust/matchup.scm new file mode 100644 index 0000000..da57e7d --- /dev/null +++ b/after/queries/rust/matchup.scm @@ -0,0 +1,77 @@ +; --------------- fn/return --------------- + +(function_item + "fn" @open.function) @scope.function +(return_expression + "return" @mid.function.1) + +; --------------- async/await --------------- + +; give async_block the same scope name, because .await always +; suspends to the innermost async scope +(function_item (function_modifiers "async" @open.async)) @scope.async +(async_block "async" @open.async) @scope.async +(await_expression "await" @mid.async.1) + +; --------------- if/else --------------- + +; the rust grammar is like (else_clause "else" (if_expression "if")) +; so the else and the if can't be given a single name covering both of them +; hence just highlighting the else. close enough +(else_clause "else" @mid.if_.1 (if_expression) @else_if) +(else_clause "else" @mid.if_.1 (if_let_expression) @else_if) +(else_clause "else" @mid.if_.2 (block)) + +; ideally neovim would support more predicates like is-not?, which would mean +; that you could recognise an if-expression being the if in an "else if" and +; use that capture @else_if to indicate that it shouldn't form its own +; @scope.if_. I actually don't know if this would work, because predicates +; are not documented anywhere that I can find. +; +; ((if_expression "if" @open.if_) @scope.if_ (is-not? @else_if)) +; ((if_let_expression "if" @open.if_) @scope.if_ (is-not? @else_if)) +; +; for now, this will suffice to prevent such "else if" if_expressions from +; introducing an inner scope, but won't match "let x = if {} else {}" at all. +(block (if_expression "if" @open.if_) @scope.if_) +(block (if_let_expression "if" @open.if_) @scope.if_) + +; --------------- while/loop/for + break/continue --------------- + +; the . matches an end, so we can explicitly refuse to handle break 'label; and +; 'label loop {} +(for_expression . "for" @open.loop) @scope.loop +(while_let_expression . "while" @open.loop) @scope.loop +(loop_expression . "loop" @open.loop) @scope.loop + +; unfortunately we can't exclude only `break 'label;` but not `break {expression};` +; as _expression is a supernode/meta node or whatever TS calls it, so you can't +; match on (expression) +(break_expression "break" @mid.loop.1 .) +(break_expression "break" @mid.loop.1 .) +(continue_expression "continue" @mid.loop.1 .) + +; this strategy would maybe work if matchup were modified to support string +; matches on scopes +; (break_expression (loop_label (identifier) @mid.looplabel_.1)) +; (loop_expression (loop_label (identifier) @open.looplabel_)) @scope.looplabel_ + +; --------------- match/arms --------------- + +; this is fun, but lots of match expressions are complex enough that this would +; be too annoying because of firstly the lost syntax highlighting of the arms +; and secondly the fact that many match arms have braces in them, and those +; braces take priority. + +; (match_expression +; "match" @open.match_ +; body: (match_block +; (match_arm +; pattern: (match_pattern)? @mid.match_.1 +; pattern: (macro_invocation)? @mid.match_.1 +; )* +; (match_arm pattern: (match_pattern) @mid.match_.2) +; . +; ) +; ) @scope.match_ + From bac765e06b9213b3580af78e09ea9bfa55cc865e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 17 Mar 2021 17:25:21 +1100 Subject: [PATCH 4/4] rust: add child @scope.function for closures now the first | matches any contained return statements, and those returns do not match the surrounding `fn`. --- after/queries/rust/matchup.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/after/queries/rust/matchup.scm b/after/queries/rust/matchup.scm index da57e7d..40a8745 100644 --- a/after/queries/rust/matchup.scm +++ b/after/queries/rust/matchup.scm @@ -2,6 +2,7 @@ (function_item "fn" @open.function) @scope.function +(closure_expression parameters: (closure_parameters . "|" @open.function "|" .) body: (block)) @scope.function (return_expression "return" @mid.function.1)