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 44d5b074973e71dc7fa70ca61e12e6dfd3f0779b Author: Robert Munteanu <[email protected]> AuthorDate: Thu Dec 9 13:07:24 2021 +0100 Completions: support completion for itemList within data-sly-{list,repeat} --- vscode-htl/data/completions-sling.json | 39 ++++++++++++++++++ vscode-htl/src/completionData.ts | 12 ++++++ vscode-htl/src/htlCompletionItemProvider.ts | 62 ++++++++++++++--------------- vscode-htl/src/test/suite/extension.test.ts | 17 ++++++++ 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/vscode-htl/data/completions-sling.json b/vscode-htl/data/completions-sling.json index 2a030ae..67c2dfd 100644 --- a/vscode-htl/data/completions-sling.json +++ b/vscode-htl/data/completions-sling.json @@ -211,5 +211,44 @@ "description": "A value map for this resource. The value map allows to read the properties of the resource" } ] + },{ + "javaType": "$io.sightly.ItemList", + "nestedCompletions": [ + { + "name": "index", + "javaType": "java.lang.Integer", + "description": "zero-based counter `(0..length-1)`" + }, + { + "name": "count", + "javaType": "java.lang.Integer", + "description": "one-based counter `(1..length)`" + }, + { + "name": "first", + "javaType": "java.lang.Boolean", + "description": "`true` if the current item is the first item" + }, + { + "name": "middle", + "javaType": "java.lang.Boolean", + "description": "`true` if the current item is neither the first nor the last item" + }, + { + "name": "last", + "javaType": "java.lang.Boolean", + "description": "`true` if the current item is the last item" + }, + { + "name": "odd", + "javaType": "java.lang.Boolean", + "description": "`true` if the index is odd" + }, + { + "name": "even", + "javaType": "java.lang.Boolean", + "description": "`true` if the index is even" + } + ] }] } \ No newline at end of file diff --git a/vscode-htl/src/completionData.ts b/vscode-htl/src/completionData.ts index 38b77cf..0b869a3 100644 --- a/vscode-htl/src/completionData.ts +++ b/vscode-htl/src/completionData.ts @@ -40,4 +40,16 @@ export class CompletionDataAccess { } return definition.nestedCompletions; } +} + +export class LocalCompletionDefinition implements CompletionDefinition { + name: string; + javaType: string; + description: string; + + constructor(name: string, javaType: string, description: string) { + this.javaType = javaType; + this.name = name; + this.description = description; + } } \ No newline at end of file diff --git a/vscode-htl/src/htlCompletionItemProvider.ts b/vscode-htl/src/htlCompletionItemProvider.ts index 8324a43..d0601ad 100644 --- a/vscode-htl/src/htlCompletionItemProvider.ts +++ b/vscode-htl/src/htlCompletionItemProvider.ts @@ -4,7 +4,7 @@ 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 {CompletionDataAccess, CompletionDefinition, LocalCompletionDefinition} from './completionData'; const slyUseRegexp = /data-sly-use\.([a-zA-Z0-9]+)=/g; const identifierAccess = /([a-zA-Z0-9]+)\./g; @@ -30,9 +30,33 @@ export class HtlCompletionItemProvider implements vscode.CompletionItemProvider } let completionContext = linePrefix.substring(completionStart + 2).trim(); - let completionProperties = this.completionData.getGlobalCompletions(); + + // 1. propose completions based on HTML document + let documentCompletions: CompletionDefinition[] = []; + 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) ) { + documentCompletions.push(new LocalCompletionDefinition(match[1], "java.lang.Object", "")); + } + if ( rawAttrs.indexOf('data-sly-repeat=') >= 0 || rawAttrs.indexOf('data-sly-list') >= 0) { + // TODO - resolve item if possible + documentCompletions.push(new LocalCompletionDefinition("item", "java.lang.Object", "")); + documentCompletions.push(new LocalCompletionDefinition("itemList", "$io.sightly.ItemList", "")); + } + // TODO - support named data-sly-repeat completions, e.g. data-sly-repeat.meh=... + }); + + let completionProperties = this.completionData.getGlobalCompletions().concat(documentCompletions); + let completionCandidate = ""; + // 2. recursively resolve any nested properties for ( const match of completionContext.matchAll(identifierAccess)) { completionCandidate = match[1]; let matchingDefinition = completionProperties.find( e => e.name === completionCandidate ); @@ -44,35 +68,8 @@ export class HtlCompletionItemProvider implements vscode.CompletionItemProvider } } - let completions: vscode.CompletionItem[] = []; - - // top-level matches, propose completions based on HTML document - if ( !completionCandidate ) { - 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) ) { - completions.push(new vscode.CompletionItem(match[1])); - } - if ( rawAttrs.indexOf('data-sly-repeat=') >= 0 ) { - 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=... - }); - } - // provide completions based on properties ( top-level bindings or nested ones) - completionProperties.forEach ( element => { - completions.push( this.toCompletionItem(element) ); - }); - - return completions; + return completionProperties.map ( element => this.toCompletionItem(element) ); } private toCompletionItem(completionDefinition: CompletionDefinition) { @@ -82,7 +79,10 @@ export class HtlCompletionItemProvider implements vscode.CompletionItemProvider description = completionDefinition.description + "\n\n"; } - description += "Type: _" + completionDefinition.javaType+"_"; + // filter out synthetic types + if ( completionDefinition.javaType.charAt(0) !== '$') { + description += "Type: _" + completionDefinition.javaType+"_"; + } item.documentation = new vscode.MarkdownString(description); return item; diff --git a/vscode-htl/src/test/suite/extension.test.ts b/vscode-htl/src/test/suite/extension.test.ts index d3473aa..706e0f0 100644 --- a/vscode-htl/src/test/suite/extension.test.ts +++ b/vscode-htl/src/test/suite/extension.test.ts @@ -51,6 +51,23 @@ suite('Extension Test Suite', () => { assert.deepStrictEqual(completionVariables?.sort(), ["item", "itemList", "properties", "request", "resolver", "resource", "response"]); }); + test('completion test with data-sly-list', () => { + let document = ` + <html> + <body data-sly-list="\${pageItems}"> + <div>\${ itemList. }</div> + </body> + </html> + `; + let completions = completionProvider.provideCompletionItems0('<div>${', document); + let completionVariables = completions?.map ( c => c.label.toString()); + assert.deepStrictEqual(completionVariables?.sort(), ["item", "itemList", "properties", "request", "resolver", "resource", "response"]); + + let itemListCompletions = completionProvider.provideCompletionItems0('<div>${ itemList.', document); + let itemListVariables = itemListCompletions?.map ( c => c.label.toString()); + assert.deepStrictEqual(itemListVariables?.sort(), ["index", "count", "first", "middle", "last", "odd", "even"].sort()); + }); + test('completion test for request', () => { let document = ` <html>
