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 97bda15f5f7f0acda389c15a01c482b4d91cf704 Author: Robert Munteanu <[email protected]> AuthorDate: Thu Dec 9 11:26:53 2021 +0100 Completions: supported nested completions --- vscode-htl/data/completions-sling.json | 50 ++++++++++++++++++-- vscode-htl/src/completionData.ts | 43 +++++++++++++++++ vscode-htl/src/htlCompletionItemProvider.ts | 71 +++++++++++++++++++---------- vscode-htl/src/test/suite/extension.test.ts | 42 +++++++++++++++++ 4 files changed, 178 insertions(+), 28 deletions(-) diff --git a/vscode-htl/data/completions-sling.json b/vscode-htl/data/completions-sling.json index f35545c..4565e09 100644 --- a/vscode-htl/data/completions-sling.json +++ b/vscode-htl/data/completions-sling.json @@ -28,11 +28,11 @@ "nestedCompletions": [ { "name": "resource", - "javaType": "org.apache.sling.api.Resource" + "javaType": "org.apache.sling.api.resource.Resource" }, { "name": "resourceResolver", - "javaType": "org.apache.sling.api.ResourceResolver" + "javaType": "org.apache.sling.api.resource.ResourceResolver" }, { "name": "requestPathInfo", @@ -44,7 +44,7 @@ }, { "name": "requestParameterList", - "javaType": "java.util.List<String>" + "javaType": "java.util.List<java.lang.String>" }, { "name": "responseContentType", @@ -52,7 +52,7 @@ }, { "name": "requestProgressTracker", - "javaType": "java.util.Enumeration<String>" + "javaType": "java.util.Enumeration<java.lang.String>" }, { "name": "authType", @@ -64,7 +64,7 @@ }, { "name": "headerNames", - "javaType": "java.util.Enumeration<String>" + "javaType": "java.util.Enumeration<java.lang.String>" }, { "name": "method", @@ -128,5 +128,45 @@ } ] + },{ + "javaType": "org.apache.sling.api.resource.Resource", + "nestedCompletions": [ + { + "name": "path", + "javaType": "java.lang.String" + }, + { + "name": "name", + "javaType": "java.lang.String" + }, + { + "name": "parent", + "javaType": "org.apache.sling.api.resource.Resource" + }, + { + "name": "children", + "javaType": "java.lang.Iterable<org.apache.sling.api.resource.Resource>" + }, + { + "name": "resourceType", + "javaType": "java.lang.String" + }, + { + "name": "resourceSuperType", + "javaType": "java.lang.String" + }, + { + "name": "resourceMetaData", + "javaType": "org.apache.sling.api.resource.ResourceMetadata" + }, + { + "name": "resourceResolver", + "javaType": "org.apache.sling.api.resource.ResourceResolver" + }, + { + "name": "valueMap", + "javaType": "org.apache.sling.api.resource.ValueMap" + } + ] }] } \ No newline at end of file diff --git a/vscode-htl/src/completionData.ts b/vscode-htl/src/completionData.ts new file mode 100644 index 0000000..38b77cf --- /dev/null +++ b/vscode-htl/src/completionData.ts @@ -0,0 +1,43 @@ +export interface CompletionData { + globalCompletions: CompletionDefinition[]; + completionProperties: CompletionProperties[]; +} + +export interface CompletionDefinition { + name: string; + javaType: string; + description: string | undefined; +} + +export interface CompletionProperties { + javaType: string; + nestedCompletions: CompletionDefinition[]; +} + +export class CompletionDataAccess { + completions: CompletionData; + + constructor(completions: CompletionData) { + this.completions = completions; + } + + getGlobalCompletions() { + return this.completions.globalCompletions; + } + + findGlobalCompletionDefinition(name: string) { + return this.completions.globalCompletions.find( element => { + return element.name === name; + }); + } + + findPropertyCompletions(javaType: String) { + let definition = this.completions.completionProperties.find( element => { + return element.javaType === javaType; + }); + if ( !definition ) { + return []; + } + return definition.nestedCompletions; + } +} \ No newline at end of file diff --git a/vscode-htl/src/htlCompletionItemProvider.ts b/vscode-htl/src/htlCompletionItemProvider.ts index 7f9d494..206cbcc 100644 --- a/vscode-htl/src/htlCompletionItemProvider.ts +++ b/vscode-htl/src/htlCompletionItemProvider.ts @@ -4,17 +4,20 @@ import * as vscode from 'vscode'; // HTML parser module used to provide context-sensitive completion import { parse } from 'node-html-parser'; import { readFileSync } from 'fs'; +import {CompletionDataAccess, CompletionDefinition} from './completionData'; +import { cpuUsage } from 'process'; const slyUseRegexp = /data-sly-use\.([a-zA-Z0-9]+)=/g; +const identifierAccess = /([a-zA-Z0-9]+)\./g; export class HtlCompletionItemProvider implements vscode.CompletionItemProvider { - completions: any; + completionData: CompletionDataAccess; 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')); + this.completionData = new CompletionDataAccess(JSON.parse(readFileSync(slingCompletions.fsPath, 'utf-8'))); } @@ -23,27 +26,40 @@ export class HtlCompletionItemProvider implements vscode.CompletionItemProvider } provideCompletionItems0(linePrefix: string, doc: string) { - if ( linePrefix.indexOf('${') === -1 ) { + let completionStart = linePrefix.indexOf('${'); + if ( completionStart === -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 completionContext = linePrefix.substring(completionStart + 2).trim(); + let completions: vscode.CompletionItem[] = []; + let completionCandidate = ""; + let previousJavaType = ""; + + for ( const match of completionContext.matchAll(identifierAccess)) { + let completionProperties: CompletionDefinition[]; + if ( previousJavaType ) { + completionProperties = this.completionData.findPropertyCompletions(previousJavaType); + } else { + completionProperties = this.completionData.getGlobalCompletions(); + } + completionCandidate = match[1]; + let matchingDefinition = completionProperties.find( e => e.name === completionCandidate ); + if ( matchingDefinition ) { + previousJavaType = matchingDefinition.javaType; + } + } - let generalCompletions: vscode.CompletionItem[] = []; + if ( completionCandidate ) { + let completionProposals = this.completionData.findPropertyCompletions(previousJavaType); - 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); + completionProposals.forEach ( element => { + completions.push( this.toCompletionItem(element) ); + }); + } else { + + this.completionData.getGlobalCompletions().map( element => { + completions.push(this.toCompletionItem(element)); }); let htmlDoc = parse(doc); @@ -55,16 +71,25 @@ export class HtlCompletionItemProvider implements vscode.CompletionItemProvider // 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])); + completions.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 + completions.push(new vscode.CompletionItem("item")); + completions.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; + return completions; + } + + private toCompletionItem(completionDefintion: CompletionDefinition) { + let item = new vscode.CompletionItem(completionDefintion.name); + if ( completionDefintion.description ) { + item.documentation = new vscode.MarkdownString(completionDefintion.description); } + return item; + } } \ 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 b42e6bc..e3d1849 100644 --- a/vscode-htl/src/test/suite/extension.test.ts +++ b/vscode-htl/src/test/suite/extension.test.ts @@ -50,4 +50,46 @@ suite('Extension Test Suite', () => { let completionVariables = completions?.map ( c => c.label.toString()); assert.deepStrictEqual(completionVariables?.sort(), ["item", "itemList", "properties", "request", "resolver", "resource", "response"]); }); + + test('completion test for request', () => { + let document = ` + <html> + <body> + \${ request. } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${request.', document); + // test a subset, otherwise it's too cumbersome + let completionVariables = completions?.map ( c => c.label.toString()).slice(0,5); + assert.deepStrictEqual(completionVariables?.sort(), ["requestParameterList", "requestParameterMap", "requestPathInfo", "resource", "resourceResolver"]); + }); + + test('completion test for resource', () => { + let document = ` + <html> + <body> + \${ resource. } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${resource.', document); + // test a subset, otherwise it's too cumbersome + let completionVariables = completions?.map ( c => c.label.toString()).slice(0,5); + assert.deepStrictEqual(completionVariables?.sort(), ["children", "name", "parent", "path", "resourceType"]); + }); + + test('nested completion test for', () => { + let document = ` + <html> + <body> + \${ request.resource.parent. } + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('${request.resource.parent.', document); + // test a subset, otherwise it's too cumbersome + let completionVariables = completions?.map ( c => c.label.toString()).slice(0,5); + assert.deepStrictEqual(completionVariables?.sort(), ["children", "name", "parent", "path", "resourceType"]); + }); });
