diff --git a/Embeddings/CSS (for Ngx).sublime-syntax b/Embeddings/CSS (for Ngx).sublime-syntax
new file mode 100644
index 0000000..270c627
--- /dev/null
+++ b/Embeddings/CSS (for Ngx).sublime-syntax
@@ -0,0 +1,42 @@
+%YAML 1.2
+---
+# A special syntax definition to drive double quoted inline style attributes
+# with {...} interpolation support
+scope: source.css.embedded.ngx
+version: 2
+hidden: true
+
+extends: Packages/CSS/CSS.sublime-syntax
+
+variables:
+ ident_start: (?:{{nmstart}}|{{)
+
+contexts:
+
+ prototype:
+ - meta_prepend: true
+ - include: NgxHTML.sublime-syntax#ng-interpolations
+
+ string-content:
+ - meta_prepend: true
+ - include: NgxHTML.sublime-syntax#ng-string-interpolations
+
+ at-keyframe-block-body:
+ # required until ST4174 (PR #3820)
+ - meta_prepend: true
+ - meta_include_prototype: false
+
+ at-layer-name-list:
+ # required until ST4174 (PR #3820)
+ - meta_prepend: true
+ - meta_include_prototype: false
+
+ property-list-body:
+ # required until ST4174 (PR #3820)
+ - meta_prepend: true
+ - meta_include_prototype: false
+
+ stylesheet-block-body:
+ # required until ST4174 (PR #3831)
+ - meta_prepend: true
+ - meta_include_prototype: false
diff --git a/NgxHTML.sublime-syntax b/NgxHTML.sublime-syntax
index 2fa7686..dd04854 100644
--- a/NgxHTML.sublime-syntax
+++ b/NgxHTML.sublime-syntax
@@ -14,6 +14,18 @@ contexts:
###[ HTML CUSTOMIZATIONS ]#####################################################
+ main:
+ - meta_prepend: true
+ - meta_scope: meta.template.ngx
+
+ cdata-content:
+ - meta_prepend: true
+ - include: ng-string-interpolations
+
+ strings-common-content:
+ - meta_prepend: true
+ - include: ng-string-interpolations
+
tag:
- meta_prepend: true
- include: ng-declarations
@@ -24,6 +36,61 @@ contexts:
- meta_prepend: true
- include: ng-directives
+ tag-generic-attribute-name:
+ - meta_prepend: true
+ - include: ng-interpolations
+
+ tag-attribute-value-content:
+ - meta_prepend: true
+ - include: ng-string-interpolations
+
+###[ HTML STYLE TAG ]##########################################################
+
+ style-css-content:
+ - meta_include_prototype: false
+ - match: '{{style_content_begin}}'
+ captures:
+ 1: comment.block.html punctuation.definition.comment.begin.html
+ pop: 1 # make sure to match only once
+ embed: scope:source.css.embedded.ngx
+ embed_scope: source.css.embedded.html
+ escape: '{{style_content_end}}'
+ escape_captures:
+ 1: source.css.embedded.html
+ 2: comment.block.html punctuation.definition.comment.end.html
+ 3: source.css.embedded.html
+ 4: comment.block.html punctuation.definition.comment.end.html
+
+###[ HTML STYLE ATTRIBUTES ]###################################################
+
+ tag-style-attribute-assignment:
+ - meta_include_prototype: false
+ - match: =
+ scope: punctuation.separator.key-value.html
+ set:
+ - immediately-pop # workaround https://github.com/sublimehq/sublime_text/issues/4069
+ - tag-style-attribute-value
+ - include: else-pop
+
+ tag-style-attribute-value:
+ - match: \"
+ scope: meta.string.html string.quoted.double.html punctuation.definition.string.begin.html
+ embed: scope:source.css.embedded.ngx#rule-list-body
+ embed_scope: meta.string.html source.css.embedded.html
+ escape: \"
+ escape_captures:
+ 0: meta.string.html string.quoted.double.html punctuation.definition.string.end.html
+ pop: 1
+ - match: \'
+ scope: meta.string.html string.quoted.single.html punctuation.definition.string.begin.html
+ embed: scope:source.css.embedded.ngx#rule-list-body
+ embed_scope: meta.string.html source.css.embedded.html
+ escape: \'
+ escape_captures:
+ 0: meta.string.html string.quoted.single.html punctuation.definition.string.end.html
+ pop: 1
+ - include: else-pop
+
###[ ANGULAR DIRECTIVES ]######################################################
ng-directives:
@@ -85,7 +152,7 @@ contexts:
- match: \"
scope: meta.string.ngx string.quoted.double.ngx punctuation.definition.string.begin.ngx
embed: ng-directive-expressions
- embed_scope: meta.directive.value.ngx meta.string.ngx meta.interpolation.ngx source.ngx.embedded.html
+ embed_scope: meta.directive.value.ngx meta.string.ngx meta.embedded.expression.ngx source.ngx.embedded.html
escape: \"
escape_captures:
0: meta.string.ngx string.quoted.double.ngx punctuation.definition.string.end.ngx
@@ -93,7 +160,7 @@ contexts:
- match: \'
scope: meta.string.ngx string.quoted.single.ngx punctuation.definition.string.begin.ngx
embed: ng-directive-expressions
- embed_scope: meta.directive.value.ngx meta.string.ngx meta.interpolation.ngx source.ngx.embedded.html
+ embed_scope: meta.directive.value.ngx meta.string.ngx meta.embedded.expression.ngx source.ngx.embedded.html
escape: \'
escape_captures:
0: meta.string.ngx string.quoted.single.ngx punctuation.definition.string.end.ngx
@@ -114,7 +181,9 @@ contexts:
ng-declarations:
- match: (@)let{{ident_break}}
- scope: meta.let.ngx keyword.declaration.variable.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ meta.let.ngx keyword.declaration.variable.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
@@ -128,15 +197,21 @@ contexts:
- include: else-pop
ng-let-assignment:
- - meta_content_scope: meta.let.identifier.ngx
+ - meta_content_scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ meta.let.identifier.ngx
- match: =
- scope: meta.let.ngx keyword.operator.assignment.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ meta.let.ngx keyword.operator.assignment.ngx
set: ng-let-value
- include: else-pop
ng-let-value:
- meta_include_prototype: false
- - meta_content_scope: meta.let.value.ngx
+ - meta_content_scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ meta.let.value.ngx
- match: ';'
scope: punctuation.terminator.expression.ngx
pop: 1
@@ -148,39 +223,51 @@ contexts:
# conditionals
# https://angular.dev/guide/templates/control-flow#conditionally-display-content-with-if-else-if-and-else
- match: (@)if{{ident_break}}
- scope: keyword.control.conditional.if.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.if.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
- ng-block
- ng-conditional-group
- match: (@)else\s+if{{ident_break}}
- scope: keyword.control.conditional.elseif.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.elseif.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
- ng-block
- ng-conditional-group
- match: (@)else{{ident_break}}
- scope: keyword.control.conditional.else.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.else.ngx
captures:
1: punctuation.definition.keyword.ngx
push: ng-block
# https://angular.dev/guide/templates/control-flow#conditionally-display-content-with-the-switch-block
- match: (@)case{{ident_break}}
- scope: keyword.control.conditional.case.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.case.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
- ng-block
- ng-conditional-group
- match: (@)default{{ident_break}}
- scope: keyword.control.conditional.case.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.case.ngx
captures:
1: punctuation.definition.keyword.ngx
push: ng-block
- match: (@)switch{{ident_break}}
- scope: keyword.control.conditional.switch.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.conditional.switch.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
@@ -188,20 +275,26 @@ contexts:
- ng-conditional-group
# https://angular.dev/guide/templates/control-flow#providing-a-fallback-for-for-blocks-with-the-empty-block
- match: (@)for{{ident_break}}
- scope: keyword.control.loop.for.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.loop.for.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
- ng-block
- ng-for-group
- match: (@)empty{{ident_break}}
- scope: keyword.control.loop.else.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.loop.else.ngx
captures:
1: punctuation.definition.keyword.ngx
push: ng-block
# https://angular.dev/guide/templates/defer
- match: (@)(?:defer|error|loading|placeholder){{ident_break}}
- scope: keyword.control.flow.ngx
+ scope:
+ meta.embedded.statement.ngx.html source.ngx.embedded.html
+ keyword.control.flow.ngx
captures:
1: punctuation.definition.keyword.ngx
push:
@@ -210,6 +303,7 @@ contexts:
ng-block:
- meta_include_prototype: false
+ - meta_content_scope: meta.embedded.statement.ngx.html source.ngx.embedded.html
- match: \{
scope: punctuation.section.block.begin.ngx
set: ng-block-body
@@ -295,16 +389,27 @@ contexts:
###[ ANGULAR INTERPOLATIONS ]##################################################
+ ng-string-interpolations:
+ - match: '{{'
+ scope: meta.embedded.expression.ngx.html punctuation.section.embedded.begin.ngx.html
+ push: ng-string-interpolation-body
+
+ ng-string-interpolation-body:
+ - clear_scopes: 1
+ - meta_include_prototype: false
+ - meta_content_scope: meta.embedded.expression.ngx.html source.ngx.embedded.html
+ - include: ng-interpolation-body
+
ng-interpolations:
- match: '{{'
- scope: meta.embedded.ngx.html punctuation.section.embedded.begin.ngx.html
+ scope: meta.embedded.expression.ngx.html punctuation.section.embedded.begin.ngx.html
push: ng-interpolation-body
ng-interpolation-body:
- meta_include_prototype: false
- - meta_content_scope: meta.embedded.ngx.html source.ngx.embedded.html
+ - meta_content_scope: meta.embedded.expression.ngx.html source.ngx.embedded.html
- match: '}}'
- scope: meta.embedded.ngx.html punctuation.section.embedded.end.ngx.html
+ scope: meta.embedded.expression.ngx.html punctuation.section.embedded.end.ngx.html
pop: 1
- include: ng-expressions
diff --git a/tests/syntax_test_scopes.component.html b/tests/syntax_test_scopes.component.html
index 0449ccb..4cbefac 100644
--- a/tests/syntax_test_scopes.component.html
+++ b/tests/syntax_test_scopes.component.html
@@ -6,6 +6,8 @@
-->
@let name = user.name;
+
+
@@ -16,6 +18,7 @@
@let func = user.func();
+
@@ -29,6 +32,7 @@
@let greeting = 'Hello, ' + name;
+
@@ -41,6 +45,7 @@
@let data = data$ | async;
+
@@ -53,6 +58,7 @@
@let item = var[10]['bar'];
+
@@ -71,6 +77,7 @@
@let path = .foo?.bar? ;
+
@@ -87,6 +94,7 @@
@let path = .orders.value()?.[0]?.$extra?.#currency.unit;
+
@@ -116,6 +124,7 @@
@let path = orders.value()?.[0]?.$extra?.#currency.unit;
+
@@ -149,9 +158,10 @@
-->
@if (a > b) {
-
-
-
+
+
+
+
@@ -160,9 +170,9 @@
{{ a }} is greater than {{ b }}
-
-
-
+
+
+
@@ -173,6 +183,7 @@
} @else if (b > a()) {
+
@@ -184,9 +195,9 @@
{{ a() }} is less than {{ b.c() }}
-
-
-
+
+
+
@@ -202,13 +213,14 @@
} @else {
-
+
+
{{ a }} is equal to {{ b }}
-
-
-
+
+
+
@@ -235,7 +247,7 @@
{{ startDate }}
-
+
@@ -250,6 +262,8 @@
-->
@switch (accessLevel) {
+
+
@@ -257,7 +271,7 @@
@case ('admin') {
-
+
@@ -277,7 +291,7 @@
@case {
-
+
@@ -292,7 +306,7 @@
@default {
-
+
@@ -311,6 +325,8 @@
-->
@for (user of users; track user.id) {
+
+
@@ -326,14 +342,16 @@
{{ user.name }}
} @empty {
-
-
+
+
+
Empty list of users
-}
-
+.html}
+
@for (item of items; track item.id; let idx = $index, e = $even) {
+
@@ -357,12 +375,12 @@
Item #{{ idx }}: {{ item.name }}
-
+
-
+
@@ -370,9 +388,11 @@
}
-
+
@if (users$ | async; as users) {
+
+
@@ -390,7 +410,7 @@
-
+
@@ -402,7 +422,7 @@
}
-
+
@defer {