diff --git a/client/src/components/ContentNavigator/ContentDataProvider.ts b/client/src/components/ContentNavigator/ContentDataProvider.ts index 3de1ab966..5c525af6e 100644 --- a/client/src/components/ContentNavigator/ContentDataProvider.ts +++ b/client/src/components/ContentNavigator/ContentDataProvider.ts @@ -14,6 +14,8 @@ import { FileType, Position, ProviderResult, + TabInputNotebook, + TabInputText, TextDocument, TextDocumentContentProvider, ThemeIcon, @@ -52,7 +54,7 @@ import { FileManipulationEvent, } from "./types"; import { - getEditorTabForItem, + getEditorTabsForItem, getFileStatement, isContainer as getIsContainer, } from "./utils"; @@ -285,23 +287,62 @@ class ContentDataProvider name: string, ): Promise { const closing = closeFileIfOpen(item); - if (!(await closing)) { + const removedTabUris = await closing; + if (!removedTabUris) { return; } const newItem = await this.model.renameResource(item, name); - if (newItem) { - const newUri = newItem.vscUri; - if (closing !== true) { - // File was open before rename, so re-open it - commands.executeCommand("vscode.open", newUri); - } + if (!newItem) { + return; + } + + const newUri = newItem.vscUri; + const oldUriToNewUriMap = [[item.vscUri, newUri]]; + const newItemIsContainer = getIsContainer(newItem); + if (closing !== true && !newItemIsContainer) { + commands.executeCommand("vscode.open", newUri); + } + const urisToOpen = getPreviouslyOpenedChildItems( + await this.getChildren(newItem), + ); + urisToOpen.forEach(([, newUri]) => + commands.executeCommand("vscode.open", newUri), + ); + oldUriToNewUriMap.push(...urisToOpen); + oldUriToNewUriMap.forEach(([uri, newUri]) => this._onDidManipulateFile.fire({ type: "rename", - uri: item.vscUri, + uri, newUri, - }); - return newUri; + }), + ); + return newUri; + + function getPreviouslyOpenedChildItems(childItems: ContentItem[]) { + const loadChildItems = closing !== true && newItemIsContainer; + if (!Array.isArray(removedTabUris) || !loadChildItems) { + return []; + } + // Here's where things get a little weird. When we rename folders in + // sas content, we _don't_ close those files. It doesn't matter since + // their path isn't hierarchical. In sas file system, the path is hierarchical, + // thus we need to re-open all the closed files. This does that by getting + // children and comparing the removedTabUris + const filteredChildItems = childItems + .map((childItem) => { + const matchingUri = removedTabUris.find((uri) => + uri.path.endsWith(childItem.name), + ); + if (!matchingUri) { + return; + } + + return [matchingUri, childItem.vscUri]; + }) + .filter((exists) => exists); + + return filteredChildItems; } } @@ -697,10 +738,26 @@ class ContentDataProvider export default ContentDataProvider; -const closeFileIfOpen = (item: ContentItem) => { - const tab = getEditorTabForItem(item); - if (tab) { - return window.tabGroups.close(tab); +const closeFileIfOpen = (item: ContentItem): Promise | boolean => { + const tabs = getEditorTabsForItem(item); + if (tabs.length > 0) { + return new Promise((resolve, reject) => { + Promise.all(tabs.map((tab) => window.tabGroups.close(tab))) + .then(() => + resolve( + tabs + .map( + (tab) => + (tab.input instanceof TabInputText || + tab.input instanceof TabInputNotebook) && + tab.input.uri, + ) + .filter((exists) => exists), + ), + ) + .catch(reject); + }); } + return true; }; diff --git a/client/src/components/ContentNavigator/index.ts b/client/src/components/ContentNavigator/index.ts index 566b59ccd..9d187b486 100644 --- a/client/src/components/ContentNavigator/index.ts +++ b/client/src/components/ContentNavigator/index.ts @@ -63,16 +63,16 @@ const folderValidator = ( class ContentNavigator implements SubscriptionProvider { private contentDataProvider: ContentDataProvider; - private contentModel: ContentModel; private sourceType: ContentNavigatorConfig["sourceType"]; private treeIdentifier: ContentNavigatorConfig["treeIdentifier"]; + get contentModel() { + return new ContentModel(this.contentAdapterForConnectionType()); + } + constructor(context: ExtensionContext, config: ContentNavigatorConfig) { this.sourceType = config.sourceType; this.treeIdentifier = config.treeIdentifier; - this.contentModel = new ContentModel( - this.contentAdapterForConnectionType(), - ); this.contentDataProvider = new ContentDataProvider( this.contentModel, context.extensionUri, @@ -400,9 +400,7 @@ class ContentNavigator implements SubscriptionProvider { if (event.affectsConfiguration("SAS.connectionProfiles")) { const endpoint = this.viyaEndpoint(); this.collapseAllContent(); - this.contentDataProvider.useModel( - new ContentModel(this.contentAdapterForConnectionType()), - ); + this.contentDataProvider.useModel(this.contentModel); if (endpoint) { await this.contentDataProvider.connect(endpoint); } else { diff --git a/client/src/components/ContentNavigator/utils.ts b/client/src/components/ContentNavigator/utils.ts index 3e72bbe4b..2defeec3d 100644 --- a/client/src/components/ContentNavigator/utils.ts +++ b/client/src/components/ContentNavigator/utils.ts @@ -84,13 +84,13 @@ export const createStaticFolder = ( ], }); -export const getEditorTabForItem = (item: ContentItem) => { +export const getEditorTabsForItem = (item: ContentItem) => { const fileUri = item.vscUri; const tabs: Tab[] = window.tabGroups.all.map((tg) => tg.tabs).flat(); - return tabs.find( + return tabs.filter( (tab) => (tab.input instanceof TabInputText || tab.input instanceof TabInputNotebook) && - tab.input.uri.query === fileUri.query, // compare the file id + tab.input.uri.query.includes(fileUri.query), // compare the file id ); }; diff --git a/client/src/connection/rest/RestSASServerAdapter.ts b/client/src/connection/rest/RestSASServerAdapter.ts index b5e6e31c0..499706bee 100644 --- a/client/src/connection/rest/RestSASServerAdapter.ts +++ b/client/src/connection/rest/RestSASServerAdapter.ts @@ -154,6 +154,10 @@ class RestSASServerAdapter implements ContentAdapter { }); const contentItem = this.filePropertiesToContentItem(response.data); + this.updateFileMetadata( + this.trimComputePrefix(contentItem.uri), + response, + ); if (buffer) { await this.updateContentOfItemAtPath( @@ -249,6 +253,7 @@ class RestSASServerAdapter implements ContentAdapter { } private async getContentOfItemAtPath(path: string) { + await this.setup(); const response = await this.fileSystemApi.getFileContentFromSystem( { sessionId: this.sessionId,