[IMP] owl-vision: syntax scripts, single quotes attributes and slot props highlight and switch below command

This commit adds the following:

- Syntax builder scripts to make syntaxes easier to read and edit
- Syntax highlight in single quote attributes
- Syntax highlight for slot props
- Basic syntax highlight for xpaths
- Added `Switch Below` command

This commit fixes the following:

- Using `Switch Besides` or `Switch Below` does not open a new panel if one was already open
- Fixed missing space in component's snippet indentation
This commit is contained in:
Bastien Fafchamps (bafa)
2023-11-02 13:33:20 +01:00
committed by Géry Debongnie
parent 398df543fe
commit 34489a2494
13 changed files with 951 additions and 261 deletions
+16 -2
View File
@@ -4,7 +4,22 @@ All notable changes to the "owl-vision" extension will be documented in this fil
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased] [0.0.1] - 2023-03-10
## [0.0.2] - 2023-2-11
### Added
- Switch Below command
- Basic syntax highlight for xpaths
- Syntax builder scripts to make syntaxes easier to read and edit
- Syntax highlight in single quote attributes
- Syntax highlight for slot props
### Fixed
- Added missing space in component's snippet indentation
- Using `Switch Besides` or `Switch Below` does not open a new panel if one was already open
## [0.0.1] - 2023-03-10
- Initial release
@@ -15,4 +30,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- `Find Component` Command - Finds the selected component definition.
- `Switch` Command - Finds the corresponding template or component file depending on the current file.
- `Switch Besides` Command - Finds the corresponding template or component file depending on the current file and opens it besides.
- `Owl Documentation` Sidebar - A webview which allows easly searching through the owl's documentation from github
+6 -5
View File
@@ -4,7 +4,9 @@ Owl Vision is an extension for the amazing [Owl framework](https://github.com/od
![Syntax highlight preview](https://raw.githubusercontent.com/odoo/owl/master/tools/owl-vision/assets/syntax_highlight.png)
This extension also adds a small Component snippet to allow you to create beautiful components as fast as possible!
This extension also adds:
- A basic component snippent.
- "Go to definition" providers for component tags in xml or in inline templates.
## Commands
@@ -13,11 +15,10 @@ This extension also adds a small Component snippet to allow you to create beauti
- If the cursor is on a component, finds the template of the selected component.
* `Owl Vision: Find Component`: Finds the selected component definition.
* `Owl Vision: Switch`: Finds the corresponding template or component depending on the current file.
* `Owl Vision: Switch Besides`: Finds the corresponding template or component depending on the current file and opens it besides.
* `Owl Vision: Switch (Besides)`: Finds the corresponding template or component depending on the current file and opens it besides.
* `Owl Vision: Switch (Below)`: Finds the corresponding template or component depending on the current file and opens it below.
## Extension Settings
This extension contributes the following settings:
## Settings
* `owl-vision.include`: Glob filter for files to include while searching.
* `owl-vision.exclude`: Glob filter for files to exclude while searching.
+9 -6
View File
@@ -4,7 +4,7 @@
"description": "Owl framework extension that highlights templates and ease navigation between components and templates.",
"publisher": "Odoo",
"license": "LGPL-3.0-only",
"version": "0.0.1",
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://github.com/odoo/owl/tree/master/tools/owl-vision"
@@ -30,7 +30,12 @@
},
{
"command": "owl-vision.switch-besides",
"title": "Switch Besides",
"title": "Switch (Besides)",
"category": "Owl Vision"
},
{
"command": "owl-vision.switch-below",
"title": "Switch (Below)",
"category": "Owl Vision"
},
{
@@ -74,9 +79,6 @@
"embeddedLanguages": {
"meta.embedded.block.javascript": "source.js"
},
"tokenTypes": {
"string.quoted.double.xml": "other"
},
"injectTo": [
"text.xml"
]
@@ -120,7 +122,8 @@
"vscode:prepublish": "npm run esbuild-base -- --minify",
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node",
"esbuild": "npm run esbuild-base -- --sourcemap",
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch"
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
"build-syntax": "node ./scripts/owl_template_syntax.mjs"
},
"devDependencies": {
"@types/node": "20.2.5",
@@ -0,0 +1,48 @@
import { createTagPattern, exportPatterns } from "./syntax_builder_utils.mjs";
import {
htmlAttributes,
owlAttributesDynamic,
owlAttributesFormattedString,
owlAttributesStatic,
propsAttributes
} from "./syntax_parts/owl_attributes.mjs";
import { xpathAttributes } from "./syntax_parts/xpath.mjs";
const componentsTags = createTagPattern("component-tags", {
match: "[A-Z][a-zA-Z0-9_]*",
name: "entity.name.type.class owl.component",
patterns: [
owlAttributesDynamic,
owlAttributesDynamic,
propsAttributes,
],
});
const htmlTags = createTagPattern("html-tags", {
match: "[a-z][a-zA-Z0-9_:.]+|[abiqsuw]",
name: "entity.name.tag.localname.xml owl.xml.tag",
patterns: [
owlAttributesFormattedString,
xpathAttributes,
owlAttributesDynamic,
owlAttributesStatic,
htmlAttributes,
],
});
const tTag = createTagPattern("t-tag", {
match: "t(?![a-zA-Z])",
name: "entity.name.tag.localname.xml owl.tag",
patterns: [
propsAttributes,
owlAttributesFormattedString,
owlAttributesDynamic,
owlAttributesStatic,
],
});
exportPatterns(
"L:text.xml -comment",
"owl.template",
[componentsTags, htmlTags, tTag]
);
@@ -0,0 +1,117 @@
import { writeFileSync } from "fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from 'url';
const REPOSITORY = {};
export function exportPatterns(injectionSelector, scopeName, patterns) {
const data = {
injectionSelector: injectionSelector,
scopeName: scopeName,
patterns: [],
repository: {}
}
for (const pattern of patterns) {
data.patterns.push({ include: `#${pattern.id}` });
}
for (const id in REPOSITORY) {
const pattern = { ...REPOSITORY[id] };
delete pattern.id;
data.repository[id] = pattern;
}
const currentDir = dirname(fileURLToPath(import.meta.url));
const filePath = resolve(currentDir, "../syntaxes", `${scopeName}.json`);
writeFileSync(filePath, JSON.stringify(data, null, 4));
console.info(`Sucessfuly build ./syntaxes/${scopeName}.json`);
}
export function createPattern(id, { name, match, begin, end, contentName, beginCaptures, endCaptures, patterns }) {
const pattern = { ...arguments[1] };
if (id) {
pattern.id = id;
REPOSITORY[id] = pattern;
}
function mapCaptures(name, captures) {
if (!captures) {
return;
}
pattern[name] = {};
for (const key in captures) {
pattern[name][key] = { name: captures[key] }
}
}
mapCaptures("beginCaptures", beginCaptures);
mapCaptures("endCaptures", endCaptures);
pattern.patterns = [];
if (patterns) {
for (const childPattern of patterns) {
if (typeof childPattern === "string") {
pattern.patterns.push({ include: childPattern });
} else if (childPattern.id) {
pattern.patterns.push({ include: `#${childPattern.id}` });
} else {
pattern.patterns.push(childPattern);
}
}
}
return pattern;
}
export function createTagPattern(id, { match, name, patterns }) {
return createPattern(id, {
begin: `(</?)(${match})`,
beginCaptures: {
"1": "punctuation.definition.tag.xml owl.xml.punctuation",
"2": name,
},
end: "\\s*([/?]?>)",
endCaptures: {
"1": "punctuation.definition.tag.xml punctuation",
},
patterns,
});
}
export function createAttributePatterns(id, { match, attributeName, contentName = "", patterns = [] }) {
return createPattern(id, {
patterns: [
createPattern(undefined, {
contentName: contentName !== null ? (contentName + " string.quoted.double.xml").trim() : undefined,
begin: `(\\s*)(${match})(=)(")`,
beginCaptures: {
"2": "entity.other.attribute-name.localname.xml " + attributeName,
"4": "punctuation.definition.string.begin.xml",
},
end: `(")`,
endCaptures: {
"0": "punctuation.definition.string.end.xml",
},
patterns,
}),
createPattern(undefined, {
contentName: contentName !== null ? (contentName + " string.quoted.single.xml").trim() : undefined,
begin: `(\\s*)(${match})(=)(')`,
beginCaptures: {
"2": "entity.other.attribute-name.localname.xml " + attributeName,
"4": "punctuation.definition.string.begin.xml",
},
end: `(')`,
endCaptures: {
"0": "punctuation.definition.string.end.xml",
},
patterns,
})
]
});
}
@@ -0,0 +1,103 @@
import { createAttributePatterns, createPattern } from "../syntax_builder_utils.mjs";
export const inilineJs = [createPattern("inline-js", {
patterns: [
{
match: "\\s(=>)\\s",
captures: {
"1": {
name: "string.quoted.double.xml owl.arrow",
}
}
},
{
match: "\\b(props)\\b",
captures: {
"1": {
name: "variable.language.js owl.expression.props",
}
}
},
{
match: "\\s(and|or)\\s",
captures: {
"1": {
name: "keyword.operator.logical.js owl.logical",
}
}
},
{
include: "source.js"
}
]
})];
const formattedString = createPattern("formatted-string", {
contentName: "meta.embedded.block.javascript",
begin: `({{)`,
beginCaptures: {
"1": "owl.double-curlybrackets",
},
end: `(}})`,
endCaptures: {
"1": "owl.double-curlybrackets",
},
patterns: inilineJs
});
// -------------------------------- Attributes --------------------------------
export const htmlAttributes = createAttributePatterns("html-attributes", {
match: "[a-z]{2}[a-z_:.-]+",
attributeName: "owl.xml.attribute",
});
export const propsAttributes = createAttributePatterns("props-attributes", {
match: "[a-zA-Z]{2}[a-zA-Z_:.]*",
contentName: "meta.embedded.block.javascript",
attributeName: "owl.attribute owl.attribute.props",
patterns: inilineJs,
});
export const owlAttributesDynamic = createAttributePatterns("owl-attributes-dynamic", {
match: [
"t-if",
"t-else",
"t-elif",
"t-foreach",
"t-as",
"t-key",
"t-esc",
"t-out",
"t-props",
"t-component",
"t-set",
"t-value",
"t-portal",
"t-slot-scope",
"t-att-[a-z_:.-]+",
"t-on-[a-z_:.-]+"
].join("|"),
contentName: "meta.embedded.block.javascript",
attributeName: "owl.attribute owl.attribute.dynamic",
patterns: inilineJs,
});
export const owlAttributesStatic = createAttributePatterns("owl-attributes-static", {
match: [
"t-name",
"t-ref",
"t-set-slot",
"t-model",
"t-inherit",
"t-inherit-mode",
"t-translation"
].join("|"),
attributeName: "owl.attribute owl.attribute.static",
});
export const owlAttributesFormattedString = createAttributePatterns("owl-attributes-formatted-string", {
match: ["t-call", "t-slot", "t-attf-[a-z_:.-]+"].join("|"),
attributeName: "owl.attribute owl.attribute.formatted",
patterns: [formattedString],
});
@@ -0,0 +1,105 @@
import { createAttributePatterns, createPattern } from "../syntax_builder_utils.mjs";
const xpathPattern = [createPattern("xpath", {
patterns: [
{
begin: "\\[",
beginCaptures: {
"0": {
name: "punctuation.definition",
}
},
end: "\\]",
endCaptures: {
"0": {
name: "punctuation.definition",
}
},
patterns: [
{
begin: "'",
beginCaptures: {
"0": {
name: "punctuation.definition",
}
},
end: "'",
endCaptures: {
"0": {
name: "punctuation.definition",
}
},
contentName: "string.quoted.single",
},
{
begin: "\\\"",
beginCaptures: {
"0": {
name: "punctuation.definition",
}
},
end: "\\\"",
endCaptures: {
"0": {
name: "punctuation.definition",
}
},
contentName: "string.quoted.double",
},
{
match: "(@)([a-zA-Z0-9_:\\-]+)\\b",
captures: {
"1": {
name: "punctuation.definition",
},
"2": {
name: "entity.other.attribute-name",
}
}
},
{
match: "(\\(|\\))",
name: "meta.brace.round",
},
{
match: "[0-9]+(\\.[0-9]+)?",
name: "constant.numeric.decimal",
},
{
match: "\\b(hasclass)\\b",
captures: {
"1": {
name: "entity.name.function",
},
}
},
]
},
{
match: "/{1,2}",
name: "text",
},
{
match: "(?<!@)([A-Z][a-zA-Z]+)\\b",
captures: {
"1": {
name: "entity.name.type.class",
}
}
},
{
match: "(?<!@)([a-z][a-zA-Z0-9_:.]+|[abiqsuw])\\b",
captures: {
"1": {
name: "entity.name.tag",
}
}
},
]
})];
export const xpathAttributes = createAttributePatterns("xpath-attributes", {
match: "expr",
attributeName: "owl.xml.attribute owl.xml.attribute.xpath",
patterns: xpathPattern,
});
+4 -2
View File
@@ -1,12 +1,14 @@
import * as vscode from 'vscode';
import { Search } from './search';
import { ComponentDefinitionProvider } from './definiton_providers';
import { OpenDirection } from './utils';
export async function activate(context: vscode.ExtensionContext) {
const search = new Search();
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch', () => search.switchCommand()));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch-besides', () => search.switchCommand(true)));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch', () => search.switch()));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch-besides', () => search.switch(OpenDirection.Besides)));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch-below', () => search.switch(OpenDirection.Below)));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.find-component', () => search.findComponentCommand()));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.find-template', () => search.findTemplateCommand()));
+38 -64
View File
@@ -1,22 +1,12 @@
import { GlobPattern, Location, Range, Uri, window, workspace } from 'vscode';
import { OpenDirection, getClosestMatch, getSelectedText, hideStatusMessage, showResult, showStatusMessage } from './utils';
import path = require('path');
import * as vscode from 'vscode';
import { getSelectedText, showStatusMessage, hideStatusMessage, getActiveCursorIndex, getClosestMatch } from './utils';
class SearchResult {
uri: vscode.Uri;
range: vscode.Range;
constructor(uri: vscode.Uri, range: vscode.Range) {
this.uri = uri;
this.range = range;
}
}
export class Search {
finderCache = new Map<string, vscode.Uri>();
finderCache = new Map<string, Uri>();
public async switchCommand(openBesides: Boolean = false) {
public async switch(openDirection: OpenDirection = OpenDirection.Active) {
if (!this.currentDocument) {
return;
}
@@ -24,26 +14,20 @@ export class Search {
let result = undefined;
const text = this.currentDocument.getText();
const isJs = this.currentDocument.fileName.endsWith(".js");
const isXml = this.currentDocument.fileName.endsWith(".xml");
const templateName = this.getTemplateName(text, isJs);
if (isJs) {
const templateName = this.getTemplateNameInJS(text);
if (templateName) {
if (templateName && isJs) {
result = await this.findTemplate(templateName);
}
} else if (isXml) {
const templateName = this.getTemplateNameInXML(text);
if (templateName) {
} else if (templateName) {
result = await this.findComponentFromTemplateName(templateName);
}
}
if (result !== undefined) {
this.showResult(result, openBesides);
showResult(result, openDirection);
} else if (isJs) {
vscode.window.showWarningMessage(`Could not find a template for current component`);
} else if (isXml) {
vscode.window.showWarningMessage(`Could not find a component for current template`);
window.showWarningMessage(`Could not find a template for current component`);
} else {
window.showWarningMessage(`Could not find a component for current template`);
}
}
@@ -56,9 +40,9 @@ export class Search {
showStatusMessage(`Searching for component "${currentWord}"`);
const result = await this.findComponent(currentWord);
if (result) {
this.showResult(result);
showResult(result);
} else {
vscode.window.showWarningMessage(`Could not find a component for "${currentWord}"`);
window.showWarningMessage(`Could not find a component for "${currentWord}"`);
}
hideStatusMessage();
}
@@ -72,14 +56,14 @@ export class Search {
showStatusMessage(`Searching for template "${currentWord}"`);
const result = await this.findTemplate(currentWord);
if (result) {
this.showResult(result);
showResult(result);
} else {
vscode.window.showWarningMessage(`Could not find a template for "${currentWord}"`);
window.showWarningMessage(`Could not find a template for "${currentWord}"`);
}
hideStatusMessage();
}
public async findComponent(componentName: string): Promise<SearchResult | undefined> {
public async findComponent(componentName: string): Promise<Location | undefined> {
if (componentName.toLowerCase() === componentName || componentName.includes(".") || componentName.includes("-")) {
return;
}
@@ -88,7 +72,7 @@ export class Search {
return await this.find(componentName, query, "js");
}
public async findTemplate(templateName: string): Promise<SearchResult | undefined> {
public async findTemplate(templateName: string): Promise<Location | undefined> {
const isComponentName = templateName.match(/^[A-Z][a-zA-Z0-9_]*$/);
if (isComponentName) {
@@ -96,9 +80,8 @@ export class Search {
if (!componentResult) {
return;
} else {
const document = await vscode.workspace.openTextDocument(componentResult.uri);
const text = document.getText();
const foundTemplateName = this.getTemplateNameInJS(text);
const text = (await workspace.openTextDocument(componentResult.uri)).getText();
const foundTemplateName = this.getTemplateName(text, true);
if (foundTemplateName) {
templateName = foundTemplateName;
} else {
@@ -111,24 +94,24 @@ export class Search {
return await this.find(templateName, query, "xml");
}
private async findComponentFromTemplateName(templateName: string): Promise<SearchResult | undefined> {
private findComponentFromTemplateName(templateName: string): Promise<Location | undefined> {
const query = this.buildQuery(`template\\s*=\\s*["']`, templateName, `["']`);
return await this.find(templateName, query, "js");
return this.find(templateName, query, "js");
}
private getTemplateNameInJS(str: string): string | undefined {
private getTemplateName(str: string, isJsFile: boolean): string | undefined {
if (isJsFile) {
return getClosestMatch(str, /template\s*=\s*["']([a-zA-Z0-9_\-\.]+)["']/g, +1);
}
private getTemplateNameInXML(str: string): string | undefined {
} else {
return getClosestMatch(str, /t-name="([a-zA-Z0-9_\-\.]+)"/g);
}
}
private async find(
public async find(
name: string,
searchQuery: string,
fileType: "js" | "xml",
) {
): Promise<Location | undefined> {
const key = `${name}-${fileType}`;
const cachedUri = this.finderCache.get(key);
if (cachedUri) {
@@ -140,8 +123,8 @@ export class Search {
}
}
const include = `{${vscode.workspace.getConfiguration().get(`owl-vision.include`)}}`;
const exclude = `{${vscode.workspace.getConfiguration().get(`owl-vision.exclude`)}}`;
const include = `{${workspace.getConfiguration().get(`owl-vision.include`)}, *.${fileType}}`;
const exclude = `{${workspace.getConfiguration().get(`owl-vision.exclude`)}}`;
const files = await this.getFiles(name, include, exclude);
for (const file of files) {
@@ -155,10 +138,10 @@ export class Search {
private async getFiles(
searchQuery: string,
include: vscode.GlobPattern,
exclude: vscode.GlobPattern,
): Promise<Array<vscode.Uri>> {
const files = await vscode.workspace.findFiles(include, exclude);
include: GlobPattern,
exclude: GlobPattern,
): Promise<Array<Uri>> {
const files = await workspace.findFiles(include, exclude);
const parts = searchQuery.split(".").flatMap(s => s.split(/(?=[A-Z])/)).map(s => s.toLowerCase());
const currentDir = this.currentDocument ? path.dirname(this.currentDocument.uri.path) : "";
@@ -182,33 +165,24 @@ export class Search {
}
private async findInFile(
file: vscode.Uri,
file: Uri,
searchQuery: string,
): Promise<SearchResult | undefined> {
const document = await vscode.workspace.openTextDocument(file);
): Promise<Location | undefined> {
const document = await workspace.openTextDocument(file);
const text = document.getText();
const match = text.match(new RegExp(searchQuery));
if (match) {
const index = match.index || 0;
return new SearchResult(file, new vscode.Range(
return new Location(file, new Range(
document.positionAt(index),
document.positionAt(index)
));
}
}
private async showResult(result: SearchResult, openBesides: Boolean = false) {
const editor = await vscode.window.showTextDocument(result.uri, {
viewColumn: openBesides ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active,
});
editor.revealRange(result.range);
editor.selection = new vscode.Selection(result.range.start, result.range.end);
}
private get currentDocument() {
return vscode.window.activeTextEditor?.document;
return window.activeTextEditor?.document;
}
private buildQuery(
+35
View File
@@ -67,3 +67,38 @@ export function getClosestMatch(str: string, regex: RegExp, lineDelta = 0): stri
return closestMatch[1];
}
export enum OpenDirection {
Active,
Besides,
Below,
}
export async function showResult(result: vscode.Location, openDirection: OpenDirection = OpenDirection.Active) {
let editor = undefined;
if (openDirection == OpenDirection.Active) {
editor = await vscode.window.showTextDocument(result.uri);
} else {
let targetColumn = vscode.ViewColumn.Beside;
const existingDocument = vscode.workspace.textDocuments.find(t => t.uri.path === result.uri.path);
if (existingDocument) {
const currentColumn = vscode.window.activeTextEditor?.viewColumn;
if (currentColumn == vscode.ViewColumn.One) {
targetColumn = vscode.ViewColumn.Two;
} else if (currentColumn == vscode.ViewColumn.Two) {
targetColumn = vscode.ViewColumn.One;
}
editor = await vscode.window.showTextDocument(existingDocument, { viewColumn: targetColumn });
} else {
editor = await vscode.window.showTextDocument(result.uri, { viewColumn: targetColumn });
}
}
if (openDirection === OpenDirection.Below) {
await vscode.commands.executeCommand('workbench.action.editorLayoutTwoRows');
}
editor.revealRange(result.range);
editor.selection = new vscode.Selection(result.range.start, result.range.end);
}
@@ -1,5 +1,5 @@
{
"injectionSelector": "L:source.js -comment",
"injectionSelector": "L:source.js -comment -(string -meta.embedded)",
"scopeName": "owl.template.inline",
"patterns": [
{
@@ -12,16 +12,19 @@
"contentName": "meta.embedded.block.xml",
"beginCaptures": {
"1": {
"name": "entity.name.function.tagged-template.js owl-inline-template"
"name": "entity.name.function.tagged-template.js"
},
"2": {
"name": "punctuation.definition.string.template.begin.js string.template.js"
"name": "punctuation.definition.string.template.begin.js"
}
},
"end": "(`)",
"endCaptures": {
"0": {
"name": "string.js"
},
"1": {
"name": "punctuation.definition.string.template.end.js string.template.js"
"name": "punctuation.definition.string.template.end.js"
}
},
"patterns": [
+453 -168
View File
@@ -5,22 +5,451 @@
{
"include": "#component-tags"
},
{
"include": "#html-tags"
},
{
"include": "#t-tag"
},
{
"include": "#xml-tags"
},
{
"include": "#props"
}
],
"repository": {
"inline-js": {
"patterns": [
{
"match": "\\s(=>)\\s",
"captures": {
"1": {
"name": "string.quoted.double.xml owl.arrow"
}
}
},
{
"match": "\\b(props)\\b",
"captures": {
"1": {
"name": "variable.language.js owl.expression.props"
}
}
},
{
"match": "\\s(and|or)\\s",
"captures": {
"1": {
"name": "keyword.operator.logical.js owl.logical"
}
}
},
{
"include": "source.js"
}
]
},
"formatted-string": {
"contentName": "meta.embedded.block.javascript",
"begin": "({{)",
"beginCaptures": {
"1": {
"name": "owl.double-curlybrackets"
}
},
"end": "(}})",
"endCaptures": {
"1": {
"name": "owl.double-curlybrackets"
}
},
"patterns": [
{
"include": "#inline-js"
}
]
},
"html-attributes": {
"patterns": [
{
"contentName": "string.quoted.double.xml",
"begin": "(\\s*)([a-z]{2}[a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.xml.attribute"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": []
},
{
"contentName": "string.quoted.single.xml",
"begin": "(\\s*)([a-z]{2}[a-z_:.-]+)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.xml.attribute"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": []
}
]
},
"props-attributes": {
"patterns": [
{
"contentName": "meta.embedded.block.javascript string.quoted.double.xml",
"begin": "(\\s*)([a-zA-Z]{2}[a-zA-Z_:.]*)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.props"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#inline-js"
}
]
},
{
"contentName": "meta.embedded.block.javascript string.quoted.single.xml",
"begin": "(\\s*)([a-zA-Z]{2}[a-zA-Z_:.]*)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.props"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#inline-js"
}
]
}
]
},
"owl-attributes-dynamic": {
"patterns": [
{
"contentName": "meta.embedded.block.javascript string.quoted.double.xml",
"begin": "(\\s*)(t-if|t-else|t-elif|t-foreach|t-as|t-key|t-esc|t-out|t-props|t-component|t-set|t-value|t-portal|t-slot-scope|t-att-[a-z_:.-]+|t-on-[a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.dynamic"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#inline-js"
}
]
},
{
"contentName": "meta.embedded.block.javascript string.quoted.single.xml",
"begin": "(\\s*)(t-if|t-else|t-elif|t-foreach|t-as|t-key|t-esc|t-out|t-props|t-component|t-set|t-value|t-portal|t-slot-scope|t-att-[a-z_:.-]+|t-on-[a-z_:.-]+)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.dynamic"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#inline-js"
}
]
}
]
},
"owl-attributes-static": {
"patterns": [
{
"contentName": "string.quoted.double.xml",
"begin": "(\\s*)(t-name|t-ref|t-set-slot|t-model|t-inherit|t-inherit-mode|t-translation)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.static"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": []
},
{
"contentName": "string.quoted.single.xml",
"begin": "(\\s*)(t-name|t-ref|t-set-slot|t-model|t-inherit|t-inherit-mode|t-translation)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.static"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": []
}
]
},
"owl-attributes-formatted-string": {
"patterns": [
{
"contentName": "string.quoted.double.xml",
"begin": "(\\s*)(t-call|t-slot|t-attf-[a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.formatted"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#formatted-string"
}
]
},
{
"contentName": "string.quoted.single.xml",
"begin": "(\\s*)(t-call|t-slot|t-attf-[a-z_:.-]+)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.formatted"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#formatted-string"
}
]
}
]
},
"xpath": {
"patterns": [
{
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"patterns": [
{
"begin": "'",
"beginCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"end": "'",
"endCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"contentName": "string.quoted.single"
},
{
"begin": "\\\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"end": "\\\"",
"endCaptures": {
"0": {
"name": "punctuation.definition"
}
},
"contentName": "string.quoted.double"
},
{
"match": "(@)([a-zA-Z0-9_:\\-]+)\\b",
"captures": {
"1": {
"name": "punctuation.definition"
},
"2": {
"name": "entity.other.attribute-name"
}
}
},
{
"match": "(\\(|\\))",
"name": "meta.brace.round"
},
{
"match": "[0-9]+(\\.[0-9]+)?",
"name": "constant.numeric.decimal"
},
{
"match": "\\b(hasclass)\\b",
"captures": {
"1": {
"name": "entity.name.function"
}
}
}
]
},
{
"match": "/{1,2}",
"name": "text"
},
{
"match": "(?<!@)([A-Z][a-zA-Z]+)\\b",
"captures": {
"1": {
"name": "entity.name.type.class"
}
}
},
{
"match": "(?<!@)([a-z][a-zA-Z0-9_:.]+|[abiqsuw])\\b",
"captures": {
"1": {
"name": "entity.name.tag"
}
}
}
]
},
"xpath-attributes": {
"patterns": [
{
"contentName": "string.quoted.double.xml",
"begin": "(\\s*)(expr)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.xml.attribute owl.xml.attribute.xpath"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#xpath"
}
]
},
{
"contentName": "string.quoted.single.xml",
"begin": "(\\s*)(expr)(=)(')",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.xml.attribute owl.xml.attribute.xpath"
},
"4": {
"name": "punctuation.definition.string.begin.xml"
}
},
"end": "(')",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml"
}
},
"patterns": [
{
"include": "#xpath"
}
]
}
]
},
"component-tags": {
"begin": "(</?)([A-Z][a-zA-Z0-9_]*)",
"beginCaptures": {
"1": {
"name": "punctuation.definition.tag.xml owl.punctuation"
"name": "punctuation.definition.tag.xml owl.xml.punctuation"
},
"2": {
"name": "entity.name.type.class owl.component"
@@ -34,45 +463,18 @@
},
"patterns": [
{
"include": "#qweb-directives-interpreted"
"include": "#owl-attributes-dynamic"
},
{
"include": "#qweb-directives-string"
"include": "#owl-attributes-dynamic"
},
{
"include": "#component-props"
"include": "#props-attributes"
}
]
},
"component-props": {
"contentName": "meta.embedded.block.javascript string.quoted.double.xml",
"begin": "(\\s*)([a-zA-Z_:.]*)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.component.props"
},
"4": {
"name": "punctuation.definition.string.begin.xml punctuation"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml punctuation"
}
},
"patterns": [
{
"include": "source.js"
},
{
"name": "keyword.operator.logical.js",
"match": "\\s(and|or)\\s"
}
]
},
"xml-tags": {
"begin": "(</?)([a-z][a-zA-Z0-9_:.]*)",
"html-tags": {
"begin": "(</?)([a-z][a-zA-Z0-9_:.]+|[abiqsuw])",
"beginCaptures": {
"1": {
"name": "punctuation.definition.tag.xml owl.xml.punctuation"
@@ -84,51 +486,32 @@
"end": "\\s*([/?]?>)",
"endCaptures": {
"1": {
"name": "punctuation.definition.tag.xml owl.xml.punctuation"
"name": "punctuation.definition.tag.xml punctuation"
}
},
"patterns": [
{
"include": "#qweb-directives-interpreted"
"include": "#owl-attributes-formatted-string"
},
{
"include": "#qweb-directives-string"
"include": "#xpath-attributes"
},
{
"include": "#qweb-directives-formatted-string"
"include": "#owl-attributes-dynamic"
},
{
"include": "#xml-attributes"
"include": "#owl-attributes-static"
},
{
"name": "string.quoted.double.xml",
"match": "(\")((.*)(=>)(.*))(\")"
"include": "#html-attributes"
}
]
},
"xml-attributes": {
"contentName": "string.quoted.double.xml owl.xml.string",
"begin": "(\\s)([a-z][a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.xml.attribute"
},
"4": {
"name": "punctuation.definition.string.begin.xml owl.xml.punctuation"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml owl.xml.punctuation"
}
}
},
"t-tag": {
"begin": "(</?)(t(?![a-zA-Z]))",
"beginCaptures": {
"1": {
"name": "punctuation.definition.tag.xml owl.punctuation"
"name": "punctuation.definition.tag.xml owl.xml.punctuation"
},
"2": {
"name": "entity.name.tag.localname.xml owl.tag"
@@ -137,121 +520,23 @@
"end": "\\s*([/?]?>)",
"endCaptures": {
"1": {
"name": "punctuation.definition.tag.xml owl.punctuation"
"name": "punctuation.definition.tag.xml punctuation"
}
},
"patterns": [
{
"include": "#qweb-directives-interpreted"
"include": "#props-attributes"
},
{
"include": "#qweb-directives-string"
"include": "#owl-attributes-formatted-string"
},
{
"include": "#xml-attributes"
"include": "#owl-attributes-dynamic"
},
{
"name": "string.quoted.double.xml",
"match": "(\")((.*)(=>)(.*))(\")"
"include": "#owl-attributes-static"
}
]
},
"qweb-directives-interpreted": {
"contentName": "meta.embedded.block.javascript string.quoted.double.xml",
"begin": "(\\s)(t-if|t-else|t-elif|t-foreach|t-as|t-key|t-esc|t-out|t-props|t-component|t-set|t-value|t-portal|(t-att-|t-on-)[a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute"
},
"5": {
"name": "punctuation.definition.string.begin.xml owl.punctuation owl.doublequote"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml owl.punctuation owl.doublequote"
}
},
"patterns": [
{
"include": "source.js"
},
{
"name": "keyword.operator.logical.js",
"match": "\\s(and|or)\\s"
}
]
},
"qweb-directives-string": {
"contentName": "string.quoted.double.xml",
"begin": "(\\s)(t-name|t-ref|t-slot|t-set-slot|t-model|t-translation)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute"
},
"5": {
"name": "punctuation.definition.string.begin.xml owl.punctuation owl.doublequote"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml owl.punctuation owl.doublequote"
}
}
},
"qweb-directives-formatted-string": {
"begin": "(\\s)(t-call|(t-attf-)[a-z_:.-]+)(=)(\")",
"beginCaptures": {
"2": {
"name": "entity.other.attribute-name.localname.xml owl.attribute owl.attribute.formatted"
},
"5": {
"name": "punctuation.definition.string.begin.xml owl.punctuation owl.doublequote"
}
},
"end": "(\")",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.xml owl.punctuation owl.doublequote"
}
},
"patterns": [
{
"include": "#formatted-string"
}
]
},
"formatted-string": {
"contentName": "meta.embedded.block.javascript string.quoted.double.xml",
"begin": "([^\"|}}]*)({{)",
"beginCaptures": {
"1": {
"name": "string.quoted.double.xml"
},
"2": {
"name": "string.quoted.double.xml owl.doublecurlybrackets"
}
},
"end": "(}})([^\"|{{]*)",
"endCaptures": {
"1": {
"name": "string.quoted.double.xml owl.doublecurlybrackets"
},
"2": {
"name": "string.quoted.double.xml"
}
},
"patterns": [
{
"include": "source.js"
}
]
},
"props": {
"name": "variable.language owl.expression.props",
"match": "\\b(props)\\b"
}
}
}