Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/isml-vs-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'b2c-vs-extension': minor
---

Add ISML language support: file associations for `.isml` and `.ds`, TextMate grammar for syntax highlighting (including embedded JavaScript inside `<isscript>` and `${}` expressions), language configuration for comment toggling/bracket pairs/auto-closing, ~50 snippets for common ISML constructs (conditionals, loops, includes, decorators, Page Designer slots/regions, `Resource.msg`, `URLUtils.url`, `dw/system/Logger`, `Transaction.wrap`, `BasketMgr`, and more), automatic insertion of closing tags when typing `>` after an opening ISML tag, Emmet abbreviation expansion, document links (cmd-click on `template="..."` in `<isinclude>`/`<isdecorate>`/`<ismodule>` jumps to the resolved template across the cartridge path), and breakpoint support in `.isml` files for the B2C script debugger.
2 changes: 2 additions & 0 deletions packages/b2c-vs-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This README is the source of truth for repo-level developer info (build/watch, l
- Log tailing into a dedicated output channel.
- Page Designer Assistant webview (Storefront Next page generation).
- B2C-DX Analytics — CIP/CCAC Query Builder, Tables Browser, curated reports, multi-realm support, saved-query library.
- ISML language support — syntax highlighting, language configuration (comments, brackets, auto-close), and snippets for `.isml` files.
- ISML language support — syntax highlighting, language configuration (comments, brackets, auto-close), snippets, automatic closing-tag insertion, and Emmet support for `.isml` and `.ds` files.

See the [docs site](https://salesforcecommercecloud.github.io/b2c-developer-tooling/vscode-extension/features) for the full tour.

Expand Down
101 changes: 101 additions & 0 deletions packages/b2c-vs-extension/language/isml/isml.snippets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"expression": {
"prefix": "${",
"body": ["\\${${1:expression}}"],
"description": "ISML expression placeholder"
},
"resource-msg": {
"prefix": "resourcemsg",
"body": ["\\${Resource.msg('${1:key}', '${2:bundle}', null)}"],
"description": "Localized message via Resource.msg"
},
"resource-msgf": {
"prefix": "resourcemsgf",
"body": ["\\${Resource.msgf('${1:key}', '${2:bundle}', null, ${3:args})}"],
"description": "Formatted localized message via Resource.msgf"
},
"url": {
"prefix": "urlutilsurl",
"body": ["\\${URLUtils.url('${1:Controller-Action}'${2:, 'param', value})}"],
"description": "Build a URL via URLUtils.url"
},
"https-url": {
"prefix": "urlutilshttps",
"body": ["\\${URLUtils.https('${1:Controller-Action}'${2:, 'param', value})}"],
"description": "Build an HTTPS URL via URLUtils.https"
},
"abs-url": {
"prefix": "urlutilsabs",
"body": ["\\${URLUtils.abs('${1:Controller-Action}'${2:, 'param', value})}"],
"description": "Build an absolute URL via URLUtils.abs"
},
"static-url": {
"prefix": "urlutilsstatic",
"body": ["\\${URLUtils.staticURL('/${1:images/example.png}')}"],
"description": "Reference a static asset via URLUtils.staticURL"
},
"request-locale": {
"prefix": "requestlocale",
"body": ["\\${request.locale}"],
"description": "Current request locale"
},
"request-httpparam": {
"prefix": "requesthttpparam",
"body": ["\\${request.httpParameterMap.${1:paramName}.stringValue}"],
"description": "Read a query/form parameter"
},
"pdict": {
"prefix": "pdict",
"body": ["\\${pdict.${1:property}}"],
"description": "Read from the pipeline dictionary (pdict)"
},
"customer-logged": {
"prefix": "customerlogged",
"body": ["\\${customer.authenticated && customer.registered}"],
"description": "Condition for 'is the customer logged in'"
},
"current-basket": {
"prefix": "currentbasket",
"body": [
"<isscript>",
"\tvar BasketMgr = require('dw/order/BasketMgr');",
"\tvar basket = BasketMgr.getCurrentBasket();",
"\t$0",
"</isscript>"
],
"description": "Get the current basket via BasketMgr"
},
"current-customer": {
"prefix": "currentcustomer",
"body": [
"<isscript>",
"\tvar CustomerMgr = require('dw/customer/CustomerMgr');",
"\tvar customer = CustomerMgr.getCustomerByLogin('${1:login}');",
"\t$0",
"</isscript>"
],
"description": "Look up a customer via CustomerMgr"
},
"current-site": {
"prefix": "currentsite",
"body": [
"<isscript>",
"\tvar Site = require('dw/system/Site');",
"\tvar site = Site.getCurrent();",
"\t$0",
"</isscript>"
],
"description": "Get the current Site"
},
"logger": {
"prefix": "dwlogger",
"body": [
"<isscript>",
"\tvar Logger = require('dw/system/Logger');",
"\tvar log = Logger.getLogger('${1:category}', '${2:filename}');",
"\tlog.info('${3:message}');",
"</isscript>"
],
"description": "Acquire a dw/system/Logger and log a message"
}
}
169 changes: 169 additions & 0 deletions packages/b2c-vs-extension/language/isml/isml.tmLanguage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "ISML",
"scopeName": "text.html.isml",
"fileTypes": ["isml"],
"patterns": [
{"include": "#isml-comment"},
{"include": "#isml-script"},
{"include": "#isml-tag"},
{"include": "#isml-expression"},
{"include": "text.html.basic"}
],
"repository": {
"isml-comment": {
"name": "comment.block.isml",
"begin": "<iscomment>",
"end": "</iscomment>",
"beginCaptures": {
"0": {"name": "punctuation.definition.comment.begin.isml"}
},
"endCaptures": {
"0": {"name": "punctuation.definition.comment.end.isml"}
}
},
"isml-script": {
"name": "meta.embedded.block.isml.isscript",
"begin": "(<)(isscript)\\b([^>]*)(>)",
"end": "(</)(isscript)\\s*(>)",
"beginCaptures": {
"1": {"name": "punctuation.definition.tag.begin.isml"},
"2": {"name": "entity.name.tag.isml"},
"3": {"patterns": [{"include": "#tag-attributes"}]},
"4": {"name": "punctuation.definition.tag.end.isml"}
},
"endCaptures": {
"1": {"name": "punctuation.definition.tag.begin.isml"},
"2": {"name": "entity.name.tag.isml"},
"3": {"name": "punctuation.definition.tag.end.isml"}
},
"patterns": [{"include": "source.js"}]
},
"isml-expression": {
"name": "meta.embedded.expression.isml",
"begin": "\\$\\{",
"end": "\\}",
"beginCaptures": {
"0": {"name": "punctuation.section.embedded.begin.isml"}
},
"endCaptures": {
"0": {"name": "punctuation.section.embedded.end.isml"}
},
"patterns": [
{"include": "#expression-content"}
]
},
"expression-content": {
"patterns": [
{
"name": "string.quoted.double.isml",
"begin": "\"",
"end": "\"",
"patterns": [{"name": "constant.character.escape.isml", "match": "\\\\."}]
},
{
"name": "string.quoted.single.isml",
"begin": "'",
"end": "'",
"patterns": [{"name": "constant.character.escape.isml", "match": "\\\\."}]
},
{
"name": "constant.numeric.isml",
"match": "\\b\\d+(\\.\\d+)?\\b"
},
{
"name": "constant.language.isml",
"match": "\\b(true|false|null|undefined)\\b"
},
{
"name": "keyword.operator.isml",
"match": "(===|!==|==|!=|<=|>=|&&|\\|\\||[+\\-*/%<>!?:])"
},
{
"name": "variable.other.isml",
"match": "\\b([a-zA-Z_$][\\w$]*)\\b"
}
]
},
"isml-tag": {
"patterns": [
{"include": "#isml-tag-with-content"},
{"include": "#isml-self-closing-tag"}
]
},
"isml-self-closing-tag": {
"name": "meta.tag.isml",
"begin": "(</?)(is[a-zA-Z][\\w-]*)\\b",
"end": "(/?>)",
"beginCaptures": {
"1": {"name": "punctuation.definition.tag.begin.isml"},
"2": {"name": "entity.name.tag.isml"}
},
"endCaptures": {
"1": {"name": "punctuation.definition.tag.end.isml"}
},
"patterns": [
{"include": "#tag-attributes"}
]
},
"isml-tag-with-content": {
"patterns": []
},
"tag-attributes": {
"patterns": [
{
"name": "meta.attribute.isml",
"begin": "([a-zA-Z_:][\\w:.-]*)\\s*(=)\\s*",
"end": "(?<=\"|')|(?=[\\s/>])",
"beginCaptures": {
"1": {"name": "entity.other.attribute-name.isml"},
"2": {"name": "punctuation.separator.key-value.isml"}
},
"patterns": [
{"include": "#attribute-value"}
]
},
{
"name": "entity.other.attribute-name.isml",
"match": "\\b[a-zA-Z_:][\\w:.-]*\\b"
}
]
},
"attribute-value": {
"patterns": [
{
"name": "string.quoted.double.isml",
"begin": "\"",
"end": "\"",
"beginCaptures": {
"0": {"name": "punctuation.definition.string.begin.isml"}
},
"endCaptures": {
"0": {"name": "punctuation.definition.string.end.isml"}
},
"patterns": [
{"include": "#isml-expression"}
]
},
{
"name": "string.quoted.single.isml",
"begin": "'",
"end": "'",
"beginCaptures": {
"0": {"name": "punctuation.definition.string.begin.isml"}
},
"endCaptures": {
"0": {"name": "punctuation.definition.string.end.isml"}
},
"patterns": [
{"include": "#isml-expression"}
]
},
{
"name": "string.unquoted.isml",
"match": "[^\\s'\"=<>`]+"
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"comments": {
"blockComment": ["<iscomment>", "</iscomment>"]
},
"brackets": [
["<!--", "-->"],
["<", ">"],
["{", "}"],
["(", ")"],
["[", "]"]
],
"autoClosingPairs": [
{"open": "{", "close": "}"},
{"open": "[", "close": "]"},
{"open": "(", "close": ")"},
{"open": "'", "close": "'"},
{"open": "\"", "close": "\""},
{"open": "<!--", "close": "-->", "notIn": ["comment", "string"]},
{"open": "${", "close": "}", "notIn": ["comment"]}
],
"surroundingPairs": [
["'", "'"],
["\"", "\""],
["{", "}"],
["[", "]"],
["(", ")"],
["<", ">"]
],
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)",
"onEnterRules": [
{
"beforeText": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr|isif|iselse|iselseif|isloop|isscript|iscomment|iscontent|isdecorate|isinclude|isset|ismodule|isprint|isredirect|isobject|isactivedatacontext|isactivedatahead|isanalyticsoff|isbreak|iscache|iscontinue|iscookies|isnext|isobject|isstatus|isslot)\\b)([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$",
"afterText": "^</([_:\\w][_:\\w-.\\d]*)\\s*>",
"action": {"indent": "indentOutdent"}
},
{
"beforeText": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)\\b)([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$",
"action": {"indent": "indent"}
}
],
"folding": {
"markers": {
"start": "^\\s*<!--\\s*#?region\\b.*-->",
"end": "^\\s*<!--\\s*#?endregion\\b.*-->"
}
}
}
Loading
Loading