Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Need help with a treesitter injection#36669

Answeredbyribru17
okuuva asked this question inQ&A
Discussion options

Tree-Sitter injection for USAGE spec

Spec

https://usage.jdx.dev/spec/

Example

Been using this as a test bed:

#!/usr/bin/env bashset -e#USAGE flag "-c --clean" help="Clean the build directory before building"#USAGE flag "-p --profile <profile>" help="Build with the specified profile" default="debug" {#USAGE   choices "debug" "release"#USAGE }#USAGE flag "-u --user <user>" help="The user to build for"#USAGE complete "user" run="mycli users"#USAGE arg "<target>" help="The target to build"if ["${usage_clean:-false}"="true" ];then  cargo cleanficargo build --profile"${usage_profile?}" --target"${usage_target?}"

The goal is to get Tree-Sitter to treat the#USAGE comments as a single KDL code block for syntax highlighting.
So basically treesitter should see this:

flag"-c --clean"help="Clean the build directory before building"flag"-p --profile <profile>"help="Build with the specified profile"default="debug" {  choices"debug""release"}flag"-u --user <user>"help="The user to build for"complete"user"run="mycli users"arg"<target>"help="The target to build"

Even though Github seems to highlight the example above correctly, here's still a screenshot of the highlighting in editor for reference so it's maybe easier to spot the difference in the other screenshots.
end-goal

The parsed tree should look like this:

Resulting tree```query(document ; [0, 0] - [7, 0] (node ; [0, 0] - [1, 0] (identifier) ; [0, 0] - [0, 4] (node_field ; [0, 5] - [0, 17] (value ; [0, 5] - [0, 17] (string ; [0, 5] - [0, 17] (string_fragment)))) ; [0, 6] - [0, 16] (node_field ; [0, 18] - [0, 66] (prop ; [0, 18] - [0, 66] (identifier) ; [0, 18] - [0, 22] (value ; [0, 23] - [0, 66] (string ; [0, 23] - [0, 66] (string_fragment)))))) ; [0, 24] - [0, 65] (node ; [1, 0] - [4, 0] (identifier) ; [1, 0] - [1, 4] (node_field ; [1, 5] - [1, 29] (value ; [1, 5] - [1, 29] (string ; [1, 5] - [1, 29] (string_fragment)))) ; [1, 6] - [1, 28] (node_field ; [1, 30] - [1, 69] (prop ; [1, 30] - [1, 69] (identifier) ; [1, 30] - [1, 34] (value ; [1, 35] - [1, 69] (string ; [1, 35] - [1, 69] (string_fragment))))) ; [1, 36] - [1, 68] (node_field ; [1, 70] - [1, 85] (prop ; [1, 70] - [1, 85] (identifier) ; [1, 70] - [1, 77] (value ; [1, 78] - [1, 85] (string ; [1, 78] - [1, 85] (string_fragment))))) ; [1, 79] - [1, 84] children: (node_children ; [1, 86] - [3, 1] (node ; [2, 2] - [3, 0] (identifier) ; [2, 2] - [2, 9] (node_field ; [2, 10] - [2, 17] (value ; [2, 10] - [2, 17] (string ; [2, 10] - [2, 17] (string_fragment)))) ; [2, 11] - [2, 16] (node_field ; [2, 18] - [2, 27] (value ; [2, 18] - [2, 27] (string ; [2, 18] - [2, 27] (string_fragment))))))) ; [2, 19] - [2, 26] (node ; [4, 0] - [5, 0] (identifier) ; [4, 0] - [4, 4] (node_field ; [4, 5] - [4, 23] (value ; [4, 5] - [4, 23] (string ; [4, 5] - [4, 23] (string_fragment)))) ; [4, 6] - [4, 22] (node_field ; [4, 24] - [4, 52] (prop ; [4, 24] - [4, 52] (identifier) ; [4, 24] - [4, 28] (value ; [4, 29] - [4, 52] (string ; [4, 29] - [4, 52] (string_fragment)))))) ; [4, 30] - [4, 51] (node ; [5, 0] - [6, 0] (identifier) ; [5, 0] - [5, 8] (node_field ; [5, 9] - [5, 15] (value ; [5, 9] - [5, 15] (string ; [5, 9] - [5, 15] (string_fragment)))) ; [5, 10] - [5, 14] (node_field ; [5, 16] - [5, 33] (prop ; [5, 16] - [5, 33] (identifier) ; [5, 16] - [5, 19] (value ; [5, 20] - [5, 33] (string ; [5, 20] - [5, 33] (string_fragment)))))) ; [5, 21] - [5, 32] (node ; [6, 0] - [7, 0] (identifier) ; [6, 0] - [6, 3] (node_field ; [6, 4] - [6, 14] (value ; [6, 4] - [6, 14] (string ; [6, 4] - [6, 14] (string_fragment)))) ; [6, 5] - [6, 13] (node_field ; [6, 15] - [6, 41] (prop ; [6, 15] - [6, 41] (identifier) ; [6, 15] - [6, 19] (value ; [6, 20] - [6, 41] (string ; [6, 20] - [6, 41] (string_fragment))))))) ; [6, 21] - [6, 40]```

