From 34489a249489abb25d23cdf6142d040cb6e32389 Mon Sep 17 00:00:00 2001 From: "Bastien Fafchamps (bafa)" Date: Thu, 2 Nov 2023 13:33:20 +0100 Subject: [PATCH] [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 --- tools/owl-vision/CHANGELOG.md | 18 +- tools/owl-vision/README.md | 11 +- tools/owl-vision/package.json | 15 +- .../scripts/owl_template_syntax.mjs | 48 ++ .../scripts/syntax_builder_utils.mjs | 117 ++++ .../scripts/syntax_parts/owl_attributes.mjs | 103 ++++ .../owl-vision/scripts/syntax_parts/xpath.mjs | 105 ++++ tools/owl-vision/snippets/component.json | 10 +- tools/owl-vision/src/extension.ts | 6 +- tools/owl-vision/src/search.ts | 110 ++-- tools/owl-vision/src/utils.ts | 35 ++ .../syntaxes/owl.template.inline.json | 13 +- tools/owl-vision/syntaxes/owl.template.json | 579 +++++++++++++----- 13 files changed, 930 insertions(+), 240 deletions(-) create mode 100644 tools/owl-vision/scripts/owl_template_syntax.mjs create mode 100644 tools/owl-vision/scripts/syntax_builder_utils.mjs create mode 100644 tools/owl-vision/scripts/syntax_parts/owl_attributes.mjs create mode 100644 tools/owl-vision/scripts/syntax_parts/xpath.mjs diff --git a/tools/owl-vision/CHANGELOG.md b/tools/owl-vision/CHANGELOG.md index 6303a2f6b..52bf8a5f9 100644 --- a/tools/owl-vision/CHANGELOG.md +++ b/tools/owl-vision/CHANGELOG.md @@ -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 diff --git a/tools/owl-vision/README.md b/tools/owl-vision/README.md index 1917d34b2..18e61f885 100644 --- a/tools/owl-vision/README.md +++ b/tools/owl-vision/README.md @@ -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. diff --git a/tools/owl-vision/package.json b/tools/owl-vision/package.json index 19ac8914c..6ad9190e0 100644 --- a/tools/owl-vision/package.json +++ b/tools/owl-vision/package.json @@ -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", diff --git a/tools/owl-vision/scripts/owl_template_syntax.mjs b/tools/owl-vision/scripts/owl_template_syntax.mjs new file mode 100644 index 000000000..d3b59632c --- /dev/null +++ b/tools/owl-vision/scripts/owl_template_syntax.mjs @@ -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] +); diff --git a/tools/owl-vision/scripts/syntax_builder_utils.mjs b/tools/owl-vision/scripts/syntax_builder_utils.mjs new file mode 100644 index 000000000..96c516a67 --- /dev/null +++ b/tools/owl-vision/scripts/syntax_builder_utils.mjs @@ -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: `()", + 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, + }) + ] + }); +} diff --git a/tools/owl-vision/scripts/syntax_parts/owl_attributes.mjs b/tools/owl-vision/scripts/syntax_parts/owl_attributes.mjs new file mode 100644 index 000000000..b65699e49 --- /dev/null +++ b/tools/owl-vision/scripts/syntax_parts/owl_attributes.mjs @@ -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], +}); diff --git a/tools/owl-vision/scripts/syntax_parts/xpath.mjs b/tools/owl-vision/scripts/syntax_parts/xpath.mjs new file mode 100644 index 000000000..f8dd521f0 --- /dev/null +++ b/tools/owl-vision/scripts/syntax_parts/xpath.mjs @@ -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: "(? 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())); diff --git a/tools/owl-vision/src/search.ts b/tools/owl-vision/src/search.ts index d033942a0..042d36bde 100644 --- a/tools/owl-vision/src/search.ts +++ b/tools/owl-vision/src/search.ts @@ -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(); + finderCache = new Map(); - 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) { - result = await this.findTemplate(templateName); - } - } else if (isXml) { - const templateName = this.getTemplateNameInXML(text); - if (templateName) { - result = await this.findComponentFromTemplateName(templateName); - } + if (templateName && isJs) { + result = await this.findTemplate(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 { + public async findComponent(componentName: string): Promise { 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 { + public async findTemplate(templateName: string): Promise { 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 { + private findComponentFromTemplateName(templateName: string): Promise { 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 { - return getClosestMatch(str, /template\s*=\s*["']([a-zA-Z0-9_\-\.]+)["']/g, +1); - } - - private getTemplateNameInXML(str: string): string | undefined { - return getClosestMatch(str, /t-name="([a-zA-Z0-9_\-\.]+)"/g); + private getTemplateName(str: string, isJsFile: boolean): string | undefined { + if (isJsFile) { + return getClosestMatch(str, /template\s*=\s*["']([a-zA-Z0-9_\-\.]+)["']/g, +1); + } 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 { 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> { - const files = await vscode.workspace.findFiles(include, exclude); + include: GlobPattern, + exclude: GlobPattern, + ): Promise> { + 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 { - const document = await vscode.workspace.openTextDocument(file); + ): Promise { + 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( diff --git a/tools/owl-vision/src/utils.ts b/tools/owl-vision/src/utils.ts index 97139b6fb..23d1cfcb2 100644 --- a/tools/owl-vision/src/utils.ts +++ b/tools/owl-vision/src/utils.ts @@ -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); +} diff --git a/tools/owl-vision/syntaxes/owl.template.inline.json b/tools/owl-vision/syntaxes/owl.template.inline.json index 4d8385c84..cb22fe518 100644 --- a/tools/owl-vision/syntaxes/owl.template.inline.json +++ b/tools/owl-vision/syntaxes/owl.template.inline.json @@ -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": [ @@ -34,4 +37,4 @@ ] } } -} +} \ No newline at end of file diff --git a/tools/owl-vision/syntaxes/owl.template.json b/tools/owl-vision/syntaxes/owl.template.json index 032c2c69b..d17ea47fd 100644 --- a/tools/owl-vision/syntaxes/owl.template.json +++ b/tools/owl-vision/syntaxes/owl.template.json @@ -6,252 +6,537 @@ "include": "#component-tags" }, { - "include": "#t-tag" - }, - { - "include": "#xml-tags" + "include": "#html-tags" }, { - "include": "#props" + "include": "#t-tag" } ], "repository": { - "component-tags": { - "begin": "()\\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": "punctuation.definition.tag.xml owl.punctuation" - }, - "2": { - "name": "entity.name.type.class owl.component" + "name": "owl.double-curlybrackets" } }, - "end": "\\s*([/?]?>)", + "end": "(}})", "endCaptures": { "1": { - "name": "punctuation.definition.tag.xml punctuation" + "name": "owl.double-curlybrackets" } }, "patterns": [ { - "include": "#qweb-directives-interpreted" - }, + "include": "#inline-js" + } + ] + }, + "html-attributes": { + "patterns": [ { - "include": "#qweb-directives-string" + "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": [] }, { - "include": "#component-props" + "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": [] } ] }, - "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" + "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" + } + ] }, - "4": { - "name": "punctuation.definition.string.begin.xml punctuation" - } - }, - "end": "(\")", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.xml punctuation" + { + "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": [ { - "include": "source.js" + "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" + } + ] }, { - "name": "keyword.operator.logical.js", - "match": "\\s(and|or)\\s" + "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" + } + ] } ] }, - "xml-tags": { - "begin": "()", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.xml owl.xml.punctuation" + { + "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": [ { - "include": "#qweb-directives-interpreted" + "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" + } + ] }, { - "include": "#qweb-directives-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" + } + } + } + ] }, { - "include": "#qweb-directives-formatted-string" + "match": "/{1,2}", + "name": "text" }, { - "include": "#xml-attributes" + "match": "(?)(.*))(\")" + "match": "(?)", "endCaptures": { "1": { - "name": "punctuation.definition.tag.xml owl.punctuation" + "name": "punctuation.definition.tag.xml punctuation" } }, "patterns": [ { - "include": "#qweb-directives-interpreted" - }, - { - "include": "#qweb-directives-string" + "include": "#owl-attributes-dynamic" }, { - "include": "#xml-attributes" + "include": "#owl-attributes-dynamic" }, { - "name": "string.quoted.double.xml", - "match": "(\")((.*)(=>)(.*))(\")" + "include": "#props-attributes" } ] }, - "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_:.-]+)(=)(\")", + "html-tags": { + "begin": "()", "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.xml owl.punctuation owl.doublequote" + "1": { + "name": "punctuation.definition.tag.xml punctuation" } }, "patterns": [ { - "include": "source.js" + "include": "#owl-attributes-formatted-string" }, { - "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" + "include": "#xpath-attributes" }, - "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" + { + "include": "#owl-attributes-dynamic" + }, + { + "include": "#owl-attributes-static" }, - "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" + "include": "#html-attributes" } ] }, - "formatted-string": { - "contentName": "meta.embedded.block.javascript string.quoted.double.xml", - "begin": "([^\"|}}]*)({{)", + "t-tag": { + "begin": "()", "endCaptures": { "1": { - "name": "string.quoted.double.xml owl.doublecurlybrackets" - }, - "2": { - "name": "string.quoted.double.xml" + "name": "punctuation.definition.tag.xml punctuation" } }, "patterns": [ { - "include": "source.js" + "include": "#props-attributes" + }, + { + "include": "#owl-attributes-formatted-string" + }, + { + "include": "#owl-attributes-dynamic" + }, + { + "include": "#owl-attributes-static" } ] - }, - "props": { - "name": "variable.language owl.expression.props", - "match": "\\b(props)\\b" } } } \ No newline at end of file