From a2a8221f6def6787e7ff90cad28a0d5cfc2b221e Mon Sep 17 00:00:00 2001 From: marcello Date: Thu, 29 Nov 2018 09:12:10 +0000 Subject: [PATCH] create working, a bit clunky --- package-lock.json | 24 ++--- package.json | 14 +-- src/abap/AbapObject.ts | 16 ++-- src/adt/AdtConnection.ts | 4 +- src/adt/AdtServer.ts | 38 ++++++-- src/adt/AdtTransports.ts | 5 +- src/adt/create/AdtObjectCreator.ts | 117 +++++++++++++++++++----- src/adt/create/AdtObjectTypes.ts | 138 +++++++++++++++++++++-------- src/commands.ts | 7 +- src/fs/AbapNode.ts | 9 +- src/fs/FsProvider.ts | 10 +-- src/test/extension.test.ts | 22 +++++ src/test/index.ts | 22 +++++ 13 files changed, 318 insertions(+), 108 deletions(-) create mode 100644 src/test/extension.test.ts create mode 100644 src/test/index.ts diff --git a/package-lock.json b/package-lock.json index b183409..986ee79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,15 +38,15 @@ "dev": true }, "@types/node": { - "version": "8.10.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.34.tgz", - "integrity": "sha512-alypNiaAEd0RBGXoWehJ2gchPYCITmw4CYBoB5nDlji8l8on7FsklfdfIs4DDmgpKLSX3OF3ha6SV+0W7cTzUA==", + "version": "8.10.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.38.tgz", + "integrity": "sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A==", "dev": true }, "@types/request": { - "version": "2.47.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.1.tgz", - "integrity": "sha512-TV3XLvDjQbIeVxJ1Z3oCTDk/KuYwwcNKVwz2YaT0F5u86Prgc4syDAp6P96rkTQQ4bIdh+VswQIC9zS6NjY7/g==", + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", "dev": true, "requires": { "@types/caseless": "*", @@ -56,9 +56,9 @@ } }, "@types/tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw==", "dev": true }, "@types/xml2js": { @@ -2355,9 +2355,9 @@ } }, "tsconfig-paths": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.6.0.tgz", - "integrity": "sha512-mrqQIP2F4e03aMTCiPdedCIT300//+q0ET53o5WqqtQjmEICxP9yfz/sHTpPqXpssuJEzODsEzJaLRaf5J2X1g==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.7.0.tgz", + "integrity": "sha512-7iE+Q/2E1lgvxD+c0Ot+GFFmgmfIjt/zCayyruXkXQ84BLT85gHXy0WSoQSiuFX9+d+keE/jiON7notV74ZY+A==", "dev": true, "requires": { "@types/json5": "^0.0.29", diff --git a/package.json b/package.json index a683b94..a30de7c 100644 --- a/package.json +++ b/package.json @@ -35,16 +35,16 @@ "build": "vsce package" }, "devDependencies": { - "typescript": "^2.6.1", - "vscode": "^1.1.21", - "vsce": "^1.52.0", - "tslint": "^5.8.0", - "@types/node": "^8.10.25", "@types/mocha": "^2.2.42", + "@types/node": "^8.10.38", + "@types/request": "^2.48.1", "@types/xml2js": "^0.4.3", - "@types/request": "^2.47.1", "ts-node": "^7.0.1", - "tsconfig-paths": "^3.6.0" + "tsconfig-paths": "^3.7.0", + "tslint": "^5.8.0", + "typescript": "^2.6.1", + "vsce": "^1.52.0", + "vscode": "^1.1.21" }, "dependencies": { "request": "^2.88.0", diff --git a/src/abap/AbapObject.ts b/src/abap/AbapObject.ts index dbc5374..80963ae 100644 --- a/src/abap/AbapObject.ts +++ b/src/abap/AbapObject.ts @@ -236,18 +236,18 @@ export class AbapObject { return this.sapguiOnly ? ".txt" : ".abap" } - getChildren( + async getChildren( connection: AdtConnection ): Promise> { if (this.isLeaf()) throw FileSystemError.FileNotADirectory(this.vsName) const nodeUri = this.getNodeUri(connection) - return connection - .request(nodeUri, "POST") - .then(pick("body")) - .then(parseNode) - .then(this.filterNodeStructure.bind(this)) - .then(aggregateNodes) + const response = await connection.request(nodeUri, "POST") + const nodes = await parseNode(response.body) + const filtered = this.filterNodeStructure(nodes) + const components = aggregateNodes(filtered) + + return components } protected getNodeUri(connection: AdtConnection): Uri { @@ -260,6 +260,8 @@ export class AbapObject { techName )}&parent_type=${encodeURIComponent( this.type + )}&user_name=${encodeURIComponent( + connection.username.toUpperCase() )}&withShortDescriptions=true` }) } diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index d63bf50..31c7b61 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -14,6 +14,8 @@ export class AdtConnection { readonly url: string readonly username: string readonly password: string + //TODO: hack for object creation, needs proper session support and backend cache invalidation + stateful = true private _csrftoken: string = "fetch" private _status: ConnStatus = ConnStatus.new private _listeners: Array = [] @@ -75,7 +77,7 @@ export class AdtConnection { method, headers: { "x-csrf-token": this._csrftoken, - "X-sap-adt-sessiontype": "stateful", + "X-sap-adt-sessiontype": this.stateful ? "stateful" : "", Accept: "*/*", ...headers } diff --git a/src/adt/AdtServer.ts b/src/adt/AdtServer.ts index 8b64a0f..56f1afe 100644 --- a/src/adt/AdtServer.ts +++ b/src/adt/AdtServer.ts @@ -71,6 +71,7 @@ export class AdtServer { if (file.abapObject.transport === TransportStatus.REQUIRED) { const transport = await selectTransport( file.abapObject.getContentsUri(this.connection), + "", this.connection ) if (transport) file.abapObject.transport = transport @@ -85,11 +86,27 @@ export class AdtServer { } findNode(uri: Uri): AbapNode { + // const parts = uriParts(uri) + // return parts.reduce((current: any, name) => { + // if (current && "getChild" in current) return current.getChild(name) + // throw FileSystemError.FileNotFound(uri) + // }, this.root) + return this.findNodeHierarcy(uri)[0] + } + + findNodeHierarcy(uri: Uri): AbapNode[] { const parts = uriParts(uri) - return parts.reduce((current: any, name) => { - if (current && "getChild" in current) return current.getChild(name) - throw FileSystemError.FileNotFound(uri) - }, this.root) + return parts.reduce( + (current: AbapNode[], name) => { + const folder = current[0] + const child = folder.isFolder && folder.getChild(name) + if (!child) throw FileSystemError.FileNotFound(uri) + + current.unshift(child) + return current + }, + [this.root] + ) } async findAbapObject(uri: Uri): Promise { @@ -109,15 +126,20 @@ export class AdtServer { async findNodePromise(uri: Uri): Promise { let node: AbapNode = this.root + let refreshable: AbapNode | undefined = node.canRefresh() ? node : undefined const parts = uriParts(uri) + for (const part of parts) { let next: AbapNode | undefined = node.getChild(part) - if (!next && node.canRefresh()) { - await node.refresh(this.connection) + if (!next && refreshable) { + //refreshable will tipically be the current node or its first abap parent (usually a package) + await refreshable.refresh(this.connection) next = node.getChild(part) } - if (next) node = next - else return Promise.reject(FileSystemError.FileNotFound(uri)) + if (next) { + node = next + if (node.canRefresh()) refreshable = node + } else return Promise.reject(FileSystemError.FileNotFound(uri)) } return node diff --git a/src/adt/AdtTransports.ts b/src/adt/AdtTransports.ts index 726bc0a..bd45646 100644 --- a/src/adt/AdtTransports.ts +++ b/src/adt/AdtTransports.ts @@ -44,6 +44,7 @@ function throwMessage(msg: ValidateTransportMessage) { } export async function getTransportCandidates( objContentUri: Uri, + devClass: string, conn: AdtConnection ): Promise { const response = await conn.request( @@ -51,6 +52,7 @@ export async function getTransportCandidates( "POST", { body: JSON2AbapXML({ + DEVCLASS: devClass, URI: objContentUri.path }) } @@ -86,9 +88,10 @@ export async function getTransportCandidates( export async function selectTransport( objContentUri: Uri, + devClass: string, conn: AdtConnection ): Promise { - const ti = await getTransportCandidates(objContentUri, conn) + const ti = await getTransportCandidates(objContentUri, devClass, conn) if (ti.DLVUNIT === "LOCAL") return "" const CREATENEW = "Create a new transport" let selection = await window.showQuickPick([ diff --git a/src/adt/create/AdtObjectCreator.ts b/src/adt/create/AdtObjectCreator.ts index 13e7177..a7d57ae 100644 --- a/src/adt/create/AdtObjectCreator.ts +++ b/src/adt/create/AdtObjectCreator.ts @@ -2,14 +2,16 @@ import { Uri, window } from "vscode" import { parsetoPromise, getNode, recxml2js } from "../AdtParserBase" import { mapWith } from "../../functions" import { AdtServer } from "../AdtServer" -import { isAbapNode } from "../../fs/AbapNode" +import { isAbapNode, AbapNode } from "../../fs/AbapNode" import { selectObjectType, - ObjType, + ObjectType, NewObjectConfig, - OBJECTTYPES + getObjectType, + PACKAGE } from "./AdtObjectTypes" import { selectTransport } from "../AdtTransports" +import { abapObjectFromNode } from "../../abap/AbapObjectUtilities" interface ValidationMessage { SEVERITY: string @@ -57,19 +59,43 @@ export class AdtObjectCreator { if (!this.types) this.types = await this.loadTypes() const parent = this.server.findNode(uri) let otype = parent && isAbapNode(parent) && parent.abapObject.type - if (otype === "DEVC/K") otype = "" + if (otype === PACKAGE) otype = "" return this.types!.filter(x => x.PARENT_OBJECT_TYPE === otype) } + private getHierarchy(uri: Uri | undefined): AbapNode[] { + if (uri) + try { + return this.server.findNodeHierarcy(uri) + } catch (e) {} + return [] + } + private async guessOrSelectObjectType( - uri: Uri | undefined - ): Promise { + hierarchy: AbapNode[] + ): Promise { //TODO: guess from URI + const base = hierarchy[0] + //if I picked the root node,a direct descendent or a package just ask the user to select any object type + // if not, for abap nodes pick child objetc types (if any) + // for non-abap nodes if it's an object type guess the type from the children + if (hierarchy.length > 2) + if (isAbapNode(base)) return selectObjectType(base.abapObject.type) + else { + const child = [...base] + .map(c => c[1]) + .find(c => isAbapNode(c) && c.abapObject.type !== PACKAGE) + if (child && isAbapNode(child)) { + const guessed = getObjectType(child.abapObject.type) + if (guessed) return guessed + } + } + //default... return selectObjectType() } private async validateObject( - objType: ObjType, + objType: ObjectType, objDetails: NewObjectConfig ): Promise { const url = this.server.connection.createUri( @@ -84,16 +110,30 @@ export class AdtObjectCreator { ) as ValidationMessage[] } + /** + * Finds or ask the user to select/create a transport for an object. + * Returns an empty string for local objects + * + * @param objType Object type + * @param objDetails Object name, description,... + */ private async selectTransport( - objType: ObjType, + objType: ObjectType, objDetails: NewObjectConfig ): Promise { //TODO: no request for temp packages const uri = this.server.connection.createUri(objType.getPath(objDetails)) - return selectTransport(uri, this.server.connection) + return selectTransport(uri, objDetails.devclass, this.server.connection) } + /** + * Creates an ABAP object + * + * @param objType Object type descriptor + * @param objDetails Object details (name, description, package,...) + * @param request Transport request + */ private async create( - objType: ObjType, + objType: ObjectType, objDetails: NewObjectConfig, request: string ) { @@ -102,27 +142,54 @@ export class AdtObjectCreator { ) let body = objType.getCreatePayload(objDetails) const query = request ? `corrNr=${request}` : "" + //TODO hack for cache invalidation, need to find a proper solution + this.server.connection.stateful = false let response = await this.server.connection.request(uri, "POST", { body, query }) - //TODO error handling + this.server.connection.stateful = true return response } + private async askInput( + prompt: string, + uppercase: boolean = true + ): Promise { + const res = (await window.showInputBox({ prompt })) || "" + return uppercase ? res.toUpperCase() : res + } + /** + * Creates an ABAP object asking the user for unknown details + * Tries to guess object type and parent/package from URI + * + * @param uri Creates an ABAP object + */ async createObject(uri: Uri | undefined) { - const objType = await this.guessOrSelectObjectType(uri) + const hierarchy = this.getHierarchy(uri) + let devclass: string = this.guessParentByType(hierarchy, PACKAGE) + const objType = await this.guessOrSelectObjectType(hierarchy) + //user didn't pick one... if (!objType) return - //TODO: objecttype dependent selection - const name = (await window.showInputBox({ prompt: "name" })) || "", - description = - (await window.showInputBox({ prompt: "description" })) || "", - parentName = (await window.showInputBox({ prompt: "parent" })) || "", - devclass = (await window.showInputBox({ prompt: "Package" })) || "" + const name = await this.askInput("name") + if (!name) return + const description = await this.askInput("description", false) + if (!description) return + let parentName + if (objType.parentType === PACKAGE) parentName = devclass + else + parentName = + this.guessParentByType(hierarchy, objType.parentType) || + (await this.askInput("parent")) + if (!parentName) return + + if (!devclass) devclass = await this.askInput("Package") + if (!devclass) return + if (objType.parentType === PACKAGE) parentName = devclass const objDetails: NewObjectConfig = { description, devclass, - parentName, + parentName: parentName || "", name } const valresult = await this.validateObject(objType, objDetails) @@ -132,9 +199,15 @@ export class AdtObjectCreator { const trnumber = await this.selectTransport(objType, objDetails) - const res = await this.create(objType, objDetails, trnumber) - //TODO:error handling - + await this.create(objType, objDetails, trnumber) //exceptions will bubble up + const obj = abapObjectFromNode(objType.objNode(objDetails)) + await obj.loadMetadata(this.server.connection) return objType.getPath(objDetails) } + guessParentByType(hierarchy: AbapNode[], type: string): string { + //find latest package parent + const pn = hierarchy.find(n => isAbapNode(n) && n.abapObject.type === type) + //return package name or blank string + return (pn && isAbapNode(pn) && pn.abapObject.name) || "" + } } diff --git a/src/adt/create/AdtObjectTypes.ts b/src/adt/create/AdtObjectTypes.ts index b301f94..67e5b34 100644 --- a/src/adt/create/AdtObjectTypes.ts +++ b/src/adt/create/AdtObjectTypes.ts @@ -1,5 +1,8 @@ -import { sapEscape } from "../../functions" +import { sapEscape, compose } from "../../functions" import { window, QuickPickItem } from "vscode" +import { ObjectNode } from "../AdtNodeStructParser" + +export const PACKAGE = "DEVC/K" export interface NewObjectConfig { name: string @@ -27,21 +30,26 @@ export function objMap(fn: (x: any) => any, orig?: Object): Object { } const escapeObj = objMap(sapEscape) -export class ObjType implements QuickPickItem { +export class ObjectType implements QuickPickItem { + protected _parentType: string + get parentType() { + return this._parentType + } constructor( public readonly type: string, public readonly label: string, - public readonly parentType: string, public readonly rootName: string, public readonly nameSpace: string, public readonly pathTemplate: string, public readonly validateTemplate: string - ) {} + ) { + this._parentType = PACKAGE + } protected escapedValues(config: NewObjectConfig): any { return escapeObj({ ...config, type: this.type, - parentType: this.parentType + parentType: this._parentType }) } getPath(config: NewObjectConfig): string { @@ -56,64 +64,120 @@ export class ObjType implements QuickPickItem { return expand(this.validateTemplate, this.escapedValues(config)) } + objNode(newObj: NewObjectConfig): ObjectNode { + return { + OBJECT_TYPE: this.type, + OBJECT_NAME: newObj.name.toLowerCase(), + TECH_NAME: newObj.name.toLowerCase(), + OBJECT_URI: this.getPath(newObj), + OBJECT_VIT_URI: "", + EXPANDABLE: "" + } + } + getCreatePayload(config: NewObjectConfig) { - const parentRef = - this.parentType === "DEVC/K" - ? `` - : `` + const payload = ` + <${this.rootName} ${this.nameSpace} + xmlns:adtcore="http://www.sap.com/adt/core" + adtcore:description="${config.description}" + adtcore:name="${config.name}" adtcore:type="${this.type}"> + + ` + return payload + } +} +class FGObjectType extends ObjectType { + constructor( + public readonly type: string, + public readonly label: string, + public readonly rootName: string, + public readonly nameSpace: string, + public readonly pathTemplate: string, + public readonly validateTemplate: string + ) { + super(type, label, rootName, nameSpace, pathTemplate, validateTemplate) + this._parentType = "FUGR/F" + } + getCreatePayload(config: NewObjectConfig) { const payload = ` -<${this.rootName} ${ - this.nameSpace - } xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${ - config.description - }" adtcore:name="${config.name}" adtcore:type="${this.type}"> - ${parentRef} - +<${this.rootName} ${this.nameSpace} + xmlns:adtcore="http://www.sap.com/adt/core" + adtcore:description="${config.description}" + adtcore:name="${config.name}" adtcore:type="${this.type}"> + ` + return payload } } -export async function selectObjectType(): Promise { - return window.showQuickPick(OBJECTTYPES) + +export function getObjectType(type: string): ObjectType | undefined { + return OBJECTTYPES.find(t => t.type === type) +} +export async function selectObjectType( + parentType?: string +): Promise { + const types = parentType + ? OBJECTTYPES.filter(t => t.parentType === parentType) + : OBJECTTYPES + return window.showQuickPick(types.length > 0 ? types : OBJECTTYPES) } -export const OBJECTTYPES: ObjType[] = [ - new ObjType( - "PROG/I", - "Include", - "DEVC/K", - "include:abapInclude", - 'xmlns:include="http://www.sap.com/adt/programs/includes"', - "/sap/bc/adt/programs/includes", - "/sap/bc/adt/includes/validation?objtype={type}&objname={name}&packagename={parentName}&description={description}" +export const OBJECTTYPES: ObjectType[] = [ + new ObjectType( + "PROG/P", + "Program", + "program:abapProgram", + 'xmlns:program="http://www.sap.com/adt/programs/programs"', + "/sap/bc/adt/programs/programs", + "/sap/bc/adt/programs/validation?objtype={type}&objname={name}&packagename={parentName}&description={description}" ), - new ObjType( + new ObjectType( + "CLAS/OC", + "Class", + "class:abapClass", + 'xmlns:class="http://www.sap.com/adt/oo/classes"', + "/sap/bc/adt/oo/classes", + "/sap/bc/adt/oo/validation/objectname?objtype={type}&objname={name}&packagename={parentName}&description={description}" + ), + new ObjectType( "INTF/OI", "Interface", - "DEVC/K", "intf:abapInterface", 'xmlns:intf="http://www.sap.com/adt/oo/interfaces"', "/sap/bc/adt/oo/interfaces", "/sap/bc/adt/oo/validation/objectname?objtype={type}&objname={name}&packagename={parentName}&description={description}" ), - new ObjType( + new ObjectType( + "FUGR/F", + "Function Group", + "group:abapFunctionGroup", + 'xmlns:group="http://www.sap.com/adt/functions/groups"', + "/sap/bc/adt/functions/groups", + "/sap/bc/adt/functions/validation?objtype={type}&objname={name}&packagename={parentName}&description={description}" + ), + new FGObjectType( "FUGR/FF", "Function module", - "FUGR/F", "fmodule:abapFunctionModule", 'xmlns:fmodule="http://www.sap.com/adt/functions/fmodules"', "/sap/bc/adt/functions/groups/{parentName}/fmodules", "/sap/bc/adt/functions/validation?objtype={type}&objname={name}&fugrname={parentName}&description={description}" ), - new ObjType( + new ObjectType( + "PROG/I", + "Include", + "include:abapInclude", + 'xmlns:include="http://www.sap.com/adt/programs/includes"', + "/sap/bc/adt/programs/includes", + "/sap/bc/adt/includes/validation?objtype={type}&objname={name}&packagename={parentName}&description={description}" + ), + new FGObjectType( "FUGR/I", "Function group include", - "FUGR/F", "finclude:abapFunctionGroupInclude", 'xmlns:finclude="http://www.sap.com/adt/functions/fincludes"', "/sap/bc/adt/functions/groups/{parentName}/includes", diff --git a/src/commands.ts b/src/commands.ts index c17bc9c..bab3b65 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -39,9 +39,10 @@ export async function createAdtObject(uri: Uri | undefined) { const server = root && fromUri(root.uri) if (!server) return const objPath = await server.creator.createObject(uri) - const path = await server!.objectFinder.findObjectPath(objPath!) - const nodePath = await server!.objectFinder.locateObject(path!) - if (nodePath) server!.objectFinder.displayNode(nodePath) + if (!objPath) return //user aborted + const path = await server.objectFinder.findObjectPath(objPath!) + const nodePath = await server.objectFinder.locateObject(path!) + if (nodePath) server.objectFinder.displayNode(nodePath) } catch (e) { window.showErrorMessage(e.toString()) } diff --git a/src/fs/AbapNode.ts b/src/fs/AbapNode.ts index d9b2ec8..5211821 100644 --- a/src/fs/AbapNode.ts +++ b/src/fs/AbapNode.ts @@ -122,11 +122,10 @@ export class AbapObjectNode implements FileStat, Iterable<[string, AbapNode]> { } } - public refresh(connection: AdtConnection): Promise { - return this.abapObject.getChildren(connection).then(objects => { - refreshObjects(this, objects) - return this - }) + public async refresh(connection: AdtConnection): Promise { + const children = await this.abapObject.getChildren(connection) + refreshObjects(this, children) + return this } public async stat(connection: AdtConnection): Promise { diff --git a/src/fs/FsProvider.ts b/src/fs/FsProvider.ts index 80452ef..5033d5b 100644 --- a/src/fs/FsProvider.ts +++ b/src/fs/FsProvider.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode" import { fromUri } from "../adt/AdtServer" -import { FileSystemError } from "vscode" +import { FileSystemError, FileChangeType } from "vscode" export class FsProvider implements vscode.FileSystemProvider { private _eventEmitter = new vscode.EventEmitter() @@ -21,11 +21,10 @@ export class FsProvider implements vscode.FileSystemProvider { return server.stat(uri) } - readDirectory( - uri: vscode.Uri - ): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { const server = fromUri(uri) const dir = server.findNode(uri) + if (dir.canRefresh()) await dir.refresh(server.connection) const contents = [...dir].map( ([name, node]) => [name, node.type] as [string, vscode.FileType] ) @@ -57,7 +56,8 @@ export class FsProvider implements vscode.FileSystemProvider { "Not a real filesystem, file creation is not supported" ) if (!file) throw FileSystemError.FileNotFound(uri) - return server.saveFile(file, content) + await server.saveFile(file, content) + this._eventEmitter.fire([{ type: FileChangeType.Changed, uri }]) } delete( uri: vscode.Uri, diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts new file mode 100644 index 0000000..a7a297f --- /dev/null +++ b/src/test/extension.test.ts @@ -0,0 +1,22 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +// 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'; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Extension Tests", function () { + + // Defines a Mocha unit test + test("Something 1", function() { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); \ No newline at end of file diff --git a/src/test/index.ts b/src/test/index.ts new file mode 100644 index 0000000..9fa2ea0 --- /dev/null +++ b/src/test/index.ts @@ -0,0 +1,22 @@ +// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +import * as testRunner from 'vscode/lib/testrunner'; + +// You can directly control Mocha options by uncommenting the following lines +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; \ No newline at end of file