Current solution

; NOTE: This won't properly parse multi-line KDL structures (like blocks with {}); but will at least highlight individual KDL statements correctly((comment) @injection.content  (#lua-match? @injection.content "^#USAGE ")  (#offset! @injection.content 0 7 0 0)  (#set! injection.language "kdl"))
current
Resulting tree```query (comment ; [3, 0] - [3, 73] (document ; [3, 7] - [3, 73] (node ; [3, 7] - [3, 73] (identifier) ; [3, 7] - [3, 11] (node_field ; [3, 12] - [3, 24] (value ; [3, 12] - [3, 24] (string ; [3, 12] - [3, 24] (string_fragment)))) ; [3, 13] - [3, 23] (node_field ; [3, 25] - [3, 73] (prop ; [3, 25] - [3, 73] (identifier) ; [3, 25] - [3, 29] (value ; [3, 30] - [3, 73] (string ; [3, 30] - [3, 73] (string_fragment)))))))) ; [3, 31] - [3, 72] (comment ; [4, 0] - [4, 94] (document ; [4, 7] - [4, 94] (ERROR ; [4, 7] - [4, 94] (identifier) ; [4, 7] - [4, 11] (node_field ; [4, 12] - [4, 36] (value ; [4, 12] - [4, 36] (string ; [4, 12] - [4, 36] (string_fragment)))) ; [4, 13] - [4, 35] (node_field ; [4, 37] - [4, 76] (prop ; [4, 37] - [4, 76] (identifier) ; [4, 37] - [4, 41] (value ; [4, 42] - [4, 76] (string ; [4, 42] - [4, 76] (string_fragment))))) ; [4, 43] - [4, 75] (node_field ; [4, 77] - [4, 92] (prop ; [4, 77] - [4, 92] (identifier) ; [4, 77] - [4, 84] (value ; [4, 85] - [4, 92] (string ; [4, 85] - [4, 92] (string_fragment)))))))) ; [4, 86] - [4, 91] (comment ; [5, 0] - [5, 34] (document ; [5, 7] - [5, 34] (node ; [5, 9] - [5, 34] (identifier) ; [5, 9] - [5, 16] (node_field ; [5, 17] - [5, 24] (value ; [5, 17] - [5, 24] (string ; [5, 17] - [5, 24] (string_fragment)))) ; [5, 18] - [5, 23] (node_field ; [5, 25] - [5, 34] (value ; [5, 25] - [5, 34] (string ; [5, 25] - [5, 34] (string_fragment))))))) ; [5, 26] - [5, 33] (comment ; [6, 0] - [6, 8] (document ; [6, 7] - [6, 8] (ERROR))) ; [6, 7] - [6, 8] (comment ; [7, 0] - [7, 59] (document ; [7, 7] - [7, 59] (node ; [7, 7] - [7, 59] (identifier) ; [7, 7] - [7, 11] (node_field ; [7, 12] - [7, 30] (value ; [7, 12] - [7, 30] (string ; [7, 12] - [7, 30] (string_fragment)))) ; [7, 13] - [7, 29] (node_field ; [7, 31] - [7, 59] (prop ; [7, 31] - [7, 59] (identifier) ; [7, 31] - [7, 35] (value ; [7, 36] - [7, 59] (string ; [7, 36] - [7, 59] (string_fragment)))))))) ; [7, 37] - [7, 58] (comment ; [8, 0] - [8, 40] (document ; [8, 7] - [8, 40] (node ; [8, 7] - [8, 40] (identifier) ; [8, 7] - [8, 15] (node_field ; [8, 16] - [8, 22] (value ; [8, 16] - [8, 22] (string ; [8, 16] - [8, 22] (string_fragment)))) ; [8, 17] - [8, 21] (node_field ; [8, 23] - [8, 40] (prop ; [8, 23] - [8, 40] (identifier) ; [8, 23] - [8, 26] (value ; [8, 27] - [8, 40] (string ; [8, 27] - [8, 40] (string_fragment)))))))) ; [8, 28] - [8, 39] (comment ; [9, 0] - [9, 48] (document ; [9, 7] - [9, 48] (node ; [9, 7] - [9, 48] (identifier) ; [9, 7] - [9, 10] (node_field ; [9, 11] - [9, 21] (value ; [9, 11] - [9, 21] (string ; [9, 11] - [9, 21] (string_fragment)))) ; [9, 12] - [9, 20] (node_field ; [9, 22] - [9, 48] (prop ; [9, 22] - [9, 48] (identifier) ; [9, 22] - [9, 26] (value ; [9, 27] - [9, 48] (string ; [9, 27] - [9, 48] (string_fragment)))))))) ; [9, 28] - [9, 47]```

