You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2021/12/09 16:19:26 UTC

[sling-whiteboard] 06/18: Completions: supported nested completions

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 <ro...@apache.org>
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"]);
+	});
 });