This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch feature/vscode-htl in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit ea71cc4d5713b8ba097d7d70e4135428200e833d Author: Robert Munteanu <[email protected]> AuthorDate: Wed Dec 8 17:48:34 2021 +0100 Move global completions to a JSON file, add some more of them. --- vscode-htl/data/completions-sling.json | 132 ++++++++++++++++++++++++++++ vscode-htl/src/extension.ts | 62 ++----------- vscode-htl/src/htlCompletionItemProvider.ts | 66 +++++++++++++- vscode-htl/src/test/suite/extension.test.ts | 46 +++++++++- 4 files changed, 242 insertions(+), 64 deletions(-) diff --git a/vscode-htl/data/completions-sling.json b/vscode-htl/data/completions-sling.json new file mode 100644 index 0000000..f35545c --- /dev/null +++ b/vscode-htl/data/completions-sling.json @@ -0,0 +1,132 @@ +{ + "globalCompletions": [ + { + "name": "properties", + "javaType": "org.apache.sling.api.resource.ValueMap", + "description": "List of properties of the current Resource. Backed by _org.apache.sling.api.resource.ValueMap_" + }, + { + "name": "request", + "javaType": "org.apache.sling.api.SlingHttpServletRequest", + "description": "The current request. Backed by _org.apache.sling.api.SlingHttpServletRequest_" + }, + { + "name": "resolver", + "javaType": "org.apache.sling.api.resource.ResourceResolver" + }, + { + "name": "resource", + "javaType": "org.apache.sling.api.resource.Resource" + }, + { + "name": "response", + "javaType": "org.apache.sling.api.SlingHttpServletResponse" + } + ], + "completionProperties": [{ + "javaType": "org.apache.sling.api.SlingHttpServletRequest", + "nestedCompletions": [ + { + "name": "resource", + "javaType": "org.apache.sling.api.Resource" + }, + { + "name": "resourceResolver", + "javaType": "org.apache.sling.api.ResourceResolver" + }, + { + "name": "requestPathInfo", + "javaType": "org.apache.sling.api.request.RequestPathInfo" + }, + { + "name": "requestParameterMap", + "javaType": "org.apache.sling.api.request.RequestParameterMap" + }, + { + "name": "requestParameterList", + "javaType": "java.util.List<String>" + }, + { + "name": "responseContentType", + "javaType": "java.lang.String" + }, + { + "name": "requestProgressTracker", + "javaType": "java.util.Enumeration<String>" + }, + { + "name": "authType", + "javaType": "java.lang.String" + }, + { + "name": "cookies", + "javaType": "javax.http.servlet.Cookie[]" + }, + { + "name": "headerNames", + "javaType": "java.util.Enumeration<String>" + }, + { + "name": "method", + "javaType": "java.lang.String" + }, + { + "name": "pathInfo", + "javaType": "java.lang.String" + }, + { + "name": "pathTranslated", + "javaType": "java.lang.String" + }, + { + "name": "contextPath", + "javaType": "java.lang.String" + }, + { + "name": "queryString", + "javaType": "java.lang.String" + }, + { + "name": "remoteUser", + "javaType": "java.lang.String" + }, + { + "name": "userPrincipal", + "javaType": "java.security.Principal" + }, + { + "name": "requestedSessionId", + "javaType": "java.lang.String" + }, + { + "name": "requestURI", + "javaType": "java.lang.String" + }, + { + "name": "requestURL", + "javaType": "java.lang.StringBuffer" + }, + { + "name": "servletPath", + "javaType": "java.lang.String" + }, + { + "name": "session", + "javaType": "javax.servlet.http.HttpSession" + }, + { + "name": "requestedSessionIdValid", + "javaType": "boolean" + }, + { + "name": "requestedSessionIdFromCookie", + "javaType": "boolean" + }, + { + "name": "requestedSessionIdFromURL", + "javaType": "boolean" + } + + ] + }] +} \ No newline at end of file diff --git a/vscode-htl/src/extension.ts b/vscode-htl/src/extension.ts index 8361de9..2e7bae5 100644 --- a/vscode-htl/src/extension.ts +++ b/vscode-htl/src/extension.ts @@ -1,11 +1,7 @@ -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below +// the module 'vscode' contains the VS Code extensibility API import * as vscode from 'vscode'; - -// HTML parser module used to provide context-sensitive completion -import { parse } from 'node-html-parser'; - -const slyUseRegexp = /data-sly-use\.([a-zA-Z0-9]+)=/g; +// import our own completion provider +import { HtlCompletionItemProvider } from './htlCompletionItemProvider'; // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -28,57 +24,9 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(helloWorld, now); - vscode.languages.registerCompletionItemProvider('html', { - provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) { - - let lineUntilPosition = document.getText(new vscode.Range(position.with(undefined, 0), position)); - if ( lineUntilPosition.indexOf('${') === -1 ) { - return null; - } - // request-specific branch - if ( lineUntilPosition.endsWith('request.') ) { - return [ - new vscode.CompletionItem('resource'), - new vscode.CompletionItem('resourceResolver'), - new vscode.CompletionItem('requestPathInfo'), - new vscode.CompletionItem('contextPath') - ]; - } else { + let completionsPath = vscode.Uri.joinPath(context.extensionUri, "data"); - let generalCompletions = []; - - // TODO - provide completions for all global bindings - let props = new vscode.CompletionItem('properties'); - props.documentation = new vscode.MarkdownString('List of properties of the current Resource. Backed by _org.apache.sling.api.resource.ValueMap_'); - generalCompletions.push(props); - - // TODO - deep auto-completion for resource and request - let req = new vscode.CompletionItem('request'); - req.documentation = new vscode.MarkdownString('The current request. Backed by _org.apache.sling.api.SlingHttpServletRequest_'); - generalCompletions.push(req); - - let htmlDoc = parse(document.getText()); - let elements = htmlDoc.getElementsByTagName("*"); - // TODO - provide only relevant completions based on the position in the document - elements - .filter( e => e.rawAttrs.indexOf('data-sly-') >= 0 ) - .forEach(e => { - // element.attributes parses data-sly-use.foo="bar" incorrectly into {data-sly-use="", foo="bar"} - let rawAttrs = e.rawAttrs; - for ( const match of rawAttrs.matchAll(slyUseRegexp) ) { - generalCompletions.push(new vscode.CompletionItem(match[1])); - } - if ( rawAttrs.indexOf('data-sly-repeat=') >= 0 ) { - generalCompletions.push(new vscode.CompletionItem("item")); - generalCompletions.push(new vscode.CompletionItem("itemList")); // TODO - expand completions for itemList - } - // TODO - support named data-sly-repeat completions, e.g. data-sly-repeat.meh=... - }); - - return generalCompletions; - } - } - }); + vscode.languages.registerCompletionItemProvider('html', new HtlCompletionItemProvider(completionsPath)); } // this method is called when your extension is deactivated diff --git a/vscode-htl/src/htlCompletionItemProvider.ts b/vscode-htl/src/htlCompletionItemProvider.ts index 121914f..7f9d494 100644 --- a/vscode-htl/src/htlCompletionItemProvider.ts +++ b/vscode-htl/src/htlCompletionItemProvider.ts @@ -1,10 +1,70 @@ 'use strict'; import * as vscode from 'vscode'; +// HTML parser module used to provide context-sensitive completion +import { parse } from 'node-html-parser'; +import { readFileSync } from 'fs'; + +const slyUseRegexp = /data-sly-use\.([a-zA-Z0-9]+)=/g; export class HtlCompletionItemProvider implements vscode.CompletionItemProvider { - - public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList<vscode.CompletionItem>> { - throw new Error('Method not implemented.'); + + completions: any; + + constructor(completionsPath: vscode.Uri) { + const slingCompletions = vscode.Uri.joinPath(completionsPath, "completions-sling.json"); + console.log("Reading completions from {}", slingCompletions.fsPath); + this.completions = JSON.parse(readFileSync(slingCompletions.fsPath, 'utf-8')); + + } + + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) { + return this.provideCompletionItems0(document.getText(new vscode.Range(position.with(undefined, 0), position)), document.getText()); + } + + provideCompletionItems0(linePrefix: string, doc: string) { + if ( linePrefix.indexOf('${') === -1 ) { + return null; + } + // request-specific branch + if ( linePrefix.endsWith('request.') ) { + return [ + new vscode.CompletionItem('resource'), + new vscode.CompletionItem('resourceResolver'), + new vscode.CompletionItem('requestPathInfo'), + new vscode.CompletionItem('contextPath') + ]; + } else { + + let generalCompletions: vscode.CompletionItem[] = []; + + this.completions.globalCompletions.forEach( (globalCompletion: any) => { + let vsCodeCompletion = new vscode.CompletionItem(globalCompletion.name); + if ( globalCompletion.description ) { + vsCodeCompletion.documentation = new vscode.MarkdownString(globalCompletion.description); + } + generalCompletions.push(vsCodeCompletion); + }); + + let htmlDoc = parse(doc); + let elements = htmlDoc.getElementsByTagName("*"); + // TODO - provide only relevant completions based on the position in the document + elements + .filter( e => e.rawAttrs.indexOf('data-sly-') >= 0 ) + .forEach(e => { + // element.attributes parses data-sly-use.foo="bar" incorrectly into {data-sly-use="", foo="bar"} + let rawAttrs = e.rawAttrs; + for ( const match of rawAttrs.matchAll(slyUseRegexp) ) { + generalCompletions.push(new vscode.CompletionItem(match[1])); + } + if ( rawAttrs.indexOf('data-sly-repeat=') >= 0 ) { + generalCompletions.push(new vscode.CompletionItem("item")); + generalCompletions.push(new vscode.CompletionItem("itemList")); // TODO - expand completions for itemList + } + // TODO - support named data-sly-repeat completions, e.g. data-sly-repeat.meh=... + }); + + return generalCompletions; + } } } \ No newline at end of file diff --git a/vscode-htl/src/test/suite/extension.test.ts b/vscode-htl/src/test/suite/extension.test.ts index 4ca0ab4..b42e6bc 100644 --- a/vscode-htl/src/test/suite/extension.test.ts +++ b/vscode-htl/src/test/suite/extension.test.ts @@ -1,15 +1,53 @@ import * as assert from 'assert'; +import { HtlCompletionItemProvider } from '../../htlCompletionItemProvider'; // You can import and use all API from the 'vscode' module // as well as import your extension to test it import * as vscode from 'vscode'; // import * as myExtension from '../../extension'; -suite('Extension Test Suite', () => { +suite('Extension Test Suite', () => { vscode.window.showInformationMessage('Start all tests.'); + const workingDir = vscode.Uri.parse("file://" + __dirname, true); + const completionsPath = vscode.Uri.joinPath(workingDir, "..", "..", "..", "data"); + const completionProvider = new HtlCompletionItemProvider(completionsPath); + + test('completion test with no additional structures', () => { + let document = ` + <html> + <body> + \${ } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${', document); + let completionVariables = completions?.map ( c => c.label.toString()); + assert.deepStrictEqual(completionVariables?.sort(), ["properties", "request", "resolver", "resource", "response"]); + }); + + test('completion test with data-sly-use', () => { + let document = ` + <html> + <body data-sly-use.foo="foo.js"> + \${ } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${', document); + let completionVariables = completions?.map ( c => c.label.toString()); + assert.deepStrictEqual(completionVariables?.sort(), ["foo", "properties", "request", "resolver", "resource", "response"]); + }); - test('Sample test', () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + test('completion test with data-sly-repeat', () => { + let document = ` + <html> + <body data-sly-repeat="\${pageItems}"> + \${ } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${', document); + let completionVariables = completions?.map ( c => c.label.toString()); + assert.deepStrictEqual(completionVariables?.sort(), ["item", "itemList", "properties", "request", "resolver", "resource", "response"]); }); });