Other attempts

All of these (except the gsub) look the same with broken highlighting and produce identical (incorrect) tree:
combined-and-multinode-offset

Resulting tree```query(document ; [3, 7] - [9, 48] (ERROR ; [3, 7] - [9, 48] (identifier) ; [3, 7] - [3, 11] (node_field ; [3, 12] - [3, 24] (value ; [3, 12] - [3, 24] (string ; [3, 12] - [3, 24] (string_fragment)))) ; [3, 13] - [3, 23] (ERROR ; [3, 25] - [4, 12] (node_field ; [3, 25] - [3, 73] (prop ; [3, 25] - [3, 73] (identifier) ; [3, 25] - [3, 29] (value ; [3, 30] - [3, 73] (string ; [3, 30] - [3, 73] (string_fragment)))))) ; [3, 31] - [3, 72] (node_field ; [4, 12] - [4, 36] (value ; [4, 12] - [4, 36] (string ; [4, 12] - [4, 36] (string_fragment)))) ; [4, 13] - [4, 35] (node_field ; [4, 37] - [4, 76] (prop ; [4, 37] - [4, 76] (identifier) ; [4, 37] - [4, 41] (value ; [4, 42] - [4, 76] (string ; [4, 42] - [4, 76] (string_fragment))))) ; [4, 43] - [4, 75] (node_field ; [4, 77] - [4, 92] (prop ; [4, 77] - [4, 92] (identifier) ; [4, 77] - [4, 84] (value ; [4, 85] - [4, 92] (string ; [4, 85] - [4, 92] (string_fragment))))) ; [4, 86] - [4, 91] (node ; [5, 9] - [9, 48] (identifier) ; [5, 9] - [5, 16] (node_field ; [5, 17] - [5, 24] (value ; [5, 17] - [5, 24] (string ; [5, 17] - [5, 24] (string_fragment)))) ; [5, 18] - [5, 23] (ERROR ; [5, 25] - [7, 12] (identifier ; [5, 25] - [5, 34] (string ; [5, 25] - [5, 34] (string_fragment)))) ; [5, 26] - [5, 33] (node_field ; [7, 12] - [7, 30] (value ; [7, 12] - [7, 30] (string ; [7, 12] - [7, 30] (string_fragment)))) ; [7, 13] - [7, 29] (ERROR ; [7, 31] - [8, 16] (node_field ; [7, 31] - [7, 59] (prop ; [7, 31] - [7, 59] (identifier) ; [7, 31] - [7, 35] (value ; [7, 36] - [7, 59] (string ; [7, 36] - [7, 59] (string_fragment)))))) ; [7, 37] - [7, 58] (node_field ; [8, 16] - [8, 22] (value ; [8, 16] - [8, 22] (string ; [8, 16] - [8, 22] (string_fragment)))) ; [8, 17] - [8, 21] (ERROR ; [8, 23] - [9, 11] (node_field ; [8, 23] - [8, 40] (prop ; [8, 23] - [8, 40] (identifier) ; [8, 23] - [8, 26] (value ; [8, 27] - [8, 40] (string ; [8, 27] - [8, 40] (string_fragment)))))) ; [8, 28] - [8, 39] (node_field ; [9, 11] - [9, 21] (value ; [9, 11] - [9, 21] (string ; [9, 11] - [9, 21] (string_fragment)))) ; [9, 12] - [9, 20] (node_field ; [9, 22] - [9, 48] (prop ; [9, 22] - [9, 48] (identifier) ; [9, 22] - [9, 26] (value ; [9, 27] - [9, 48] (string ; [9, 27] - [9, 48] (string_fragment)))))))) ; [9, 28] - [9, 47]```

