[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/), 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 - 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. - `Find Component` Command - Finds the selected component definition.
- `Switch` Command - Finds the corresponding template or component file depending on the current file. - `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. - `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) ![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 ## 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. - 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: Find Component`: Finds the selected component definition.
* `Owl Vision: Switch`: Finds the corresponding template or component depending on the current file. * `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 ## Settings
This extension contributes the following settings:
* `owl-vision.include`: Glob filter for files to include while searching. * `owl-vision.include`: Glob filter for files to include while searching.
* `owl-vision.exclude`: Glob filter for files to exclude 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.", "description": "Owl framework extension that highlights templates and ease navigation between components and templates.",
"publisher": "Odoo", "publisher": "Odoo",
"license": "LGPL-3.0-only", "license": "LGPL-3.0-only",
"version": "0.0.1", "version": "0.0.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/odoo/owl/tree/master/tools/owl-vision" "url": "https://github.com/odoo/owl/tree/master/tools/owl-vision"
@@ -30,7 +30,12 @@
}, },
{ {
"command": "owl-vision.switch-besides", "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" "category": "Owl Vision"
}, },
{ {
@@ -74,9 +79,6 @@
"embeddedLanguages": { "embeddedLanguages": {
"meta.embedded.block.javascript": "source.js" "meta.embedded.block.javascript": "source.js"
}, },
"tokenTypes": {
"string.quoted.double.xml": "other"
},
"injectTo": [ "injectTo": [
"text.xml" "text.xml"
] ]
@@ -120,7 +122,8 @@
"vscode:prepublish": "npm run esbuild-base -- --minify", "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-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node",
"esbuild": "npm run esbuild-base -- --sourcemap", "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": { "devDependencies": {
"@types/node": "20.2.5", "@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,
});
+5 -5
View File
@@ -3,13 +3,13 @@
"prefix": "owlcomponent", "prefix": "owlcomponent",
"body": [ "body": [
"export class ${1:component-name} extends Component {", "export class ${1:component-name} extends Component {",
" static template = \"${2:template-name}\";", " static template = \"${2:template-name}\";",
" static components = {};", " static components = {};",
" static props = {};", " static props = {};",
"", "",
" setup() {", " setup() {",
"", "",
" }", " }",
"}", "}",
"" ""
], ],
+4 -2
View File
@@ -1,12 +1,14 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Search } from './search'; import { Search } from './search';
import { ComponentDefinitionProvider } from './definiton_providers'; import { ComponentDefinitionProvider } from './definiton_providers';
import { OpenDirection } from './utils';
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
const search = new Search(); const search = new Search();
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch', () => search.switchCommand())); context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch', () => search.switch()));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.switch-besides', () => search.switchCommand(true))); 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-component', () => search.findComponentCommand()));
context.subscriptions.push(vscode.commands.registerCommand('owl-vision.find-template', () => search.findTemplateCommand())); context.subscriptions.push(vscode.commands.registerCommand('owl-vision.find-template', () => search.findTemplateCommand()));
+42 -68
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 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 { 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) { if (!this.currentDocument) {
return; return;
} }
@@ -24,26 +14,20 @@ export class Search {
let result = undefined; let result = undefined;
const text = this.currentDocument.getText(); const text = this.currentDocument.getText();
const isJs = this.currentDocument.fileName.endsWith(".js"); const isJs = this.currentDocument.fileName.endsWith(".js");
const isXml = this.currentDocument.fileName.endsWith(".xml"); const templateName = this.getTemplateName(text, isJs);
if (isJs) { if (templateName && isJs) {
const templateName = this.getTemplateNameInJS(text); result = await this.findTemplate(templateName);
if (templateName) { } else if (templateName) {
result = await this.findTemplate(templateName); result = await this.findComponentFromTemplateName(templateName);
}
} else if (isXml) {
const templateName = this.getTemplateNameInXML(text);
if (templateName) {
result = await this.findComponentFromTemplateName(templateName);
}
} }
if (result !== undefined) { if (result !== undefined) {
this.showResult(result, openBesides); showResult(result, openDirection);
} else if (isJs) { } else if (isJs) {
vscode.window.showWarningMessage(`Could not find a template for current component`); window.showWarningMessage(`Could not find a template for current component`);
} else if (isXml) { } else {
vscode.window.showWarningMessage(`Could not find a component for current template`); window.showWarningMessage(`Could not find a component for current template`);
} }
} }
@@ -56,9 +40,9 @@ export class Search {
showStatusMessage(`Searching for component "${currentWord}"`); showStatusMessage(`Searching for component "${currentWord}"`);
const result = await this.findComponent(currentWord); const result = await this.findComponent(currentWord);
if (result) { if (result) {
this.showResult(result); showResult(result);
} else { } else {
vscode.window.showWarningMessage(`Could not find a component for "${currentWord}"`); window.showWarningMessage(`Could not find a component for "${currentWord}"`);
} }
hideStatusMessage(); hideStatusMessage();
} }
@@ -72,14 +56,14 @@ export class Search {
showStatusMessage(`Searching for template "${currentWord}"`); showStatusMessage(`Searching for template "${currentWord}"`);
const result = await this.findTemplate(currentWord); const result = await this.findTemplate(currentWord);
if (result) { if (result) {
this.showResult(result); showResult(result);
} else { } else {
vscode.window.showWarningMessage(`Could not find a template for "${currentWord}"`); window.showWarningMessage(`Could not find a template for "${currentWord}"`);
} }
hideStatusMessage(); 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("-")) { if (componentName.toLowerCase() === componentName || componentName.includes(".") || componentName.includes("-")) {
return; return;
} }
@@ -88,7 +72,7 @@ export class Search {
return await this.find(componentName, query, "js"); 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_]*$/); const isComponentName = templateName.match(/^[A-Z][a-zA-Z0-9_]*$/);
if (isComponentName) { if (isComponentName) {
@@ -96,9 +80,8 @@ export class Search {
if (!componentResult) { if (!componentResult) {
return; return;
} else { } else {
const document = await vscode.workspace.openTextDocument(componentResult.uri); const text = (await workspace.openTextDocument(componentResult.uri)).getText();
const text = document.getText(); const foundTemplateName = this.getTemplateName(text, true);
const foundTemplateName = this.getTemplateNameInJS(text);
if (foundTemplateName) { if (foundTemplateName) {
templateName = foundTemplateName; templateName = foundTemplateName;
} else { } else {
@@ -111,24 +94,24 @@ export class Search {
return await this.find(templateName, query, "xml"); 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, `["']`); 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 {
return getClosestMatch(str, /template\s*=\s*["']([a-zA-Z0-9_\-\.]+)["']/g, +1); 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 getTemplateNameInXML(str: string): string | undefined { public async find(
return getClosestMatch(str, /t-name="([a-zA-Z0-9_\-\.]+)"/g);
}
private async find(
name: string, name: string,
searchQuery: string, searchQuery: string,
fileType: "js" | "xml", fileType: "js" | "xml",
) { ): Promise<Location | undefined> {
const key = `${name}-${fileType}`; const key = `${name}-${fileType}`;
const cachedUri = this.finderCache.get(key); const cachedUri = this.finderCache.get(key);
if (cachedUri) { if (cachedUri) {
@@ -140,8 +123,8 @@ export class Search {
} }
} }
const include = `{${vscode.workspace.getConfiguration().get(`owl-vision.include`)}}`; const include = `{${workspace.getConfiguration().get(`owl-vision.include`)}, *.${fileType}}`;
const exclude = `{${vscode.workspace.getConfiguration().get(`owl-vision.exclude`)}}`; const exclude = `{${workspace.getConfiguration().get(`owl-vision.exclude`)}}`;
const files = await this.getFiles(name, include, exclude); const files = await this.getFiles(name, include, exclude);
for (const file of files) { for (const file of files) {
@@ -155,10 +138,10 @@ export class Search {
private async getFiles( private async getFiles(
searchQuery: string, searchQuery: string,
include: vscode.GlobPattern, include: GlobPattern,
exclude: vscode.GlobPattern, exclude: GlobPattern,
): Promise<Array<vscode.Uri>> { ): Promise<Array<Uri>> {
const files = await vscode.workspace.findFiles(include, exclude); const files = await workspace.findFiles(include, exclude);
const parts = searchQuery.split(".").flatMap(s => s.split(/(?=[A-Z])/)).map(s => s.toLowerCase()); const parts = searchQuery.split(".").flatMap(s => s.split(/(?=[A-Z])/)).map(s => s.toLowerCase());
const currentDir = this.currentDocument ? path.dirname(this.currentDocument.uri.path) : ""; const currentDir = this.currentDocument ? path.dirname(this.currentDocument.uri.path) : "";
@@ -182,33 +165,24 @@ export class Search {
} }
private async findInFile( private async findInFile(
file: vscode.Uri, file: Uri,
searchQuery: string, searchQuery: string,
): Promise<SearchResult | undefined> { ): Promise<Location | undefined> {
const document = await vscode.workspace.openTextDocument(file); const document = await workspace.openTextDocument(file);
const text = document.getText(); const text = document.getText();
const match = text.match(new RegExp(searchQuery)); const match = text.match(new RegExp(searchQuery));
if (match) { if (match) {
const index = match.index || 0; const index = match.index || 0;
return new SearchResult(file, new vscode.Range( return new Location(file, new Range(
document.positionAt(index), document.positionAt(index),
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() { private get currentDocument() {
return vscode.window.activeTextEditor?.document; return window.activeTextEditor?.document;
} }
private buildQuery( private buildQuery(
+35
View File
@@ -67,3 +67,38 @@ export function getClosestMatch(str: string, regex: RegExp, lineDelta = 0): stri
return closestMatch[1]; 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", "scopeName": "owl.template.inline",
"patterns": [ "patterns": [
{ {
@@ -12,16 +12,19 @@
"contentName": "meta.embedded.block.xml", "contentName": "meta.embedded.block.xml",
"beginCaptures": { "beginCaptures": {
"1": { "1": {
"name": "entity.name.function.tagged-template.js owl-inline-template" "name": "entity.name.function.tagged-template.js"
}, },
"2": { "2": {
"name": "punctuation.definition.string.template.begin.js string.template.js" "name": "punctuation.definition.string.template.begin.js"
} }
}, },
"end": "(`)", "end": "(`)",
"endCaptures": { "endCaptures": {
"0": {
"name": "string.js"
},
"1": { "1": {
"name": "punctuation.definition.string.template.end.js string.template.js" "name": "punctuation.definition.string.template.end.js"
} }
}, },
"patterns": [ "patterns": [
@@ -34,4 +37,4 @@
] ]
} }
} }
} }
+453 -168
View File
@@ -5,22 +5,451 @@
{ {
"include": "#component-tags" "include": "#component-tags"
}, },
{
"include": "#html-tags"
},
{ {
"include": "#t-tag" "include": "#t-tag"
},
{
"include": "#xml-tags"
},
{
"include": "#props"
} }
], ],
"repository": { "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": { "component-tags": {
"begin": "(</?)([A-Z][a-zA-Z0-9_]*)", "begin": "(</?)([A-Z][a-zA-Z0-9_]*)",
"beginCaptures": { "beginCaptures": {
"1": { "1": {
"name": "punctuation.definition.tag.xml owl.punctuation" "name": "punctuation.definition.tag.xml owl.xml.punctuation"
}, },
"2": { "2": {
"name": "entity.name.type.class owl.component" "name": "entity.name.type.class owl.component"
@@ -34,45 +463,18 @@
}, },
"patterns": [ "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": { "html-tags": {
"contentName": "meta.embedded.block.javascript string.quoted.double.xml", "begin": "(</?)([a-z][a-zA-Z0-9_:.]+|[abiqsuw])",
"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_:.]*)",
"beginCaptures": { "beginCaptures": {
"1": { "1": {
"name": "punctuation.definition.tag.xml owl.xml.punctuation" "name": "punctuation.definition.tag.xml owl.xml.punctuation"
@@ -84,51 +486,32 @@
"end": "\\s*([/?]?>)", "end": "\\s*([/?]?>)",
"endCaptures": { "endCaptures": {
"1": { "1": {
"name": "punctuation.definition.tag.xml owl.xml.punctuation" "name": "punctuation.definition.tag.xml punctuation"
} }
}, },
"patterns": [ "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", "include": "#html-attributes"
"match": "(\")((.*)(=>)(.*))(\")"
} }
] ]
}, },
"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": { "t-tag": {
"begin": "(</?)(t(?![a-zA-Z]))", "begin": "(</?)(t(?![a-zA-Z]))",
"beginCaptures": { "beginCaptures": {
"1": { "1": {
"name": "punctuation.definition.tag.xml owl.punctuation" "name": "punctuation.definition.tag.xml owl.xml.punctuation"
}, },
"2": { "2": {
"name": "entity.name.tag.localname.xml owl.tag" "name": "entity.name.tag.localname.xml owl.tag"
@@ -137,121 +520,23 @@
"end": "\\s*([/?]?>)", "end": "\\s*([/?]?>)",
"endCaptures": { "endCaptures": {
"1": { "1": {
"name": "punctuation.definition.tag.xml owl.punctuation" "name": "punctuation.definition.tag.xml punctuation"
} }
}, },
"patterns": [ "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", "include": "#owl-attributes-static"
"match": "(\")((.*)(=>)(.*))(\")"
} }
] ]
},
"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"
} }
} }
} }