From 131ad9cd247f29514873bddc7af35ea09ae700d9 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Tue, 28 Nov 2023 22:10:24 -0500 Subject: [PATCH] add SUV interfacing stuff to vscode --- config/languages/zql.tmLanguage.json | 530 ++++++++++++++++++++++++ icons/commands/add-connection-dark.svg | 1 + icons/commands/add-connection-light.svg | 1 + icons/dark/explorer/class.svg | 102 +++++ icons/light/explorer/class.svg | 102 +++++ package.json | 95 ++++- src/ModuleExplorerTreeDataProvider.ts | 33 ++ src/extension.ts | 176 +++++++- src/localhost.crt | 29 ++ src/tdp/GenericTreeDataItem.ts | 68 +++ src/tdp/GenericTreeDataProvider.ts | 87 ++++ 11 files changed, 1215 insertions(+), 9 deletions(-) create mode 100644 config/languages/zql.tmLanguage.json create mode 100644 icons/commands/add-connection-dark.svg create mode 100644 icons/commands/add-connection-light.svg create mode 100755 icons/dark/explorer/class.svg create mode 100755 icons/light/explorer/class.svg create mode 100644 src/ModuleExplorerTreeDataProvider.ts create mode 100644 src/localhost.crt create mode 100644 src/tdp/GenericTreeDataItem.ts create mode 100644 src/tdp/GenericTreeDataProvider.ts diff --git a/config/languages/zql.tmLanguage.json b/config/languages/zql.tmLanguage.json new file mode 100644 index 0000000..54ad480 --- /dev/null +++ b/config/languages/zql.tmLanguage.json @@ -0,0 +1,530 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/Microsoft/vscode-mssql/blob/master/syntaxes/SQL.plist", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/Microsoft/vscode-mssql/commit/cd754662e5607c62ecdc51d2a2dc844546a0bbb6", + "name": "Mocha ZQL", + "scopeName": "source.zql", + "patterns": [ + { + "match": "^[ \\t]*-{2,}\\s+(@[\\w_]+)\\s+(.+)?$", + "captures":{ + "1": { + "name": "text.variable" + }, + "2": { + "name": "entity.name.type.instance.metadata" + } + } + }, + { + "match": "((?]?=|<>|<|>", + "name": "keyword.operator.comparison.sql" + }, + { + "match": "-|\\+|/", + "name": "keyword.operator.math.sql" + }, + { + "match": "\\|\\|", + "name": "keyword.operator.concatenator.sql" + }, + { + "match": "(?i)\\b(avg|checksum_agg|count|count_big|grouping|grouping_id|max|min|sum|stdev|stdevp|var|varp)\\b", + "name": "support.function.aggregate.sql" + }, + { + "match": "(?i)\\b(cast|convert|parse|try_cast|try_convert|try_parse)\\b", + "name": "support.function.conversion.sql" + }, + { + "match": "(?i)\\b(cursor_status)\\b", + "name": "support.function.cursor.sql" + }, + { + "match": "(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|eomonth|switchoffset|todatetimeoffset|isdate)\\b", + "name": "support.function.datetime.sql" + }, + { + "match": "(?i)\\b(coalesce|nullif)\\b", + "name": "support.function.expression.sql" + }, + { + "match": "(? \ No newline at end of file diff --git a/icons/commands/add-connection-light.svg b/icons/commands/add-connection-light.svg new file mode 100644 index 0000000..611e3a5 --- /dev/null +++ b/icons/commands/add-connection-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/dark/explorer/class.svg b/icons/dark/explorer/class.svg new file mode 100755 index 0000000..067c403 --- /dev/null +++ b/icons/dark/explorer/class.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/light/explorer/class.svg b/icons/light/explorer/class.svg new file mode 100755 index 0000000..f3caf9b --- /dev/null +++ b/icons/light/explorer/class.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json index 7dac63e..8508efb 100644 --- a/package.json +++ b/package.json @@ -15,31 +15,74 @@ "commands": [ { "command": "suvmanager.suv_manager", - "title": "Mocha: SUV Manager" + "title": "Manage SUVs", + "category": "Mocha" }, { "command": "suvmanager.suv_up", - "title": "Mocha: SUV Up" + "title": "Launch SUV", + "category": "Mocha" + }, + { + "command": "mocha.suvmanager.suv_new", + "title": "Provision New SUV", + "category": "Mocha", + "icon": { + "light": "icons/commands/add-connection-light.svg", + "dark": "icons/commands/add-connection-dark.svg" + } }, { "command": "mocha.add_documentation_comment", - "title": "Mocha: Add Documentation Comment" + "title": "Add Documentation Comment", + "category": "Mocha" }, { "command": "mocha.add_extension_qualifier", - "title": "Mocha: Add Extension Qualifier" + "title": "Add Extension Qualifier", + "category": "Mocha" }, { "command": "mocha.publish_zq_stub_with_implementation", - "title": "Mocha: Publish as ZQ stub and upload implementation" + "title": "Publish as ZQ stub and upload implementation", + "category": "Mocha" }, { "command": "mocha.publish_zq_stub", - "title": "Mocha: Publish as ZQ stub method" + "title": "Publish as ZQ stub method", + "category": "Mocha" }, { "command": "mocha.import_function_implementation_from_zq", - "title": "Mocha: Import Function Implementation from ZQ" + "title": "Import Function Implementation from ZQ", + "category": "Mocha" + }, + { + "command": "mocha.zq_lookup_instance", + "title": "Look up ZQ Instance", + "category": "Mocha" + }, + { + "command": "mocha.zq_lookup_resource", + "title": "Look up ZQ Resource", + "category": "Mocha" + }, + { + "command": "mocha.zq_open_document", + "enablement": "never", + "category": "Mocha" + }, + { + "command": "mocha.zq_import_function_signature", + "title": "Import ZQ Function Signature", + "category": "Mocha" + } + ], + "keybindings": [ + { + "command": "mocha.zq_lookup_instance", + "key": "Ctrl+O", + "when": "view == mocha.zqEditor" } ], "viewsContainers": { @@ -51,6 +94,35 @@ } ] }, + "customEditors": [ + { + "viewType": "mocha.zqEditor", + "displayName": "Mocha ZQ Editor", + "selector": [ + { + "filenamePattern": "*.zql" + } + ] + } + ], + "grammars": [ + { + "language": "zql", + "scopeName": "source.zql", + "path": "./config/languages/zql.tmLanguage.json" + } + ], + "languages": [ + { + "id": "zql", + "aliases": [ + "Mocha ZQL" + ], + "filenamePatterns": [ + "*.zql" + ] + } + ], "views": { "mocha": [ { @@ -64,6 +136,15 @@ "type": "tree" } ] + }, + "menus": { + "view/title": [ + { + "command": "mocha.suvmanager.suv_new", + "when": "view == mocha.suvManager", + "group": "navigation@1" + } + ] } }, "scripts": { diff --git a/src/ModuleExplorerTreeDataProvider.ts b/src/ModuleExplorerTreeDataProvider.ts new file mode 100644 index 0000000..b558be0 --- /dev/null +++ b/src/ModuleExplorerTreeDataProvider.ts @@ -0,0 +1,33 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { GenericTreeDataItem } from './tdp/GenericTreeDataItem'; +import { Uri } from 'vscode'; +import { GenericTreeDataProvider } from './tdp/GenericTreeDataProvider'; + +export class ModuleExplorerTreeDataProvider extends GenericTreeDataProvider { + + makeIconPath(pathstr : string) + { + return { + "light": path.join(__filename, "..", "..", "icons", "light", pathstr), + "dark": path.join(__filename, "..", "..", "icons", "dark", pathstr) + }; + } + + override getIconPath(element: GenericTreeDataItem): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri; } | vscode.ThemeIcon | undefined { + + let attr = element.customAttributes["type"]; + switch (attr) + { + case "class": + return this.makeIconPath("explorer/class.svg"); + case "module": + return vscode.ThemeIcon.Folder; + } + + } + + +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 121027e..b800002 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,6 +3,95 @@ import * as vscode from 'vscode'; import { SuvManagerTreeDataProvider } from './SuvManagerTreeDataProvider'; +import { GenericTreeDataProvider } from './tdp/GenericTreeDataProvider'; +import { GenericTreeDataItem } from './tdp/GenericTreeDataItem'; +import { ModuleExplorerTreeDataProvider } from './ModuleExplorerTreeDataProvider'; +import { userInfo } from 'os'; +import * as path from 'path'; + +import https from 'node:https'; + +const cp = require('child_process'); + +export function mkotsuri(suvId : string, tenantName : string, command : string, serviceName : string = "zq", version : string = "v1") : vscode.Uri +{ + let otsUri = vscode.Uri.parse("https://" + suvId + ".privatesuv.com/ots/" + tenantName + "/services/" + serviceName + "/" + version + "/" + command); + return otsUri; +} + +function updateModuleExplorerTreeView() { + let dpModuleExplorer = new ModuleExplorerTreeDataProvider(); + /* + dpModuleExplorer.items.push(new GenericTreeDataItem("tools", "tools", [ + new GenericTreeDataItem("tools:xpresso", "xpressO", [ + new GenericTreeDataItem("tools:xpressO:method", "method", [ + new GenericTreeDataItem("tools:xpresso:method:AccessModifier", "AccessModifier", undefined, { + "type": "class", + "instanceId": "1$4170" + }), + new GenericTreeDataItem("tools:xpresso:method:Method", "Method", undefined, { + "type": "class", + "instanceId": "1$4171" + }) + ], { + "type": "module" + }) + ], { + "type": "module" + })], + { + "type": "module" + } + )); + */ + let suvId : string = "i-0c0398f84acecb702"; + let tenantName : string = "super"; + let otsuri = mkotsuri(suvId, tenantName, "module/list"); + + cp.exec("curl " + otsuri.toString(), (err: string, stdout: string, stderr: string) => + { + let json = JSON.parse(stdout); + for (var i = 0; i < json.items.length; i++) + {2 + recursiveAddItemToTreeView(dpModuleExplorer, json.items[i], null); + } + + //dpModuleExplorer.refresh(); + + let treeModuleExplorer = vscode.window.createTreeView("mocha.moduleExplorer", { "canSelectMany": true, "showCollapseAll": true, "treeDataProvider": dpModuleExplorer }); + //console.log(stdout); + }); +} + +function recursiveAddItemToTreeView(dp : GenericTreeDataProvider, item : any, parent : GenericTreeDataItem | null) +{ + var p; + if (item.instanceId) + { + p = new GenericTreeDataItem(item.name, item.title, undefined, { "type": item.type, "instanceId": item.instanceId }); + } + else + { + p = new GenericTreeDataItem(item.name, item.title, undefined, { "type": item.type }); + } + if (item.items) + { + for (var i = 0; i < item.items.length; i++) + { + recursiveAddItemToTreeView(dp, item.items[i], p); + } + } + if (parent === null) + { + console.log("adding item '" + p.name + "' to root"); + dp.items.push(p); + } + else + { + console.log("adding item '" + p.name + "' to parent '" + parent.name + "'"); + parent.children.push(p); + } +} // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -11,13 +100,97 @@ export function activate(context: vscode.ExtensionContext) { let treeDataProvider = new SuvManagerTreeDataProvider(); let treeSuvManager = vscode.window.createTreeView("mocha.suvManager", { "canSelectMany": true, "showCollapseAll": true, "treeDataProvider": treeDataProvider }); treeDataProvider.treeview = treeSuvManager; - treeSuvManager.badge = { "value": 1, "tooltip": "1 SUV(s) running" }; + + updateModuleExplorerTreeView(); // Use the console to output diagnostic information (console.log) and errors (console.error) // This line of code will only be executed once when your extension is activated console.log('Congratulations, your extension "suvmanager" is now active!'); + let cmd_zq_import_function_signature = vscode.commands.registerCommand("mocha.zq_import_function_signature", () => { +/* + const rootCas = require('ssl-root-cas').create(); + rootCas.addFile(path.resolve(__dirname, 'localhost.crt')); + https.globalAgent.options.ca = rootCas; + let req = https.request({ + "hostname": otsuri.authority, + "port": 443, + "path": otsuri.path, + "method": "GET", + "headers": + { + } + }, (res) => { + res.on("data", (chunk) => { + console.log("BODY: " + chunk); + }); + res.on("end", () => { + console.log("end transmission"); + }); + }); + + req.on('error', (e) => { + console.error(`problem with request: ${e.message}`); + }); + + req.end(); + + */ + + vscode.window.showQuickPick([ "Access Modifier@get Access Modifier.for Metadata With Access Modifier(GR)", + "Access Modifier@get Access Modifiers Effectively Equivalent to A2 Root Acess(GSI)*S", +"Access Modifier@get Access Modifiers with Override Restrictions(GSI)*S", +"Access Modifier@get Active Method Accesses(IOP)*S", +"Access Modifier@get Method Access Public Instance(GSI)*S(public)", +"Access Modifier@get Methods for Access Modifiers(IOP)*P*S", +"Access Modifier@get Most Restrictive from Set(SS)*P*S"], { title: "Function name or instance ID"}).then ((choice) => +{ + let ed = vscode.window.activeTextEditor; + if (ed !== undefined) + { + ed.edit((edit) => { + + if (ed !== undefined) + { + if (choice !== undefined) + { + var ppp = choice.split("@"); + var qqq = ppp[1].replaceAll(" ", "").replaceAll(".", ""); + var xxx = qqq.substring(0, qqq.indexOf('(')); + + edit.insert(ed.selection.active, "static stub function " + xxx + "() : AccessModifier"); + } + } + + }); + } + +}); + + }); + + let cmd_mocha_open_doc = vscode.commands.registerCommand("mocha.zq_open_document", (item : GenericTreeDataItem) => { + + console.log(item.name); + + if (item.customAttributes["instanceId"]) + { + let tdi = vscode.workspace.openTextDocument(vscode.Uri.file(path.join(userInfo().homedir, item.title + ".zql") ).with({ scheme: "untitled" })).then( (doc) => + { + vscode.window.showTextDocument(doc).then((editor) => + { + editor.edit(edit => { + edit.delete(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(editor.document.lineCount - 1, editor.document.lineAt(editor.document.lineCount-1).range.end.character))) + + edit.insert(new vscode.Position(0, 0), "class " + item.title + ", " + item.customAttributes["instanceId"] + " {\n\nattributes:\n name: 4$1 : text\n order, 4$7 : text\n\nrelationships:\n forMetadataWithAccessModifier, 3$11854\n\nfunctions:\n\ninstances:\n Public, 4170$1\n}"); + }); + }); + }); + } + + }); + // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json @@ -32,7 +205,6 @@ export function activate(context: vscode.ExtensionContext) { let cmd_suv_up = vscode.commands.registerCommand('suvmanager.suv_up', () => { // The code you place here will be executed every time your command is executed // Display a message box to the user - const cp = require('child_process') cp.exec('mocha suv list', (err: string, stdout: string, stderr: string) => { let list = stdout.split(' '); diff --git a/src/localhost.crt b/src/localhost.crt new file mode 100644 index 0000000..d2035bd --- /dev/null +++ b/src/localhost.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE7zCCA9egAwIBAgIURvpBSseeEDIKEO0c1VBMWkLexMQwDQYJKoZIhvcNAQEL +BQAwgZkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJGTDEQMA4GA1UEBwwHT3JsYW5k +bzEfMB0GA1UECgwWTUJTIEJ1c2luZXNzIFNvbHV0aW9uczEkMCIGA1UEAwwbTUJT +IEludGVybmFsIERldmVsb3BtZW50IENBMSQwIgYJKoZIhvcNAQkBFhVzdXBwb3J0 +QHRldHJvbmljYS5jb20wHhcNMjMxMTA3MTI0MjMyWhcNMjUxMTA2MTI0MjMyWjBo +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCRkwxEDAOBgNVBAcMB09ybGFuZG8xHzAd +BgNVBAoMFk1CUyBCdXNpbmVzcyBTb2x1dGlvbnMxGTAXBgNVBAMMECoucHJpdmF0 +ZXN1di5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCB79lGqz1t +wC5KDJ6TMsJk6/BK7vQiyT3umuut2RPhwkMHfihz+zfxaYo4C7KHkSoCPT4v3u1y +MONJfiev8E+/ZzHlPNYhxs/Su1iSavJQHPvKzKMSkvjbMQFX/Cqzp/A2NL5EkmYv +HrFd9WiV2azp5knQ8hLWdvWR8gUHJZK0FocKA6qbQNQ3G/McOsEsaUZtjCcb1oJw +fKt4G8i2Iv0aMMTOJfCQYhrpuGuX8qkcD1gR9imb8qhthiLw54LwcrtQcIVEwFAG +YyDPVRsw6xvLYHchRkx+DvRdgy/UKMha9tq/3lzF9Fm1/3cnelEsKe7W51ZGkU+3 +apVqNovaYZ+ty1rRxMe/tj4XtHaOLTioG/UMT7AL0LK3darEAS29n8UdT+xORBsU +7iENL112ZcY4yrzCDzUz1Ys0NJAl9a4p6kW33lu0idRTq75xwOYoKhX69Kff0bF8 +dAAebxZSYcIF9/uKHpKW31zK8ac9d1bHYnkL8Ej2yA6Ps98tYLDUecC3dbYk+k2I +igz2BN2UhyEonb5DUz6dSlR+RR3kB884ycMrBi9FNEhjBhm5+iOHs1nAh1Hzm/IJ +Koiw49XyWZIxNYWkcqq9h4wQEQIiZ/3S1FeJWxj+vt+tZKAhDc71V5kSHHJXCh3X +EIqXLZYKXPAG0uST+H8VY5bXahKW/A60UQIDAQABo18wXTAbBgNVHREEFDASghAq +LnByaXZhdGVzdXYuY29tMB0GA1UdDgQWBBSCn5UhCbR7QG5M5RgZXI4y4LoFSDAf +BgNVHSMEGDAWgBSkDws8lTr7dn6nUzawl/gS5J2i3DANBgkqhkiG9w0BAQsFAAOC +AQEAPozqKZadO7QR4HxdU2KNuBlfbvZ62KS2UoiISnUS/cHEejkSdU6RaWN1wVv4 +rimBhhVX+vkIBcd4OiaRTxFBQpgkyTxI7L+B/fKTmwUP3KEl2GSiWFwmAcRQjn4u +tNuABnn7d7UTl9NCR/n3981A1gl6cIAjv6XBEuDWCCTSCVWgWDBlpG2OA0Fp5+GL +J4Jl7xfjpiFAdOllVi/Cd63DiQmv6Fxuc2wBeugatLYCM8Mu6WOJ8+SvbJ57zYec +1oWftLmRr5WxpgGrbDMcAwwD74OXlTOuNX/Jx7uX2Y4Qlqysl7gHJtztlTQCO+23 +RRiyHDf6iKxeh2S16xnVi2vtWw== +-----END CERTIFICATE----- diff --git a/src/tdp/GenericTreeDataItem.ts b/src/tdp/GenericTreeDataItem.ts new file mode 100644 index 0000000..f014448 --- /dev/null +++ b/src/tdp/GenericTreeDataItem.ts @@ -0,0 +1,68 @@ +import * as vscode from 'vscode'; +import { ThemeIcon, Uri } from 'vscode'; + +type Dictionary = { + [ key: string ] : any; +}; + +export class GenericTreeDataItem +{ + + public constructor(name : string, title: string, children? : GenericTreeDataItem[], customAttributes? : Dictionary ) + { + this._name = name; + this._title = title; + if (children !== undefined) + { + this._children = children; + } + this._customAttributes = customAttributes; + } + + private _name : string = ""; + public get name() : string { + return this._name; + } + public set name(v : string) { + this._name = v; + } + + private _title : string = ""; + public get title() : string { + return this._title; + } + public set title(v : string) { + this._title = v; + } + + private _customAttributes? : Dictionary; + public get customAttributes() : Dictionary { + return this._customAttributes || []; + } + + /** + * The icon path or {@link ThemeIcon} for the tree item. + * When `falsy`, {@link ThemeIcon.Folder Folder Theme Icon} is assigned, if item is collapsible otherwise {@link ThemeIcon.File File Theme Icon}. + * When a file or folder {@link ThemeIcon} is specified, icon is derived from the current file icon theme for the specified theme icon using {@link TreeItem.resourceUri resourceUri} (if provided). + */ + icon?: string | Uri | { + /** + * The icon path for the light theme. + */ + light: string | Uri; + /** + * The icon path for the dark theme. + */ + dark: string | Uri; + } | ThemeIcon; + + hasChildren() : boolean { + return this.children.length > 0; + } + + private _children : GenericTreeDataItem[] = []; + public get children() : GenericTreeDataItem[] { + return this._children || []; + } + +} \ No newline at end of file diff --git a/src/tdp/GenericTreeDataProvider.ts b/src/tdp/GenericTreeDataProvider.ts new file mode 100644 index 0000000..324c98f --- /dev/null +++ b/src/tdp/GenericTreeDataProvider.ts @@ -0,0 +1,87 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { GenericTreeDataItem } from './GenericTreeDataItem'; +import { Uri } from 'vscode'; + +export class GenericTreeDataProvider implements vscode.TreeDataProvider { + + onActivated?: vscode.Event | undefined; + + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + + readonly onDidChangeTreeData: vscode.Event = this + ._onDidChangeTreeData.event; + + refresh(item : GenericTreeDataItem | undefined = undefined): void { + this._onDidChangeTreeData.fire(item); + } + + private _items : GenericTreeDataItem[] = []; + public get items() : GenericTreeDataItem[] + { + return this._items; + } + + getIconPath(element : GenericTreeDataItem) : string | Uri | { + /** + * The icon path for the light theme. + */ + light: string | Uri; + /** + * The icon path for the dark theme. + */ + dark: string | Uri; + } | vscode.ThemeIcon | undefined + { + return element.icon; + } + + + getTreeItem(element: GenericTreeDataItem): vscode.TreeItem { + let item = new vscode.TreeItem(element.title, element.hasChildren() ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); + item.command = { + command: "mocha.zq_open_document", + arguments: [ element ], + title: "OpenFile" + }; + item.iconPath = this.getIconPath(element); + + /* + switch (element.type) + { + case "module": + { + item.iconPath = new vscode.ThemeIcon("folder"); + break; + } + case "class": + { + //item.iconPath = new vscode.ThemeIcon("symbol-class"); + item.iconPath = + { + "light": path.join(__filename, "..", "..", "icons", "light", "explorer", "class.svg"), + "dark": path.join(__filename, "..", "..", "icons", "dark", "explorer", "class.svg") + }; + // console.log('looking for icon @ ' + item.iconPath); + break; + } + } + */ + return item; + } + + getChildren(element?: GenericTreeDataItem): Thenable { + if (element) { + return Promise.resolve(element.children); + } + return new Promise((resolve, reject) => + { + resolve(this.items); + }); + } + + + +}