Merge pull request #859 from savetheclocktower/tree-sitter-january

Tree-sitter rolling fixes (January edition)
This commit is contained in:
Andrew Dupont 2024-01-29 21:10:58 -08:00 committed by GitHub
commit 2dbd99582e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
145 changed files with 2419 additions and 1116 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
*.ts

View File

@ -19,6 +19,12 @@ module.exports = {
asyncArrow: "always",
named: "never"
}],
"node/no-missing-require": [
"error",
{
allowModules: ["atom"]
}
],
"node/no-unpublished-require": [
"error",
{

View File

@ -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

View File

@ -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'}},

View File

@ -9,3 +9,4 @@
@import "styles/syntax/base.less";
@import "styles/syntax/css.less";
@import "styles/syntax/html.less";
@import "styles/syntax/json.less";

View File

@ -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 */

View File

@ -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;
}
}
}

View File

@ -8,3 +8,4 @@
@import "styles/syntax/base.less";
@import "styles/syntax/css.less";
@import "styles/syntax/json.less";

View File

@ -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 */

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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"
}
};

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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'] });
}
};

View File

@ -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"
}
}
}
}

View File

@ -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'

View File

@ -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"))

View File

@ -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'] });
};

View File

@ -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"
}
}
}
}

View File

@ -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

View File

@ -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);
}
});
};

View File

@ -10,5 +10,12 @@
},
"devDependencies": {
"coffeescript": "^1.7.0"
}
},
"consumedServices": {
"hyperlink.injection": {
"versions": {
"0.1.0": "consumeHyperlinkInjection"
}
}
}
}

View File

@ -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'

View File

@ -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

View File

@ -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'] });
};

View File

@ -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"
}
}
}
}

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'] });
};

View File

@ -19,5 +19,17 @@
},
"devDependencies": {
"dedent": "^0.7.0"
},
"consumedServices": {
"hyperlink.injection": {
"versions": {
"0.1.0": "consumeHyperlinkInjection"
}
},
"todo.injection": {
"versions": {
"0.1.0": "consumeTodoInjection"
}
}
}
}

View File

@ -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"
}
};

View File

@ -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
});
}
},
}
}
};

View File

@ -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"
}
}
}
}

View File

@ -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"
}
};

View File

@ -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

View File

@ -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'] });
};

View File

@ -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"
}
}
}
}

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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))

View File

@ -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 `<Foo />`.
(jsx_self_closing_element
name: (identifier) @entity.name.tag.js
) @meta.tag.js
) @meta.tag.jsx.js
; The "Foo" in `<Foo>`.
(jsx_opening_element
@ -752,8 +772,6 @@
; The "Foo" in `</Foo>`.
(jsx_closing_element
"/" @punctuation.definition.tag.end.js
(#set! capture.final true)
name: (identifier) @entity.name.tag.js)
; The "bar" in `<Foo bar={true} />`.
@ -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 `<Foo />`, 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"))
[
"&&="

View File

@ -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))

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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"
}
}
}
}

View File

@ -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|$)"

View File

@ -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'

View File

@ -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

View File

@ -1,8 +1,9 @@
;
["{" "(" "["] @indent
["}" ")" "]"] @dedent
":" @indent
; if ($foo):
(colon_block ":" @indent)
["endif" "endfor" "endforeach" "enddeclare" "endswitch"] @dedent

View File

@ -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

View File

@ -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 `<?php`.
// The actual structure of the tree is utter chaos for us. The best way
// to make sense of it is to grab all the delimiters of the ranges we're
// interested in, sort them, pair them off, and turn them into fake
// ranges. As long as we return objects with the range properties that
// actual nodes have, _and_ we opt into `includeChildren` (so that it
// doesn't try to read the `children` property to subtract child nodes'
// ranges), this works just fine.
//
// In practice, it seems that `text` is always a child of the root except
// inside of `text_interpolation`, and `text_interpolation` is always a
// child of the root. The only exceptions I've noticed are when the tree
// is in an error state, so they may not be worth worrying about.
for (let child of node.children) {
if (child.type === 'text') { continue; }
if (child.type === 'text_interpolation') {
for (let grandchild of child.children) {
if (grandchild.type === 'text') { continue; }
results.push(grandchild);
}
continue;
}
results.push(child);
}
return results;
// If you're ever skeptical about whether this is really the easiest way
// to do this, fire up `tree-sitter-tools` and take a look at the
// structure of a PHP file in `tree-sitter-php`. If you know of something
// simpler, I'm all ears.
//
// TODO: This method should be allowed to return actual ordinary `Range`
// instances, in which case we'd understand that no futher processing
// need take place by the language mode.
let boundaries = node.descendantsOfType(['php_tag', '?>']);
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'] });
};

View File

@ -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"
}
}
}
}

View File

@ -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"
}
};

View File

@ -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'

View File

@ -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

View File

@ -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'] });
};

View File

@ -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"
}
}
}
}

View File

@ -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'

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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'] });
};

Some files were not shown because too many files have changed in this diff Show More