-
-
Notifications
You must be signed in to change notification settings - Fork 247
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
[WIP] Treesitter-postfix Support #980
Conversation
That was fast, awesome! |
Q: would it be possible to also work with queries? |
Please take a look. Introduce two parameters:
|
Hey, I'll be a bit slower in communicating, I'm rather busy this week, and the next, just FYI 😅 Have you considered accepting the query in the postfix-constructor, and then parse it via Also, I don't understand the need for |
Of course, but i'm thinking of if i should introduce a new parameter for query text. This idea is from when I'm trying to implement a snippet In this sample code: #include <compare>
struct Test {
int foo;
int bar;
};
int main(int argc, const char* argv) {
xxxxx.test();
} Its language tree should be:
After input the trigger "fn", it becomes: #include <compare>
struct Test {
int foo;
int bar;
};
fn
int main(int argc, const char* argv) {
xxxxx.test();
} The tree becomes,
Now, the matched trigger "fn" now is a part of Here's my snippet using this feature: |
Ahh of course, very cool!
I like the current solution because it doesn't touch the buffer at all, but maybe this dirtier approach is faster than creating that giant string and parsing it
Wouldn't that just be an alternative to |
i haven't tried that way. i'm not sure if the treesitter's incremental parse will be triggered in this situation. will test it tomorrow. i'm thinking of allow maybe like this,
|
I think as long as What would we need |
It works. I will update my implementation.
The user should specify the interest in which |
Should we allow all cases, eg. no reparsing, reparse using the live-buffer, or reparse using the string? Using the live buffer is probably a sane default, but I could see it causing issues, and exposing the option doesn't seem to cause much more complexity (at least as far as I can tell, if you think otherwise, we should leave it out)
Sorry, I still don't understand, can't we just pass through all captures to the snippet-env? |
yeah, i will change the option to
ah, the word |
What about just string, and have the options Okay I think we were talking about different things: My suggestion was to have an api that could be called like local s = ts_postfix({
query = [[
(assignment_expression
left: (identifier) @lhs
right: (call_expression) @rhs)
]],
query_lang = "cpp",
trig = ".foo"
}) Where the user would then have env-variables:
But you also have a point, re-using the queries provided by nvim-treesitter is probably a good idea, since there might be some unnecessary effort in having to come up with the correct queries, if they are already available. |
yeah, just a string is more clearly. oh, i forget the provided query text case. what about give all captures if the query text is provided and only these interest captures if the captures name and query group is provided? |
Ahh okay, I think I understand the whole query-business a bit better now: Regarding the captures, couldn't we just check if the match contains the desired capture, and then provide, in |
I'm trying to find all matches and setup these in |
Oh yeah, that's what I meant with the desired capture 👍 |
a user provided a user provided |
In my mind, the algorithm we should apply (given a query (either as file, or string) and a capture-name
Regarding matching specific node-sequences, I think we should implement that separately, including that in the query-implementation seems messy, or I don't yet see how closely they are related.
I don't think that's a bad thing, for example the |
Is your question about |
No, not really, I was suggesting an alternative api, where If I understand the current code correctly, nodes can be filtered first via a query (passed as |
It's not important if there's a final node to act as POASTFIX_MATCH. I think the only reason that we need to finally find one node is the node's range should to be act as Here's an example: ; extends
[
(call_expression)
(identifier)
(template_function)
(subscript_expression)
(field_expression)
(user_defined_literal)
] @any_expr tsp.treesitter_postfix({
trig = ".be",
name = "begin..end",
dscr = "Completes a variable with both begin() and end().",
matchTSNode = {
captures = "any_expr",
},
}, {
f(function(_, parent)
vim.print(parent.env.TREESITTER_MATCHES)
-- return su.replace_all(
-- parent.snippet.env.TSNODETEXT_MATCH,
-- "%s.begin(), %s.end()"
-- )
end, {}),
}),
In this case, it's hard to determine the |
I can't quite see why this is ambiguous.. can these nodes overlap? |
Some of these node overlap #include <compare>
struct Test {
int foo;
int bar;
};
int main(int argc, const char* argv) {
test().abasdf.asdfasd
}
In this case, we got:
The user should select if they want |
Okay I see, thanks 👍 Unfortunately this complicates (and makes slower) the simple case of matching a pattern that does not have any ambiguity, where the first match at the trigger is the one we'd like to use. Different question: So we should allow options like |
If the user provide a Ah, options like |
I have updated the implementation. Now the
The definition of |
Hi, I finally have some time to get back into this :) Could you update the documentation? I think the interface is pretty nice now, and we probably don't have to change it again (I hope :D) |
DOC has updated. you can take a look.:) |
Hey, I decided, instead of trying to convey each of my desired changes via words here, to just implement them. I have a few more things in mind (eg. make range of nodes accessible in env), but wanted to see what you think before I go ahead and make any more. |
One example: ls.setup_snip_env()
local ts_post = require("luasnip.extras.treesitter_postfix").treesitter_postfix
ls.add_snippets("all", {
ts_post({
matchTSNode = {
query = [[
(function_declaration
name: (identifier) @fname
parameters: (parameters) @params
body: (block) @body
) @prefix
]],
query_lang = "lua",
},
trig = "var"
}, fmt([[
local {} = function{}
{}
end
]], {
l(l.TS_CAPTURE_fname),
l(l.TS_CAPTURE_params),
l(l.TS_CAPTURE_body),
}))
}, {key = "asdf"}) 1694370215.mp4 |
I looked a bit more into providing node-ranges and other data via So, we could still provide the data in |
I think we can just provide data in |
7e69578
to
a0532ab
Compare
Okay, so I don't have anything more to add, what do you think about it? |
0dda26b
to
3a03271
Compare
I added some tests, and required a minimum-version of nvim-0.9 for requiring Also switched 0.7 (which we used for running tests on an older version to 0.8 and 0.9, since 0.7 no longer compiled for me. I'll try looking into those issues, and re-including 0.7 tomorrow or so 😅) |
f7f8e8a
to
71f844e
Compare
Alright fixed it, now we have 0.7, 0.9 and master. |
2c14ab6
to
e232c3e
Compare
Alright, so I think this is done, at least as far as queries are concerned.. |
Of course. Some postfix I used: tsp.treesitter_postfix({
trig = ".be",
name = "begin..end",
dscr = "Completes a variable with both begin() and end().",
wordTrig = false,
reparseBuffer = nil,
matchTSNode = tsp.builtin.tsnode_matcher.find_topmost_types({
"call_expression",
"identifier",
"template_function",
"subscript_expression",
"field_expression",
"user_defined_literal",
}, ".be"),
}, {
f(function(_, parent)
return su.replace_all(
parent.snippet.env.LS_TSMATCH,
"%s.begin(), %s.end()"
)
end, {}),
}), Other snippets with a simple wrapper: ts_postfix_any_expr {
".mv",
name = "move(expr)",
dscr = "Wraps an expression with 'std::move' if it is available",
nodes = {
f(function(_, parent)
return su.replace_all(parent.snippet.env.POSTFIX_MATCH, "std::move(%s)")
end, {}),
},
},
ts_postfix_any_expr {
".fwd",
name = "forward(expr)",
dscr = "Wraps an expression with 'std::forward' if it is available.",
nodes = {
f(function(_, parent)
return su.replace_all(
parent.snippet.env.POSTFIX_MATCH,
"std::forward<decltype(%s)>(%s)"
)
end, {}),
},
},
ts_postfix_any_expr {
".val",
name = "declval<expr>()",
dscr = "Wraps an expression with 'std::declval' if it is available.",
nodes = {
f(function(_, parent)
return su.replace_all(
parent.snippet.env.POSTFIX_MATCH,
"std::declval<%s>()"
)
end, {}),
},
},
ts_postfix_any_expr {
".uu",
name = "(void)expr",
dscr = "Wraps an expression with '(void)expr' to silence unused variable warnings.",
nodes = {
f(function(_, parent)
return su.replace_all(parent.snippet.env.POSTFIX_MATCH, "(void)%s;")
end, {}),
},
},
ts_postfix_any_expr {
".dt",
name = "decltype(expr)",
dscr = "Wraps an expression with 'decltype' to get the type of an expression.",
nodes = {
f(function(_, parent)
return su.replace_all(parent.snippet.env.POSTFIX_MATCH, "decltype(%s)")
end, {}),
},
},
ts_postfix_any_expr {
".for",
name = "for (expr)",
dscr = "Wraps an expression with a range-based 'for' loop.",
nodes = fmta(
[[
for (<typ> <item> : <expr>) {
<body>
}
]],
{
body = i(0),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
typ = c(1, {
t("const auto&"),
t("auto&&"),
}),
item = i(2, "item"),
}
),
},
function()
local fori_types = vim.deepcopy(ts_postfix.any_expr_types)
fori_types[#fori_types + 1] = "number_literal"
return ts_postfix.ts_postfix_maker(fori_types) {
".fori",
name = "for (int i = 0; i < expr; i++)",
dscr = "Wraps an expression with a 'for' loop.",
nodes = fmta(
[[
for (decltype(<expr>) i = 0; i << <expr>; i++) {
<body>
}
]],
{
body = i(0),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
}
),
}
end,
ts_postfix_any_expr {
".if",
name = "if (expr)",
dscr = "Wraps an expression with an 'if' condition.",
nodes = fmta(
[[
if (<expr>) {
<body>
}
]],
{
body = i(0),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
}
),
},
ts_postfix_any_expr {
".else",
name = "if (!expr)",
dscr = "Negates an expression and wraps it with 'if'.",
nodes = fmta(
[[
if (!<expr>) {
<body>
}
]],
{
body = i(0),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
}
),
},
ts_postfix_any_expr {
".sc",
name = "static_cast<>(expr)",
dscr = "Wraps an expression with 'static_cast'.",
nodes = fmt(
[[
static_cast<{body}>({expr}){end}
]],
{
body = i(1),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
["end"] = i(0),
}
),
},
ts_postfix_any_expr {
".cast",
name = "xxx_cast<>(expr)",
dscr = "Wraps an expression with a cast expression.",
nodes = fmt(
[[
{cast}<{body}>({expr}){end}
]],
{
cast = c(1, {
t("static_cast"),
t("reinterpret_cast"),
t("dynamic_cast"),
t("const_cast"),
}, {
desc = "Type conversion casts",
}),
body = i(2),
expr = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
["end"] = i(0),
}
),
},
ts_postfix_ident_only {
".fd",
name = "if (..find)",
dscr = "Find a member exists in given indent.",
nodes = fmta(
[[
if (auto it = <ident>.find(<key>); it <equal> <ident>.end()) {
<body>
}
]],
{
ident = f(function(_, parent)
return parent.snippet.env.POSTFIX_MATCH
end, {}),
key = i(1, "key"),
equal = c(2, {
t("=="),
t("!="),
}, {
desc = "Equals, Not Equals",
}),
body = i(0),
}
),
},
|
a968ae1
to
a83abb2
Compare
See DOC.md-changes for extensive documentation and examples.
Validate treesitter-postfix on oldest release where it is enabled.
333f51b
to
5f6929f
Compare
Alright, I think this is it, sorry for being a bit more demanding on this one 😅 |
Looks great! |
Alriight, thank you for your help! |
No description provided.