Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concealing range between nodes with one character using treesitter #25515

Open
mawkler opened this issue Oct 5, 2023 · 9 comments
Open

Concealing range between nodes with one character using treesitter #25515

mawkler opened this issue Oct 5, 2023 · 9 comments
Labels

Comments

@mawkler
Copy link
Sponsor Contributor

mawkler commented Oct 5, 2023

Problem

I'm trying to conceal Markdown checkboxes and the minus sign in front of them with one conceal character. Here's my query:

(
  (list_marker_minus)
  (task_list_marker_unchecked)
) @checkbox (#set! conceal "")

However, only the first sibling, list_marker_minus gets concealed.

Here's a file that I'm using as an example:

- [x] foo
- [ ] bar
  - [ ] baz

Here's what the syntax tree looks like:

section [0, 0] - [3, 0]
  list [0, 0] - [3, 0]
    list_item [0, 0] - [1, 0]
      list_marker_minus [0, 0] - [0, 2]
      task_list_marker_checked [0, 2] - [0, 5]
      ...
    list_item [1, 0] - [3, 0]
      list_marker_minus [1, 0] - [1, 2]
      task_list_marker_unchecked [1, 2] - [1, 5]
      ...
      list [2, 2] - [3, 0]
        list_item [2, 2] - [3, 0]
          list_marker_minus [2, 2] - [2, 4]
          task_list_marker_unchecked [2, 4] - [2, 7]
          ...

I feel like this is a pretty trivial use case.

Expected behavior

This does seem to be an issue with treesitter. However, one way I see that Neovim could work around this is to add the ability to specify a start and an end to a conceal range with treesitter captures.

(
  (list_marker_minus) @conceal_start
  (task_list_marker_unchecked) @conceal_end
)

Then I could through some new conceal API tell Neovim that I want everything between @conceal_starts and @conceal_ends to be conceald with some character, including (or excluding) the captures.

What do you say?

@mawkler mawkler added the enhancement feature request label Oct 5, 2023
@clason
Copy link
Member

clason commented Oct 5, 2023

No, we would strongly prefer to let treesitter handle this. After all, that's the whole point of it.

@justinmk justinmk added the needs:response waiting for reply from the author label Oct 5, 2023
@ahlinc
Copy link

ahlinc commented Oct 5, 2023

No, we would strongly prefer to let treesitter handle this. After all, that's the whole point of it.

@clason Could you explain how do you imagine this? The whole current idea of Tree-sitter queries and captures exist around that captures points to distinct and concrete tree nodes and not to a set of some related nodes. At the same time nodes pointed by captures behaves like quick entry points into a tree where additional actions may be done over a nodes' API.

Edit: To don't break the current queries API, it's possible to introduce a concept of ephemeral nodes that would behave like just containers for point ranges and wouldn't allow to use nodes API on them. But I don't think that such additional complexity and concept worth to add just for one this use case, because this makes queries understanding even more harder.

@clason
Copy link
Member

clason commented Oct 6, 2023

I am referring to the linked tree-sitter issue, which should cover this? If I misunderstood something, please correct me.

For this specific instance, though, I don't see why a new feature is needed; can't you just do

(
  (list_marker_minus) @conceal (#set! conceal "")
  (task_list_marker_unchecked) @conceal (#set! conceal " ")
)

@mawkler
Copy link
Sponsor Contributor Author

mawkler commented Oct 6, 2023

@clason That's a good idea that I hadn't thought of. However, when I tried it for some reason the list_marker_minus gets the conceal character, and not task_list_marker_unchecked:

I even tried giving the minus a different capture group like this

(
   (list_marker_minus) @minus (#set! conceal "")
   (task_list_marker_checked) @conceal (#set! conceal "")
)

But I still get the same behaviour:

image

@github-actions github-actions bot removed the needs:response waiting for reply from the author label Oct 6, 2023
@clason
Copy link
Member

clason commented Oct 6, 2023

That could be an issue with multiple directives in a single pattern not working properly, which would be a bug that needs to be fixed.

@ribru17
Copy link
Contributor

ribru17 commented Dec 18, 2023

@mawkler As a workaround, I use something like this:

((task_list_marker_unchecked)
 @text.todo.unchecked
 (#offset! @text.todo.unchecked 0 -2 0 0)
 (#set! conceal "")) ;
((task_list_marker_checked)
 @text.todo.checked
 (#offset! @text.todo.checked 0 -2 0 0)
 (#set! conceal "")) ;

the #offset! directive will conceal the preceding minus signs. The only caveat is that this will only work properly when there is one space between the minus sign and the checkbox. If you only want this conceal to apply to checkboxes with the minus sign, you should be able to modify the query to make that work.

@mawkler
Copy link
Sponsor Contributor Author

mawkler commented Dec 19, 2023

@ribru17 That works great as a workaround, thank you!

@savetheclocktower
Copy link

That could be an issue with multiple directives in a single pattern not working properly, which would be a bug that needs to be fixed.

Unless I'm misunderstanding you, I don't think this is a bug.

The capture metadata for #set! predicates takes the form of a hash/object. It might be different for your bindings, but in web-tree-sitter,

(
  (list_marker_minus) @conceal (#set! foo "a")
  (task_list_marker_unchecked) @conceal (#set! foo "b")
)

run on a single list item would create two capture groups, each of which has a setProperties property whose value is { foo: "b" }. That's because both #set! properties apply to both captures, and the second one overwrites the property set by the first.

I've sometimes wondered why #set!, #is?, and #is-not? don't accept a capture as their first argument the way that #match? and #eq? do. That would make this behavior a bit easier to understand. Ideally it'd be backward-compatible such that those predicates would act on a single capture if one is specified as the first argument, or else on all captures if the first argument is a string.

@clason
Copy link
Member

clason commented Jan 14, 2024

They do, in Neovim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants