diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..6461deecd
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+*.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index 520c2aeb7..08b4fff87 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,6 +19,12 @@ module.exports = {
asyncArrow: "always",
named: "never"
}],
+ "node/no-missing-require": [
+ "error",
+ {
+ allowModules: ["atom"]
+ }
+ ],
"node/no-unpublished-require": [
"error",
{
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1a25cc86a..277496c14 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -208,7 +208,8 @@ jobs:
node ./rolling-release-binary-upload.js
- name: Upload Video Artifacts
- if: runner.os != 'Linux'
+ # Run whether this job passed or failed, unless explicitly cancelled.
+ if: ${{ !cancelled() && runner.os != 'Linux' }}
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }} Videos
@@ -267,7 +268,8 @@ jobs:
node ./rolling-release-binary-upload.js
- name: Upload Video Artifacts - Linux
- if: runner.os == 'Linux'
+ # Run whether this job passed or failed, unless explicitly cancelled.
+ if: ${{ !cancelled() && runner.os == 'Linux' }}
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }} Videos
diff --git a/integration/workspace.spec.js b/integration/workspace.spec.js
index 67c53c07e..10c49797a 100644
--- a/integration/workspace.spec.js
+++ b/integration/workspace.spec.js
@@ -23,7 +23,7 @@ const languages = [
// {language: "mustache", code: '10', checks: {numeric: '10'}},
{language: "Objective C", code: '10', checks: {numeric: '10'}},
{language: "Perl", code: '10', checks: {numeric: '10'}},
- {language: "PHP", code: ' 10 %>', checks: {numeric: '10'}},
+ {language: "PHP", code: ' $foo ?>', checks: {variable: '$foo'}},
// {language: "property-list", code: '10', checks: {numeric: '10'}},
{language: "Python", code: '10', checks: {numeric: '10'}},
{language: "Ruby on Rails", code: '10', checks: {numeric: '10'}},
diff --git a/packages/atom-dark-syntax/index.less b/packages/atom-dark-syntax/index.less
index 161afc862..7a20f8eb9 100644
--- a/packages/atom-dark-syntax/index.less
+++ b/packages/atom-dark-syntax/index.less
@@ -9,3 +9,4 @@
@import "styles/syntax/base.less";
@import "styles/syntax/css.less";
@import "styles/syntax/html.less";
+@import "styles/syntax/json.less";
diff --git a/packages/atom-dark-syntax/styles/syntax/base.less b/packages/atom-dark-syntax/styles/syntax/base.less
index d5fe55930..729b801df 100644
--- a/packages/atom-dark-syntax/styles/syntax/base.less
+++ b/packages/atom-dark-syntax/styles/syntax/base.less
@@ -229,6 +229,12 @@
&.syntax--italic {
font-style: italic;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: #8A8A8A;
+ }
}
// /* comment */
diff --git a/packages/atom-dark-syntax/styles/syntax/json.less b/packages/atom-dark-syntax/styles/syntax/json.less
new file mode 100644
index 000000000..011feb3b2
--- /dev/null
+++ b/packages/atom-dark-syntax/styles/syntax/json.less
@@ -0,0 +1,11 @@
+
+.syntax--source.syntax--json {
+
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: #96CBFE;
+ }
+ }
+
+}
diff --git a/packages/atom-light-syntax/index.less b/packages/atom-light-syntax/index.less
index d86a25ba2..639a66be4 100644
--- a/packages/atom-light-syntax/index.less
+++ b/packages/atom-light-syntax/index.less
@@ -8,3 +8,4 @@
@import "styles/syntax/base.less";
@import "styles/syntax/css.less";
+@import "styles/syntax/json.less";
diff --git a/packages/atom-light-syntax/styles/syntax/base.less b/packages/atom-light-syntax/styles/syntax/base.less
index aa5de28f8..18f5ce6d8 100644
--- a/packages/atom-light-syntax/styles/syntax/base.less
+++ b/packages/atom-light-syntax/styles/syntax/base.less
@@ -198,6 +198,12 @@
&.syntax--italic {
font-style: italic;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: #999988;
+ }
}
// /* comment */
diff --git a/packages/atom-light-syntax/styles/syntax/json.less b/packages/atom-light-syntax/styles/syntax/json.less
new file mode 100644
index 000000000..6891b18a3
--- /dev/null
+++ b/packages/atom-light-syntax/styles/syntax/json.less
@@ -0,0 +1,11 @@
+
+.syntax--source.syntax--json {
+
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: #008080;
+ }
+ }
+
+}
diff --git a/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/_base.less b/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/_base.less
index e7eab57a6..758f42dfd 100644
--- a/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/_base.less
+++ b/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/_base.less
@@ -267,6 +267,12 @@
&.syntax--raw {
color: @green;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @gray;
+ }
}
.syntax--source.syntax--gfm {
diff --git a/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/json.less b/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/json.less
index 9d451633f..a80b366aa 100644
--- a/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/json.less
+++ b/packages/base16-tomorrow-dark-theme/styles/syntax-legacy/json.less
@@ -8,6 +8,13 @@
}
}
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @red;
+ }
+ }
+
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
diff --git a/packages/base16-tomorrow-light-theme/styles/syntax-legacy/_base.less b/packages/base16-tomorrow-light-theme/styles/syntax-legacy/_base.less
index e7eab57a6..758f42dfd 100644
--- a/packages/base16-tomorrow-light-theme/styles/syntax-legacy/_base.less
+++ b/packages/base16-tomorrow-light-theme/styles/syntax-legacy/_base.less
@@ -267,6 +267,12 @@
&.syntax--raw {
color: @green;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @gray;
+ }
}
.syntax--source.syntax--gfm {
diff --git a/packages/base16-tomorrow-light-theme/styles/syntax-legacy/json.less b/packages/base16-tomorrow-light-theme/styles/syntax-legacy/json.less
index 9d451633f..a80b366aa 100644
--- a/packages/base16-tomorrow-light-theme/styles/syntax-legacy/json.less
+++ b/packages/base16-tomorrow-light-theme/styles/syntax-legacy/json.less
@@ -8,6 +8,13 @@
}
}
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @red;
+ }
+ }
+
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
diff --git a/packages/bracket-matcher/spec/.eslintrc.js b/packages/bracket-matcher/spec/.eslintrc.js
new file mode 100644
index 000000000..fa2293002
--- /dev/null
+++ b/packages/bracket-matcher/spec/.eslintrc.js
@@ -0,0 +1,12 @@
+module.exports = {
+ env: { jasmine: true },
+ globals: {
+ waitsForPromise: true,
+ },
+ rules: {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+};
diff --git a/packages/language-c/grammars/modern-tree-sitter-c.cson b/packages/language-c/grammars/modern-tree-sitter-c.cson
index c1f376e33..c4be00a85 100644
--- a/packages/language-c/grammars/modern-tree-sitter-c.cson
+++ b/packages/language-c/grammars/modern-tree-sitter-c.cson
@@ -7,6 +7,7 @@ firstLineRegex: '-\\*-[^*]*(Mode:\\s*)?C(\\s*;.*?)?\\s*-\\*-'
injectionRegex: '^(c|C)$'
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-c#212a80f86452bb1316324fa0db730cf52f29e05a'
grammar: 'tree-sitter-c/tree-sitter-c.wasm'
highlightsQuery: 'tree-sitter-c/highlights.scm'
tagsQuery: 'tree-sitter-c/tags.scm'
diff --git a/packages/language-c/grammars/modern-tree-sitter-cpp.cson b/packages/language-c/grammars/modern-tree-sitter-cpp.cson
index 98352d101..93e1c2978 100644
--- a/packages/language-c/grammars/modern-tree-sitter-cpp.cson
+++ b/packages/language-c/grammars/modern-tree-sitter-cpp.cson
@@ -6,7 +6,7 @@ parser: 'tree-sitter-cpp'
injectionRegex: '^(c|C)(\\+\\+|pp|PP)$'
treeSitter:
- parserSource: 'github:tree-sitter/tree-sitter-cpp#a90f170f92d5d70e7c2d4183c146e61ba5f3a457'
+ parserSource: 'github:tree-sitter/tree-sitter-cpp#a71474021410973b29bfe99440d57bcd750246b1'
grammar: 'tree-sitter-cpp/tree-sitter-cpp.wasm'
highlightsQuery: 'tree-sitter-cpp/highlights.scm'
tagsQuery: 'tree-sitter-cpp/tags.scm'
diff --git a/packages/language-c/grammars/tree-sitter-c/highlights.scm b/packages/language-c/grammars/tree-sitter-c/highlights.scm
index 96dccf2de..3a2db6c79 100644
--- a/packages/language-c/grammars/tree-sitter-c/highlights.scm
+++ b/packages/language-c/grammars/tree-sitter-c/highlights.scm
@@ -13,6 +13,10 @@
"#define" @keyword.control.directive.define.c
"#include" @keyword.control.directive.include.c
+(["#if" "#ifdef" "#ifndef" "#endif" "#elif" "#else" "#define" "#include"] @punctuation.definition.directive.c
+ (#set! adjust.endAfterFirstMatchOf "^#"))
+
+
; This will match if the more specific rules above haven't matched. The
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
@@ -43,30 +47,41 @@
(type_identifier) @_IGNORE_
(#set! capture.final true))
-(primitive_type) @support.type.builtin.c
-(type_identifier) @support.type.other.c
+(primitive_type) @support.storage.type.builtin.c
+(type_identifier) @support.other.storage.type.c
; These types are all reserved words; if we see an identifier with this name,
; it must be a type.
-((identifier) @support.type.builtin.c
- (#match? @support.type.builtin.c "^(char|int|float|double|long)$"))
+((identifier) @support.storage.type.builtin.c
+ (#match? @support.storage.type.builtin.c "^(char|int|float|double|long)$"))
; Assume any identifier that ends in `_t` is a type. This convention is not
; always followed, but it's a very strong indicator when it's present.
-((identifier) @support.type.other.c
- (#match? @support.type.other.c "_t$"))
+((identifier) @support.other.storage.type.c
+ (#match? @support.other.storage.type.c "_t$"))
+; These refer to language constructs and remain in the `storage` namespace.
[
"enum"
- "long"
- "short"
- "signed"
"struct"
"typedef"
"union"
- "unsigned"
] @storage.type.c
+; These refer to value types and go under `support`.
+[
+ "long"
+ "short"
+] @support.storage.type.builtin.c
+
+; These act as modifiers to value types and also go under `support`.
+[
+ "signed"
+ "unsigned"
+] @support.storage.modifier.builtin.c
+
+; These act as general language modifiers and remain in the `storage`
+; namespace.
[
"const"
"extern"
@@ -75,10 +90,10 @@
"restrict"
"static"
"volatile"
-] @storage.modifier.c
+] @storage.modifier._TYPE_.c
-((primitive_type) @support.type.stdint.c
- (#match? @support.type.stdint.c "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$"))
+((primitive_type) @support.storage.type.stdint.c
+ (#match? @support.storage.type.stdint.c "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$"))
(enum_specifier
name: (type_identifier) @variable.other.declaration.type.c)
@@ -116,32 +131,57 @@
; Declarations and assignments
; ----------------------------
-; The "x" in `int x`;
+; The "x" in `int x;`
(declaration
declarator: (identifier) @variable.declaration.c)
-; The "x" in `int x = y`;
+; The "x" in `int x = y;`
(init_declarator
declarator: (identifier) @variable.declaration.c)
+; The "x" in `SomeType *x;`
+; (Should work no matter how many pointers deep we are.)
+(pointer_declarator
+ declarator: [(identifier) (field_identifier)] @variable.declaration.pointer.c
+ (#is? test.descendantOfType "declaration field_declaration"))
+
+; A member of a struct.
(field_declaration
- (field_identifier) @entity.other.attribute-name.c)
+ (field_identifier) @variable.declaration.member.c)
+
+; An attribute in a C99 struct designated initializer:
+; the "foo" in `MY_TYPE a = { .foo = true };
+(initializer_pair
+ (field_designator
+ (field_identifier) @variable.declaration.member.c))
+
+; (and the associated ".")
+(initializer_pair
+ (field_designator
+ "." @keyword.operator.accessor.c))
(field_declaration
(pointer_declarator
- (field_identifier) @entity.other.attribute-name.c))
+ (field_identifier) @variable.declaration.member.c))
(field_declaration
(array_declarator
- (field_identifier) @entity.other.attribute-name.c))
+ (field_identifier) @variable.declaration.member.c))
(init_declarator
(pointer_declarator
- (identifier) @entity.other.attribute-name.c))
+ (identifier) @variable.declaration.member.c))
+; The "x" in `x = y;`
(assignment_expression
left: (identifier) @variable.other.assignment.c)
+; The "foo" in `something->foo = "bar";`
+(assignment_expression
+ left: (field_expression
+ field: (field_identifier) @variable.other.member.assignment.c)
+ (#set! capture.final))
+
; Function parameters
; -------------------
@@ -154,9 +194,10 @@
declarator: (identifier) @variable.parameter.c)
; The "foo" in `const char *foo` within a parameter list.
-(parameter_declaration
- declarator: (pointer_declarator
- declarator: (identifier) @variable.parameter.c))
+; (Should work no matter how many pointers deep we are.)
+(pointer_declarator
+ declarator: [(identifier) (field_identifier)] @variable.parameter.pointer.c
+ (#is? test.descendantOfType "parameter_declaration"))
; The "foo" in `const char foo[]` within a parameter list.
(parameter_declaration
@@ -172,7 +213,7 @@
; The "size" in `finfo->size`.
(field_expression
"->"
- field: (field_identifier) @support.other.property.c)
+ field: (field_identifier) @variable.other.member.c)
; FUNCTIONS
@@ -309,8 +350,10 @@
";" @punctuation.terminator.statement.c
-"," @punctuation.separator.comma.c
-"->" @punctuation.separator.pointer-access.c
+("," @punctuation.separator.comma.c
+ (#set! capture.shy))
+("->" @keyword.operator.accessor.pointer-access.c
+ (#set! capture.shy))
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.c
@@ -335,6 +378,22 @@
"[" @punctuation.definition.array.begin.bracket.square.c
"]" @punctuation.definition.array.end.bracket.square.c
+
+; META
+; ====
+
+((compound_statement) @meta.block.c
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
+((enumerator_list) @meta.block.enum.c
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
+((field_declaration_list) @meta.block.field.c
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
; TODO:
;
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
diff --git a/packages/language-c/grammars/tree-sitter-c/tree-sitter-c.wasm b/packages/language-c/grammars/tree-sitter-c/tree-sitter-c.wasm
index 3f54a976c..771b9c53b 100755
Binary files a/packages/language-c/grammars/tree-sitter-c/tree-sitter-c.wasm and b/packages/language-c/grammars/tree-sitter-c/tree-sitter-c.wasm differ
diff --git a/packages/language-c/grammars/tree-sitter-cpp/highlights.scm b/packages/language-c/grammars/tree-sitter-cpp/highlights.scm
index 335640633..bc3fc2c3c 100644
--- a/packages/language-c/grammars/tree-sitter-cpp/highlights.scm
+++ b/packages/language-c/grammars/tree-sitter-cpp/highlights.scm
@@ -13,6 +13,10 @@
"#define" @keyword.control.directive.define.cpp
"#include" @keyword.control.directive.include.cpp
+(["#if" "#ifdef" "#ifndef" "#endif" "#elif" "#else" "#define" "#include"] @punctuation.definition.directive.c
+ (#set! adjust.endAfterFirstMatchOf "^#"))
+
+
; This will match if the more specific rules above haven't matched. The
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
@@ -69,28 +73,38 @@
; These types are all reserved words; if we see an identifier with this name,
; it must be a type.
-((identifier) @support.type.builtin.cpp
- (#match? @support.type.builtin.cpp "^(char|int|float|double|long)$"))
+((identifier) @support.storage.type.builtin.cpp
+ (#match? @support.storage.type.builtin.cpp "^(char|int|float|double|long)$"))
; Assume any identifier that ends in `_t` is a type. This convention is not
; always followed, but it's a very strong indicator when it's present.
-((identifier) @support.type.other.cpp
- (#match? @support.type.other.cpp "_t$"))
+((identifier) @support.other.storage.type.cpp
+ (#match? @support.other.storage.type.cpp "_t$"))
+; These refer to language constructs and remain in the `storage` namespace.
[
"enum"
- "long"
- "short"
- "signed"
"struct"
"typedef"
"union"
- "unsigned"
-
"template"
] @storage.type.cpp
+; These refer to value types and go under `support`.
+[
+ "long"
+ "short"
+] @support.storage.type.builtin.cpp
+
+; These act as modifiers to value types and also go under `support`.
+[
+ "signed"
+ "unsigned"
+] @support.storage.modifier.builtin.cpp
+
+; These act as general language modifiers and remain in the `storage`
+; namespace.
[
"const"
"extern"
@@ -110,15 +124,15 @@
"override"
"final"
"noexcept"
-] @storage.modifier.cpp
+
+ "typename"
+] @storage.modifier._TYPE_.cpp
(
- (primitive_type) @support.type.stdint.cpp
- (#match? @support.type.stdint.cpp "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$")
+ (primitive_type) @support.storage.type.stdint.cpp
+ (#match? @support.storage.type.stdint.cpp "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$")
)
-"typename" @storage.modifier.typename.cpp
-
; FUNCTIONS
; =========
@@ -207,36 +221,56 @@
; Declarations and assignments
; ----------------------------
-; The "x" in `int x`;
+; The "x" in `int x;`
(declaration
declarator: (identifier) @variable.declaration.cpp)
-; The "x" in `int x = y`;
+; The "x" in `int x = y;`
(init_declarator
declarator: (identifier) @variable.declaration.cpp)
+; The "x" in `SomeType *x;`
+; (Should work no matter how many pointers deep we are.)
+(pointer_declarator
+ declarator: [(identifier) (field_identifier)] @variable.declaration.pointer.c
+ (#is? test.descendantOfType "declaration field_declaration"))
+
+; A member of a struct.
(field_declaration
- (field_identifier) @variable.declaration.cpp)
+ (field_identifier) @variable.declaration.member.cpp)
+
+; An attribute in a C99 struct designated initializer:
+; the "foo" in `MY_TYPE a = { .foo = true };
+(initializer_pair
+ (field_designator
+ (field_identifier) @variable.declaration.member.cpp))
+
+; (and the associated ".")
+(initializer_pair
+ (field_designator
+ "." @keyword.operator.accessor.cpp))
(field_declaration
(pointer_declarator
- (field_identifier) @variable.declaration.cpp))
+ (field_identifier) @variable.declaration.member.cpp))
(field_declaration
(array_declarator
- (field_identifier) @variable.declaration.cpp))
+ (field_identifier) @variable.declaration.member.cpp))
(init_declarator
(pointer_declarator
- (identifier) @variable.declaration.cpp))
+ (identifier) @variable.declaration.member.cpp))
+; The "x" in `x = y;`
(assignment_expression
left: (identifier) @variable.other.assignment.cpp)
-; The "foo" in `bar.foo = "baz"`.
+; The "foo" in `something->foo = "bar";`
(assignment_expression
left: (field_expression
- field: (field_identifier) @variable.other.member.assignment.cpp))
+ field: (field_identifier) @variable.other.member.assignment.cpp)
+ (#set! capture.final))
((reference_declarator
(identifier) @variable.declaration.cpp)
@@ -248,18 +282,20 @@
(preproc_params
(identifier) @variable.parameter.preprocessor.cpp)
+; The "foo" in `const char foo` within a parameter list.
(parameter_declaration
declarator: (identifier) @variable.parameter.cpp)
-(parameter_declaration
- declarator: (pointer_declarator
- declarator: (identifier) @variable.parameter.cpp))
+; The "foo" in `const char *foo` within a parameter list.
+; (Should work no matter how many pointers deep we are.)
+(pointer_declarator
+ declarator: [(identifier) (field_identifier)] @variable.parameter.pointer.c
+ (#is? test.descendantOfType "parameter_declaration"))
(parameter_declaration
declarator: (reference_declarator
(identifier) @variable.parameter.cpp))
-
; The "foo" in `const char foo[]` within a parameter list.
(parameter_declaration
declarator: (array_declarator
@@ -420,8 +456,10 @@
";" @punctuation.terminator.statement.cpp
-"," @punctuation.separator.comma.cpp
-"->" @keyword.operator.accessor.cpp
+("," @punctuation.separator.comma.cpp
+ (#set! capture.shy))
+("->" @keyword.operator.accessor.pointer-access.cpp
+ (#set! capture.shy))
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.cpp
@@ -446,6 +484,22 @@
"[" @punctuation.definition.array.begin.bracket.square.cpp
"]" @punctuation.definition.array.end.bracket.square.cpp
+; META
+; ====
+
+((compound_statement) @meta.block.cpp
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
+((enumerator_list) @meta.block.enum.cpp
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
+((field_declaration_list) @meta.block.field.cpp
+ (#set! adjust.startAt firstChild.endPosition)
+ (#set! adjust.endAt lastChild.startPosition))
+
+
; TODO:
;
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
diff --git a/packages/language-c/grammars/tree-sitter-cpp/tree-sitter-cpp.wasm b/packages/language-c/grammars/tree-sitter-cpp/tree-sitter-cpp.wasm
index ae93e20ae..002712b10 100755
Binary files a/packages/language-c/grammars/tree-sitter-cpp/tree-sitter-cpp.wasm and b/packages/language-c/grammars/tree-sitter-cpp/tree-sitter-cpp.wasm differ
diff --git a/packages/language-c/lib/main.js b/packages/language-c/lib/main.js
index 6e432cd64..1ae428d1d 100644
--- a/packages/language-c/lib/main.js
+++ b/packages/language-c/lib/main.js
@@ -12,28 +12,19 @@ exports.activate = function () {
}
});
}
-
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint(`source.${language}`, {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- for (let type of ['string_literal', 'comment']) {
- atom.grammars.addInjectionPoint(`source.${language}`, {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
+ }
+};
+
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ for (const language of ['c', 'cpp']) {
+ hyperlink.addInjectionPoint(`source.${language}`, {
+ types: ['comment', 'string_literal']
+ });
+ }
+};
+
+exports.consumeTodoInjection = (todo) => {
+ for (const language of ['c', 'cpp']) {
+ todo.addInjectionPoint(`source.${language}`, { types: ['comment'] });
}
};
diff --git a/packages/language-c/package.json b/packages/language-c/package.json
index 2b891a171..845d28471 100644
--- a/packages/language-c/package.json
+++ b/packages/language-c/package.json
@@ -15,5 +15,17 @@
"dependencies": {
"tree-sitter-c": "0.20.2",
"tree-sitter-cpp": "0.20.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-css/grammars/modern-tree-sitter-css.cson b/packages/language-css/grammars/modern-tree-sitter-css.cson
index 87069bcd3..2c0f5ff50 100644
--- a/packages/language-css/grammars/modern-tree-sitter-css.cson
+++ b/packages/language-css/grammars/modern-tree-sitter-css.cson
@@ -8,6 +8,7 @@ fileTypes: [
]
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-css#98c7b3dceb24f1ee17f1322f3947e55638251c37'
grammar: 'tree-sitter/tree-sitter-css.wasm'
highlightsQuery: 'tree-sitter/queries/highlights.scm'
foldsQuery: 'tree-sitter/queries/folds.scm'
diff --git a/packages/language-css/grammars/tree-sitter/queries/highlights.scm b/packages/language-css/grammars/tree-sitter/queries/highlights.scm
index 685e46243..4caea0895 100644
--- a/packages/language-css/grammars/tree-sitter/queries/highlights.scm
+++ b/packages/language-css/grammars/tree-sitter/queries/highlights.scm
@@ -1,36 +1,36 @@
-; WORKAROUND:
+
+; NOTE: `tree-sitter-css` recovers poorly from invalidity inside a block when
+; you're adding a new property-value pair above others in a list. When the user
+; is typing and the file is temporarily invalid, it will make incorrect guesses
+; about tokens that occur between the cursor and the end of the block.
;
-; When you're typing a new property name inside of a list, tree-sitter-css will
-; assume the thing you're typing is a descendant selector tag name until you
-; get to the colon. This prevents it from highlighting the incomplete line like
-; a selector tag name.
+; The fix here is for `tree-sitter-css` to get better at recovering from its
+; parsing error, but parser authors don't currently have much control over
+; that. In the meantime, this query is a decent mitigation: it colors the
+; affected tokens like plain text instead of assuming (nearly always
+; incorrectly) them to be tag names.
+;
+; Ideally, this is temporary, and we can remove it soon. Until then, it makes
+; syntax highlighting less obnoxious.
-(descendant_selector
- (tag_name) @_IGNORE_
- (#set! capture.final true))
+((tag_name) @_IGNORE_
+ (#is? test.descendantOfType "ERROR")
+ (#set! capture.final))
(ERROR
(attribute_name) @_IGNORE_
- (#set! capture.final true))
+ (#set! capture.final))
((ERROR
(attribute_name) @invalid.illegal)
- (#set! capture.final true))
+ (#set! capture.final))
; WORKAROUND:
;
-; `:hover` and other pseudo-classes don't highlight correctly inside a media
-; query (https://github.com/tree-sitter/tree-sitter-css/issues/28)
-(
- (ERROR) @entity.other.attribute-name.pseudo-class.css
- (#match? @entity.other.attribute-name.pseudo-class.css "^:[\\w-]+$")
-)
-
-; WORKAROUND:
-;
-; In `::after`, the "after" has a node type of `tag_name`. We want to catch it
-; here so that it doesn't get scoped like an HTML tag name in a selector.
+; In `::after`, the "after" has a node type of `tag_name`. Unclear whether this
+; is a bug or intended behavior. We want to catch it here so that it doesn't
+; get scoped like an HTML tag name in a selector.
; Scope the entire `::after` range as one unit.
((pseudo_element_selector)
@@ -61,9 +61,6 @@
; (selectors "," @punctuation.separator.list.comma.css)
-; The "div" in `div.foo {`.
-(tag_name) @entity.name.tag.css
-
; The "foo" in `div[attr=foo] {`.
(attribute_selector (plain_value) @string.unquoted.css)
@@ -77,10 +74,30 @@
(id_selector
"#" @punctuation.definition.entity.id.css) @entity.other.attribute-name.id.css
-; KNOWN ISSUE: Namespace selectors like `svg|link` are not supported. See:
-; https://github.com/tree-sitter/tree-sitter-css/issues/33
+; Declaration of a namespace:
+; The "svg" in `@namespace svg url(http://www.w3.org/2000/svg);`
+(namespace_name) @entity.other.namespace-prefix.css
-;(namespace_name) @entity.other.namespace-prefix.css
+; A namespaced tag name:
+; The "svg" in `svg|a {}`.
+(namespace_selector
+ . (tag_name) @entity.other.namespace-prefix.css
+ "|" @punctuation.separator.namespace.css
+ (#set! capture.final))
+
+; Not sure if this is intended, but a namespaced attribute in an attribute
+; selector is construed as two tag-name children of the `attribute_name`.
+; The "xl" in `[xl|href] {}`.
+(attribute_name
+ . (tag_name) @entity.other.namespace-prefix.css
+ "|" @punctuation.separator.namespace.css
+ (tag_name) @entity.other.attribute_name.css
+ (#set! capture.final)) @_IGNORE_
+
+; The "div" in `div.foo {`.
+(tag_name) @entity.name.tag.css
+; The "*" in `*[foo="bar"]`.
+(universal_selector) @entity.name.tag.universal.css
; The '.' in `.foo`.
(class_selector
@@ -101,29 +118,41 @@
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition))
+; Punctuation around the arguments of a pseudo-class or a function.
(arguments
"(" @punctuation.definition.arguments.begin.bracket.round.css
")" @punctuation.definition.arguments.end.bracket.round.css)
+; Punctuation around an attribute selector.
(attribute_selector
"[" @punctuation.definition.entity.begin.bracket.square.css
(attribute_name) @entity.other.attribute-name.css
"]" @punctuation.definition.entity.end.bracket.square.css)
+; Operators inside attribute selectors.
(attribute_selector
["=" "^=" "$=" "~=" "|="] @keyword.operator.pattern.css)
-; The `foo` in `@keyframes foo {`.
+; The "foo" in `@keyframes foo {`.
(keyframes_name) @entity.name.keyframes.css
; VARIABLES
; =========
+; Variable declaration:
+; The "--link-visited" in `--link-visited: #039;`.
(declaration
(property_name) @variable.other.assignment.css
(#match? @variable.other.assignment.css "^--" )
(#set! capture.final true))
+; Variable usage:
+; The ""--link--visited" in `color: var(--link-visited);`.
+((function_name) @support.function.var.css
+ (arguments (plain_value) @variable.css)
+ (#eq? @support.function.var.css "var"))
+
+
; PROPERTIES
; ==========
@@ -147,9 +176,9 @@
(#match? @string.quoted.single.css "^'")
(#match? @string.quoted.single.css "'$"))
+; The punctuation around quoted strings.
((string_value) @punctuation.definition.string.begin.css
(#set! adjust.startAndEndAroundFirstMatchOf "^[\"']"))
-
((string_value) @punctuation.definition.string.end.css
(#set! adjust.startAndEndAroundFirstMatchOf "[\"']$"))
@@ -208,10 +237,6 @@
; (#eq? @support.function.var.css "var")
; )
-((function_name) @support.function.var.css
- (arguments (plain_value) @variable.css)
- (#eq? @support.function.var.css "var"))
-
((function_name) @support.function._TEXT_.css
; Because we just handled it above.
(#not-eq? @support.function._TEXT_.css "var"))
diff --git a/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm b/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm
index f6293088e..711c7732f 100755
Binary files a/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm and b/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm differ
diff --git a/packages/language-css/lib/main.js b/packages/language-css/lib/main.js
index c12fb4b56..cf2d7837c 100644
--- a/packages/language-css/lib/main.js
+++ b/packages/language-css/lib/main.js
@@ -1,43 +1,26 @@
-exports.activate = () => {
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint('source.css', {
- type: 'comment',
- language(node) {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.css', {
+ types: ['comment', 'string_value']
});
- for (let type of ['comment', 'string_value']) {
- atom.grammars.addInjectionPoint('source.css', {
- type,
- language(node) {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
-
// Catch things like
//
// @import url(https://www.example.com/style.css);
//
// where the URL is unquoted.
- atom.grammars.addInjectionPoint('source.css', {
- type: 'call_expression',
+ hyperlink.addInjectionPoint('source.css', {
+ types: ['call_expression'],
language: () => 'hyperlink',
- content: (node) => {
+ content(node) {
let functionName = node.descendantsOfType('function_value')[0]?.text;
if (!functionName === 'url') { return null; }
return node.descendantsOfType('plain_value');
- },
- languageScope: null
+ }
});
-
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.css', { types: ['comment'] });
};
diff --git a/packages/language-css/package.json b/packages/language-css/package.json
index 96c2d0d7a..7e2d03d1b 100644
--- a/packages/language-css/package.json
+++ b/packages/language-css/package.json
@@ -14,5 +14,17 @@
"license": "MIT",
"dependencies": {
"tree-sitter-css": "^0.19.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-gfm/grammars/tree-sitter/tree-sitter-markdown/highlights.scm b/packages/language-gfm/grammars/tree-sitter/tree-sitter-markdown/highlights.scm
index 220a409c0..42e12ebe6 100644
--- a/packages/language-gfm/grammars/tree-sitter/tree-sitter-markdown/highlights.scm
+++ b/packages/language-gfm/grammars/tree-sitter/tree-sitter-markdown/highlights.scm
@@ -39,7 +39,7 @@
(paragraph) @markup.paragraph.gfm
-(thematic_break) @punctuation.definition.horizontal-rule.gfm
+(thematic_break) @markup.horizontal-rule.gfm
(block_quote) @markup.quote.blockquote.gfm
((block_quote) @punctuation.definition.blockquote.gfm
@@ -140,8 +140,9 @@
(code_span) @meta.embedded.line.inline-code.gfm @markup.raw.inline.gfm
(info_string) @storage.modifier.language._TEXT_.gfm
-(fenced_code_block) @markup.code.fenced.gfm @meta.embedded.block.fenced-code.gfm
-(indented_code_block) @markup.code.indented.gfm @meta.embedded.block.indented-code.gfm
+(fenced_code_block
+ (code_fence_content) @markup.raw.block.fenced.gfm) @meta.embedded.block.fenced-code.gfm
+(indented_code_block) @markup.raw.block.indented.gfm @meta.embedded.block.indented-code.gfm
; BOLD/ITALIC/OTHER
diff --git a/packages/language-gfm/lib/main.js b/packages/language-gfm/lib/main.js
index f416bb04a..d4c62e1b4 100644
--- a/packages/language-gfm/lib/main.js
+++ b/packages/language-gfm/lib/main.js
@@ -88,3 +88,40 @@ exports.activate = () => {
includeChildren: true
});
};
+
+
+// Since this parser isn't guaranteed to detect all URLs in paragraphs (see
+// https://github.com/pulsar-edit/pulsar/issues/885), we'll inject the
+// `hyperlink` parser into `text` nodes in paragraphs when there appear to be
+// URLs in them.
+exports.consumeHyperlinkInjection = (hyperlink) => {
+
+ function textChildren(node) {
+ let results = [];
+ for (let i = 0; i < node.namedChildCount; i++) {
+ let child = node.child(i);
+ if (child.type === 'text') {
+ results.push(child);
+ }
+ }
+ return results;
+ }
+
+ hyperlink.addInjectionPoint('source.gfm.embedded', {
+ types: ['paragraph'],
+ // Override the language callback so that it doesn't test URLs that are
+ // already handled in `uri_autolink` nodes.
+ language(node) {
+ for (let child of textChildren(node)) {
+ if (hyperlink.test(child)) {
+ return 'hyperlink';
+ }
+ }
+ return null;
+ },
+ content(node) {
+ return textChildren(node);
+ }
+ });
+
+};
diff --git a/packages/language-gfm/package.json b/packages/language-gfm/package.json
index 54610b63b..f3d297ea7 100644
--- a/packages/language-gfm/package.json
+++ b/packages/language-gfm/package.json
@@ -10,5 +10,12 @@
},
"devDependencies": {
"coffeescript": "^1.7.0"
- }
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ }
+ }
}
diff --git a/packages/language-go/grammars/modern-tree-sitter-go.cson b/packages/language-go/grammars/modern-tree-sitter-go.cson
index 3f9166c42..5a0eab7e4 100644
--- a/packages/language-go/grammars/modern-tree-sitter-go.cson
+++ b/packages/language-go/grammars/modern-tree-sitter-go.cson
@@ -11,6 +11,7 @@ comments:
start: '// '
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-go#ff86c7f1734873c8c4874ca4dd95603695686d7a'
grammar: 'tree-sitter-go/tree-sitter-go.wasm'
highlightsQuery: 'tree-sitter-go/highlights.scm'
foldsQuery: 'tree-sitter-go/folds.scm'
diff --git a/packages/language-go/grammars/tree-sitter-go/highlights.scm b/packages/language-go/grammars/tree-sitter-go/highlights.scm
index b3bc38a65..330d4455a 100644
--- a/packages/language-go/grammars/tree-sitter-go/highlights.scm
+++ b/packages/language-go/grammars/tree-sitter-go/highlights.scm
@@ -47,6 +47,8 @@
[
"struct"
+ "interface"
+ "map"
] @storage.type._TYPE_.go
(struct_type
@@ -55,13 +57,14 @@
(field_identifier) @entity.other.attribute-name.go)))
(keyed_element
- (field_identifier) @entity.other.attribute-name.go
- .
- ":" @punctuation.separator.key-value.go)
+ . (literal_element) @entity.other.attribute-name.go)
+
+(keyed_element ":" @punctuation.separator.key-value.go)
[
"break"
"case"
+ "chan"
"continue"
"default"
"defer"
@@ -78,7 +81,10 @@
] @keyword.control._TYPE_.go
+; Function names: the "foo" in `func foo() {`
(function_declaration (identifier) @entity.name.function.go)
+; Method names: the "Foo" in `func (x Bar) Foo {`
+(method_declaration (field_identifier) @entity.name.function.method.go)
(call_expression
(identifier) @support.function.builtin.go
@@ -252,7 +258,8 @@
";" @punctuation.terminator.go
"," @punctuation.separator.comma.go
-":" @punctuation.separator.colon.go
+(":" @punctuation.separator.colon.go
+ (#set! capture.shy))
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.go
diff --git a/packages/language-go/grammars/tree-sitter-go/tree-sitter-go.wasm b/packages/language-go/grammars/tree-sitter-go/tree-sitter-go.wasm
index a748e9e1d..1d17adba7 100755
Binary files a/packages/language-go/grammars/tree-sitter-go/tree-sitter-go.wasm and b/packages/language-go/grammars/tree-sitter-go/tree-sitter-go.wasm differ
diff --git a/packages/language-go/lib/main.js b/packages/language-go/lib/main.js
index a74d32bef..50a8e11ff 100644
--- a/packages/language-go/lib/main.js
+++ b/packages/language-go/lib/main.js
@@ -1,25 +1,10 @@
-exports.activate = () => {
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
- for (let type of ['comment', 'interpreted_string_literal', 'raw_string_literal']) {
- atom.grammars.addInjectionPoint('source.go', {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
-
- atom.grammars.addInjectionPoint('source.go', {
- type: 'comment',
- language(node) {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.go', {
+ types: ['comment', 'interpreted_string_literal', 'raw_string_literal']
});
-
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.go', { types: ['comment'] });
};
diff --git a/packages/language-go/package.json b/packages/language-go/package.json
index 91bb8d0e0..01a1355f6 100644
--- a/packages/language-go/package.json
+++ b/packages/language-go/package.json
@@ -14,5 +14,17 @@
"repository": "https://github.com/pulsar-edit/pulsar",
"dependencies": {
"tree-sitter-go": "0.19.1"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-html/grammars/modern-tree-sitter-ejs.cson b/packages/language-html/grammars/modern-tree-sitter-ejs.cson
index f3b7bf157..4b54d8bdd 100644
--- a/packages/language-html/grammars/modern-tree-sitter-ejs.cson
+++ b/packages/language-html/grammars/modern-tree-sitter-ejs.cson
@@ -11,6 +11,7 @@ fileTypes: [
injectionRegex: '^(ejs|EJS)$'
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205'
grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm'
highlightsQuery: 'tree-sitter-embedded-template/ejs/highlights.scm'
foldsQuery: 'tree-sitter-embedded-template/ejs/folds.scm'
diff --git a/packages/language-html/grammars/modern-tree-sitter-erb.cson b/packages/language-html/grammars/modern-tree-sitter-erb.cson
index da4750fd9..1e7d28e6e 100644
--- a/packages/language-html/grammars/modern-tree-sitter-erb.cson
+++ b/packages/language-html/grammars/modern-tree-sitter-erb.cson
@@ -11,6 +11,7 @@ fileTypes: [
injectionRegex: '^(erb|ERB)$'
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205'
grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm'
highlightsQuery: 'tree-sitter-embedded-template/erb/highlights.scm'
foldsQuery: 'tree-sitter-embedded-template/erb/folds.scm'
diff --git a/packages/language-html/grammars/modern-tree-sitter-html.cson b/packages/language-html/grammars/modern-tree-sitter-html.cson
index 5cdc42ba0..fa5fcc41c 100644
--- a/packages/language-html/grammars/modern-tree-sitter-html.cson
+++ b/packages/language-html/grammars/modern-tree-sitter-html.cson
@@ -6,6 +6,7 @@ parser: 'tree-sitter-html'
injectionRegex: '(HTML|html|Html)$'
treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-html#d742025fa2d8e6100f134a6ea990443aa1f074b3'
grammar: 'tree-sitter-html/tree-sitter-html.wasm'
highlightsQuery: 'tree-sitter-html/highlights.scm'
foldsQuery: 'tree-sitter-html/folds.scm'
diff --git a/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm b/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm
index 756f894af..eec102a46 100755
Binary files a/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm and b/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm differ
diff --git a/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm b/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm
index f71b6baa8..2d2835ca4 100755
Binary files a/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm and b/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm differ
diff --git a/packages/language-html/lib/main.js b/packages/language-html/lib/main.js
index 55cce8174..c0c230c9f 100644
--- a/packages/language-html/lib/main.js
+++ b/packages/language-html/lib/main.js
@@ -1,4 +1,4 @@
-exports.activate = function() {
+exports.activate = function () {
atom.grammars.addInjectionPoint('text.html.basic', {
type: 'script_element',
language() {
@@ -19,38 +19,6 @@ exports.activate = function() {
}
});
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint('text.html.basic', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- atom.grammars.addInjectionPoint('text.html.basic', {
- type: 'comment',
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- atom.grammars.addInjectionPoint('text.html.basic', {
- type: 'attribute_value',
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- // TODO: Inject hyperlink grammar into plain text?
-
// EMBEDDED
atom.grammars.addInjectionPoint('text.html.ejs', {
@@ -95,3 +63,14 @@ exports.activate = function() {
}
});
};
+
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ // TODO: Inject hyperlink grammar into plain text?
+ hyperlink.addInjectionPoint('text.html.basic', {
+ types: ['comment', 'attribute_value']
+ });
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('text.html.basic', { types: ['comment'] });
+};
diff --git a/packages/language-html/package.json b/packages/language-html/package.json
index e2fd7b41b..721353260 100644
--- a/packages/language-html/package.json
+++ b/packages/language-html/package.json
@@ -19,5 +19,17 @@
},
"devDependencies": {
"dedent": "^0.7.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-html/spec/.eslintrc.js b/packages/language-html/spec/.eslintrc.js
new file mode 100644
index 000000000..5226d6921
--- /dev/null
+++ b/packages/language-html/spec/.eslintrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+ env: { jasmine: true },
+ rules: {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+};
diff --git a/packages/language-hyperlink/lib/main.js b/packages/language-hyperlink/lib/main.js
new file mode 100644
index 000000000..16223af3a
--- /dev/null
+++ b/packages/language-hyperlink/lib/main.js
@@ -0,0 +1,58 @@
+
+const HYPERLINK_PATTERN = /\bhttps?:/
+
+module.exports = {
+ provideHyperlinkInjection() {
+ return {
+ // Private: Test whether a Tree-sitter node's text contains any tokens
+ // that would benefit from a hyperlink injection.
+ //
+ // Useful if you want to call {GrammarRegistry::addInjectionPoint}
+ // yourself and want to use this logic in a `language` callback.
+ //
+ // * `node` A Tree-sitter tree node.
+ test(node) {
+ return HYPERLINK_PATTERN.test(node.text);
+ },
+
+ // Private: specify one or more types of syntax nodes for a given grammar
+ // that may embed the hyperlink grammar.
+ //
+ // * `scopeName` The {String} ID of the parent language.
+ // * `options` An {Object} with the following keys:
+ // * `types` An {Array} or {String} indicating the type or types of
+ // Tree-sitter tree nodes that may receive injections.
+ // * `language` (optional) A {Function} that may be called to add extra
+ // logic for determining which language should be used in an
+ // injection. If present, will be called before the default logic.
+ // If it returns `undefined`, the default logic will apply. If it
+ // returns a {String} or `null`, the default logic will be preempted.
+ // * `content` (optional) A {Function} that will be used to determine
+ // which of the injection node's children, if any, will be injected
+ // into. The default `content` callback is one that returns the
+ // original node.
+ addInjectionPoint(scopeName, options) {
+ let types = options.types;
+ if (!Array.isArray(types)) types = [types];
+
+ for (let type of types) {
+ atom.grammars.addInjectionPoint(scopeName, {
+ type,
+ language(node) {
+ if (options.language) {
+ let result = options.language(node);
+ if (result !== undefined) return result;
+ }
+ return HYPERLINK_PATTERN.test(node.text) ?
+ 'hyperlink' : undefined;
+ },
+ content(node) {
+ return options.content ? options.content(node) : node;
+ },
+ languageScope: null
+ });
+ }
+ },
+ }
+ }
+};
diff --git a/packages/language-hyperlink/package.json b/packages/language-hyperlink/package.json
index 676c2425f..9d1df06b3 100644
--- a/packages/language-hyperlink/package.json
+++ b/packages/language-hyperlink/package.json
@@ -1,11 +1,19 @@
{
"name": "language-hyperlink",
"version": "0.17.1",
+ "main": "lib/main",
"description": "Hyperlink colorization in Atom",
"engines": {
"atom": "*",
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
- "license": "MIT"
+ "license": "MIT",
+ "providedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "provideHyperlinkInjection"
+ }
+ }
+ }
}
diff --git a/packages/language-hyperlink/spec/.eslintrc.js b/packages/language-hyperlink/spec/.eslintrc.js
new file mode 100644
index 000000000..77888e061
--- /dev/null
+++ b/packages/language-hyperlink/spec/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+ env: { jasmine: true },
+ globals: {
+ waitsForPromise: true,
+ advanceClock: true
+ },
+ rules: {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+};
diff --git a/packages/language-java/grammars/tree-sitter-java/highlights.scm b/packages/language-java/grammars/tree-sitter-java/highlights.scm
index d7644e1c3..86a06a56a 100644
--- a/packages/language-java/grammars/tree-sitter-java/highlights.scm
+++ b/packages/language-java/grammars/tree-sitter-java/highlights.scm
@@ -217,9 +217,13 @@
(identifier) @variable.parameter.lambda.java))
(variable_declarator
- name: (identifier) @variable.other.assignment.java)
+ name: (identifier) @variable.other.declaration.java)
+(assignment_expression
+ left: (identifier) @variable.other.assignment.java)
+(update_expression
+ (identifier) @variable.other.assignment.java)
; PACKAGES
; ========
@@ -359,6 +363,8 @@
(binary_expression
["&" "|" "^" "~" "<<" ">>" ">>>"] @keyword.operator.bitwise.java)
+["++" "--"] @keyword.operator.increment.java
+
"." @keyword.operator.accessor.dot.java
"::" @keyword.operator.accessor.method-reference.java
diff --git a/packages/language-java/lib/main.js b/packages/language-java/lib/main.js
index a1e953a7a..59f36c832 100644
--- a/packages/language-java/lib/main.js
+++ b/packages/language-java/lib/main.js
@@ -1,24 +1,10 @@
-exports.activate = () => {
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
- atom.grammars.addInjectionPoint('source.java', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.java', {
+ types: ['comment', 'string_literal']
});
-
- for (let type of ['string_literal', 'comment']) {
- atom.grammars.addInjectionPoint('source.java', {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.java', { types: ['comment'] });
};
diff --git a/packages/language-java/package.json b/packages/language-java/package.json
index f330983e5..11667e670 100644
--- a/packages/language-java/package.json
+++ b/packages/language-java/package.json
@@ -11,5 +11,17 @@
"license": "MIT",
"dependencies": {
"tree-sitter-java": "0.19.1"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-javascript/grammars/tree-sitter-2-javascript.cson b/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson
similarity index 51%
rename from packages/language-javascript/grammars/tree-sitter-2-javascript.cson
rename to packages/language-javascript/grammars/modern-tree-sitter-javascript.cson
index 41430d861..b2fc9fc6a 100644
--- a/packages/language-javascript/grammars/tree-sitter-2-javascript.cson
+++ b/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson
@@ -4,13 +4,15 @@ type: 'modern-tree-sitter'
parser: 'tree-sitter-javascript'
injectionRegex: '^(js|javascript|JS|JAVASCRIPT)$'
+
treeSitter:
- grammar: 'ts/grammar.wasm'
- highlightsQuery: 'ts/highlights.scm'
- localsQuery: 'ts/locals.scm'
- foldsQuery: 'ts/folds.scm'
- indentsQuery: 'ts/indents.scm'
- tagsQuery: 'ts/tags.scm'
+ parserSource: 'github:tree-sitter/tree-sitter-javascript#f1e5a09b8d02f8209a68249c93f0ad647b228e6e'
+ grammar: 'tree-sitter/tree-sitter-javascript.wasm'
+ highlightsQuery: 'tree-sitter/highlights.scm'
+ localsQuery: 'tree-sitter/locals.scm'
+ foldsQuery: 'tree-sitter/folds.scm'
+ indentsQuery: 'tree-sitter/indents.scm'
+ tagsQuery: 'tree-sitter/tags.scm'
firstLineRegex: [
# shebang line
diff --git a/packages/language-javascript/grammars/modern-tree-sitter-jsdoc.cson b/packages/language-javascript/grammars/modern-tree-sitter-jsdoc.cson
new file mode 100644
index 000000000..ab7df419f
--- /dev/null
+++ b/packages/language-javascript/grammars/modern-tree-sitter-jsdoc.cson
@@ -0,0 +1,13 @@
+name: 'JSDoc'
+scopeName: 'source.jsdoc'
+type: 'modern-tree-sitter'
+parser: 'tree-sitter-jsdoc'
+
+injectionRegex: '^jsdoc$'
+
+treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-jsdoc#a5e363a98676136d9f5884cb558086e5f1fc32b6'
+ grammar: 'tree-sitter/jsdoc/tree-sitter-jsdoc.wasm'
+ highlightsQuery: 'tree-sitter/jsdoc/highlights.scm'
+ foldsQuery: 'tree-sitter/jsdoc/folds.scm'
+ indentsQuery: 'tree-sitter/jsdoc/indents.scm'
diff --git a/packages/language-javascript/grammars/modern-tree-sitter-regex.cson b/packages/language-javascript/grammars/modern-tree-sitter-regex.cson
new file mode 100644
index 000000000..f79ba9352
--- /dev/null
+++ b/packages/language-javascript/grammars/modern-tree-sitter-regex.cson
@@ -0,0 +1,11 @@
+name: 'JavaScript RegExp'
+scopeName: 'source.js.regexp'
+type: 'modern-tree-sitter'
+parser: 'tree-sitter-regex'
+
+injectionRegex: '^(js-regex)$'
+
+treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-regex#2354482d7e2e8f8ff33c1ef6c8aa5690410fbc96'
+ grammar: 'tree-sitter/regex/tree-sitter-regex.wasm'
+ highlightsQuery: 'tree-sitter/regex/highlights.scm'
diff --git a/packages/language-javascript/grammars/tree-sitter-2-jsdoc.cson b/packages/language-javascript/grammars/tree-sitter-2-jsdoc.cson
deleted file mode 100644
index cd1a6be5b..000000000
--- a/packages/language-javascript/grammars/tree-sitter-2-jsdoc.cson
+++ /dev/null
@@ -1,12 +0,0 @@
-name: 'JSDoc'
-scopeName: 'source.jsdoc'
-type: 'modern-tree-sitter'
-parser: 'tree-sitter-jsdoc'
-
-injectionRegex: '^jsdoc$'
-
-treeSitter:
- grammar: 'ts/jsdoc/tree-sitter-jsdoc.wasm'
- highlightsQuery: 'ts/jsdoc/highlights.scm'
- foldsQuery: 'ts/jsdoc/folds.scm'
- indentsQuery: 'ts/jsdoc/indents.scm'
diff --git a/packages/language-javascript/grammars/tree-sitter-2-regex.cson b/packages/language-javascript/grammars/tree-sitter-2-regex.cson
deleted file mode 100644
index 0ac0d535d..000000000
--- a/packages/language-javascript/grammars/tree-sitter-2-regex.cson
+++ /dev/null
@@ -1,9 +0,0 @@
-name: 'JavaScript RegExp'
-scopeName: 'source.js.regexp'
-type: 'modern-tree-sitter'
-parser: 'tree-sitter-regex'
-
-injectionRegex: '^(js-regex)$'
-treeSitter:
- grammar: 'ts/regex/tree-sitter-regex.wasm'
- highlightsQuery: 'ts/regex/highlights.scm'
diff --git a/packages/language-javascript/grammars/ts/folds.scm b/packages/language-javascript/grammars/tree-sitter/folds.scm
similarity index 96%
rename from packages/language-javascript/grammars/ts/folds.scm
rename to packages/language-javascript/grammars/tree-sitter/folds.scm
index 0d5f95ae1..5035311ca 100644
--- a/packages/language-javascript/grammars/ts/folds.scm
+++ b/packages/language-javascript/grammars/tree-sitter/folds.scm
@@ -80,4 +80,4 @@
((jsx_self_closing_element) @fold
; Exclude both the slash and angle bracket `/>` from the fold.
- (#set! fold.endAt lastChild.previousSibling.startPosition))
+ (#set! fold.endAt lastChild.startPosition))
diff --git a/packages/language-javascript/grammars/ts/highlights.scm b/packages/language-javascript/grammars/tree-sitter/highlights.scm
similarity index 94%
rename from packages/language-javascript/grammars/ts/highlights.scm
rename to packages/language-javascript/grammars/tree-sitter/highlights.scm
index cd067c58d..16d8dd99d 100644
--- a/packages/language-javascript/grammars/ts/highlights.scm
+++ b/packages/language-javascript/grammars/tree-sitter/highlights.scm
@@ -71,17 +71,26 @@
(assignment_expression
left: (identifier) @variable.other.assignment.js)
-; The "bar" in `foo.bar = true`
+; Mark all the properties whose right-hand sides are functions so that we can
+; exclude them from the next query.
(assignment_expression
left: (member_expression
- property: (property_identifier) @variable.other.assignment.property.js))
+ property: (property_identifier) @variable.other.assignment.property.js)
+ right: [(arrow_function) (function)] @_IGNORE_
+ (#set! isFunctionProperty true))
-; The "bar" in `foo.#bar = true`
+; The "bar" in `foo.bar = true`.
+(assignment_expression
+ left: (member_expression
+ property: (property_identifier) @variable.other.assignment.property.js)
+ (#is-not? test.rangeWithData isFunctionProperty)
+ (#set! capture.final))
+
+; The "bar" in `foo.#bar = true`.
(assignment_expression
left: (member_expression
property: (private_property_identifier) @variable.other.assignment.property.private.js))
-
; The "foo" in `foo += 1`.
(augmented_assignment_expression
left: (identifier) @variable.other.assignment.js)
@@ -90,6 +99,13 @@
(update_expression
argument: (identifier) @variable.other.assignment.js)
+; Public field definition in a class body:
+; The "foo" in `foo = "bar";`
+(field_definition
+ property: (property_identifier) @variable.other.assignment.property.public.js)
+
+; Private field definition in a class body:
+; The "#foo" in `#foo = "bar";`
(field_definition
property: (private_property_identifier) @variable.other.assignment.property.private.js)
@@ -139,18 +155,17 @@
(rest_pattern
(identifier) @variable.other.assignment.destructuring.rest.js))
-; A variable array destructuring:
-; The "foo" and "bar" in `let [foo, bar] = something`
-(variable_declarator
- (array_pattern
+; An array-destructured assignment or reassignment, regardless of depth:
+; The "foo" in `[foo] = bar;` and `[[foo]] = bar;`.
+(array_pattern
+ (identifier) @variable.other.assignment.destructuring.js)
+
+; An array-destructured assignment or reassignment with a default, regardless of depth:
+; The "baz" in `let [foo, bar, baz = false] = something;` and `let [[baz = 5]] = something`;
+(array_pattern
+ (assignment_pattern
(identifier) @variable.other.assignment.destructuring.js))
-; A variable array destructuring with a default:
-; The "baz" in `let [foo, bar, baz = false] = something`
-(variable_declarator
- (array_pattern
- (assignment_pattern
- (identifier) @variable.other.assignment.destructuring.js)))
; A variable declaration in a for…(in|of) loop:
; The "foo" in `for (let foo of bar) {`
@@ -250,6 +265,11 @@
(method_definition
name: (property_identifier) @entity.name.function.method.definition.js)
+; Private method definitions:
+; the "#foo" in `#foo () {` (inside a class body)
+(method_definition
+ name: (private_property_identifier) @entity.name.function.method.private.definition.js)
+
; Function property assignment:
; The "foo" in `thing.foo = (arg) => {}`
(assignment_expression
@@ -744,7 +764,7 @@
; The "Foo" in ``.
(jsx_self_closing_element
name: (identifier) @entity.name.tag.js
- ) @meta.tag.js
+ ) @meta.tag.jsx.js
; The "Foo" in ``.
(jsx_opening_element
@@ -752,8 +772,6 @@
; The "Foo" in ``.
(jsx_closing_element
- "/" @punctuation.definition.tag.end.js
- (#set! capture.final true)
name: (identifier) @entity.name.tag.js)
; The "bar" in ``.
@@ -769,23 +787,18 @@
(jsx_opening_element
"<" @punctuation.definition.tag.begin.js
- ">" @punctuation.definition.tag.end.js)
+ ">" @punctuation.definition.tag.end.js) @meta.tag.jsx.js
(jsx_closing_element
- "<" @punctuation.definition.tag.begin.js
- ">" @punctuation.definition.tag.end.js)
+ "" @punctuation.definition.tag.begin.js
+ ">" @punctuation.definition.tag.end.js) @meta.tag.jsx.js
(jsx_self_closing_element
"<" @punctuation.definition.tag.begin.js
(#set! capture.final true))
-((jsx_self_closing_element
- ; The "/>" in ``, extended to cover both anonymous nodes at once.
- "/") @punctuation.definition.tag.end.js
- (#set! adjust.startAt lastChild.previousSibling.startPosition)
- (#set! adjust.endAt lastChild.endPosition)
- (#set! capture.final true))
-
+(jsx_self_closing_element
+ "/>" @punctuation.definition.tag.end.js)
; OPERATORS
; ==========
@@ -812,7 +825,13 @@
(unary_expression ["+" "-"] @keyword.operator.unary.js)
-(ternary_expression ["?" ":"] @keyword.operator.ternary.js)
+(ternary_expression ["?" ":"] @keyword.operator.ternary.js
+ (#set! capture.final))
+
+; Try to highlight `?` like an operator while the user is typing without
+; waiting for its paired `:`.
+("?" @keyword.operator.ternary.js
+ (#is? test.descendantOfType "ERROR"))
[
"&&="
diff --git a/packages/language-javascript/grammars/ts/indents.scm b/packages/language-javascript/grammars/tree-sitter/indents.scm
similarity index 60%
rename from packages/language-javascript/grammars/ts/indents.scm
rename to packages/language-javascript/grammars/tree-sitter/indents.scm
index dfac5ef7e..b351b7bcf 100644
--- a/packages/language-javascript/grammars/ts/indents.scm
+++ b/packages/language-javascript/grammars/tree-sitter/indents.scm
@@ -1,7 +1,3 @@
-
-; ((template_string) @ignore
- ; (#is-not? test.OnStartingOrEndingRow true))
-
; STATEMENT BLOCKS
; ================
@@ -20,11 +16,19 @@
(#is? test.last true))
(#set! indent.matchIndentOf parent.startPosition))
-; 'case' and 'default' need to be indented one level more than their containing
-; `switch`. TODO: Might need to make this configurable.
+; By default, `case` and `default` need to be indented one level more than their containing
+; `switch`.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is-not? test.config "language-javascript.indentation.alignCaseWithSwitch"))
+
+; When this config setting is enabled, `case` and `default` need to be indented
+; to match their containing `switch`.
+(["case" "default"] @match
+ (#set! indent.matchIndentOf parent.parent.startPosition)
+ (#set! indent.offsetIndent 0)
+ (#is? test.config "language-javascript.indentation.alignCaseWithSwitch"))
; ONE-LINE CONDITIONALS
@@ -33,10 +37,12 @@
; An `if` statement without an opening brace should indent the next line…
(if_statement
condition: (parenthesized_expression ")" @indent
- (#is? test.lastTextOnRow true)))
+ (#is? test.lastTextOnRow true)
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf")))
; (as should a braceless `else`…)
("else" @indent
- (#is? test.lastTextOnRow true))
+ (#is? test.lastTextOnRow true)
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf"))
; …and keep that indent level if the user types a comment before the
; consequence…
@@ -44,7 +50,8 @@
consequence: (empty_statement) @match
(#is-not? test.startsOnSameRowAs parent.startPosition)
(#set! indent.matchIndentOf parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf"))
; …and keep that indent level after the user starts typing…
(if_statement
@@ -61,7 +68,8 @@
; of an `expression_statement`, for some reason.
(#not-match? @match "^\\s*{")
(#set! indent.matchIndentOf parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf"))
; …but dedent after exactly one statement.
(if_statement
@@ -76,7 +84,8 @@
] @dedent.next
; When an opening curly brace is unpaired, it might get interpreted as part
; of an `expression_statement`, for some reason.
- (#not-match? @dedent.next "^\\s*{"))
+ (#not-match? @dedent.next "^\\s*{")
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf"))
(else_clause
[
@@ -87,7 +96,8 @@
(throw_statement)
(debugger_statement)
] @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ (#is? test.config "language-javascript.indentation.indentAfterBracelessIf"))
; HANGING INDENT ON SPLIT LINES
@@ -97,14 +107,22 @@
; `config` scope test.
; Any of these at the end of a line indicate the next line should be indented…
-(["||" "&&" "?"] @indent
+(["||" "&&"] @indent
+ (#is? test.config "language-javascript.indentation.addHangingIndentAfterLogicalOperators")
+ (#is? test.lastTextOnRow true))
+
+("?" @indent
+ (#is? test.config "language-javascript.indentation.addHangingIndentAfterTernaryOperators")
(#is? test.lastTextOnRow true))
; …and the line after that should be dedented…
(binary_expression
["||" "&&"]
right: (_) @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
+ (#is? test.config "language-javascript.indentation.addHangingIndentAfterLogicalOperators")
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ ; …unless the right side of the expression spans multiple lines.
+ (#is? test.endsOnSameRowAs startPosition))
; …unless it's a ternary, in which case the dedent should wait until the
; alternative clause.
@@ -115,20 +133,11 @@
;
(ternary_expression
alternative: (_) @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
-
-
-; DEDENT-NEXT IN LIMITED SCENARIOS
-; ================================
-
-; Catches unusual hanging-indent scenarios when calling a method, such as:
-;
-; return this.veryLongMethodNameWithSeveralArgumentsThat(are, too,
-; short, forEach, toHave, itsOwn, line);
-;
-; (arguments ")" @dedent.next
-; (#is-not? test.startsOnSameRowAs parent.firstChild.startPosition)
-; (#is-not? test.firstTextOnRow true))
+ (#is? test.config "language-javascript.indentation.addHangingIndentAfterTernaryOperators")
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ ; Only dedent the next line if the alternative doesn't itself span multiple
+ ; lines.
+ (#is? test.endsOnSameRowAs startPosition))
; GENERAL
@@ -137,21 +146,27 @@
; Weed out `}`s that should not signal dedents.
(template_substitution "}" @_IGNORE_ (#set! capture.final true))
-[
- "{"
- "("
- "["
-] @indent
+; As strange as it may seem to make all of these basic indentation hints
+; configurable, some brace styles are incompatible with some of these choices;
+; see https://github.com/orgs/pulsar-edit/discussions/249.
+("{" @indent
+ (#is? test.config "language-javascript.indentation.indentBraces"))
+("}" @dedent
+ (#is? test.config "language-javascript.indentation.indentBraces"))
+
+("[" @indent
+ (#is? test.config "language-javascript.indentation.indentBrackets"))
+("]" @dedent
+ (#is? test.config "language-javascript.indentation.indentBrackets"))
+
+("(" @indent
+ (#is? test.config "language-javascript.indentation.indentParentheses"))
+(")" @dedent
+ (#is? test.config "language-javascript.indentation.indentParentheses"))
-[
- "}"
- ")"
- "]"
-] @dedent
["case" "default"] @indent
-
; JSX
; ===
@@ -186,7 +201,7 @@
; point, so the usual heuristic won't work. Instead we set `indent.force` and
; use `test.lastTextOnRow` to ensure that the dedent fires exactly once while
; typing.
-((jsx_self_closing_element ">" @dedent)
+((jsx_self_closing_element "/>" @dedent)
(#is-not? test.startsOnSameRowAs parent.firstChild.startPosition)
(#is? test.lastTextOnRow)
(#set! indent.force true))
diff --git a/packages/language-javascript/grammars/ts/jsdoc/folds.scm b/packages/language-javascript/grammars/tree-sitter/jsdoc/folds.scm
similarity index 100%
rename from packages/language-javascript/grammars/ts/jsdoc/folds.scm
rename to packages/language-javascript/grammars/tree-sitter/jsdoc/folds.scm
diff --git a/packages/language-javascript/grammars/ts/jsdoc/highlights.scm b/packages/language-javascript/grammars/tree-sitter/jsdoc/highlights.scm
similarity index 96%
rename from packages/language-javascript/grammars/ts/jsdoc/highlights.scm
rename to packages/language-javascript/grammars/tree-sitter/jsdoc/highlights.scm
index 10d05b3a0..29b84d02c 100644
--- a/packages/language-javascript/grammars/ts/jsdoc/highlights.scm
+++ b/packages/language-javascript/grammars/tree-sitter/jsdoc/highlights.scm
@@ -21,7 +21,7 @@
((inline_tag) @meta.inline-tag.jsdoc.js)
-(tag_name) @entity.name.tag.jsdoc.js
+(tag_name) @keyword.other.tag.jsdoc.js
((tag (type)) @storage.type.instance.jsdoc.js
; Join the type with its surrounding braces.
diff --git a/packages/language-javascript/grammars/ts/jsdoc/indents.scm b/packages/language-javascript/grammars/tree-sitter/jsdoc/indents.scm
similarity index 100%
rename from packages/language-javascript/grammars/ts/jsdoc/indents.scm
rename to packages/language-javascript/grammars/tree-sitter/jsdoc/indents.scm
diff --git a/packages/language-javascript/grammars/tree-sitter/jsdoc/tree-sitter-jsdoc.wasm b/packages/language-javascript/grammars/tree-sitter/jsdoc/tree-sitter-jsdoc.wasm
new file mode 100755
index 000000000..d16945f9e
Binary files /dev/null and b/packages/language-javascript/grammars/tree-sitter/jsdoc/tree-sitter-jsdoc.wasm differ
diff --git a/packages/language-javascript/grammars/ts/locals.scm b/packages/language-javascript/grammars/tree-sitter/locals.scm
similarity index 100%
rename from packages/language-javascript/grammars/ts/locals.scm
rename to packages/language-javascript/grammars/tree-sitter/locals.scm
diff --git a/packages/language-ruby/grammars/ts/regex/highlights.scm b/packages/language-javascript/grammars/tree-sitter/regex/highlights.scm
similarity index 81%
rename from packages/language-ruby/grammars/ts/regex/highlights.scm
rename to packages/language-javascript/grammars/tree-sitter/regex/highlights.scm
index b0018b6c7..3f79debaf 100644
--- a/packages/language-ruby/grammars/ts/regex/highlights.scm
+++ b/packages/language-javascript/grammars/tree-sitter/regex/highlights.scm
@@ -1,8 +1,3 @@
-; CAVEATS:
-;
-; * No support for lookbehind as of March 2023 (waiting on
-; https://github.com/tree-sitter/tree-sitter-regex/pull/15)
-
(non_capturing_group) @meta.group.non-capturing.regexp
[
@@ -17,6 +12,8 @@
[
(boundary_assertion)
+ (start_assertion)
+ (end_assertion)
] @keyword.control.anchor.regexp
[
@@ -24,10 +21,10 @@
(lazy)
] @keyword.operator.quantifier.regexp
-((lookahead_assertion) @keyword.operator.lookahead.regexp
+((lookaround_assertion) @keyword.operator.lookaround.regexp
(#set! adjust.startAndEndAroundFirstMatchOf "\\?="))
-((lookahead_assertion) @keyword.operator.lookahead.negated.regexp
+((lookaround_assertion) @keyword.operator.lookaround.negated.regexp
(#set! adjust.startAndEndAroundFirstMatchOf "\\?!"))
((non_capturing_group) @keyword.operator.group.non-capturing.regexp
diff --git a/packages/language-javascript/grammars/tree-sitter/regex/tree-sitter-regex.wasm b/packages/language-javascript/grammars/tree-sitter/regex/tree-sitter-regex.wasm
new file mode 100755
index 000000000..dd1988d42
Binary files /dev/null and b/packages/language-javascript/grammars/tree-sitter/regex/tree-sitter-regex.wasm differ
diff --git a/packages/language-javascript/grammars/ts/tags.scm b/packages/language-javascript/grammars/tree-sitter/tags.scm
similarity index 100%
rename from packages/language-javascript/grammars/ts/tags.scm
rename to packages/language-javascript/grammars/tree-sitter/tags.scm
diff --git a/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm b/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm
new file mode 100755
index 000000000..5857388d3
Binary files /dev/null and b/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm differ
diff --git a/packages/language-javascript/grammars/ts/grammar.wasm b/packages/language-javascript/grammars/ts/grammar.wasm
deleted file mode 100755
index 76c513e17..000000000
Binary files a/packages/language-javascript/grammars/ts/grammar.wasm and /dev/null differ
diff --git a/packages/language-javascript/grammars/ts/jsdoc/tree-sitter-jsdoc.wasm b/packages/language-javascript/grammars/ts/jsdoc/tree-sitter-jsdoc.wasm
deleted file mode 100755
index 7b9bde3d6..000000000
Binary files a/packages/language-javascript/grammars/ts/jsdoc/tree-sitter-jsdoc.wasm and /dev/null differ
diff --git a/packages/language-javascript/grammars/ts/regex/tree-sitter-regex.wasm b/packages/language-javascript/grammars/ts/regex/tree-sitter-regex.wasm
deleted file mode 100755
index 553c23b6b..000000000
Binary files a/packages/language-javascript/grammars/ts/regex/tree-sitter-regex.wasm and /dev/null differ
diff --git a/packages/language-javascript/lib/main.js b/packages/language-javascript/lib/main.js
index ed04e1978..015c289b6 100644
--- a/packages/language-javascript/lib/main.js
+++ b/packages/language-javascript/lib/main.js
@@ -57,19 +57,6 @@ exports.activate = function () {
languageScope: null
});
- // TODO: Ideal would be to have one `language-todo` injection for the whole
- // document responsible for highlighting TODOs in all comments, but
- // performance needs to be better than it is now for that to be possible.
- // Injecting into individual line comments results in less time parsing
- // during buffer modification, but _lots_ of language layers.
- //
- // Compromise is to test the content first and then only inject a layer for
- // `language-todo` when we know it'll be needed. All this also applies for
- // `language-hyperlink`.
- //
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
atom.grammars.addInjectionPoint('source.js', {
type: 'comment',
language(comment) {
@@ -81,28 +68,16 @@ exports.activate = function () {
languageScope: null,
coverShallowerScopes: true
});
+};
- // Experiment: better to have one layer with lots of nodes, or lots of
- // layers each managing one node?
- atom.grammars.addInjectionPoint('source.js', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.js', {
+ types: ['comment', 'template_string', 'string_fragment']
});
+};
- for (let type of ['template_string', 'string_fragment', 'comment']) {
- atom.grammars.addInjectionPoint('source.js', {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.js', { types: ['comment'] });
};
const CSS_REGEX = /\bstyled\b|\bcss\b/i;
diff --git a/packages/language-javascript/package.json b/packages/language-javascript/package.json
index 5f8c71c82..63b13e6a4 100644
--- a/packages/language-javascript/package.json
+++ b/packages/language-javascript/package.json
@@ -16,5 +16,74 @@
"tree-sitter-javascript": "0.19.0",
"tree-sitter-jsdoc": "0.19.0",
"tree-sitter-regex": "0.19.0"
+ },
+ "configSchema": {
+ "indentation": {
+ "title": "Indentation",
+ "type": "object",
+ "properties": {
+ "indentBraces": {
+ "title": "Indent Curly Braces",
+ "type": "boolean",
+ "default": true,
+ "order": 1,
+ "description": "Indent after `{`."
+ },
+ "indentBrackets": {
+ "title": "Indent Brackets",
+ "type": "boolean",
+ "default": true,
+ "order": 2,
+ "description": "Indent after `[`."
+ },
+ "indentParentheses": {
+ "title": "Indent Parentheses",
+ "type": "boolean",
+ "default": true,
+ "order": 3,
+ "description": "Indent after `(`."
+ },
+ "alignCaseWithSwitch": {
+ "title": "Align “case” With ”switch”",
+ "type": "boolean",
+ "default": false,
+ "order": 4,
+ "description": "When enabled, `case` and `default` statements in `switch` blocks will match the indent level of the enclosing `switch` instead of indenting themselves one level."
+ },
+ "indentAfterBracelessIf": {
+ "title": "Indent After Braceless “if” And “else”",
+ "type": "boolean",
+ "default": true,
+ "order": 5,
+ "description": "When enabled, `if` and `else` statements without a brace on the initial line will trigger an indent, then a dedent after a single statement. Disable if your brace style is incompatible with this pattern."
+ },
+ "addHangingIndentAfterLogicalOperators": {
+ "title": "Add Hanging Indent After Logical Operators",
+ "type": "boolean",
+ "default": true,
+ "order": 6,
+ "description": "When enabled, will add a hanging indent when a line ends with `&&` or `||`, continuing the indent until the end of the statement."
+ },
+ "addHangingIndentAfterTernaryOperators": {
+ "title": "Add Hanging Indent After Ternary Operators",
+ "type": "boolean",
+ "default": true,
+ "order": 7,
+ "description": "When enabled, will add a hanging indent when a line ends with `?`, continuing the indent through the ensuing `:` until the end of the statement."
+ }
+ }
+ }
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-php/grammars/modern-tree-sitter-php-html.cson b/packages/language-php/grammars/modern-tree-sitter-php-html.cson
index 2946872b4..77b49260f 100644
--- a/packages/language-php/grammars/modern-tree-sitter-php-html.cson
+++ b/packages/language-php/grammars/modern-tree-sitter-php-html.cson
@@ -6,12 +6,8 @@ parser: 'tree-sitter-php'
injectionRegex: 'php|PHP'
treeSitter:
- parserSource: 'github:tree-sitter/tree-sitter-php#d5e7cacb6c27e0e131c7f76c0dbfee56dfcc61e3'
+ parserSource: 'github:tree-sitter/tree-sitter-php#b569a5f2c0d592e67430520d1a0e1f765d83ceb0'
grammar: 'tree-sitter/tree-sitter-php.wasm'
- highlightsQuery: 'tree-sitter/queries/highlights.scm'
- tagsQuery: 'tree-sitter/queries/tags.scm'
- foldsQuery: 'tree-sitter/queries/folds.scm'
- indentsQuery: 'tree-sitter/queries/indents.scm'
fileTypes: [
'aw'
@@ -28,7 +24,3 @@ fileTypes: [
'phtml'
'profile'
]
-
-firstLineRegex: "^\\#!.*(?:\\s|\\/)php\\d?(?:$|\\s)|^\\s*<\\?(php|=|\\s|$)"
-
-contentRegex: "<\\?(php|=|\\s|$)"
diff --git a/packages/language-php/grammars/modern-tree-sitter-php.cson b/packages/language-php/grammars/modern-tree-sitter-php.cson
index 01b82d73c..c0014ee84 100644
--- a/packages/language-php/grammars/modern-tree-sitter-php.cson
+++ b/packages/language-php/grammars/modern-tree-sitter-php.cson
@@ -3,10 +3,13 @@ type: 'modern-tree-sitter'
parser: 'tree-sitter-php'
# Give it a precise injectionRegex that won't get accidentally matched with
-# anything. This grammar only exists as a way to apply the `source.php` scope.
+# anything.
injectionRegex: '^(internal-php)$'
treeSitter:
- parserSource: 'github:tree-sitter/tree-sitter-php#594b8bad093abe739c3d2a2cae5abae33c5fb23d'
+ parserSource: 'github:tree-sitter/tree-sitter-php#b569a5f2c0d592e67430520d1a0e1f765d83ceb0'
grammar: 'tree-sitter/tree-sitter-php.wasm'
- highlightsQuery: 'tree-sitter/queries/empty.scm'
+ highlightsQuery: 'tree-sitter/queries/highlights.scm'
+ tagsQuery: 'tree-sitter/queries/tags.scm'
+ foldsQuery: 'tree-sitter/queries/folds.scm'
+ indentsQuery: 'tree-sitter/queries/indents.scm'
diff --git a/packages/language-php/grammars/tree-sitter/queries/highlights.scm b/packages/language-php/grammars/tree-sitter/queries/highlights.scm
index 5801f81a6..0af6dde5d 100644
--- a/packages/language-php/grammars/tree-sitter/queries/highlights.scm
+++ b/packages/language-php/grammars/tree-sitter/queries/highlights.scm
@@ -7,14 +7,35 @@
; SUPPORT
; =======
+; There are lots of constructs that look like ordinary function calls but are
+; actually special language statements.
(array_creation_expression
- "array" @support.function.builtin.array.php)
+ "array" @support.function.builtin.array.php
+ "(" @punctuation.definition.parameters.begin.bracket.round.php
+ ")" @punctuation.definition.parameters.end.bracket.round.php)
-(list_literal "list" @support.function.builtin.list.php)
+(list_literal "list" @support.function.builtin.list.php
+ "(" @punctuation.definition.parameters.begin.bracket.round.php
+ ")" @punctuation.definition.parameters.end.bracket.round.php)
+
+(unset_statement
+ "unset" @support.function.unset.php
+ "(" @punctuation.definition.parameters.begin.bracket.round.php
+ ")" @punctuation.definition.parameters.end.bracket.round.php)
+
+(print_intrinsic
+ ; Don't delimit the parentheses like parameter punctuation; they're optional
+ ; for `print`.
+ "print" @support.function.print.php)
; The list of standard library methods in `php.cson` is… a lot. This is my
; biased attempt to pare it down to the most important functions.
+(function_call_expression
+ function: (name) @support.function._TEXT_.php
+ (#match? @support.function._TEXT_.php "^(isset|eval|empty)$")
+ (#set! capture.final))
+
(function_call_expression
function: (name) @support.function.array.php
(#match? @support.function.array.php "^(shuffle|sizeof|sort|next|nat(case)?sort|count|compact|current|in_array|usort|uksort|uasort|pos|prev|end|each|extract|ksort|key(_exists)?|krsort|list|asort|arsort|rsort|reset|range|array(_(shift|sum|splice|search|slice|chunk|change_key_case|count_values|column|combine|(diff|intersect)(_(u)?(key|assoc))?|u(diff|intersect)(_(u)?assoc)?|unshift|unique|pop|push|pad|product|values|keys|key_exists|filter|fill(_keys)?|flip|walk(_recursive)?|reduce|replace(_recursive)?|reverse|rand|multisort|merge(_recursive)?|map)?))$"))
@@ -31,10 +52,6 @@
function: (name) @support.function.class-obj.php
(#match? @support.function.class-obj.php "^(class_alias|all_user_method(_array)?|is_(a|subclass_of)|__autoload|(class|interface|method|property|trait)_exists|get_(class(_(vars|methods))?|(called|parent)_class|object_vars|declared_(classes|interfaces|traits)))$"))
-(function_call_expression
- function: (name) @support.function.construct.php
- (#match? @support.function.construct.php "^(isset|unset|eval|empty)$"))
-
(function_call_expression
function: (name) @support.function.construct.output.php
(#match? @support.function.construct.output.php "^(print|echo)$"))
@@ -101,7 +118,7 @@
(function_call_expression
function: (name) @support.function.math.php
- (#match? @support.function.math.php "^((a)?(cos|sin|tan)(h)?|sqrt|srand|hypot|hexdec|ceil|is_(nan|(in)?finite)|octdec|dec(hex|oct|bin)|deg2rad|pi|pow|exp(m1)?|floor|fmod|lcg_value|log(1(p|0))?|atan2|abs|round|rand|rad2deg|getrandmax|mt_(srand|rand|getrandmax)|max|min|bindec|base_convert)$"))
+ (#match? @support.function.math.php "^((a)?(cos|sin|tan)(h)?|sqrt|srand|hypot|hexdec|ceil|is_(nan|(in)?finite)|octdec|dec(hex|oct|bin)|deg2rad|pi|pow|exp(m1)?|floor|f(mod|div)|lcg_value|log(1(p|0))?|atan2|abs|round|rand|rad2deg|getrandmax|mt_(srand|rand|getrandmax)|max|min|bindec|base_convert|intdiv)$"))
(function_call_expression
function: (name) @support.function.mbstring.php
@@ -263,14 +280,15 @@
(class_constant_access_expression . (name) @support.class.php)
(class_constant_access_expression (name) @support.other.property.php .)
-; The "Foo" and "bar" in "Foo::bar()".
-(scoped_call_expression
- scope: (name) @support.class.php
- name: (name) @support.other.function.method.static.php)
-
-; The "Foo" and "$bar" in "Foo::$bar()".
+; The "Foo" in `Foo::bar()` and `Foo::$bar()`.
(scoped_call_expression
scope: (name) @support.class.php)
+
+; The "bar" in `Foo::bar()`.
+(scoped_call_expression
+ name: (name) @support.other.function.method.static.php)
+
+; The "$bar" in `Foo::$bar()`.
(scoped_call_expression
name: (variable_name) @variable.other.method.static.php)
@@ -300,6 +318,10 @@
name: (variable_name) @variable.other.property.php
(#set! capture.final true))
+; The "Foo" in `new Foo();`.
+(object_creation_expression
+ (name) @support.class.php)
+
; TRAITS
; ======
@@ -316,13 +338,17 @@
; TYPES
; =====
-(primitive_type) @storage.type.builtin.php
-(cast_type) @storage.type.builtin.php
-(named_type (name) @storage.type.php)
-(named_type (qualified_name) @storage.type.php)
+; Primitive types are value types, hence are placed in `support.storage.type`.
+(primitive_type) @support.storage.type.builtin.php
+(cast_type) @support.storage.type.builtin.php
+(named_type (name) @support.storage.type.php)
+(named_type (qualified_name) @support.storage.type.php)
+
+; Acts as a modifier on all variables, regardless of value type, hence `storage.modifier`.
"global" @storage.modifier.global.php
+; Core language constructs go in `storage.type`.
["enum" "interface" "trait" "class"] @storage.type._TYPE_.php
(enum_case "case" @storage.type.case.php)
"function" @storage.type.function.php
@@ -376,8 +402,10 @@
((dynamic_variable_name) @punctuation.definition.variable.begin.php
(#set! adjust.startBeforeFirstMatchOf "^\\}$"))
-((name) @constant.other.php
- (#match? @constant.other.php "^_?[A-Z][A-Z\\d_]+$"))
+; ((name) @constant.other.php
+; (#match? @constant.other.php "^_?[A-Z][A-Z\\d_]+$"))
+
+(const_declaration (const_element) @variable.other.constant.php)
((name) @constant.language.php
(#match? @constant.language.php "^__[A-Z][A-Z\d_]+__$"))
@@ -483,11 +511,16 @@
(#match? @punctuation.definition.comment.php "^#")
(#set! adjust.startAndEndAroundFirstMatchOf "^#"))
-; Don't highlight PHPDoc comments because the injection will handle them.
+; Capture these because the PHPDoc injection won't process them…
+((comment) @comment.block.documentation.php
+ (#match? @comment.block.documentation.php "^/\\*\\*\\*"))
+
+; …but otherwise leave this style of comment to be handled by PHPDoc.
((comment) @_IGNORE_
(#match? @_IGNORE_ "^/\\*\\*")
(#set! capture.final true))
+
((comment) @comment.block.php
(#match? @comment.block.php "^/\\*(?!\\*)"))
@@ -588,8 +621,10 @@
"{" @punctuation.definition.block.begin.bracket.curly.php
"}" @punctuation.definition.block.end.bracket.curly.php
-"(" @punctuation.definition.begin.bracket.round.php
-")" @punctuation.definition.end.bracket.round.php
+("(" @punctuation.definition.begin.bracket.round.php
+ (#set! capture.shy true))
+(")" @punctuation.definition.end.bracket.round.php
+ (#set! capture.shy true))
"[" @punctuation.definition.begin.bracket.square.php
"]" @punctuation.definition.end.bracket.square.php
diff --git a/packages/language-php/grammars/tree-sitter/queries/indents.scm b/packages/language-php/grammars/tree-sitter/queries/indents.scm
index c7f198a40..2b888d187 100644
--- a/packages/language-php/grammars/tree-sitter/queries/indents.scm
+++ b/packages/language-php/grammars/tree-sitter/queries/indents.scm
@@ -1,8 +1,9 @@
-;
+
["{" "(" "["] @indent
["}" ")" "]"] @dedent
-":" @indent
+; if ($foo):
+(colon_block ":" @indent)
["endif" "endfor" "endforeach" "enddeclare" "endswitch"] @dedent
diff --git a/packages/language-php/grammars/tree-sitter/queries/phpdoc/highlights.scm b/packages/language-php/grammars/tree-sitter/queries/phpdoc/highlights.scm
index e9ee2c4d6..dfb4fee84 100644
--- a/packages/language-php/grammars/tree-sitter/queries/phpdoc/highlights.scm
+++ b/packages/language-php/grammars/tree-sitter/queries/phpdoc/highlights.scm
@@ -5,7 +5,8 @@
((document) @punctuation.definition.end.comment.phpdoc.php
(#set! adjust.startAndEndAroundFirstMatchOf "(?:\\*)?\\*/$"))
-(tag_name) @entity.name.tag.phpdoc.php
+(tag_name) @keyword.other.tag.phpdoc.php
+(primitive_type) @storage.type.primitive.phpdoc.php
(named_type) @storage.type.instance.phpdoc.php
(variable_name) @variable.other.phpdoc.php
(uri) @markup.underline.link.phpdoc.php
diff --git a/packages/language-php/grammars/tree-sitter/tree-sitter-php.wasm b/packages/language-php/grammars/tree-sitter/tree-sitter-php.wasm
index 759c60b33..502625bb7 100755
Binary files a/packages/language-php/grammars/tree-sitter/tree-sitter-php.wasm and b/packages/language-php/grammars/tree-sitter/tree-sitter-php.wasm differ
diff --git a/packages/language-php/lib/main.js b/packages/language-php/lib/main.js
index bc8b18c34..938997354 100644
--- a/packages/language-php/lib/main.js
+++ b/packages/language-php/lib/main.js
@@ -1,3 +1,83 @@
+const { Point, Range } = require('atom');
+
+function isPhpDoc(node) {
+ let { text } = node;
+ return text.startsWith('/**') && !text.startsWith('/***')
+}
+
+function comparePoints(a, b) {
+ const rows = a.row - b.row;
+ if (rows === 0) {
+ return a.column - b.column;
+ } else {
+ return rows;
+ }
+}
+
+// Given a series of opening and closing PHP tags, pairs and groups them as a
+// series of node specs suitable for defining the bounds of an injection.
+function interpret(nodes) {
+ let sorted = [...nodes].sort((a, b) => {
+ return comparePoints(a.startPosition, b.startPosition);
+ });
+
+ let ranges = [];
+ let currentStart = null;
+ let lastIndex = nodes.length - 1;
+
+ for (let [index, node] of sorted.entries()) {
+ let isStart = node.type === 'php_tag';
+ let isEnd = node.type === '?>';
+ let isLast = index === lastIndex;
+
+ if (isStart) {
+ if (currentStart) {
+ throw new Error('Unbalanced content!');
+ }
+ currentStart = node;
+
+ if (isLast) {
+ // There's no ending tag to match this starting tag. This is valid and
+ // simply signifies that the rest of the file is PHP. We can return a
+ // range from here to `Infinity` and let the language mode clip it to
+ // the edge of the buffer.
+ let spec = {
+ startIndex: currentStart.startIndex,
+ startPosition: currentStart.startPosition,
+ endIndex: Infinity,
+ endPosition: Point.INFINITY,
+ range: new Range(
+ currentStart.range.start,
+ Point.INFINITY
+ )
+ };
+ ranges.push(spec);
+ currentStart = null;
+ break;
+ }
+ }
+
+ if (isEnd) {
+ if (!currentStart) {
+ throw new Error('Unbalanced content!');
+ }
+ let spec = {
+ startIndex: currentStart.startIndex,
+ startPosition: currentStart.startPosition,
+ endIndex: node.endIndex,
+ endPosition: node.endPosition,
+ range: new Range(
+ currentStart.range.start,
+ node.range.end
+ )
+ };
+ ranges.push(spec);
+ currentStart = null;
+ }
+ }
+ return ranges;
+}
+
exports.activate = function () {
// Here's how we handle the mixing of PHP and HTML:
@@ -45,67 +125,49 @@ exports.activate = function () {
return 'internal-php';
},
content(node) {
- let results = [];
- // At the top level we should ignore `text` nodes, since they're just
- // HTML. We should also ignore the middle children of
- // `text_interpolation` nodes (also `text`), but we need to include their
- // first and last children, which correspond to `?>` and `']);
+ return interpret(boundaries);
},
includeChildren: true,
- newlinesBetween: true,
- includeAdjacentWhitespace: true
+ newlinesBetween: false,
+ // includeAdjacentWhitespace: true,
+
+ // For parity with the TextMate PHP grammar, we need to be able to scope
+ // this region with not just `source.php` but also `meta.embedded.X.php`,
+ // where X is one of `line` or `block` depending on whether the range spans
+ // multiple lines.
+ //
+ // There is no way to do this via queries because there is no discrete node
+ // against which we could conditionally add `meta.embedded.block` or
+ // `meta.embedded.line`… because of the aforementioned lunacy of the tree
+ // structure.
+ //
+ // So we had to invent a feature for it. When `languageScope` is a function,
+ // it allows the injection to decide on a range-by-range basis what the
+ // scope name is… _and_ it can return more than one scope name.
+ languageScope(grammar, _buffer, range) {
+ let extraScope = range.start.row !== range.end.row ?
+ 'meta.embedded.block.php' : 'meta.embedded.line.php';
+ return [grammar.scopeName, extraScope];
+ }
});
- // TODOs and URLs
- // ==============
-
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- function isPhpDoc(node) {
- let { text } = node;
- return text.startsWith('/**') && !text.startsWith('/***')
- }
-
- atom.grammars.addInjectionPoint('text.html.php', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- for (let type of ['comment', 'string_value']) {
- atom.grammars.addInjectionPoint('text.html.php', {
- type,
- language(node) {
- // PHPDoc can parse URLs better than we can.
- if (isPhpDoc(node)) return undefined;
- return HYPERLINK_PATTERN.test(node.text) ?
- 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
// HEREDOCS and NOWDOCS
// ====================
@@ -141,7 +203,7 @@ exports.activate = function () {
// PHPDoc
// ======
-
+
atom.grammars.addInjectionPoint('text.html.php', {
type: 'comment',
language(node) {
@@ -153,3 +215,19 @@ exports.activate = function () {
});
};
+
+// TODOs and URLs
+// ==============
+
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('text.html.php', {
+ types: ['comment', 'string_value'],
+ language(node) {
+ if (isPhpDoc(node)) return null;
+ }
+ });
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('text.html.php', { types: ['comment'] });
+};
diff --git a/packages/language-php/package.json b/packages/language-php/package.json
index 2b3e7aa3c..011bba7ae 100644
--- a/packages/language-php/package.json
+++ b/packages/language-php/package.json
@@ -8,5 +8,17 @@
"node": ">=12"
},
"repository": "https://github.com/pulsar-edit/pulsar",
- "license": "MIT"
+ "license": "MIT",
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
+ }
}
diff --git a/packages/language-php/spec/.eslintrc.js b/packages/language-php/spec/.eslintrc.js
new file mode 100644
index 000000000..77888e061
--- /dev/null
+++ b/packages/language-php/spec/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+ env: { jasmine: true },
+ globals: {
+ waitsForPromise: true,
+ advanceClock: true
+ },
+ rules: {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+};
diff --git a/packages/language-python/grammars/modern-tree-sitter-python.cson b/packages/language-python/grammars/modern-tree-sitter-python.cson
index 1360c54d5..d69d15cb4 100644
--- a/packages/language-python/grammars/modern-tree-sitter-python.cson
+++ b/packages/language-python/grammars/modern-tree-sitter-python.cson
@@ -27,8 +27,7 @@ fileTypes: [
]
treeSitter:
- # Built from tree-sitter-python 62827156d01c74dc1538266344e788da74536b8a
- # to add support for `match` statements.
+ parserSource: 'github:tree-sitter/tree-sitter-python#4bfdd9033a2225cc95032ce77066b7aeca9e2efc'
grammar: 'ts/tree-sitter-python.wasm'
highlightsQuery: 'ts/highlights.scm'
tagsQuery: 'ts/tags.scm'
diff --git a/packages/language-python/grammars/ts/highlights.scm b/packages/language-python/grammars/ts/highlights.scm
index 88874bd3b..03e8db484 100644
--- a/packages/language-python/grammars/ts/highlights.scm
+++ b/packages/language-python/grammars/ts/highlights.scm
@@ -187,24 +187,27 @@
; similarly here. No need to account for the rawness of a string in the scope
; name unless someone requests that feature.
+((string) @string.quoted.triple.block.format.python
+ (#match? @string.quoted.triple.block.format.python "^[fFrR]+\"\"\"")
+ (#set! capture.final))
+
((string) @string.quoted.triple.block.python
(#match? @string.quoted.triple.block.python "^[bBrRuU]*\"\"\""))
-((string) @string.quoted.triple.block.format.python
- (#match? @string.quoted.triple.block.format.python "^[fFrR]*\"\"\""))
+((string) @string.quoted.double.single-line.format.python
+ (#match? @string.quoted.double.single-line.format.python "^[fFrR]+\"")
+ (#set! capture.final))
((string) @string.quoted.double.single-line.python
(#match? @string.quoted.double.single-line.python "^[bBrRuU]*\"(?!\")"))
-((string) @string.quoted.double.single-line.format.python
- (#match? @string.quoted.double.single-line.format.python "^[fFrR]*\""))
+((string) @string.quoted.single.single-line.format.python
+ (#match? @string.quoted.single.single-line.format.python "^[fFrR]+?\'")
+ (#set! capture.final))
((string) @string.quoted.single.single-line.python
(#match? @string.quoted.single.single-line.python "^[bBrRuU]*\'"))
-((string) @string.quoted.single.single-line.format.python
- (#match? @string.quoted.single.single-line.format.python "^[fFrR]*?\'"))
-
(string_content (escape_sequence) @constant.character.escape.python)
(interpolation
@@ -219,7 +222,7 @@
_ @punctuation.definition.string.end.python
(#is? test.last true))
-(string prefix: _ @storage.type.string.python
+(string (string_start) @storage.type.string.python
(#match? @storage.type.string.python "^[bBfFrRuU]+")
(#set! adjust.endAfterFirstMatchOf "^[bBfFrRuU]+"))
@@ -255,6 +258,7 @@
] @keyword.control.statement._TYPE_.python
[
+ "break"
"continue"
"for"
"while"
@@ -393,6 +397,9 @@
"or"
] @keyword.operator.logical._TYPE_.python
+; The 'not' and 'in' are each scoped separately, each one being an anonymous
+; node incorrectly named "not in".
+"not in" @keyword.operator.logical.not-in.python
"is not" @keyword.operator.logical.is-not.python
(call
diff --git a/packages/language-python/grammars/ts/tree-sitter-python.wasm b/packages/language-python/grammars/ts/tree-sitter-python.wasm
index 0c26bbf66..8ff8ab2a6 100755
Binary files a/packages/language-python/grammars/ts/tree-sitter-python.wasm and b/packages/language-python/grammars/ts/tree-sitter-python.wasm differ
diff --git a/packages/language-python/lib/main.js b/packages/language-python/lib/main.js
index 3d5a1fbed..a1f13423a 100644
--- a/packages/language-python/lib/main.js
+++ b/packages/language-python/lib/main.js
@@ -1,30 +1,6 @@
exports.activate = function () {
if (!atom.grammars.addInjectionPoint) return;
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint('source.python', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- for (let type of ['comment', 'string']) {
- atom.grammars.addInjectionPoint('source.python', {
- type,
- language(node) {
- return HYPERLINK_PATTERN.test(node.text) ?
- 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
-
// TODO: There's no regex literal in Python. The TM-style grammar has a
// very obscure option that, when enabled, assumes all raw strings are
// regexes and highlights them accordingly. This might be worth doing in the
@@ -42,3 +18,13 @@ exports.activate = function () {
// languageScope: null
// });
}
+
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.python', {
+ types: ['comment', 'string_content']
+ });
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.python', { types: ['comment'] });
+};
diff --git a/packages/language-python/package.json b/packages/language-python/package.json
index d35fac18d..364d49faf 100644
--- a/packages/language-python/package.json
+++ b/packages/language-python/package.json
@@ -15,5 +15,17 @@
"dependencies": {
"atom-grammar-test": "^0.6.4",
"tree-sitter-python": "0.19.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-ruby/grammars/modern-tree-sitter-regex.cson b/packages/language-ruby/grammars/modern-tree-sitter-regex.cson
new file mode 100644
index 000000000..f451e88a2
--- /dev/null
+++ b/packages/language-ruby/grammars/modern-tree-sitter-regex.cson
@@ -0,0 +1,10 @@
+scopeName: 'source.rb.regexp'
+type: 'modern-tree-sitter'
+parser: 'tree-sitter-regex'
+
+injectionRegex: '^(rb-regex)$'
+
+treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-regex#2354482d7e2e8f8ff33c1ef6c8aa5690410fbc96'
+ grammar: 'tree-sitter-regex/tree-sitter-regex.wasm'
+ highlightsQuery: 'tree-sitter-regex/highlights.scm'
diff --git a/packages/language-ruby/grammars/tree-sitter-2-ruby.cson b/packages/language-ruby/grammars/modern-tree-sitter-ruby.cson
similarity index 52%
rename from packages/language-ruby/grammars/tree-sitter-2-ruby.cson
rename to packages/language-ruby/grammars/modern-tree-sitter-ruby.cson
index ebb414df9..928fda052 100644
--- a/packages/language-ruby/grammars/tree-sitter-2-ruby.cson
+++ b/packages/language-ruby/grammars/modern-tree-sitter-ruby.cson
@@ -6,12 +6,13 @@ parser: 'tree-sitter-ruby'
injectionRegex: 'rb|ruby|RB|RUBY'
treeSitter:
- grammar: 'ts/grammar.wasm'
- highlightsQuery: 'ts/highlights.scm'
- localsQuery: 'ts/locals.scm'
- foldsQuery: 'ts/folds.scm'
- indentsQuery: 'ts/indents.scm'
- tagsQuery: 'ts/tags.scm'
+ parserSource: 'github:tree-sitter/tree-sitter-ruby#4d9ad3f010fdc47a8433adcf9ae30c8eb8475ae7'
+ grammar: 'tree-sitter-ruby/tree-sitter-ruby.wasm'
+ highlightsQuery: 'tree-sitter-ruby/highlights.scm'
+ localsQuery: 'tree-sitter-ruby/locals.scm'
+ foldsQuery: 'tree-sitter-ruby/folds.scm'
+ indentsQuery: 'tree-sitter-ruby/indents.scm'
+ tagsQuery: 'tree-sitter-ruby/tags.scm'
firstLineRegex: [
# shebang line
diff --git a/packages/language-ruby/grammars/tree-sitter-2-regex.cson b/packages/language-ruby/grammars/tree-sitter-2-regex.cson
deleted file mode 100644
index 004b818bc..000000000
--- a/packages/language-ruby/grammars/tree-sitter-2-regex.cson
+++ /dev/null
@@ -1,13 +0,0 @@
-scopeName: 'source.regexp'
-type: 'modern-tree-sitter'
-parser: 'tree-sitter-regex'
-
-# TODO: Decide whether to have one regex grammar that's shared among grammars
-# _or_ one separate instance of a tree-sitter-regex grammar for each language.
-# In the latter case, they could still share the same `wasm` file and perhaps
-# differ only in the query files.
-injectionRegex: '^(rb-regex)$'
-
-treeSitter:
- grammar: 'ts/regex/tree-sitter-regex.wasm'
- highlightsQuery: 'ts/regex/highlights.scm'
diff --git a/packages/language-ruby/grammars/tree-sitter-regex/highlights.scm b/packages/language-ruby/grammars/tree-sitter-regex/highlights.scm
new file mode 100644
index 000000000..3f79debaf
--- /dev/null
+++ b/packages/language-ruby/grammars/tree-sitter-regex/highlights.scm
@@ -0,0 +1,50 @@
+(non_capturing_group) @meta.group.non-capturing.regexp
+
+[
+ (anonymous_capturing_group)
+] @meta.group.capturing.regexp
+
+[
+ (identity_escape)
+ (control_escape)
+ (character_class_escape)
+] @constant.character.escape.backslash.regexp
+
+[
+ (boundary_assertion)
+ (start_assertion)
+ (end_assertion)
+] @keyword.control.anchor.regexp
+
+[
+ (optional)
+ (lazy)
+] @keyword.operator.quantifier.regexp
+
+((lookaround_assertion) @keyword.operator.lookaround.regexp
+ (#set! adjust.startAndEndAroundFirstMatchOf "\\?="))
+
+((lookaround_assertion) @keyword.operator.lookaround.negated.regexp
+ (#set! adjust.startAndEndAroundFirstMatchOf "\\?!"))
+
+((non_capturing_group) @keyword.operator.group.non-capturing.regexp
+ (#set! adjust.startAndEndAroundFirstMatchOf "\\?:"))
+
+(anonymous_capturing_group
+ "(" @punctuation.definition.group.begin.bracket.round.regexp
+ ")" @punctuation.definition.group.end.bracket.round.regexp
+ (#set! capture.final true))
+
+"|" @keyword.operator.or.regexp
+["*" "+"] @keyword.operator.quantifier.regexp
+
+(character_class) @constant.other.character-class.set.regexp
+
+(character_class
+ "[" @punctuation.definition.character-class.begin.regexp)
+
+(character_class
+ "]" @punctuation.definition.character-class.end.regexp)
+
+(character_class
+ "^" @keyword.operator.negation.regexp)
diff --git a/packages/language-ruby/grammars/tree-sitter-regex/tree-sitter-regex.wasm b/packages/language-ruby/grammars/tree-sitter-regex/tree-sitter-regex.wasm
new file mode 100755
index 000000000..dd1988d42
Binary files /dev/null and b/packages/language-ruby/grammars/tree-sitter-regex/tree-sitter-regex.wasm differ
diff --git a/packages/language-ruby/grammars/ts/folds.scm b/packages/language-ruby/grammars/tree-sitter-ruby/folds.scm
similarity index 100%
rename from packages/language-ruby/grammars/ts/folds.scm
rename to packages/language-ruby/grammars/tree-sitter-ruby/folds.scm
diff --git a/packages/language-ruby/grammars/ts/highlights.scm b/packages/language-ruby/grammars/tree-sitter-ruby/highlights.scm
similarity index 100%
rename from packages/language-ruby/grammars/ts/highlights.scm
rename to packages/language-ruby/grammars/tree-sitter-ruby/highlights.scm
diff --git a/packages/language-ruby/grammars/ts/indents.scm b/packages/language-ruby/grammars/tree-sitter-ruby/indents.scm
similarity index 100%
rename from packages/language-ruby/grammars/ts/indents.scm
rename to packages/language-ruby/grammars/tree-sitter-ruby/indents.scm
diff --git a/packages/language-ruby/grammars/ts/locals.scm b/packages/language-ruby/grammars/tree-sitter-ruby/locals.scm
similarity index 100%
rename from packages/language-ruby/grammars/ts/locals.scm
rename to packages/language-ruby/grammars/tree-sitter-ruby/locals.scm
diff --git a/packages/language-ruby/grammars/ts/tags.scm b/packages/language-ruby/grammars/tree-sitter-ruby/tags.scm
similarity index 100%
rename from packages/language-ruby/grammars/ts/tags.scm
rename to packages/language-ruby/grammars/tree-sitter-ruby/tags.scm
diff --git a/packages/language-ruby/grammars/tree-sitter-ruby/tree-sitter-ruby.wasm b/packages/language-ruby/grammars/tree-sitter-ruby/tree-sitter-ruby.wasm
new file mode 100755
index 000000000..c4818c3ce
Binary files /dev/null and b/packages/language-ruby/grammars/tree-sitter-ruby/tree-sitter-ruby.wasm differ
diff --git a/packages/language-ruby/grammars/ts/grammar.wasm b/packages/language-ruby/grammars/ts/grammar.wasm
deleted file mode 100755
index 182c005c9..000000000
Binary files a/packages/language-ruby/grammars/ts/grammar.wasm and /dev/null differ
diff --git a/packages/language-ruby/grammars/ts/regex/tree-sitter-regex.wasm b/packages/language-ruby/grammars/ts/regex/tree-sitter-regex.wasm
deleted file mode 100755
index 553c23b6b..000000000
Binary files a/packages/language-ruby/grammars/ts/regex/tree-sitter-regex.wasm and /dev/null differ
diff --git a/packages/language-ruby/lib/main.js b/packages/language-ruby/lib/main.js
index 5f343a42f..de523dfb6 100644
--- a/packages/language-ruby/lib/main.js
+++ b/packages/language-ruby/lib/main.js
@@ -24,33 +24,14 @@ exports.activate = function () {
// coverShallowerScopes: false
});
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
+};
- atom.grammars.addInjectionPoint('source.ruby', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : null;
- },
- content: (node) => node,
- languageScope: null
- });
-
- atom.grammars.addInjectionPoint('source.ruby', {
- type: 'comment',
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : null;
- },
- content: (node) => node,
- languageScope: null
- });
-
- atom.grammars.addInjectionPoint('source.ruby', {
- type: 'string_content',
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : null;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.ruby', {
+ types: ['comment', 'string_content']
});
};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.ruby', { types: ['comment'] });
+};
diff --git a/packages/language-ruby/package.json b/packages/language-ruby/package.json
index 56e9c2a0d..b24fb4926 100644
--- a/packages/language-ruby/package.json
+++ b/packages/language-ruby/package.json
@@ -18,5 +18,17 @@
},
"devDependencies": {
"dedent": "^0.7.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-rust-bundled/grammars/tree-sitter-rust/queries/highlights.scm b/packages/language-rust-bundled/grammars/tree-sitter-rust/queries/highlights.scm
index 73eb3c9b5..3a74b2073 100644
--- a/packages/language-rust-bundled/grammars/tree-sitter-rust/queries/highlights.scm
+++ b/packages/language-rust-bundled/grammars/tree-sitter-rust/queries/highlights.scm
@@ -32,7 +32,7 @@
; -----------
; Wrap the "foo" and "!" of `foo!()`.
-((macro_invocation) @support.other.function.rust
+((macro_invocation (identifier)) @support.other.function.rust
(#set! adjust.endAt firstChild.nextSibling.endPosition))
(call_expression
@@ -211,6 +211,8 @@
">>="
] @keyword.operator.assignment.compound.rust
+(reference_expression "&" @keyword.operator.reference.rust)
+
(binary_expression
["&" "|" "^" ">>" "<<"] @keyword.operator.bitwise.rust)
diff --git a/packages/language-rust-bundled/lib/main.js b/packages/language-rust-bundled/lib/main.js
index e4dc9d244..33f19c5e6 100644
--- a/packages/language-rust-bundled/lib/main.js
+++ b/packages/language-rust-bundled/lib/main.js
@@ -13,29 +13,21 @@ exports.activate = function () {
coverShallowerScopes: true
});
}
-
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- for (let type of ['line_comment', 'block_comment']) {
- atom.grammars.addInjectionPoint('source.rust', {
- type,
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
-
- for (let type of ['string_literal', 'raw_string_literal', 'line_comment', 'block_comment']) {
- atom.grammars.addInjectionPoint('source.rust', {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
+};
+
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.rust', {
+ types: [
+ 'line_comment',
+ 'block_comment',
+ 'string_literal',
+ 'raw_string_literal'
+ ]
+ });
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.rust', {
+ types: ['line_comment', 'block_comment']
+ });
};
diff --git a/packages/language-rust-bundled/package.json b/packages/language-rust-bundled/package.json
index 603b2c602..fae238b76 100644
--- a/packages/language-rust-bundled/package.json
+++ b/packages/language-rust-bundled/package.json
@@ -16,5 +16,17 @@
"engines": {
"atom": ">=1.0.0 <2.0.0",
"node": ">=12"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-shellscript/grammars/tree-sitter/highlights.scm b/packages/language-shellscript/grammars/tree-sitter/highlights.scm
index eaf6fcee2..f117e0065 100644
--- a/packages/language-shellscript/grammars/tree-sitter/highlights.scm
+++ b/packages/language-shellscript/grammars/tree-sitter/highlights.scm
@@ -33,10 +33,11 @@
"in"
"do"
"done"
+ "while"
] @keyword.control._TYPE_.shell
(declaration_command
- ["local" "export"] @storage.modifier._TYPE_.shell)
+ ["local" "export" "declare" "readonly"] @storage.modifier._TYPE_.shell)
(variable_assignment
(variable_name) @variable.other.member.shell
@@ -90,7 +91,30 @@
(command_substitution) @meta.embedded.line.subshell.shell
(#set! capture.final true))
-(command_substitution) @string.interpolation.backtick.shell
+; Command substitution with backticks: var=`cmd`
+((command_substitution) @string.quoted.interpolated.backtick.shell
+ (#match? @string.quoted.interpolated.backtick.shell "^`"))
+
+((command_substitution) @punctuation.definition.string.begin.shell
+ (#match? @punctuation.definition.string.begin.shell "^`")
+ (#set! adjust.endAfterFirstMatchOf "^`"))
+
+((command_substitution) @punctuation.definition.string.end.shell
+ (#match? @punctuation.definition.string.end.shell "`$")
+ (#set! adjust.startBeforeFirstMatchOf "`$"))
+
+; Command substitution of the form: var=$(cmd)
+((command_substitution) @string.quoted.interpolated.dollar.shell
+ (#match? @string.quoted.interpolated.dollar.shell "^\\$\\("))
+
+((command_substitution) @punctuation.definition.string.begin.shell
+ (#match? @punctuation.definition.string.begin.shell "^\\$\\(")
+ (#set! adjust.endAfterFirstMatchOf "^\\$\\("))
+
+((command_substitution) @punctuation.definition.string.end.shell
+ (#match? @punctuation.definition.string.end.shell "\\)$")
+ (#set! adjust.startBeforeFirstMatchOf "\\)$"))
+
(heredoc_start) @punctuation.definition.string.begin.heredoc.shell
(heredoc_body) @string.unquoted.heredoc.shell
@@ -137,6 +161,7 @@
(file_redirect
[
+ "<"
">"
">&"
"&>"
@@ -154,6 +179,15 @@
(number) @constant.numeric.decimal.shell
+; TODO: Double parentheses are used like `let` expressions, but ((i++)) is not
+; understood by `tree-sitter-bash` as a variable increment. It needs an equals
+; sign before it construes the contents as math.
+(test_command
+ "((" @punctuation.brace.double-round.begin.shell)
+(test_command
+ "))" @punctuation.brace.double-round.end.shell)
+
+
(test_command
"[[" @punctuation.brace.double-square.begin.shell)
(test_command
@@ -162,12 +196,12 @@
; PUNCTUATION
; ===========
-"{" @punctuation.brace.curly.begin.shell
-"}" @punctuation.brace.curly.end.shell
-"(" @punctuation.brace.round.begin.shell
-")" @punctuation.brace.round.end.shell
-"[" @punctuation.brace.square.begin.shell
-"]" @punctuation.brace.square.end.shell
+("{" @punctuation.brace.curly.begin.shell (#set! capture.shy))
+("}" @punctuation.brace.curly.end.shell (#set! capture.shy))
+("(" @punctuation.brace.round.begin.shell (#set! capture.shy))
+(")" @punctuation.brace.round.end.shell (#set! capture.shy))
+("[" @punctuation.brace.square.begin.shell (#set! capture.shy))
+("]" @punctuation.brace.square.end.shell (#set! capture.shy))
-";" @punctuation.terminator.statement.shell
-":" @punctuation.separator.colon.shell
+(";" @punctuation.terminator.statement.shell (#set! capture.shy))
+(":" @punctuation.separator.colon.shell (#set! capture.shy))
diff --git a/packages/language-shellscript/lib/main.js b/packages/language-shellscript/lib/main.js
index e2eb7368a..706fc91f0 100644
--- a/packages/language-shellscript/lib/main.js
+++ b/packages/language-shellscript/lib/main.js
@@ -1,25 +1,10 @@
-exports.activate = () => {
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint('source.shell', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.shell', {
+ types: ['comment']
});
-
- atom.grammars.addInjectionPoint('source.shell', {
- type: 'comment',
- language(node) {
- return HYPERLINK_PATTERN.test(node.text) ?
- 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.shell', { types: ['comment'] });
};
diff --git a/packages/language-shellscript/package.json b/packages/language-shellscript/package.json
index 47fa9bc27..0c352b7ef 100644
--- a/packages/language-shellscript/package.json
+++ b/packages/language-shellscript/package.json
@@ -14,5 +14,17 @@
"license": "MIT",
"dependencies": {
"tree-sitter-bash": "0.19.0"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-todo/lib/main.js b/packages/language-todo/lib/main.js
new file mode 100644
index 000000000..7c20f2bb2
--- /dev/null
+++ b/packages/language-todo/lib/main.js
@@ -0,0 +1,65 @@
+
+const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
+
+module.exports = {
+ provideTodoInjection() {
+ return {
+ // Private: Test whether a Tree-sitter node's text contains any tokens
+ // that would benefit from a TODO injection.
+ //
+ // Useful if you want to call {GrammarRegistry::addInjectionPoint}
+ // yourself and want to use this logic in a `language` callback.
+ //
+ // * `node` A Tree-sitter tree node.
+ test(node) {
+ return TODO_PATTERN.test(node.text);
+ },
+
+ // Private: specify one or more types of syntax nodes for a given grammar
+ // that may embed the TODO grammar.
+ //
+ // * `scopeName` The {String} ID of the parent language.
+ // * `options` An {Object} with the following keys:
+ // * `types` An {Array} or {String} indicating the type or types of
+ // Tree-sitter tree nodes that may receive injections.
+ // * `language` (optional) A {Function} that may be called to add extra
+ // logic for determining which language should be used in an
+ // injection. If present, will be called before the default logic.
+ // If it returns `undefined`, the default logic will apply. If it
+ // returns a {String} or `null`, the default logic will be preempted.
+ // * `content` (optional) A {Function} that will be used to determine
+ // which of the injection node's children, if any, will be injected
+ // into. The default `content` callback is one that returns the
+ // original node.
+ addInjectionPoint(scopeName, options) {
+ let types = options.types;
+ if (!Array.isArray(types)) types = [types];
+
+ // TODO: Ideal would be to have one `language-todo` injection for the
+ // whole document responsible for highlighting TODOs in all comments,
+ // but performance needs to be better than it is now for that to be
+ // possible. Injecting into individual nodes results in less time
+ // parsing during buffer modification, but _lots_ of language layers.
+ //
+ // Compromise is to test the content first and then only inject a layer
+ // for `language-todo` when we know it'll be needed.
+ for (let type of types) {
+ atom.grammars.addInjectionPoint(scopeName, {
+ type,
+ language(node) {
+ if (options.language) {
+ let result = options.language(node);
+ if (result !== undefined) return result;
+ }
+ return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
+ },
+ content(node) {
+ return options.content ? options.content(node) : node;
+ },
+ languageScope: null
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/packages/language-todo/package.json b/packages/language-todo/package.json
index ef6be0a45..074634e69 100644
--- a/packages/language-todo/package.json
+++ b/packages/language-todo/package.json
@@ -1,11 +1,19 @@
{
"name": "language-todo",
"version": "0.29.4",
+ "main": "lib/main",
"description": "TODO/FIXME highlighting support in Atom",
"engines": {
"atom": "*",
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
- "license": "MIT"
+ "license": "MIT",
+ "providedServices": {
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "provideTodoInjection"
+ }
+ }
+ }
}
diff --git a/packages/language-toml/lib/main.js b/packages/language-toml/lib/main.js
index db2280423..efe8b29c1 100644
--- a/packages/language-toml/lib/main.js
+++ b/packages/language-toml/lib/main.js
@@ -1,24 +1,10 @@
-exports.activate = () => {
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- const HYPERLINK_PATTERN = /\bhttps?:/
-
- atom.grammars.addInjectionPoint('source.toml', {
- type: 'string',
- language(node) {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.toml', {
+ types: ['comment', 'string']
});
-
- atom.grammars.addInjectionPoint('source.toml', {
- type: 'comment',
- language(node) {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.toml', { types: ['comment'] });
};
diff --git a/packages/language-toml/package.json b/packages/language-toml/package.json
index 0436a7eb8..2752ad583 100644
--- a/packages/language-toml/package.json
+++ b/packages/language-toml/package.json
@@ -11,5 +11,17 @@
},
"devDependencies": {
"tree-sitter-toml": "^0.5.1"
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-typescript/grammars/common/highlights.scm b/packages/language-typescript/grammars/common/highlights.scm
index 8757d6b72..0e78ab2dd 100644
--- a/packages/language-typescript/grammars/common/highlights.scm
+++ b/packages/language-typescript/grammars/common/highlights.scm
@@ -9,6 +9,10 @@
(import_specifier
(identifier) @variable.other.assignment.import._LANG_)
+; The "*" in `import * as Foo from './bar'`
+(import_clause
+ (namespace_import "*" @variable.other.assignment.import.all._LANG_))
+
; The "foo" in `import * as foo from './bar'`
(namespace_import
(identifier) @variable.other.assignment.import.namespace._LANG_)
@@ -74,8 +78,6 @@
(asserts "asserts" @keyword.type.asserts._LANG_)
(asserts (identifier) @variable.other.type._LANG_)
-["var" "const" "let"] @storage.type._TYPE_._LANG_
-
; A simple variable declaration:
; The "foo" in `let foo = true`
(variable_declarator
@@ -86,6 +88,11 @@
(assignment_expression
left: (identifier) @variable.other.assignment._LANG_)
+; The "bar" in `foo.bar = true`
+(assignment_expression
+ left: (member_expression
+ property: (property_identifier) @variable.other.assignment.property._LANG_))
+
; The "foo" in `foo += 1`.
(augmented_assignment_expression
left: (identifier) @variable.other.assignment._LANG_)
@@ -130,18 +137,16 @@
value: (assignment_pattern
left: (identifier) @variable.other.assignment.destructuring._LANG_)))
-; A variable array destructuring:
-; The "foo" and "bar" in `let [foo, bar] = something`
-(variable_declarator
- (array_pattern
- (identifier) @variable.other.assignment.destructuring._LANG_))
+; An array-destructured assignment or reassignment, regardless of depth:
+; The "foo" in `[foo] = bar;` and `[[foo]] = bar;`.
+(array_pattern
+ (identifier) @variable.other.assignment.destructuring._LANG_)
-; A variable array destructuring with a default:
-; The "baz" in `let [foo, bar, baz = false] = something`
-(variable_declarator
- (array_pattern
- (assignment_pattern
- (identifier) @variable.other.assignment.destructuring.js)))
+; An array-destructured assignment or reassignment with a default, regardless of depth:
+; The "baz" in `let [foo, bar, baz = false] = something;` and `let [[baz = 5]] = something`;
+(array_pattern
+ (assignment_pattern
+ (identifier) @variable.other.assignment.destructuring._LANG_))
; A variable declaration in a for…(in|of) loop:
; The "foo" in `for (let foo of bar) {`
@@ -265,6 +270,16 @@
(internal_module
name: (identifier) @entity.name.type.namespace._LANG_)
+; The "Bar" of `namespace Foo.Bar` and `namespace Foo.Baz.Bar`.
+(internal_module
+ name: (nested_identifier
+ (identifier) @entity.name.type.namespace._LANG_ .)
+ (#set! isLastNamespaceSegment true))
+
+; The "Foo" and "Baz" of `namespace Foo.Bar` and `namespace Foo.Baz.Bar`.
+(nested_identifier (identifier) @support.type.namespace._LANG_
+ (#is? test.descendantOfType "internal_module")
+ (#is-not? test.rangeWithData isLastNamespaceSegment))
; DECLARATIONS
; ============
@@ -277,65 +292,273 @@
; ==========
(interface_declaration
- name: (_) @entity.name.type.interface._LANG_)
+ name: (_) @entity.name.type.interface._LANG_
+ (#set! capture.final))
; TYPES
; =====
-["var" "let" "const"] @storage.modifier._TYPE_._LANG_
+; These go under `storage.type`/`storage.modifier` because they’re core
+; language constructs.
+["var" "let" "const" "class" "function"] @storage.type._TYPE_._LANG_
["extends" "static" "async" "infer"] @storage.modifier._TYPE_._LANG_
-["class" "function"] @storage.type._TYPE_._LANG_
+(type_arguments "<" @punctuation.definition.parameters.begin.bracket.angle._LANG_
+ (#set! capture.final))
+(type_arguments ">" @punctuation.definition.parameters.end.bracket.angle._LANG_
+ (#set! capture.final))
+
+(type_parameters "<" @punctuation.definition.parameters.begin.bracket.angle._LANG_
+ (#set! capture.final))
+(type_parameters ">" @punctuation.definition.parameters.end.bracket.angle._LANG_
+ (#set! capture.final))
"=>" @storage.type.arrow._LANG_
; TODO: If I allow scopes like `storage.type.string._LANG_`, I will make a lot of
; text look like strings by accident. This really needs to be fixed in syntax
; themes.
-(predefined_type _ @storage.type._LANG_ @support.type._LANG_)
+;
+; NOTE: To settle the long debate (in my head) about whether value types are
+; `support.type` or `storage.type`, I’ve adopted the same compromised used
+; by legacy Tree-sitter: value types are filed under `support.storage.type`.
+
+; These appear to be the primitives like `number`, `string`, `boolean`, `void`,
+; et cetera. `null` and `undefined` get their own nodes.
+(predefined_type _ @support.storage.type.predefined._LANG_)
(type_alias_declaration
name: (type_identifier) @variable.declaration.type._LANG_)
-((literal_type [(null) (undefined)]) @storage.type._TEXT_._LANG_)
-((literal_type [(null) (undefined)]) @support.type._TEXT_._LANG_
- (#set! capture.final true))
+((literal_type [(null) (undefined)]) @support.storage.type._TEXT_._LANG_
+ (#set! capture.final))
; TODO: Decide whether other literal types — strings, booleans, and whatnot —
; should be highlighted as they are in JS, or should be highlighted like other
; types in annotations.
+; These are `storage.type` because they are core language constructs rather
+; than value types.
[
- "implements"
"namespace"
"enum"
"interface"
"module"
"declare"
+] @storage.type._TYPE_._LANG_
+"type" @storage.type._LANG_
+
+; These are `storage.modifier` becase they act as adjectives and verbs for
+; language constructs.
+[
+ "implements"
"public"
"private"
"protected"
"readonly"
"satisfies"
- "type"
] @storage.modifier._TYPE_._LANG_
(index_signature
name: (identifier) @entity.other.attribute-name.type._LANG_)
-((type_identifier) @storage.type._LANG_
- ; (#is? test.descendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
+; The utility types documented at
+; https://www.typescriptlang.org/docs/handbook/utility-types.html.
+(generic_type
+ (type_identifier) @support.storage.type.builtin.utility._LANG_
+ (#match? @support.storage.type.builtin.utility._LANG_ "^(Awaited|Partial|Required|Readonly|Record|Pick|Omit|Exclude|Extract|NonNullable|(?:Constructor)?Parameters|(?:Return|Instance|(?:Omit)?ThisParameter|This)Type|(?:Upper|Lower)case|Capitalize|Uncapitalize)$")
+ (#set! capture.final))
+
+; All core language builtin types.
+((type_identifier) @support.storage.type.builtin._LANG_
+(#match? @support.storage.type.builtin._LANG_ "^(AggregateError|Array|ArrayBuffer|BigInt|BigInt64Array|BigUint64Array|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array|Function|ImageCapture|Int8Array|Int16Array|Int32Array|Map|Object|Promise|Proxy|RangeError|ReferenceError|RegExp|Set|Symbol|SyntaxError|TypeError|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array|URIError|URL|WeakMap|WeakRef|WeakSet|XMLHttpRequest)$")
+ (#set! capture.final))
+
+; TODO: We could add a special scope name to the entire suite of DOM types, but
+; I don't have the strength for that right now.
+
+;
+((type_identifier) @support.storage.other.type._LANG_
)
-; A capture can satisfy more than one of these criteria, so we need to guard
-; against multiple matches. That's why we use `test.final` here, and why the
-; two capture names are applied in separate captures — otherwise `test.final`
-; would be applied after the first capture.
-((type_identifier) @support.type._LANG_
- ; (#is? test.descendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
+; SUPPORT
+; =======
+
+; Array methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Array")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(from|isArray|of)$")
+ (#set! capture.final true))
+
+; Date methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Date")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(now|parse|UTC)$")
+ (#set! capture.final true))
+
+; JSON methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "JSON")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(parse|stringify)$")
+ (#set! capture.final true))
+
+; Math methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Math")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(abs|acos|acosh|asin|asinh|atan|atanh|atan2|cbrt|ceil|clz32|cos|cosh|exp|expm1|floor|fround|hypot|imul|log|log1p|log10|log2|max|min|pow|random|round|sign|sin|sinh|sqrt|tan|tanh|trunc)$")
+ (#set! capture.final true))
+
+; Object methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Object")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(assign|create|defineProperty|defineProperties|entries|freeze|fromEntries|getOwnPropertyDescriptor|getOwnPropertyDescriptors|getOwnPropertyNames|getOwnPropertySymbols|getPrototypeOf|is|isExtensible|isFrozen|isSealed|keys|preventExtensions|seal|setPrototypeOf|values)$")
+ (#set! capture.final true))
+
+; Reflect methods.
+(member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Reflect")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(apply|construct|defineProperty|deleteProperty|get|getOwnPropertyDescriptor|getPrototypeOf|has|isExtensible|ownKeys|preventExtensions|set|setPrototypeOf)$")
+ (#set! capture.final true))
+
+; Intl.X instantiations.
+(new_expression
+ constructor: (member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "Intl")
+ property: (property_identifier) @support.class.builtin._LANG_
+ (#match? @support.class.builtin._LANG_ "^(Collator|DateTimeFormat|DisplayNames|ListFormat|Locale|NumberFormat|PluralRules|Segmenter)$"))
+ (#set! capture.final true))
+
+; Built-in class instantiations.
+(new_expression
+ constructor: (identifier) @support.class.builtin.instance._LANG_
+ (#match? @support.class.builtin.instance._LANG_ "^(AggregateError|Array|ArrayBuffer|BigInt64Array|BigUint64Array|Boolean|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array|Function|ImageCapture|Int8Array|Int16Array|Int32Array|Map|Number|Object|Promise|RangeError|ReferenceError|RegExp|Set|String|SyntaxError|TypeError|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array|URIError|URL|WeakMap|WeakRef|WeakSet|XMLHttpRequest)$")
+ (#set! capture.final true))
+
+; Built-in constructors that can be invoked without `new`.
+(call_expression
+ (identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(AggregateError|Array|ArrayBuffer|Boolean|BigInt|Error|EvalError|Function|Number|Object|Proxy|RangeError|String|Symbol|SyntaxError|URIError)$")
(#set! capture.final true))
+; Built-in functions.
+(call_expression
+ (identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt)$")
+ (#set! capture.final true))
+
+; Built-in `console` functions.
+
+(member_expression
+ object: (identifier) @support.class.builtin.console._LANG_
+ (#eq? @support.class.builtin.console._LANG_ "console")
+ property: (property_identifier) @support.function.builtin.console._LANG_
+ (#match? @support.function.builtin.console._LANG_ "^(assert|clear|count(Reset)?|debug|dir(xml)?|error|group(End)?info|log|profile(End)?|table|time(End|Log|Stamp)?|trace|warn)$")
+ (#set! capture.final true))
+
+; Static methods of `Promise`.
+(member_expression
+ object: (identifier) @support.class.builtin._LANG_
+ (#eq? @support.class.builtin._LANG_ "Promise")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(all|allSettled|any|race|resolve|reject)$")
+ (#set! capture.final true))
+
+; All “well-known” symbols (as they are referred to in the spec).
+(member_expression
+ object: (identifier) @support.class.builtin._LANG_
+ property: (property_identifier) @support.property.builtin._LANG_
+ (#eq? @support.class.builtin._LANG_ "Symbol")
+ (#match? @support.property.builtin._LANG_ "^(asyncIterator|hasInstance|isConcatSpreadable|iterator|match|matchAll|replace|search|split|species|toPrimitive|toStringTag|unscopables)$")
+ (#set! capture.final true))
+
+; Static methods of `Symbol`.
+(member_expression
+ object: (identifier) @support.class.builtin._LANG_
+ (#eq? @support.class.builtin._LANG_ "Symbol")
+ property: (property_identifier) @support.function.builtin._LANG_
+ (#match? @support.function.builtin._LANG_ "^(for|keyFor)$")
+ (#set! capture.final true))
+
+; Other built-in objects.
+((identifier) @support.class.builtin._LANG_
+ (#match? @support.class.builtin._LANG_ "^(Symbol)$")
+ (#set! capture.final true))
+
+; Deprecated built-in functions.
+(call_expression
+ (identifier) @invalid.deprecated.function._LANG_
+ (#match? @invalid.deprecated.function._LANG_ "^(escape|unescape)$")
+ (#set! capture.final true))
+
+; Built-in DOM classes.
+((identifier) @support.class.builtin._LANG_
+ (#match? @support.class.builtin._LANG_ "^(Document|Element|HTMLElement|HTMLDocument|HTML(Select|BR|HR|LI|Div|Map|Mod|Pre|Area|Base|Body|Data|Font|Form|Head|Html|Link|Menu|Meta|Slot|Span|Time|Audio|DList|Embed|Image|Input|Label|Media|Meter|OList|Param|Quote|Style|Table|Title|Track|UList|Video|Anchor|Button|Canvas|Dialog|IFrame|Legend|Object|Option|Output|Script|Source|Content|Details|Heading|Marquee|Picture|Unknown|DataList|FieldSet|FrameSet|MenuItem|OptGroup|Progress|TableCol|TableRow|Template|TextArea|Paragraph|TableCell|Options|TableCaption|TableSection|FormControls))$")
+ (#set! capture.final true))
+
+; Deprecated built-in DOM classes.
+((identifier) @invalid.deprecated.class._LANG_
+ (#match? @invalid.deprecated.class._LANG_ "^(HTMLShadowElement)$")
+ (#set! capture.final true))
+
+; Built-in DOM methods on `document`.
+(call_expression
+ function: (member_expression
+ object: (identifier) @support.object.builtin._LANG_
+ (#eq? @support.object.builtin._LANG_ "document")
+ property: (property_identifier) @support.function.method.builtin._LANG_
+ (#match? @support.function.method.builtin._LANG_ "^(adoptNode|append|caretPositionFromPoint|caretRangeFromPoint|createAttribute(?:NS)?|createCDATASection|createComment|createDocumentFragment|createElement(?:NS)?|createEvent|createNodeIterator|createProcessingInstruction|createRange|createTextNode|createTreeWalker|elementFromPoint|elementsFromPoint|exitFullscreen|exitPictureInPicture|exitPointerLock|getAnimations|getElementById|getElementsByClassName|getElementsByTagName(?:NS)?|getSelection|hasStorageAccess|importNode|prepend|querySelector|querySelectorAll|releaseCapture|replaceChildren|requestStorageAccess|createExpression|createNSResolver|evaluate|getElementsByName|hasFocus|write|writeln|open|close)$")
+ (#set! capture.final true)))
+
+; Built-in DOM methods on nodes. These will show up as builtins on _any_ class, but
+; they're distinctive enough that we're OK with that possibility.
+(call_expression
+ function: (member_expression
+ property: (property_identifier) @support.function.method.builtin._LANG_
+ (#match? @support.function.method.builtin._LANG_ "^(addEventListener|appendChild|cloneNode|compareDocumentPosition|contains|getElementsByClassName|getElementsByTagName(?:NS)?|getRootNode|hasChildNodes|insertBefore|isDefaultNamespace|isEqualNode|isSameNode|lookupPrefix|lookupNamespaceURI|normalize|querySelector|querySelectorAll|removeChild|replaceChild|removeEventListener)$")
+ (#set! capture.final true)))
+
+; BUILTINS
+; ========
+
+((identifier) @support.object.builtin._TEXT_._LANG_
+ (#match? @support.object.builtin._TEXT_._LANG_ "^(arguments|module|window|document)$")
+ (#is-not? local)
+ (#set! capture.final true))
+
+((identifier) @support.object.builtin.filename._LANG_
+ (#eq? @support.object.builtin.filename._LANG_ "__filename")
+ (#is-not? local)
+ (#set! capture.final true))
+
+((identifier) @support.object.builtin.dirname._LANG_
+ (#eq? @support.object.builtin.dirname._LANG_ "__dirname")
+ (#is-not? local)
+ (#set! capture.final true))
+
+((identifier) @support.function.builtin.require._LANG_
+ (#eq? @support.function.builtin.require._LANG_ "require")
+ (#is-not? local)
+ (#set! capture.final true))
+
+((identifier) @constant.language.infinity._LANG_
+ (#eq? @constant.language.infinity._LANG_ "Infinity")
+ (#set! capture.final true))
+
+
; OBJECTS
; =======
@@ -353,14 +576,20 @@
(#match? @constant.other.object._LANG_ "^[_A-Z]+$")
(#set! capture.final true))
+
; The "foo" in `foo.bar`.
(member_expression
object: (identifier) @support.other.object._LANG_)
-; The "bar" in `foo.bar.baz`.
+; The "bar" in `foo.bar`, `foo.bar.baz`, and `foo.bar[baz]`.
(member_expression
- object: (member_expression
- property: (property_identifier) @support.other.object._LANG_))
+ property: (property_identifier) @support.other.property._LANG_)
+
+; ; The "bar" in `foo.bar.baz`.
+; (member_expression
+; object: (member_expression
+; property: (property_identifier) @support.other.object._LANG_)
+; (#set! capture.final))
(method_signature
(property_identifier) @entity.other.attribute-name.method._LANG_)
@@ -369,13 +598,9 @@
(property_identifier) @entity.other.attribute-name._LANG_)
-
; FUNCTIONS
; =========
-(method_definition
- name: (property_identifier) @entity.name.function.method._LANG_)
-
(call_expression
function: (member_expression
property: (property_identifier) @support.other.function.method._LANG_))
@@ -405,6 +630,11 @@
(method_definition
name: (property_identifier) @entity.name.function.method.definition._LANG_)
+; Private field method definitions:
+; the "#foo" in `#foo () {` (inside a class body)
+(method_definition
+ name: (private_property_identifier) @entity.name.function.method.private.definition._LANG_)
+
; Function property assignment:
; The "foo" in `thing.foo = (arg) => {}`
(assignment_expression
@@ -431,193 +661,25 @@
key: (property_identifier) @entity.name.function.method.definition._LANG_
value: [(function) (arrow_function)])
+; Function is `storage.type` because it's a core language construct.
(function "function" @storage.type.function._LANG_)
(function_declaration "function" @storage.type.function._LANG_)
(generator_function "function" @storage.type.function._LANG_)
(generator_function_declaration "function" @storage.type.function._LANG_)
+; The `*` sigil acts as a modifier on a core language construct, hence
+; `storage.modifier`.
(generator_function "*" @storage.modifier.generator._LANG_)
(generator_function_declaration "*" @storage.modifier.generator._LANG_)
(method_definition "*" @storage.modifier.generator._LANG_)
(asserts "asserts" @keyword.control.type.asserts._LANG_)
-; SUPPORT
-; =======
-
-; Array methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Array")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(from|isArray|of)$")
- (#set! capture.final true))
-
-; Date methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Date")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(now|parse|UTC)$")
- (#set! capture.final true))
-
-; JSON methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "JSON")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(parse|stringify)$")
- (#set! capture.final true))
-
-; Math methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Math")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(abs|acos|acosh|asin|asinh|atan|atanh|atan2|cbrt|ceil|clz32|cos|cosh|exp|expm1|floor|fround|hypot|imul|log|log1p|log10|log2|max|min|pow|random|round|sign|sin|sinh|sqrt|tan|tanh|trunc)$")
- (#set! capture.final true))
-
-; Object methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Object")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(assign|create|defineProperty|defineProperties|entries|freeze|fromEntries|getOwnPropertyDescriptor|getOwnPropertyDescriptors|getOwnPropertyNames|getOwnPropertySymbols|getPrototypeOf|is|isExtensible|isFrozen|isSealed|keys|preventExtensions|seal|setPrototypeOf|values)$")
- (#set! capture.final true))
-
-; Reflect methods.
-(member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Reflect")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(apply|construct|defineProperty|deleteProperty|get|getOwnPropertyDescriptor|getPrototypeOf|has|isExtensible|ownKeys|preventExtensions|set|setPrototypeOf)$")
- (#set! capture.final true))
-
-; Intl.X instantiations.
-(new_expression
- constructor: (member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "Intl")
- property: (property_identifier) @support.class.builtin.js
- (#match? @support.class.builtin.js "^(Collator|DateTimeFormat|DisplayNames|ListFormat|Locale|NumberFormat|PluralRules|Segmenter)$"))
- (#set! capture.final true))
-
-; Built-in class instantiations.
-(new_expression
- constructor: (identifier) @support.class.builtin.instance.js
- (#match? @support.class.builtin.instance.js "^(AggregateError|Array|ArrayBuffer|BigInt64Array|BigUint64Array|Boolean|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array|Function|ImageCapture|Int8Array|Int16Array|Int32Array|Map|Number|Object|Promise|RangeError|ReferenceError|RegExp|Set|String|SyntaxError|TypeError|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array|URIError|URL|WeakMap|WeakRef|WeakSet|XMLHttpRequest)$")
- (#set! capture.final true))
-
-; Built-in constructors that can be invoked without `new`.
+; An invocation of any function.
(call_expression
- (identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(AggregateError|Array|ArrayBuffer|Boolean|BigInt|Error|EvalError|Function|Number|Object|Proxy|RangeError|String|Symbol|SyntaxError|URIError)$")
- (#set! capture.final true))
-
-; Built-in functions.
-(call_expression
- (identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt)$")
- (#set! capture.final true))
-
-; Built-in `console` functions.
-
-(member_expression
- object: (identifier) @support.class.builtin.console.js
- (#eq? @support.class.builtin.console.js "console")
- property: (property_identifier) @support.function.builtin.console.js
- (#match? @support.function.builtin.console.js "^(assert|clear|count(Reset)?|debug|dir(xml)?|error|group(End)?info|log|profile(End)?|table|time(End|Log|Stamp)?|trace|warn)$")
- (#set! capture.final true))
-
-; Static methods of `Promise`.
-(member_expression
- object: (identifier) @support.class.builtin.js
- (#eq? @support.class.builtin.js "Promise")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(all|allSettled|any|race|resolve|reject)$")
- (#set! capture.final true))
-
-; All “well-known” symbols (as they are referred to in the spec).
-(member_expression
- object: (identifier) @support.class.builtin.js
- property: (property_identifier) @support.property.builtin.js
- (#eq? @support.class.builtin.js "Symbol")
- (#match? @support.property.builtin.js "^(asyncIterator|hasInstance|isConcatSpreadable|iterator|match|matchAll|replace|search|split|species|toPrimitive|toStringTag|unscopables)$")
- (#set! capture.final true))
-
-; Static methods of `Symbol`.
-(member_expression
- object: (identifier) @support.class.builtin.js
- (#eq? @support.class.builtin.js "Symbol")
- property: (property_identifier) @support.function.builtin.js
- (#match? @support.function.builtin.js "^(for|keyFor)$")
- (#set! capture.final true))
-
-; Other built-in objects.
-((identifier) @support.class.builtin.js
- (#match? @support.class.builtin.js "^(Symbol)$")
- (#set! capture.final true))
-
-; Deprecated built-in functions.
-(call_expression
- (identifier) @invalid.deprecated.function.js
- (#match? @invalid.deprecated.function.js "^(escape|unescape)$")
- (#set! capture.final true))
-
-; Built-in DOM classes.
-((identifier) @support.class.builtin.js
- (#match? @support.class.builtin.js "^(Document|Element|HTMLElement|HTMLDocument|HTML(Select|BR|HR|LI|Div|Map|Mod|Pre|Area|Base|Body|Data|Font|Form|Head|Html|Link|Menu|Meta|Slot|Span|Time|Audio|DList|Embed|Image|Input|Label|Media|Meter|OList|Param|Quote|Style|Table|Title|Track|UList|Video|Anchor|Button|Canvas|Dialog|IFrame|Legend|Object|Option|Output|Script|Source|Content|Details|Heading|Marquee|Picture|Unknown|DataList|FieldSet|FrameSet|MenuItem|OptGroup|Progress|TableCol|TableRow|Template|TextArea|Paragraph|TableCell|Options|TableCaption|TableSection|FormControls))$")
- (#set! capture.final true))
-
-; Deprecated built-in DOM classes.
-((identifier) @invalid.deprecated.class.js
- (#match? @invalid.deprecated.class.js "^(HTMLShadowElement)$")
- (#set! capture.final true))
-
-; Built-in DOM methods on `document`.
-(call_expression
- function: (member_expression
- object: (identifier) @support.object.builtin.js
- (#eq? @support.object.builtin.js "document")
- property: (property_identifier) @support.function.method.builtin.js
- (#match? @support.function.method.builtin.js "^(adoptNode|append|caretPositionFromPoint|caretRangeFromPoint|createAttribute(?:NS)?|createCDATASection|createComment|createDocumentFragment|createElement(?:NS)?|createEvent|createNodeIterator|createProcessingInstruction|createRange|createTextNode|createTreeWalker|elementFromPoint|elementsFromPoint|exitFullscreen|exitPictureInPicture|exitPointerLock|getAnimations|getElementById|getElementsByClassName|getElementsByTagName(?:NS)?|getSelection|hasStorageAccess|importNode|prepend|querySelector|querySelectorAll|releaseCapture|replaceChildren|requestStorageAccess|createExpression|createNSResolver|evaluate|getElementsByName|hasFocus|write|writeln|open|close)$")
- (#set! capture.final true)))
-
-; Built-in DOM methods on nodes. These will show up as builtins on _any_ class, but
-; they're distinctive enough that we're OK with that possibility.
-(call_expression
- function: (member_expression
- property: (property_identifier) @support.function.method.builtin.js
- (#match? @support.function.method.builtin.js "^(addEventListener|appendChild|cloneNode|compareDocumentPosition|contains|getElementsByClassName|getElementsByTagName(?:NS)?|getRootNode|hasChildNodes|insertBefore|isDefaultNamespace|isEqualNode|isSameNode|lookupPrefix|lookupNamespaceURI|normalize|querySelector|querySelectorAll|removeChild|replaceChild|removeEventListener)$")
- (#set! capture.final true)))
-
-; BUILTINS
-; ========
-
-((identifier) @support.object.builtin._TEXT_._LANG_
- (#match? @support.object.builtin._TEXT_._LANG_ "^(arguments|module|window|document)$")
- (#is-not? local)
- (#set! capture.final true))
-
-((identifier) @support.object.builtin.filename._LANG_
- (#eq? @support.object.builtin.filename._LANG_ "__filename")
- (#is-not? local)
- (#set! capture.final true))
-
-((identifier) @support.object.builtin.dirname._LANG_
- (#eq? @support.object.builtin.dirname._LANG_ "__dirname")
- (#is-not? local)
- (#set! capture.final true))
-
-((identifier) @support.function.builtin.require._LANG_
- (#eq? @support.function.builtin.require._LANG_ "require")
- (#is-not? local)
- (#set! capture.final true))
-
-((identifier) @constant.language.infinity._LANG_
- (#eq? @constant.language.infinity._LANG_ "Infinity")
- (#set! capture.final true))
+ function: (identifier) @support.other.function._LANG_
+ (#set! capture.shy true))
; Things that `LOOK_LIKE_CONSTANTS`.
([(property_identifier) (identifier)] @constant.other._LANG_
@@ -733,24 +795,21 @@
"||="
] @keyword.operator.assignment.compound._LANG_
-[
- "+"
- "-"
- "*"
- "/"
- "%"
-] @keyword.operator.arithmetic._LANG_
+(binary_expression
+ ["+" "-" "*" "/" "%"] @keyword.operator.arithmetic._LANG_)
-[
- "=="
- "==="
- "!="
- "!=="
- ">="
- "<="
- ">"
- "<"
-] @keyword.operator.comparison._LANG_
+(binary_expression
+ [
+ "=="
+ "==="
+ "!="
+ "!=="
+ ">="
+ "<="
+ ">"
+ "<"
+ ] @keyword.operator.comparison._LANG_
+)
["++" "--"] @keyword.operator.increment._LANG_
@@ -783,10 +842,17 @@
(ternary_expression
- ["?" ":"] @keyword.operator.ternary._LANG_)
+ ["?" ":"] @keyword.operator.ternary._LANG_
+ (#set! capture.final))
(conditional_type
- ["?" ":"] @keyword.operator.ternary._LANG_)
+ ["?" ":"] @keyword.operator.ternary._LANG_
+ (#set! capture.final))
+
+; Try to highlight `?` like an operator while the user is typing without
+; waiting for its paired `:`.
+("?" @keyword.operator.ternary._LANG_
+ (#is? test.descendantOfType "ERROR"))
; PUNCTUATION
; ===========
diff --git a/packages/language-typescript/grammars/common/indents.scm b/packages/language-typescript/grammars/common/indents.scm
index 427f19f0c..20329696a 100644
--- a/packages/language-typescript/grammars/common/indents.scm
+++ b/packages/language-typescript/grammars/common/indents.scm
@@ -16,12 +16,19 @@
(#is? test.last true))
(#set! indent.matchIndentOf parent.startPosition))
-; 'case' and 'default' need to be indented one level more than their containing
-; `switch`. TODO: Might need to make this configurable.
+; By default, `case` and `default` need to be indented one level more than their containing
+; `switch`.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is-not? test.config "language-typescript.indentation.alignCaseWithSwitch"))
+; When this config setting is enabled, `case` and `default` need to be indented
+; to match their containing `switch`.
+(["case" "default"] @match
+ (#set! indent.matchIndentOf parent.parent.startPosition)
+ (#set! indent.offsetIndent 0)
+ (#is? test.config "language-typescript.indentation.alignCaseWithSwitch"))
; ONE-LINE CONDITIONALS
; =====================
@@ -29,10 +36,12 @@
; An `if` statement without an opening brace should indent the next line…
(if_statement
condition: (parenthesized_expression ")" @indent
- (#is? test.lastTextOnRow true)))
+ (#is? test.lastTextOnRow true)
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf")))
; (as should a braceless `else`…)
("else" @indent
- (#is? test.lastTextOnRow true))
+ (#is? test.lastTextOnRow true)
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf"))
; …and keep that indent level if the user types a comment before the
; consequence…
@@ -40,7 +49,8 @@
consequence: (empty_statement) @match
(#is-not? test.startsOnSameRowAs parent.startPosition)
(#set! indent.matchIndentOf parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf"))
; …and keep that indent level after the user starts typing…
(if_statement
@@ -57,7 +67,8 @@
; of an `expression_statement`, for some reason.
(#not-match? @match "^\\s*{")
(#set! indent.matchIndentOf parent.startPosition)
- (#set! indent.offsetIndent 1))
+ (#set! indent.offsetIndent 1)
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf"))
; …but dedent after exactly one statement.
(if_statement
@@ -72,7 +83,8 @@
] @dedent.next
; When an opening curly brace is unpaired, it might get interpreted as part
; of an `expression_statement`, for some reason.
- (#not-match? @dedent.next "^\\s*{"))
+ (#not-match? @dedent.next "^\\s*{")
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf"))
(else_clause
[
@@ -83,7 +95,8 @@
(throw_statement)
(debugger_statement)
] @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ (#is? test.config "language-typescript.indentation.indentAfterBracelessIf"))
; HANGING INDENT ON SPLIT LINES
; =============================
@@ -92,14 +105,22 @@
; `config` scope test.
; Any of these at the end of a line indicate the next line should be indented…
-(["||" "&&" "?"] @indent
+(["||" "&&"] @indent
+ (#is? test.config "language-typescript.indentation.addHangingIndentAfterLogicalOperators")
(#is? test.lastTextOnRow true))
-; …and the line after that should be dedented.
+("?" @indent
+ (#is? test.config "language-typescript.indentation.addHangingIndentAfterTernaryOperators")
+ (#is? test.lastTextOnRow true))
+
+; …and the line after that should be dedented…
(binary_expression
["||" "&&"]
right: (_) @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
+ (#is? test.config "language-typescript.indentation.addHangingIndentAfterLogicalOperators")
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ ; …unless the right side of the expression spans multiple lines.
+ (#is? test.endsOnSameRowAs startPosition))
; …unless it's a ternary, in which case the dedent should wait until the
; alternative clause.
@@ -110,7 +131,11 @@
;
(ternary_expression
alternative: (_) @dedent.next
- (#is-not? test.startsOnSameRowAs parent.startPosition))
+ (#is? test.config "language-typescript.indentation.addHangingIndentAfterTernaryOperators")
+ (#is-not? test.startsOnSameRowAs parent.startPosition)
+ ; Only dedent the next line if the alternative doesn't itself span multiple
+ ; lines.
+ (#is? test.endsOnSameRowAs startPosition))
; GENERAL
@@ -120,17 +145,24 @@
(template_substitution "}" @_IGNORE_ (#set! capture.final true))
-[
- "{"
- "("
- "["
-] @indent
+; As strange as it may seem to make all of these basic indentation hints
+; configurable, some brace styles are incompatible with some of these choices;
+; see https://github.com/orgs/pulsar-edit/discussions/249.
+("{" @indent
+ (#is? test.config "language-typescript.indentation.indentBraces"))
+("}" @dedent
+ (#is? test.config "language-typescript.indentation.indentBraces"))
+
+("[" @indent
+ (#is? test.config "language-typescript.indentation.indentBrackets"))
+("]" @dedent
+ (#is? test.config "language-typescript.indentation.indentBrackets"))
+
+("(" @indent
+ (#is? test.config "language-typescript.indentation.indentParentheses"))
+(")" @dedent
+ (#is? test.config "language-typescript.indentation.indentParentheses"))
-[
- "}"
- ")"
- "]"
-] @dedent
(type_parameters "<" @indent)
(type_parameters ">" @dedent)
diff --git a/packages/language-typescript/grammars/modern-tree-sitter-regex.cson b/packages/language-typescript/grammars/modern-tree-sitter-regex.cson
new file mode 100644
index 000000000..66e8e414c
--- /dev/null
+++ b/packages/language-typescript/grammars/modern-tree-sitter-regex.cson
@@ -0,0 +1,10 @@
+scopeName: 'source.ts.regexp'
+type: 'modern-tree-sitter'
+parser: 'tree-sitter-regex'
+
+injectionRegex: '^(ts-regex)$'
+
+treeSitter:
+ parserSource: 'github:tree-sitter/tree-sitter-regex#2354482d7e2e8f8ff33c1ef6c8aa5690410fbc96'
+ grammar: 'tree-sitter-regex/tree-sitter-regex.wasm'
+ highlightsQuery: 'tree-sitter-regex/highlights.scm'
diff --git a/packages/language-javascript/grammars/ts/regex/highlights.scm b/packages/language-typescript/grammars/tree-sitter-regex/highlights.scm
similarity index 81%
rename from packages/language-javascript/grammars/ts/regex/highlights.scm
rename to packages/language-typescript/grammars/tree-sitter-regex/highlights.scm
index b0018b6c7..ce8ed0a90 100644
--- a/packages/language-javascript/grammars/ts/regex/highlights.scm
+++ b/packages/language-typescript/grammars/tree-sitter-regex/highlights.scm
@@ -1,7 +1,3 @@
-; CAVEATS:
-;
-; * No support for lookbehind as of March 2023 (waiting on
-; https://github.com/tree-sitter/tree-sitter-regex/pull/15)
(non_capturing_group) @meta.group.non-capturing.regexp
@@ -17,6 +13,8 @@
[
(boundary_assertion)
+ (start_assertion)
+ (end_assertion)
] @keyword.control.anchor.regexp
[
@@ -24,10 +22,10 @@
(lazy)
] @keyword.operator.quantifier.regexp
-((lookahead_assertion) @keyword.operator.lookahead.regexp
+((lookaround_assertion) @keyword.operator.lookaround.regexp
(#set! adjust.startAndEndAroundFirstMatchOf "\\?="))
-((lookahead_assertion) @keyword.operator.lookahead.negated.regexp
+((lookaround_assertion) @keyword.operator.lookaround.negated.regexp
(#set! adjust.startAndEndAroundFirstMatchOf "\\?!"))
((non_capturing_group) @keyword.operator.group.non-capturing.regexp
diff --git a/packages/language-typescript/grammars/tree-sitter-regex/tree-sitter-regex.wasm b/packages/language-typescript/grammars/tree-sitter-regex/tree-sitter-regex.wasm
new file mode 100755
index 000000000..dd1988d42
Binary files /dev/null and b/packages/language-typescript/grammars/tree-sitter-regex/tree-sitter-regex.wasm differ
diff --git a/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm b/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm
index c8b90d23a..c3a11ab01 100644
--- a/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm
+++ b/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm
@@ -9,18 +9,33 @@
; The "Foo" in ``.
(jsx_opening_element
- name: (identifier) @entity.name.tag.ts.tsx)
+ name: (identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx
+
+; The "Foo.Bar" in ``.
+(jsx_opening_element
+ name: (nested_identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx
; The "Foo" in ``.
(jsx_closing_element
- "/" @punctuation.definition.tag.end.ts.tsx
- (#set! capture.final true)
- name: (identifier) @entity.name.tag.ts.tsx)
+ name: (identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx
+
+; The "Foo.Bar" in ``.
+(jsx_closing_element
+ name: (nested_identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx
; The "bar" in ``.
(jsx_attribute
(property_identifier) @entity.other.attribute-name.ts.tsx)
+; The empty tag used as a shorthand for a fragment: `<>`.
+(jsx_fragment) @meta.tag.ts.tsx
+
+; The slashes in closing tags should not be interpreted as math operators.
+(jsx_self_closing_element "/" @punctuation.definition.tag.end.ts.tsx
+ (#set! capture.final true))
+(jsx_closing_element "/" @punctuation.definition.tag.end.ts.tsx
+ (#set! capture.final true))
+
; All JSX expressions/interpolations within braces.
((jsx_expression) @meta.embedded.block.ts.tsx
(#match? @meta.embedded.block.ts.tsx "\\n")
@@ -28,6 +43,18 @@
(jsx_expression) @meta.embedded.line.ts.tsx
+(jsx_opening_element
+ "<" @punctuation.definition.tag.begin.ts.tsx
+ ">" @punctuation.definition.tag.end.ts.tsx)
+
+(jsx_closing_element
+ "<" @punctuation.definition.tag.begin.ts.tsx
+ ">" @punctuation.definition.tag.end.ts.tsx)
+
+(jsx_fragment
+ "<" @punctuation.definition.tag.begin.ts.tsx
+ ">" @punctuation.definition.tag.end.ts.tsx)
+
(jsx_self_closing_element
"<" @punctuation.definition.tag.begin.ts.tsx
(#set! capture.final true))
@@ -40,6 +67,7 @@
(#set! capture.final true))
+
; META
; ====
diff --git a/packages/language-typescript/lib/main.js b/packages/language-typescript/lib/main.js
index dd7f40051..339496549 100644
--- a/packages/language-typescript/lib/main.js
+++ b/packages/language-typescript/lib/main.js
@@ -58,38 +58,31 @@ exports.activate = function () {
atom.grammars.addInjectionPoint(scopeName, {
type: 'regex_pattern',
language() {
- return 'js-regex';
+ return 'ts-regex';
},
content(regex) {
return regex;
},
languageScope: null
});
-
- atom.grammars.addInjectionPoint(scopeName, {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
- for (let type of ['template_string', 'string_fragment', 'comment']) {
- atom.grammars.addInjectionPoint(scopeName, {
- type,
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
- }
}
};
-const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
-const HYPERLINK_PATTERN = /\bhttps?:/
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ for (const scopeName of ['source.ts', 'source.tsx', 'source.flow']) {
+ hyperlink.addInjectionPoint(scopeName, {
+ types: ['template_string', 'string_fragment', 'comment']
+ });
+ }
+};
+
+exports.consumeTodoInjection = (todo) => {
+ for (const scopeName of ['source.ts', 'source.tsx', 'source.flow']) {
+ todo.addInjectionPoint(scopeName, { types: ['comment'] });
+ }
+};
+
+
const STYLED_REGEX = /\bstyled\b/i;
function languageStringForTemplateTag(tag) {
diff --git a/packages/language-typescript/package.json b/packages/language-typescript/package.json
index bf01bce0f..a8b8d4365 100644
--- a/packages/language-typescript/package.json
+++ b/packages/language-typescript/package.json
@@ -14,5 +14,74 @@
"license": "MIT",
"dependencies": {
"tree-sitter-typescript": "0.20.1"
+ },
+ "configSchema": {
+ "indentation": {
+ "title": "Indentation",
+ "type": "object",
+ "properties": {
+ "indentBraces": {
+ "title": "Indent Curly Braces",
+ "type": "boolean",
+ "default": true,
+ "order": 1,
+ "description": "Indent after `{`."
+ },
+ "indentBrackets": {
+ "title": "Indent Brackets",
+ "type": "boolean",
+ "default": true,
+ "order": 2,
+ "description": "Indent after `[`."
+ },
+ "indentParentheses": {
+ "title": "Indent Parentheses",
+ "type": "boolean",
+ "default": true,
+ "order": 3,
+ "description": "Indent after `(`."
+ },
+ "alignCaseWithSwitch": {
+ "title": "Align “case” With ”switch”",
+ "type": "boolean",
+ "default": false,
+ "order": 4,
+ "description": "When enabled, `case` and `default` statements in `switch` blocks will match the indent level of the enclosing `switch` instead of indenting themselves one level."
+ },
+ "indentAfterBracelessIf": {
+ "title": "Indent After Braceless “if” And “else”",
+ "type": "boolean",
+ "default": true,
+ "order": 5,
+ "description": "When enabled, `if` and `else` statements without a brace on the initial line will trigger an indent, then a dedent after a single statement. Disable if your brace style is incompatible with this pattern."
+ },
+ "addHangingIndentAfterLogicalOperators": {
+ "title": "Add Hanging Indent After Logical Operators",
+ "type": "boolean",
+ "default": true,
+ "order": 6,
+ "description": "When enabled, will add a hanging indent when a line ends with `&&` or `||`, continuing the indent until the end of the statement."
+ },
+ "addHangingIndentAfterTernaryOperators": {
+ "title": "Add Hanging Indent After Ternary Operators",
+ "type": "boolean",
+ "default": true,
+ "order": 7,
+ "description": "When enabled, will add a hanging indent when a line ends with `?`, continuing the indent through the ensuing `:` until the end of the statement."
+ }
+ }
+ }
+ },
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
}
}
diff --git a/packages/language-typescript/settings/TypeScriptReact.cson b/packages/language-typescript/settings/TypeScriptReact.cson
index 4ffd62638..b99255b1f 100644
--- a/packages/language-typescript/settings/TypeScriptReact.cson
+++ b/packages/language-typescript/settings/TypeScriptReact.cson
@@ -17,3 +17,12 @@
'commentEnd': ' */}',
'increaseIndentPattern': "{[^}\"']*$|\\[[^\\]\"']*$|\\([^)\"']*$|<[a-zA-Z][^/]*$|^\\s*>$",
'decreaseIndentPattern': "^\\s*(\\s*/[*].*[*]/\\s*)*[}\\])]|^\\s*(|/>)"
+
+'.meta.tag.tsx .meta.block.ts.tsx':
+ editor:
+ commentStart: '// '
+ comments:
+ line: '//'
+ block:
+ start: '/*'
+ end: '*/'
diff --git a/packages/language-yaml/grammars/tree-sitter/highlights.scm b/packages/language-yaml/grammars/tree-sitter/highlights.scm
index 2f45509bb..cf7d804b6 100644
--- a/packages/language-yaml/grammars/tree-sitter/highlights.scm
+++ b/packages/language-yaml/grammars/tree-sitter/highlights.scm
@@ -33,7 +33,7 @@
; STRINGS
; =======
-((string_scalar) @string.quoted.yaml)
+((string_scalar) @string.unquoted.yaml)
(single_quote_scalar) @string.quoted.single.yaml
diff --git a/packages/language-yaml/lib/main.js b/packages/language-yaml/lib/main.js
index deab0a708..793695407 100644
--- a/packages/language-yaml/lib/main.js
+++ b/packages/language-yaml/lib/main.js
@@ -1,23 +1,10 @@
-exports.activate = () => {
- const HYPERLINK_PATTERN = /\bhttps?:/
- const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
- atom.grammars.addInjectionPoint('source.yaml', {
- type: 'comment',
- language: (node) => {
- return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
- },
- content: (node) => node,
- languageScope: null
+exports.consumeHyperlinkInjection = (hyperlink) => {
+ hyperlink.addInjectionPoint('source.yaml', {
+ types: ['comment']
});
-
- atom.grammars.addInjectionPoint('source.yaml', {
- type: 'comment',
- language: (node) => {
- return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
- },
- content: (node) => node,
- languageScope: null
- });
-
+};
+
+exports.consumeTodoInjection = (todo) => {
+ todo.addInjectionPoint('source.yaml', { types: ['comment'] });
};
diff --git a/packages/language-yaml/package.json b/packages/language-yaml/package.json
index 47957ef42..52fc83dfb 100644
--- a/packages/language-yaml/package.json
+++ b/packages/language-yaml/package.json
@@ -8,5 +8,17 @@
"node": ">=12"
},
"repository": "https://github.com/pulsar-edit/pulsar",
- "license": "MIT"
+ "license": "MIT",
+ "consumedServices": {
+ "hyperlink.injection": {
+ "versions": {
+ "0.1.0": "consumeHyperlinkInjection"
+ }
+ },
+ "todo.injection": {
+ "versions": {
+ "0.1.0": "consumeTodoInjection"
+ }
+ }
+ }
}
diff --git a/packages/one-dark-syntax/styles/syntax-legacy/_base.less b/packages/one-dark-syntax/styles/syntax-legacy/_base.less
index c21e13c5b..e71c968b2 100644
--- a/packages/one-dark-syntax/styles/syntax-legacy/_base.less
+++ b/packages/one-dark-syntax/styles/syntax-legacy/_base.less
@@ -82,6 +82,13 @@
}
}
+// Style `property` (anywhere in a scope name) like `variable` to keep
+// continuity of appearance after some scope name changes in
+// JavaScript/TypeScript.
+.syntax--property {
+ color: @hue-5;
+}
+
.syntax--variable {
color: @hue-5;
@@ -313,4 +320,10 @@
&.syntax--raw {
color: @hue-4;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @mono-3;
+ }
}
diff --git a/packages/one-dark-syntax/styles/syntax-legacy/json.less b/packages/one-dark-syntax/styles/syntax-legacy/json.less
index b179ca91d..0969ac8c4 100644
--- a/packages/one-dark-syntax/styles/syntax-legacy/json.less
+++ b/packages/one-dark-syntax/styles/syntax-legacy/json.less
@@ -8,6 +8,13 @@
}
}
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @hue-5;
+ }
+ }
+
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
diff --git a/packages/one-light-syntax/styles/syntax-legacy/_base.less b/packages/one-light-syntax/styles/syntax-legacy/_base.less
index ac2a34a90..7b3f887c8 100644
--- a/packages/one-light-syntax/styles/syntax-legacy/_base.less
+++ b/packages/one-light-syntax/styles/syntax-legacy/_base.less
@@ -82,6 +82,13 @@
}
}
+// Style `property` (anywhere in a scope name) like `variable` to keep
+// continuity of appearance after some scope name changes in
+// JavaScript/TypeScript.
+.syntax--property {
+ color: @hue-5;
+}
+
.syntax--variable {
color: @hue-5;
@@ -313,4 +320,10 @@
&.syntax--raw {
color: @hue-4;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @mono-3;
+ }
}
diff --git a/packages/one-light-syntax/styles/syntax-legacy/json.less b/packages/one-light-syntax/styles/syntax-legacy/json.less
index 6bbce0639..1b4105251 100644
--- a/packages/one-light-syntax/styles/syntax-legacy/json.less
+++ b/packages/one-light-syntax/styles/syntax-legacy/json.less
@@ -8,6 +8,13 @@
}
}
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @hue-5;
+ }
+ }
+
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json,
.syntax--meta.syntax--structure.syntax--array.syntax--json {
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
diff --git a/packages/pulsar-updater/spec/.eslintrc b/packages/pulsar-updater/spec/.eslintrc
new file mode 100644
index 000000000..097feaa4c
--- /dev/null
+++ b/packages/pulsar-updater/spec/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "env": { "jasmine": true },
+ "rules": {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+}
diff --git a/packages/solarized-dark-syntax/index.less b/packages/solarized-dark-syntax/index.less
index 66afd1ccf..b1a8c3699 100644
--- a/packages/solarized-dark-syntax/index.less
+++ b/packages/solarized-dark-syntax/index.less
@@ -12,6 +12,7 @@
@import "styles/syntax-legacy/css.less";
// @import "styles/syntax-legacy/go.less";
@import "styles/syntax-legacy/java.less";
+@import "styles/syntax-legacy/json.less";
// @import "styles/syntax-legacy/javascript.less";
@import "styles/syntax-legacy/markdown.less";
@import "styles/syntax-legacy/markup.less";
diff --git a/packages/solarized-dark-syntax/styles/syntax-legacy/json.less b/packages/solarized-dark-syntax/styles/syntax-legacy/json.less
new file mode 100644
index 000000000..cdfa507c0
--- /dev/null
+++ b/packages/solarized-dark-syntax/styles/syntax-legacy/json.less
@@ -0,0 +1,11 @@
+
+.syntax--source.syntax--json {
+
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @blue;
+ }
+ }
+
+}
diff --git a/packages/solarized-dark-syntax/styles/syntax-legacy/markup.less b/packages/solarized-dark-syntax/styles/syntax-legacy/markup.less
index dd798c269..dc58be982 100644
--- a/packages/solarized-dark-syntax/styles/syntax-legacy/markup.less
+++ b/packages/solarized-dark-syntax/styles/syntax-legacy/markup.less
@@ -37,4 +37,10 @@
color: @syntax-comment-color;
font-style: italic;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @syntax-comment-color;
+ }
}
diff --git a/packages/solarized-light-syntax/index.less b/packages/solarized-light-syntax/index.less
index 66afd1ccf..b1a8c3699 100644
--- a/packages/solarized-light-syntax/index.less
+++ b/packages/solarized-light-syntax/index.less
@@ -12,6 +12,7 @@
@import "styles/syntax-legacy/css.less";
// @import "styles/syntax-legacy/go.less";
@import "styles/syntax-legacy/java.less";
+@import "styles/syntax-legacy/json.less";
// @import "styles/syntax-legacy/javascript.less";
@import "styles/syntax-legacy/markdown.less";
@import "styles/syntax-legacy/markup.less";
diff --git a/packages/solarized-light-syntax/styles/syntax-legacy/json.less b/packages/solarized-light-syntax/styles/syntax-legacy/json.less
new file mode 100644
index 000000000..cdfa507c0
--- /dev/null
+++ b/packages/solarized-light-syntax/styles/syntax-legacy/json.less
@@ -0,0 +1,11 @@
+
+.syntax--source.syntax--json {
+
+ // Color JSON keys differently from other strings.
+ .syntax--meta.syntax--structure.syntax--key {
+ .syntax--string.syntax--quoted.syntax--double {
+ color: @blue;
+ }
+ }
+
+}
diff --git a/packages/solarized-light-syntax/styles/syntax-legacy/markup.less b/packages/solarized-light-syntax/styles/syntax-legacy/markup.less
index dd798c269..dc58be982 100644
--- a/packages/solarized-light-syntax/styles/syntax-legacy/markup.less
+++ b/packages/solarized-light-syntax/styles/syntax-legacy/markup.less
@@ -37,4 +37,10 @@
color: @syntax-comment-color;
font-style: italic;
}
+
+ // Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
+ // we assign the color of a comment to this new scope.
+ &.syntax--horizontal-rule {
+ color: @syntax-comment-color;
+ }
}
diff --git a/packages/styleguide/spec/.eslintrc.js b/packages/styleguide/spec/.eslintrc.js
new file mode 100644
index 000000000..5226d6921
--- /dev/null
+++ b/packages/styleguide/spec/.eslintrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+ env: { jasmine: true },
+ rules: {
+ "node/no-unpublished-require": "off",
+ "node/no-extraneous-require": "off",
+ "no-unused-vars": "off",
+ "no-empty": "off"
+ }
+};
diff --git a/script/validate-wasm-grammar-prs.js b/script/validate-wasm-grammar-prs.js
index 706ce1f78..ff208903b 100644
--- a/script/validate-wasm-grammar-prs.js
+++ b/script/validate-wasm-grammar-prs.js
@@ -62,6 +62,11 @@ if (wasmFilesChanged.length === 0) {
// are also accompanied by a change in the `parserSource` key
for (const wasmFile of wasmFilesChanged) {
+ // Ignore files that have been deleted or moved.
+ if (!fs.existsSync(wasmFile)) {
+ console.log(`Skipping file that no longer exists: ${wasmFile}`);
+ continue;
+ }
const wasmPath = path.dirname(wasmFile);
// Don't check the base `tree-sitter.wasm` file.
diff --git a/spec/config-spec.js b/spec/config-spec.js
index b6b63c65c..f292542f9 100644
--- a/spec/config-spec.js
+++ b/spec/config-spec.js
@@ -150,6 +150,41 @@ describe('Config', () => {
).toBe(4);
});
+ it("merges project-specific settings with other settings when the keypath is an object", () => {
+ atom.config.set('x.y', 1);
+ atom.config.set('x.z', "fibrinolysis");
+
+ atom.project.replace({
+ originPath: 'TEST',
+ paths: atom.project.getPaths(),
+ config: {
+ "*": {
+ "x": {
+ "y": 4
+ }
+ }
+ }
+ });
+
+ // Project-specific settings work fine, as the spec below shows, when
+ // the value being retrieved is a primitive. But until recently, Pulsar
+ // didn't know what to do if the value being retrieved was an object.
+ //
+ // Imagine asking for _all_ config settings. The non-project-specific
+ // lookup returns everything. The project-specific lookup returns only
+ // a few overrides. Pulsar needs to _blend_ these two objects, but was
+ // previously choosing the project-specific lookup just because it
+ // wasn't undefined.
+
+ // Here we demonstrate that it now retrieves an object for the given
+ // key path at the normal location and applies a project-specific
+ // “patch…”
+ expect(atom.config.get('x')).toEqual({ y: 4, z: "fibrinolysis" })
+
+ // …without any general settings leaking into the project config.
+ expect(atom.config.projectSettings.x.z).toBeUndefined();
+ });
+
it("ignores the project-specific source when 'excludeSources' tells it to", () => {
atom.config.set('x.y', 1);
diff --git a/spec/grammar-registry-spec.js b/spec/grammar-registry-spec.js
index fb5c33b42..d8e932e46 100644
--- a/spec/grammar-registry-spec.js
+++ b/spec/grammar-registry-spec.js
@@ -258,7 +258,7 @@ describe('GrammarRegistry', () => {
// TODO: Why doesn't this path resolution work like the one above?
const modernTreeSitterGrammar = grammarRegistry.loadGrammarSync(
require.resolve(
- '../packages/language-javascript/grammars/tree-sitter-2-javascript.cson'
+ '../packages/language-javascript/grammars/modern-tree-sitter-javascript.cson'
)
);
expect(buffer.getLanguageMode().grammar).toBe(modernTreeSitterGrammar);
@@ -285,7 +285,7 @@ describe('GrammarRegistry', () => {
// TODO: Why doesn't this path resolution work like the one above?
const modernTreeSitterGrammar = grammarRegistry.loadGrammarSync(
require.resolve(
- '../packages/language-javascript/grammars/tree-sitter-2-javascript.cson'
+ '../packages/language-javascript/grammars/modern-tree-sitter-javascript.cson'
)
);
diff --git a/spec/scope-resolver-spec.js b/spec/scope-resolver-spec.js
index a8158c31f..0aee61c60 100644
--- a/spec/scope-resolver-spec.js
+++ b/spec/scope-resolver-spec.js
@@ -23,12 +23,12 @@ function resolve(modulePath) {
}
const jsGrammarPath = resolve(
- 'language-javascript/grammars/tree-sitter-2-javascript.cson'
+ 'language-javascript/grammars/modern-tree-sitter-javascript.cson'
);
let jsConfig = CSON.readFileSync(jsGrammarPath);
const jsRegexGrammarPath = resolve(
- 'language-javascript/grammars/tree-sitter-2-regex.cson'
+ 'language-javascript/grammars/modern-tree-sitter-regex.cson'
);
let jsRegexConfig = CSON.readFileSync(jsRegexGrammarPath);
@@ -191,6 +191,67 @@ describe('ScopeResolver', () => {
]);
});
+ it('does not apply any scopes when @_IGNORE_ is used', async () => {
+ await grammar.setQueryForTest('highlightsQuery', `
+ (lexical_declaration kind: _ @_IGNORE_
+ (#match? @_IGNORE_ "const"))
+ (lexical_declaration kind: _ @let
+ (#match? @let "let"))
+ `);
+
+ const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
+ buffer.setLanguageMode(languageMode);
+ buffer.setText(dedent`
+ // this is a comment
+ const foo = "ahaha";
+ let bar = 'troz'
+ `);
+ await languageMode.ready;
+
+ let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
+
+ for (let capture of captures) {
+ let { node, name } = capture;
+ let result = scopeResolver.store(capture);
+ if (name === '_IGNORE_') {
+ expect(!!result).toBe(false);
+ } else {
+ expect(!!result).toBe(true);
+ }
+ }
+ });
+
+ it('does not apply any scopes when multiple @_IGNORE_s are used', async () => {
+ await grammar.setQueryForTest('highlightsQuery', `
+ (variable_declarator
+ (identifier) @_IGNORE_.identifier
+ (string) @_IGNORE_.string
+ )
+ `);
+
+ const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
+ buffer.setLanguageMode(languageMode);
+ buffer.setText(dedent`
+ // this is a comment
+ const foo = "ahaha";
+ let bar = false
+ `);
+ await languageMode.ready;
+
+ let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
+
+ for (let capture of captures) {
+ let { node, name } = capture;
+ let result = scopeResolver.store(capture);
+ if (name.startsWith('_IGNORE_')) {
+ expect(!!result).toBe(false);
+ } else {
+ expect(!!result).toBe(true);
+ }
+ }
+ });
+
+
describe('adjustments', () => {
it('adjusts ranges with (#set! adjust.startAt)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
diff --git a/spec/wasm-tree-sitter-language-mode-spec.js b/spec/wasm-tree-sitter-language-mode-spec.js
index f899f7586..9e97324d2 100644
--- a/spec/wasm-tree-sitter-language-mode-spec.js
+++ b/spec/wasm-tree-sitter-language-mode-spec.js
@@ -22,15 +22,15 @@ const pythonGrammarPath = resolve(
'language-python/grammars/modern-tree-sitter-python.cson'
);
const jsGrammarPath = resolve(
- 'language-javascript/grammars/tree-sitter-2-javascript.cson'
+ 'language-javascript/grammars/modern-tree-sitter-javascript.cson'
);
const jsRegexGrammarPath = resolve(
- 'language-javascript/grammars/tree-sitter-2-regex.cson'
+ 'language-javascript/grammars/modern-tree-sitter-regex.cson'
);
const jsdocGrammarPath = resolve(
- 'language-javascript/grammars/tree-sitter-2-jsdoc.cson'
+ 'language-javascript/grammars/modern-tree-sitter-jsdoc.cson'
);
const htmlGrammarPath = resolve(
'language-html/grammars/modern-tree-sitter-html.cson'
@@ -39,7 +39,7 @@ const ejsGrammarPath = resolve(
'language-html/grammars/modern-tree-sitter-ejs.cson'
);
const rubyGrammarPath = resolve(
- 'language-ruby/grammars/tree-sitter-2-ruby.cson'
+ 'language-ruby/grammars/modern-tree-sitter-ruby.cson'
);
const rustGrammarPath = resolve(
'language-rust-bundled/grammars/modern-tree-sitter-rust.cson'
@@ -587,9 +587,7 @@ describe('WASMTreeSitterLanguageMode', () => {
]
]);
- console.log('adding: ()');
buffer.setTextInRange([[0, 3], [0, 3]], '()');
- console.log('done: ()');
expectTokensToEqual(editor, [
[
@@ -598,9 +596,7 @@ describe('WASMTreeSitterLanguageMode', () => {
]
]);
- console.log('adding: new');
buffer.setTextInRange([[0, 0], [0, 0]], 'new ');
- console.log('done: new');
expectTokensToEqual(editor, [
[
@@ -613,7 +609,6 @@ describe('WASMTreeSitterLanguageMode', () => {
await nextHighlightingUpdate(languageMode);
// await wait(0);
// await languageMode.atTransactionEnd();
- console.log('proceeding!');
expectTokensToEqual(editor, [
[
@@ -1031,6 +1026,32 @@ describe('WASMTreeSitterLanguageMode', () => {
]);
});
+ it('handles injections with no highlights query', async () => {
+ jasmine.useRealClock();
+ atom.grammars.addGrammar(jsGrammar);
+ atom.grammars.addGrammar(htmlGrammar);
+ htmlGrammar.highlightsQuery = false;
+ // Pretend this grammar doesn't have a highlights query.
+ spyOn(htmlGrammar, 'getQuery').andReturn(Promise.resolve(null));
+ const languageMode = new WASMTreeSitterLanguageMode({
+ grammar: jsGrammar,
+ buffer,
+ config: atom.config,
+ grammars: atom.grammars
+ });
+ buffer.setLanguageMode(languageMode);
+ await languageMode.ready;
+
+ buffer.setText('text = html`
`');
+ await languageMode.atTransactionEnd();
+
+ // An injection should still be able to add its root scope even when
+ // its grammar has no `highlightsQuery`.
+ let descriptor = editor.scopeDescriptorForBufferPosition([0, 15]);
+
+ expect(descriptor.getScopesArray()).toContain('text.html.basic');
+ });
+
it('terminates comment token at the end of an injection, so that the next injection is NOT a continuation of the comment', async () => {
jasmine.useRealClock();
const ejsGrammar = new WASMTreeSitterGrammar(
@@ -1407,6 +1428,73 @@ describe('WASMTreeSitterLanguageMode', () => {
).toBe(true);
});
+ it('allows multiple base scopes on the injected layer when `languageScope` is a function', async () => {
+
+ let customJsConfig = { ...jsConfig };
+ let customJsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, customJsConfig);
+
+ await jsGrammar.setQueryForTest('highlightsQuery', `
+ (comment) @comment
+ (property_identifier) @property
+ (call_expression (identifier) @function)
+ (template_string) @string
+ (template_substitution
+ ["\${" "}"] @interpolation)
+ `);
+
+ let customHtmlConfig = { ...htmlConfig };
+ let customHtmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, customHtmlConfig);
+
+ await htmlGrammar.setQueryForTest('highlightsQuery', `
+ (fragment) @html
+ (tag_name) @tag
+ (attribute_name) @attr
+ `);
+
+ customHtmlGrammar.addInjectionPoint({
+ ...SCRIPT_TAG_INJECTION_POINT,
+ languageScope: (grammar, _buffer, range) => {
+ return [grammar.scopeName, `meta.line${range.start.row}`];
+ }
+ });
+
+ jasmine.useRealClock();
+ atom.grammars.addGrammar(customJsGrammar);
+ atom.grammars.addGrammar(customHtmlGrammar);
+ buffer.setText('\n\n
\n');
+
+ const languageMode = new WASMTreeSitterLanguageMode({
+ grammar: customHtmlGrammar,
+ buffer,
+ config: atom.config,
+ grammars: atom.grammars
+ });
+ buffer.setLanguageMode(languageMode);
+ await languageMode.ready;
+
+ let descriptor = languageMode.scopeDescriptorForPosition([1, 1]);
+ expect(
+ descriptor.getScopesArray().includes('source.js')
+ ).toBe(true);
+ expect(
+ descriptor.getScopesArray().includes(`meta.line0`)
+ ).toBe(true);
+ expect(
+ descriptor.getScopesArray().includes(`meta.line5`)
+ ).toBe(false);
+
+ descriptor = languageMode.scopeDescriptorForPosition([6, 1]);
+ expect(
+ descriptor.getScopesArray().includes('source.js')
+ ).toBe(true);
+ expect(
+ descriptor.getScopesArray().includes(`meta.line5`)
+ ).toBe(true);
+ expect(
+ descriptor.getScopesArray().includes(`meta.line0`)
+ ).toBe(false);
+ });
+
it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async () => {
const promise = new Promise(resolve => {
editor.onDidTokenize(event => {
@@ -1799,7 +1887,7 @@ describe('WASMTreeSitterLanguageMode', () => {
(#set! fold.endAt lastChild.previousSibling.endPosition))
((jsx_self_closing_element) @fold
- (#set! fold.endAt lastChild.previousSibling.startPosition))
+ (#set! fold.endAt lastChild.startPosition))
`);
buffer.setText(dedent`
diff --git a/src/config.js b/src/config.js
index 2f808abf1..fa0d407fd 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1098,8 +1098,23 @@ class Config {
!sources || sources.includes(this.projectFile)
)
) {
- const projectValue = getValueAtKeyPath(this.projectSettings, keyPath);
- value = projectValue === undefined ? value : projectValue;
+ let projectValue = getValueAtKeyPath(this.projectSettings, keyPath);
+ if (projectValue === undefined) {
+ // There is no project-specific override for this key path. `value`
+ // stays as `value` and we pretend this never happened.
+ } else if (isPlainObject(value) && isPlainObject(projectValue)) {
+ // This key path returned an object, so we need to merge the contents
+ // of the two objects into a third composite object. First we clone
+ // the project object so as not to modify it…
+ projectValue = this.deepClone(projectValue);
+ // …then we copy over the regular value's properties, preferring the
+ // project-specific value wherever there is overlap.
+ this.deepDefaults(projectValue, value);
+ value = projectValue;
+ } else {
+ // This is a single value, so we prefer the project version.
+ value = projectValue;
+ }
}
}
diff --git a/src/grammar-registry.js b/src/grammar-registry.js
index c5eeeb8d2..0a233c783 100644
--- a/src/grammar-registry.js
+++ b/src/grammar-registry.js
@@ -288,6 +288,8 @@ module.exports = class GrammarRegistry {
if (isNewTreeSitter) {
if (parserConfig === 'wasm-tree-sitter') {
score += 0.1;
+ } else if (parserConfig === 'textmate') {
+ score = -1;
}
} else if (isOldTreeSitter) {
if (parserConfig === 'node-tree-sitter') {
@@ -298,6 +300,8 @@ module.exports = class GrammarRegistry {
// score, but just a bit less than we'd bump it if this were a
// modern Tree-sitter grammar.
score += 0.09;
+ } else if (parserConfig === 'textmate') {
+ score = -1;
}
}
diff --git a/src/scope-resolver.js b/src/scope-resolver.js
index 035aaaa3c..1760e4a9d 100644
--- a/src/scope-resolver.js
+++ b/src/scope-resolver.js
@@ -487,12 +487,15 @@ class ScopeResolver {
this.setDataForRange(range, props);
}
- if (name === '_IGNORE_') {
+ if (name === '_IGNORE_' || name.startsWith('_IGNORE_.')) {
// "@_IGNORE_" is a magical variable in an SCM file that will not be
// applied in the grammar, but which allows us to prevent other kinds of
// scopes from matching. We purposefully allowed this syntax node to set
// data for a given range, but not to apply its scope ID to any
// boundaries.
+ //
+ // A query can also use multiple different @_IGNORE_-style variables by
+ // adding segments after the @_IGNORE_, such as @_IGNORE_.foo.bar.
return false;
}
diff --git a/src/wasm-tree-sitter-grammar.js b/src/wasm-tree-sitter-grammar.js
index 85f76b9fb..fc6c87647 100644
--- a/src/wasm-tree-sitter-grammar.js
+++ b/src/wasm-tree-sitter-grammar.js
@@ -138,7 +138,12 @@ module.exports = class WASMTreeSitterGrammar {
async getLanguage() {
await parserInitPromise;
if (!this._language) {
- this._language = await Parser.Language.load(this.treeSitterGrammarPath);
+ try {
+ this._language = await Parser.Language.load(this.treeSitterGrammarPath);
+ } catch (err) {
+ console.error(`Error loading grammar for ${this.scopeName}; original error follows`);
+ throw err;
+ }
}
if (!this._queryFilesLoaded) {
@@ -148,10 +153,6 @@ module.exports = class WASMTreeSitterGrammar {
}
async loadQueryFiles(grammarPath, queryPaths) {
- if (!('highlightsQuery' in queryPaths)) {
- throw new Error(`Highlights query must be present`);
- }
-
if (this._loadQueryFilesPromise) {
return this._loadQueryFilesPromise;
}
diff --git a/src/wasm-tree-sitter-language-mode.js b/src/wasm-tree-sitter-language-mode.js
index 008f4f944..e7e54df13 100644
--- a/src/wasm-tree-sitter-language-mode.js
+++ b/src/wasm-tree-sitter-language-mode.js
@@ -1138,7 +1138,7 @@ class WASMTreeSitterLanguageMode {
break;
}
}
- return Math.floor(indentLength / tabLength);
+ return indentLength / tabLength
}
// Get the suggested indentation level for an existing line in the buffer.
@@ -1163,6 +1163,8 @@ class WASMTreeSitterLanguageMode {
...rawOptions
};
+ let originalControllingLayer = options.controllingLayer;
+
let comparisonRow = options.comparisonRow;
if (comparisonRow === undefined) {
comparisonRow = row - 1;
@@ -1249,7 +1251,11 @@ class WASMTreeSitterLanguageMode {
// resolvers.
scopeResolver.reset();
- let indentTree = options.tree;
+ let indentTree = null;
+ if (options.tree && originalControllingLayer === controllingLayer) {
+ // Make sure this tree belongs to the layer we expect it to.
+ indentTree = options.tree;
+ }
if (!indentTree) {
if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.useAsyncParsing || !this.useAsyncIndent) {
@@ -1514,7 +1520,7 @@ class WASMTreeSitterLanguageMode {
}
scopeResolver.reset();
- let finalIndent = comparisonRowIndent + indentDelta + dedentDelta;
+ let finalIndent = comparisonRowIndent + indentDelta + dedentDelta + existingIndent;
// console.log('score:', comparisonRowIndent, '+', indentDelta, '-', ((dedentDelta < 0) ? -dedentDelta : dedentDelta), '=', finalIndent);
return Math.max(finalIndent - existingIndent, 0);
@@ -1556,9 +1562,10 @@ class WASMTreeSitterLanguageMode {
// the current row.
let controllingLayer = this.controllingLayerAtPoint(
this.buffer.clipPosition(new Point(row - 1, Infinity)),
+ // This query isn't as precise as the one we end up making later, but
+ // that's OK. This is just a first pass.
(layer) => !!layer.indentsQuery && !!layer.tree
);
-
if (isPastedText) {
// In this mode, we're not trying to auto-indent every line; instead,
// we're trying to auto-indent the _first_ line of a region of text
@@ -1581,7 +1588,11 @@ class WASMTreeSitterLanguageMode {
let firstLineIdealIndent = this.suggestedIndentForBufferRow(
row,
tabLength,
- { ...options, tree }
+ {
+ ...options,
+ controllingLayer,
+ tree
+ }
);
if (firstLineIdealIndent == null) {
@@ -2571,32 +2582,36 @@ class HighlightIterator {
getCloseScopeIds() {
let iterator = last(this.iterators);
- if (this.currentScopeIsCovered) {
- // console.log(
- // iterator.name,
- // iterator.depth,
- // 'would close',
- // iterator._inspectScopes(
- // iterator.getCloseScopeIds()
- // ),
- // 'at',
- // iterator.getPosition().toString(),
- // 'but scope is covered!'
- // );
- } else {
- // console.log(
- // iterator.name,
- // iterator.depth,
- // 'CLOSING',
- // iterator.getPosition().toString(),
- // iterator._inspectScopes(
- // iterator.getCloseScopeIds()
- // )
- // );
- }
+ // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') {
+ // console.log(
+ // iterator.name,
+ // iterator.depth,
+ // 'would close',
+ // iterator._inspectScopes(
+ // iterator.getCloseScopeIds()
+ // ),
+ // 'at',
+ // iterator.getPosition().toString(),
+ // 'but scope is covered!'
+ // );
+ // } else {
+ // console.log(
+ // iterator.name,
+ // iterator.depth,
+ // 'CLOSING',
+ // iterator.getPosition().toString(),
+ // iterator._inspectScopes(
+ // iterator.getCloseScopeIds()
+ // )
+ // );
+ // }
if (iterator) {
- if (this.currentScopeIsCovered) {
- return iterator.getOpenScopeIds().filter(id => {
+ // If this iterator is covered completely, or if it's covered in a
+ // position that prevents us from closing scopes…
+ if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') {
+ // …then the only closing scope we're allowed to apply is one that ends
+ // the base scope of an injection range.
+ return iterator.getCloseScopeIds().filter(id => {
return iterator.languageLayer.languageScopeId === id;
});
} else {
@@ -2609,31 +2624,35 @@ class HighlightIterator {
getOpenScopeIds() {
let iterator = last(this.iterators);
// let ids = iterator.getOpenScopeIds();
- if (this.currentScopeIsCovered) {
- // console.log(
- // iterator.name,
- // iterator.depth,
- // 'would open',
- // iterator._inspectScopes(
- // iterator.getOpenScopeIds()
- // ),
- // 'at',
- // iterator.getPosition().toString(),
- // 'but scope is covered!'
- // );
- } else {
- // console.log(
- // iterator.name,
- // iterator.depth,
- // 'OPENING',
- // iterator.getPosition().toString(),
- // iterator._inspectScopes(
- // iterator.getOpenScopeIds()
- // )
- // );
- }
+ // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') {
+ // console.log(
+ // iterator.name,
+ // iterator.depth,
+ // 'would open',
+ // iterator._inspectScopes(
+ // iterator.getOpenScopeIds()
+ // ),
+ // 'at',
+ // iterator.getPosition().toString(),
+ // 'but scope is covered!'
+ // );
+ // } else {
+ // console.log(
+ // iterator.name,
+ // iterator.depth,
+ // 'OPENING',
+ // iterator.getPosition().toString(),
+ // iterator._inspectScopes(
+ // iterator.getOpenScopeIds()
+ // )
+ // );
+ // }
if (iterator) {
- if (this.currentScopeIsCovered) {
+ // If this iterator is covered completely, or if it's covered in a
+ // position that prevents us from opening scopes…
+ if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') {
+ // …then the only opening scope we're allowed to apply is one that ends
+ // the base scope of an injection range.
return iterator.getOpenScopeIds().filter(id => {
return iterator.languageLayer.languageScopeId === id;
});
@@ -2651,20 +2670,22 @@ class HighlightIterator {
if (layerCount > 1) {
const rest = [...this.iterators];
const leader = rest.pop();
- let covered = rest.some(it => {
- return it.coversIteratorAtPosition(
- leader,
- leader.getPosition()
- );
- });
+ let covers = false;
+ for (let it of rest) {
+ let iteratorCovers = it.coversIteratorAtPosition(leader, leader.getPosition());
+ if (iteratorCovers !== false) {
+ covers = iteratorCovers;
+ break;
+ }
+ }
- if (covered) {
- this.currentScopeIsCovered = true;
+ if (covers) {
+ this.currentIteratorIsCovered = covers;
return;
}
}
- this.currentScopeIsCovered = false;
+ this.currentIteratorIsCovered = false;
}
logPosition() {
@@ -2673,6 +2694,8 @@ class HighlightIterator {
}
}
+const EMPTY_SCOPES = Object.freeze([]);
+
// Iterates through everything that a `LanguageLayer` is responsible for,
// marking boundaries for scope insertion.
class LayerHighlightIterator {
@@ -2730,16 +2753,40 @@ class LayerHighlightIterator {
// …and this iterator is deeper than the other…
if (iterator.depth > this.depth) { return false; }
- // …and this iterator's ranges actually include this position.
+ // …and one of this iterator's content ranges actually includes this
+ // position. (With caveats!)
let ranges = this.languageLayer.getCurrentRanges();
if (ranges) {
- return ranges.some(range => range.containsPoint(position));
- }
+ // A given layer's content ranges aren't allowed to overlap each other.
+ // So only a single range from this list can possibly match.
+ let overlappingRange = ranges.find(range => range.containsPoint(position))
+ if (!overlappingRange) return false;
- // TODO: Despite all this, we may want to allow parent layers to apply
- // scopes at the very edges of this layer's ranges/extent; or perhaps to
- // apply ending scopes at starting positions and vice-versa; or at least to
- // make it a configurable behavior.
+ // If the current position is right in the middle of an injection's
+ // range, then it should cover all attempts to apply scopes. But what if
+ // we're on one of its edges? Since closing scopes act before opening
+ // scopes,
+ //
+ // * if this iterator _starts_ a range at position X, it doesn't get to
+ // prevent another iterator from _ending_ a scope at position X;
+ // * if this iterator _ends_ a range at position X, it doesn't get to
+ // prevent another iterator from _starting_ a scope at position X.
+ //
+ // So at a given position, `currentIteratorIsCovered` can be `true` (all
+ // scopes suppressed), `false` (none suppressed), `"close"` (only closing
+ // scopes suppressed), or `"open"` (only opening scopes suppressed).
+ if (overlappingRange.end.compare(position) === 0) {
+ // We're at the right edge of the injection range. We want to prevent
+ // iterators from closing scopes, but not from opening them.
+ return 'close';
+ } else if (overlappingRange.start.compare(position) === 0) {
+ // We're at the left edge of the injection range. We want to prevent
+ // iterators from opening scopes, but not from closing them.
+ return 'open';
+ } else {
+ return true;
+ }
+ }
}
seek(start, endRow) {
@@ -2766,7 +2813,7 @@ class LayerHighlightIterator {
}
isAtInjectionBoundary() {
- let position = Point.fromObject(this.iterator.key);
+ let position = Point.fromObject(this.iterator.key.position);
return position.isEqual(this.start) || position.isEqual(this.end);
}
@@ -2778,61 +2825,38 @@ class LayerHighlightIterator {
}
getOpenScopeIds() {
- let openScopeIds = this.iterator.value.openScopeIds;
- // if (openScopeIds.length > 0) {
- // console.log(
- // this.name,
- // this.depth,
- // 'OPENING',
- // this.getPosition().toString(),
- // this._inspectScopes(
- // this.iterator.value.openScopeIds
- // )
- // );
- // }
- return [...openScopeIds];
+ let { key, value } = this.iterator;
+ return key.boundary === 'end' ? EMPTY_SCOPES : [...value.scopeIds];
}
getCloseScopeIds() {
- let closeScopeIds = this.iterator.value.closeScopeIds;
- // if (closeScopeIds.length > 0) {
- // console.log(
- // this.name,
- // 'CLOSING',
- // this.getPosition().toString(),
- // this._inspectScopes(
- // this.iterator.value.closeScopeIds
- // )
- // );
- // }
- return [...closeScopeIds];
+ let { key, value } = this.iterator;
+ return key.boundary === 'start' ? EMPTY_SCOPES : [...value.scopeIds];
}
opensScopes() {
- let scopes = this.getOpenScopeIds();
- return scopes.length > 0;
+ return this.iterator?.key?.boundary === 'start';
}
closesScopes() {
- let scopes = this.getCloseScopeIds();
- return scopes.length > 0;
+ return this.iterator?.key?.boundary === 'end';
}
getPosition() {
- return this.iterator.key || Point.INFINITY;
+ return this.iterator?.key?.position ?? Point.INFINITY;
}
logPosition() {
let pos = this.getPosition();
+ let { key, value } = this.iterator;
let { languageMode } = this.languageLayer;
+ let verb = key.boundary === 'end' ? 'close' : 'open';
console.log(
`[highlight] (${pos.row}, ${pos.column})`,
- 'close',
- this.iterator.value.closeScopeIds.map(id => languageMode.scopeNameForScopeId(id)),
- 'open',
- this.iterator.value.openScopeIds.map(id => languageMode.scopeNameForScopeId(id)),
+ verb,
+ value.scopeIds.map(id => languageMode.scopeNameForScopeId(id)),
'next?',
this.iterator.hasNext
);
@@ -2840,35 +2864,33 @@ class LayerHighlightIterator {
compare(other) {
// First, favor the one whose current position is earlier.
- const result = comparePoints(this.iterator.key, other.iterator.key);
+ const result = comparePoints(
+ this.iterator.key.position,
+ other.iterator.key.position
+ );
if (result !== 0) { return result; }
// Failing that, favor iterators that need to close scopes over those that
// don't.
- if (this.closesScopes() && !other.closesScopes()) {
+ let ourBoundary = this.iterator.key.boundary;
+ let theirBoundary = other.iterator.key.boundary;
+ let bothClosing = ourBoundary === 'end' && theirBoundary === 'end';
+
+ if (ourBoundary === 'end' && !bothClosing) {
return -1;
- } else if (other.closesScopes() && !this.closesScopes()) {
+ } else if (theirBoundary === 'end' && !bothClosing) {
return 1;
}
- let bothOpening = this.opensScopes() && other.opensScopes();
- let bothClosing = this.closesScopes() && other.closesScopes();
-
if (bothClosing) {
// When both iterators are closing scopes, the deeper layer should act
// first.
return other.languageLayer.depth - this.languageLayer.depth;
} else {
- // When both iterators are opening scopes — or if there's a mix of
- // opening and closing — the shallower layer should act first.
+ // When both iterators are opening scopes, the shallower layer should act
+ // first.
return this.languageLayer.depth - other.languageLayer.depth;
}
-
- // TODO: We need to move to a system where every point in the iterator
- // _either_ closes scopes _or_ opens them, with the former visited before
- // the latter. Otherwise there's no correct way to sort them when two
- // different layers have the same position and both want to close _and_
- // open scopes.
}
moveToSuccessor() {
@@ -2893,7 +2915,7 @@ class LayerHighlightIterator {
if (!this.end) { return false; }
let next = this.peekAtSuccessor();
- return comparePoints(next, this.end) > 0;
+ return comparePoints(next.position, this.end) > 0;
}
}
@@ -2949,22 +2971,14 @@ class LanguageLayer {
// off this promise. We can `await this.languageLoaded` later on.
this.languageLoaded = this.grammar.getLanguage().then(language => {
this.language = language;
- // TODO: Currently, we require a highlights query, but we might want to
- // rethink this. There are use cases for treating the root layer merely
- // as a way to delegate to injections, in which case syntax highlighting
- // wouldn't be needed.
- return this.grammar.getQuery('highlightsQuery').then(highlightsQuery => {
- this.highlightsQuery = highlightsQuery;
- }).catch(() => {
- throw new GrammarLoadError(grammar, 'highlightsQuery');
- });
- }).then(() => {
- // All other queries are optional. Regular expression language layers,
- // for instance, don't really have a need for any of these.
- let otherQueries = ['foldsQuery', 'indentsQuery', 'localsQuery', 'tagsQuery'];
+ // All queries are optional. Regular expression language layers, for
+ // instance, don't really have a need for any queries other than
+ // `highlightsQuery`, and some kinds of layers don't even need
+ // `highlightsQuery`.
+ let queries = ['highlightsQuery', 'foldsQuery', 'indentsQuery', 'localsQuery', 'tagsQuery'];
let promises = [];
- for (let queryType of otherQueries) {
+ for (let queryType of queries) {
if (grammar[queryType]) {
let promise = this.grammar.getQuery(queryType).then(query => {
this[queryType] = query;
@@ -2979,6 +2993,9 @@ class LanguageLayer {
if (err.name === 'GrammarLoadError') {
console.warn(err.message);
if (err.queryType === 'highlightsQuery') {
+ // Recover by setting an empty `highlightsQuery` so that we don't
+ // propagate errors.
+ //
// TODO: Warning?
grammar.highlightsQuery = grammar.setQueryForTest(
'highlightsQuery',
@@ -3012,10 +3029,8 @@ class LanguageLayer {
// injected.
languageScope = injectionPoint.languageScope;
- // The `languageScope` parameter can be a function.
- if (typeof languageScope === 'function') {
- languageScope = languageScope(this.grammar);
- }
+ // The `languageScope` parameter can be a function. That means we won't
+ // decide now; we'll decide later on a range-by-range basis.
// Honor an explicit `null`, but fall back to the default scope name
// otherwise.
@@ -3025,7 +3040,10 @@ class LanguageLayer {
}
this.languageScope = languageScope;
- if (languageScope === null) {
+ if (languageScope === null || typeof languageScope === 'function') {
+ // If `languageScope` is a function, we'll still often end up with a
+ // `languageScopeId` (or several); we just won't be able to compute it
+ // ahead of time.
this.languageScopeId = null;
} else {
this.languageScopeId = this.languageMode.idForScope(languageScope);
@@ -3110,17 +3128,17 @@ class LanguageLayer {
// through a `ScopeResolver`.
getSyntaxBoundaries(from, to) {
let { buffer } = this.languageMode;
- if (!(this.language && this.tree && this.highlightsQuery)) {
+ if (!(this.language && this.tree)) {
return [[], new OpenScopeMap()];
}
from = buffer.clipPosition(Point.fromObject(from, true));
to = buffer.clipPosition(Point.fromObject(to, true));
- let boundaries = createTree(comparePoints);
+ let boundaries = createTree(compareBoundaries);
let extent = this.getExtent();
- const captures = this.highlightsQuery.captures(this.tree.rootNode, from, to);
+ let captures = this.highlightsQuery?.captures(this.tree.rootNode, from, to) ?? [];
this.scopeResolver.reset();
for (let capture of captures) {
@@ -3142,7 +3160,7 @@ class LanguageLayer {
// `allowEmpty` to force these to be considered, but for marking scopes,
// there's no need for it; it'd just cause us to open and close a scope
// in the same position.
- if (node.text === '') { continue; }
+ if (node.childCount === 0 && node.text === '') { continue; }
// Ask the `ScopeResolver` to process each capture in turn. Some captures
// will be ignored if they fail certain tests, and some will have their
@@ -3205,37 +3223,65 @@ class LanguageLayer {
//
let includedRanges = this.depth === 0 ? [extent] : this.getCurrentRanges();
- if (this.languageScopeId) {
+ let languageScopeIdForRange = () => this.languageScopeId;
+ if (typeof this.languageScope === 'function') {
+ languageScopeIdForRange = (range) => {
+ let scopeName = this.languageScope(this.grammar, this.languageMode.buffer, range);
+ if (Array.isArray(scopeName)) {
+ return scopeName.map(s => this.languageMode.idForScope(s));
+ } else {
+ return this.languageMode.idForScope(scopeName);
+ }
+ };
+ }
+
+ if (this.languageScopeId || typeof this.languageScope === 'function') {
for (let range of includedRanges) {
// Filter out ranges that have no intersection with ours.
if (range.end.isLessThanOrEqual(from)) { continue; }
if (range.start.isGreaterThanOrEqual(to)) { continue; }
+ let languageScopeIds = languageScopeIdForRange(range);
+ if (!languageScopeIds) continue;
+
+ if (!Array.isArray(languageScopeIds)) {
+ languageScopeIds = [languageScopeIds];
+ }
+
if (range.start.isLessThan(from)) {
// If we get this far, we know that the base language scope was open
// when our range began.
- alreadyOpenScopes.set(range.start, [this.languageScopeId]);
+ alreadyOpenScopes.set(
+ range.start,
+ languageScopeIds
+ );
} else {
// Range start must be between `from` and `to`, or else equal `from`
// exactly.
- this.scopeResolver.setBoundary(
- range.start,
- this.languageScopeId,
- 'open',
- { root: true, length: Infinity }
- );
+ for (let id of languageScopeIds) {
+ this.scopeResolver.setBoundary(
+ range.start,
+ id,
+ 'open',
+ { root: true, length: Infinity }
+ );
+ }
}
if (range.end.isGreaterThan(to)) {
// Do nothing; we don't need to set this boundary.
} else {
// The range must end somewhere within our range.
- this.scopeResolver.setBoundary(
- range.end,
- this.languageScopeId,
- 'close',
- { root: true, length: Infinity }
- );
+ //
+ // Close the boundaries in the opposite order of how we opened them.
+ for (let i = languageScopeIds.length - 1; i >= 0; i--) {
+ this.scopeResolver.setBoundary(
+ range.end,
+ languageScopeIds[i],
+ 'close',
+ { root: true, length: Infinity }
+ );
+ }
}
}
}
@@ -3255,12 +3301,20 @@ class LanguageLayer {
continue;
}
- let bundle = {
- closeScopeIds: [...data.close],
- openScopeIds: [...data.open]
- };
+ let OPEN_KEY = { position: point, boundary: 'start' };
+ let CLOSE_KEY = { position: point, boundary: 'end' };
- boundaries = boundaries.insert(point, bundle);
+ if (data.close.length > 0) {
+ boundaries = boundaries.insert(CLOSE_KEY, {
+ scopeIds: Object.freeze(data.close)
+ });
+ }
+
+ if (data.open.length > 0) {
+ boundaries = boundaries.insert(OPEN_KEY, {
+ scopeIds: Object.freeze(data.open)
+ });
+ }
}
return [boundaries, alreadyOpenScopes];
@@ -3747,7 +3801,7 @@ class LanguageLayer {
}
getOrParseTree({ force = true, anonymous = false } = {}) {
- if (!this.treeIsDirty || !force) { return this.tree; }
+ if (this.tree && (!this.treeIsDirty || !force)) { return this.tree; }
// Eventually we'll take this out, but for now it serves as an indicator of
// how often we have to manually re-parse in between transactions —
@@ -3821,11 +3875,11 @@ class LanguageLayer {
// If the cursor is resting before column X, we want all scopes that cover
// the character in column X.
- let captures = this.highlightsQuery.captures(
+ let captures = this.highlightsQuery?.captures(
this.tree.rootNode,
point,
{ row: point.row, column: point.column + 1 }
- );
+ ) ?? [];
let results = [];
for (let capture of captures) {
@@ -4110,7 +4164,7 @@ class NodeRangeSet {
}
}
- getNodeSpec (node, getChildren) {
+ getNodeSpec(node, getChildren) {
let { startIndex, endIndex, startPosition, endPosition, id } = node;
let result = { startIndex, endIndex, startPosition, endPosition, id };
if (node.children && getChildren) {
@@ -4126,8 +4180,15 @@ class NodeRangeSet {
const previousRanges = this.previous?.getRanges(buffer);
let result = [];
- for (const node of this.nodeSpecs) {
+ for (let node of this.nodeSpecs) {
+ // An injection point isn't given the point at which the buffer ends, so
+ // it's free to return an `endIndex` of `Infinity` here and rely on us to
+ // clip it to the boundary of the buffer.
+ if (node.endIndex === Infinity) {
+ node = this._clipRange(node, buffer);
+ }
let position = node.startPosition, index = node.startIndex;
+
if (node.children && !this.includeChildren) {
// If `includeChildren` is `false`, we're effectively collecting all
// the disjoint text nodes that are direct descendants of this node.
@@ -4183,6 +4244,13 @@ class NodeRangeSet {
return this._consolidateRanges(result);
}
+ _clipRange(range, buffer) {
+ // Convert this range spec to an actual `Range`, clip it, then convert it
+ // back to a range spec with accurate `startIndex` and `endIndex` values.
+ let clippedRange = buffer.clipRange(rangeForNode(range));
+ return rangeToTreeSitterRangeSpec(clippedRange, buffer);
+ }
+
// Combine adjacent ranges to minimize the number of boundaries.
_consolidateRanges(ranges) {
if (ranges.length === 1) { return ranges; }