nvim 0.11

Usinginjection.combined

((comment) @injection.content  (#lua-match? @injection.content "^#USAGE ")  (#offset! @injection.content 0 7 0 0)  (#set! injection.combined)  (#set! injection.language "kdl"))

Using#gsub! instead of#offset!

((comment) @injection.content  (#lua-match? @injection.content "^#USAGE ")  (#gsub! @injection.content "^#USAGE " "")  (#set! injection.combined)  (#set! injection.language "kdl"))

This one produces slightly different tree, but still incorrect:
gsub

Resulting tree```query(document ; [3, 0] - [9, 48] (ERROR ; [3, 0] - [9, 48] (identifier) ; [3, 0] - [3, 6] (ERROR ; [3, 7] - [3, 11] (identifier)) ; [3, 7] - [3, 11] (node_field ; [3, 12] - [3, 24] (value ; [3, 12] - [3, 24] (string ; [3, 12] - [3, 24] (string_fragment)))) ; [3, 13] - [3, 23] (ERROR ; [3, 25] - [4, 12] (node_field ; [3, 25] - [3, 73] (prop ; [3, 25] - [3, 73] (identifier) ; [3, 25] - [3, 29] (value ; [3, 30] - [3, 73] (string ; [3, 30] - [3, 73] (string_fragment)))))) ; [3, 31] - [3, 72] (node_field ; [4, 12] - [4, 36] (value ; [4, 12] - [4, 36] (string ; [4, 12] - [4, 36] (string_fragment)))) ; [4, 13] - [4, 35] (node_field ; [4, 37] - [4, 76] (prop ; [4, 37] - [4, 76] (identifier) ; [4, 37] - [4, 41] (value ; [4, 42] - [4, 76] (string ; [4, 42] - [4, 76] (string_fragment))))) ; [4, 43] - [4, 75] (node_field ; [4, 77] - [4, 92] (prop ; [4, 77] - [4, 92] (identifier) ; [4, 77] - [4, 84] (value ; [4, 85] - [4, 92] (string ; [4, 85] - [4, 92] (string_fragment))))) ; [4, 86] - [4, 91] (node ; [5, 0] - [9, 48] (identifier) ; [5, 0] - [5, 6] (ERROR ; [5, 9] - [5, 16] (identifier)) ; [5, 9] - [5, 16] (node_field ; [5, 17] - [5, 24] (value ; [5, 17] - [5, 24] (string ; [5, 17] - [5, 24] (string_fragment)))) ; [5, 18] - [5, 23] (ERROR ; [5, 25] - [7, 12] (identifier ; [5, 25] - [5, 34] (string ; [5, 25] - [5, 34] (string_fragment)))) ; [5, 26] - [5, 33] (node_field ; [7, 12] - [7, 30] (value ; [7, 12] - [7, 30] (string ; [7, 12] - [7, 30] (string_fragment)))) ; [7, 13] - [7, 29] (ERROR ; [7, 31] - [8, 16] (node_field ; [7, 31] - [7, 59] (prop ; [7, 31] - [7, 59] (identifier) ; [7, 31] - [7, 35] (value ; [7, 36] - [7, 59] (string ; [7, 36] - [7, 59] (string_fragment)))))) ; [7, 37] - [7, 58] (node_field ; [8, 16] - [8, 22] (value ; [8, 16] - [8, 22] (string ; [8, 16] - [8, 22] (string_fragment)))) ; [8, 17] - [8, 21] (ERROR ; [8, 23] - [9, 11] (node_field ; [8, 23] - [8, 40] (prop ; [8, 23] - [8, 40] (identifier) ; [8, 23] - [8, 26] (value ; [8, 27] - [8, 40] (string ; [8, 27] - [8, 40] (string_fragment)))))) ; [8, 28] - [8, 39] (node_field ; [9, 11] - [9, 21] (value ; [9, 11] - [9, 21] (string ; [9, 11] - [9, 21] (string_fragment)))) ; [9, 12] - [9, 20] (node_field ; [9, 22] - [9, 48] (prop ; [9, 22] - [9, 48] (identifier) ; [9, 22] - [9, 26] (value ; [9, 27] - [9, 48] (string ; [9, 27] - [9, 48] (string_fragment)))))))) ; [9, 28] - [9, 47]```

nvim nightly

Both of these depend on#34383

Match one or more comment lines

((comment)+ @injection.content  (#lua-match? @injection.content "^#USAGE ")  (#offset! @injection.content 0 7 0 0)  (#set! injection.language "kdl"))

Multiple@injection.content captures

Became possible with#34352

(((comment) @injection.content)+  (#lua-match? @injection.content "^#USAGE ")  (#offset! @injection.content 0 7 0 0)  (#set! injection.language "kdl"))

Thoughts

Based onthis comment andthis PR I think either of thenightly solutions should work.
But they still seem to work like this

#USAGE *****************************

rather than like this

#USAGE *****#USAGE *****#USAGE *****

like discussedhere.

Questions

Is what I'm trying to achieve here even possible? If so, could someone help me out and point where I'm messing up the queries so that they either mostly work or just sorta work?

You must be logged in to vote

@ribru17 sorry to bother you with a direct ping

no worries :)

The issue here is pretty subtle (and the behavior could use documenting, because it has cropped up once or twice before). Basically, thebash parser does not include the trailing newline as part of the comment content. This makes sense, but if we want to combine the results of multiple lines of comments into one injection tree, weneed to include those newlines, otherwise thekdl parser will think that all of our text is on one line. This is why it doesn't parse correctly. The following works for me (on nightly, mileage may vary on 0.11.x):

((comment)+ @injection.content  (#lua-match? @injection.content"^#USAGE"); Exten…

Replies: 1 comment 2 replies

Comment options

@ribru17 sorry to bother you with a direct ping, but could you check this at some point if you have time? I hate to ping individual contributors like this but since you've authored the PRs I linked here I figured you might spot something I'm missing here. That, and I've been trying to figure this out by myself for a few days now and I'm running out of ideas 😓

You must be logged in to vote
2 replies
@ribru17
Comment options

ribru17Nov 23, 2025
Collaborator

@ribru17 sorry to bother you with a direct ping

no worries :)

The issue here is pretty subtle (and the behavior could use documenting, because it has cropped up once or twice before). Basically, thebash parser does not include the trailing newline as part of the comment content. This makes sense, but if we want to combine the results of multiple lines of comments into one injection tree, weneed to include those newlines, otherwise thekdl parser will think that all of our text is on one line. This is why it doesn't parse correctly. The following works for me (on nightly, mileage may vary on 0.11.x):

((comment)+ @injection.content  (#lua-match? @injection.content"^#USAGE"); Extend the range one byte to the right, to include the trailing newline.  (#offset! @injection.content0701)  (#set! injection.language"kdl"))
image
Answer selected byjustinmk
@okuuva
Comment options

Nice, thank you so much! 🙏🏼 For those who might stumble upon this later on: that indeed only works on nightly, on 0.11 you need to use theinjection.combined property:

((comment) @injection.content  (#lua-match? @injection.content "^#USAGE ")  (#offset! @injection.content 0 7 0 1)  (#set! injection.combined)  (#set! injection.language "kdl"))
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
2 participants
@okuuva@ribru17

[8]ページ先頭

©2009-2025 Movatter.jp