feat(queries): add embedded language injections for Bruno blocks

This commit is contained in:
2026-03-11 15:23:44 +02:00
parent 1b1aead30e
commit 7739598c04
7 changed files with 954 additions and 927 deletions

View File

@@ -11,6 +11,7 @@ module.exports = grammar({
name: "bruno",
extras: (_) => [/\s/],
externals: ($) => [$.rawtext],
rules: {
source_file: ($) => repeat($.section),
@@ -77,14 +78,14 @@ module.exports = grammar({
$.bodyformmultipart,
$.bodyraw,
),
bodyraw: ($) => seq(alias("body", $.keyword), $.raw_block),
bodyjson: ($) => seq(alias("body:json", $.keyword), $.raw_block),
bodytext: ($) => seq(alias("body:text", $.keyword), $.raw_block),
bodyxml: ($) => seq(alias("body:xml", $.keyword), $.raw_block),
bodysparql: ($) => seq(alias("body:sparql", $.keyword), $.raw_block),
bodygraphql: ($) => seq(alias("body:graphql", $.keyword), $.raw_block),
bodyraw: ($) => seq(alias("body", $.keyword), $.textblock),
bodyjson: ($) => seq(alias("body:json", $.keyword), $.textblock),
bodytext: ($) => seq(alias("body:text", $.keyword), $.textblock),
bodyxml: ($) => seq(alias("body:xml", $.keyword), $.textblock),
bodysparql: ($) => seq(alias("body:sparql", $.keyword), $.textblock),
bodygraphql: ($) => seq(alias("body:graphql", $.keyword), $.textblock),
bodygraphqlvars: ($) =>
seq(alias("body:graphql:vars", $.keyword), $.raw_block),
seq(alias("body:graphql:vars", $.keyword), $.textblock),
bodyformurlencoded: ($) =>
seq(alias("body:form-urlencoded", $.keyword), $.object_block),
bodyformmultipart: ($) =>
@@ -100,17 +101,17 @@ module.exports = grammar({
assert: ($) => seq(alias("assert", $.keyword), $.assert_block),
script: ($) => choice($.scriptreq, $.scriptres),
scriptreq: ($) => seq(alias("script:pre-request", $.keyword), $.raw_block),
scriptreq: ($) => seq(alias("script:pre-request", $.keyword), $.textblock),
scriptres: ($) =>
seq(alias("script:post-response", $.keyword), $.raw_block),
seq(alias("script:post-response", $.keyword), $.textblock),
params: ($) => choice($.params_path, $.params_query),
params_query: ($) => seq(alias("params:query", $.keyword), $.object_block),
params_path: ($) => seq(alias("params:path", $.keyword), $.object_block),
tests: ($) => seq(alias("tests", $.keyword), $.raw_block),
tests: ($) => seq(alias("tests", $.keyword), $.textblock),
docs: ($) => seq(alias("docs", $.keyword), $.raw_block),
docs: ($) => seq(alias("docs", $.keyword), $.textblock),
object_block: ($) => seq("{", repeat($.dictionary_pair), "}"),
dictionary_pair: ($) => seq($.key, ":", $.dictionary_value),
@@ -124,9 +125,7 @@ module.exports = grammar({
array_value: ($) =>
choice($.template_value, $.quoted_value, $.bare_value),
raw_block: ($) => seq("{", repeat($.raw_fragment), "}"),
raw_fragment: ($) => choice($.raw_text, $.raw_block),
raw_text: (_) => token(prec(-1, /[^{}]+/)),
textblock: ($) => seq("{", optional($.rawtext), "}"),
assert_key: (_) => /[^\r\n:]+/,

59
queries/injections.scm Normal file
View File

@@ -0,0 +1,59 @@
((bodyraw
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "json"))
((bodyjson
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "json"))
((bodygraphql
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "graphql"))
((bodygraphqlvars
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "json"))
((bodyxml
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "xml"))
((bodysparql
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "sparql"))
((scriptreq
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "javascript"))
((scriptres
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "javascript"))
((tests
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "javascript"))
((docs
(keyword)
(textblock
(rawtext) @injection.content))
(#set! injection.language "markdown"))

70
src/grammar.json generated
View File

@@ -345,7 +345,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -363,7 +363,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -381,7 +381,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -399,7 +399,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -417,7 +417,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -435,7 +435,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -453,7 +453,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -631,7 +631,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -649,7 +649,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -716,7 +716,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -734,7 +734,7 @@
},
{
"type": "SYMBOL",
"name": "raw_block"
"name": "textblock"
}
]
},
@@ -883,7 +883,7 @@
}
]
},
"raw_block": {
"textblock": {
"type": "SEQ",
"members": [
{
@@ -891,11 +891,16 @@
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "raw_fragment"
}
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "rawtext"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
@@ -903,30 +908,6 @@
}
]
},
"raw_fragment": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "raw_text"
},
{
"type": "SYMBOL",
"name": "raw_block"
}
]
},
"raw_text": {
"type": "TOKEN",
"content": {
"type": "PREC",
"value": -1,
"content": {
"type": "PATTERN",
"value": "[^{}]+"
}
}
},
"assert_key": {
"type": "PATTERN",
"value": "[^\\r\\n:]+"
@@ -1012,7 +993,12 @@
],
"conflicts": [],
"precedences": [],
"externals": [],
"externals": [
{
"type": "SYMBOL",
"name": "rawtext"
}
],
"inline": [],
"supertypes": [],
"reserved": {}

73
src/node-types.json generated
View File

@@ -314,7 +314,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -333,7 +333,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -352,7 +352,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -371,7 +371,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -390,7 +390,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -409,7 +409,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -428,7 +428,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -489,7 +489,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -648,40 +648,6 @@
]
}
},
{
"type": "raw_block",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "raw_fragment",
"named": true
}
]
}
},
{
"type": "raw_fragment",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "raw_block",
"named": true
},
{
"type": "raw_text",
"named": true
}
]
}
},
{
"type": "script",
"named": true,
@@ -714,7 +680,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -733,7 +699,7 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
@@ -827,7 +793,22 @@
"named": true
},
{
"type": "raw_block",
"type": "textblock",
"named": true
}
]
}
},
{
"type": "textblock",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "rawtext",
"named": true
}
]
@@ -1001,7 +982,7 @@
"named": true
},
{
"type": "raw_text",
"type": "rawtext",
"named": true
},
{

1549
src/parser.c generated

File diff suppressed because it is too large Load Diff

98
src/scanner.c Normal file
View File

@@ -0,0 +1,98 @@
#include "tree_sitter/parser.h"
#include <stdbool.h>
#include <stdint.h>
enum TokenType {
RAWTEXT,
};
void *tree_sitter_bruno_external_scanner_create(void) {
return NULL;
}
void tree_sitter_bruno_external_scanner_destroy(void *payload) {
(void)payload;
}
unsigned tree_sitter_bruno_external_scanner_serialize(void *payload, char *buffer) {
(void)payload;
(void)buffer;
return 0;
}
void tree_sitter_bruno_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
(void)payload;
(void)buffer;
(void)length;
}
static void advance(TSLexer *lexer) {
lexer->advance(lexer, false);
}
static void skip(TSLexer *lexer) {
lexer->advance(lexer, true);
}
static bool scan_quoted(TSLexer *lexer, int32_t quote) {
advance(lexer);
while (lexer->lookahead) {
if (lexer->lookahead == '\\') {
advance(lexer);
if (lexer->lookahead) advance(lexer);
continue;
}
if (lexer->lookahead == quote) {
advance(lexer);
return true;
}
advance(lexer);
}
return false;
}
bool tree_sitter_bruno_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
(void)payload;
if (!valid_symbols[RAWTEXT]) return false;
if (lexer->lookahead == '}') return false;
unsigned depth = 0;
bool has_content = false;
while (lexer->lookahead) {
if (lexer->lookahead == '\'' || lexer->lookahead == '"' || lexer->lookahead == '`') {
has_content = true;
if (!scan_quoted(lexer, lexer->lookahead)) break;
continue;
}
if (lexer->lookahead == '{') {
has_content = true;
depth++;
advance(lexer);
continue;
}
if (lexer->lookahead == '}') {
if (depth == 0) break;
depth--;
has_content = true;
advance(lexer);
continue;
}
has_content = true;
advance(lexer);
}
if (!has_content) return false;
lexer->result_symbol = RAWTEXT;
return true;
}

View File

@@ -6,12 +6,13 @@
"camelcase": "Bruno",
"title": "Bruno",
"scope": "source.bruno",
"highlights": "queries/highlights.scm",
"file-types": [
"bru"
],
"injection-regex": "^bruno$",
"class-name": "TreeSitterBruno"
"class-name": "TreeSitterBruno",
"highlights": "queries/highlights.scm",
"injections": "queries/injections.scm"
}
],
"metadata": {