Skip to content

Added feature to check for unsaved files in a folder upon deletion. #1559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ae98ac4
Needs testing with live connection. But should fix error.
Jul 8, 2025
774bc8e
added feature to check for unsaved files in folder
Jul 16, 2025
18e96b2
Merge branch 'main' into kibpat_VSExt_1136
kpatl1 Jul 16, 2025
8edc365
Merge branch 'main' of https://github.com/sassoftware/vscode-sas-exte…
Jul 16, 2025
8faca94
Merge branch 'kibpat_VSExt_1136' of https://github.com/sassoftware/vs…
Jul 16, 2025
4b8f211
undo changes on diff file
Jul 16, 2025
405d6e4
Update ContentDataProvider.ts
kpatl1 Jul 16, 2025
904a406
Update ContentDataProvider.ts
kpatl1 Jul 16, 2025
42ca077
DCO Remediation Commit for Kishan Patel <[email protected]>
Jul 16, 2025
877ec93
testing a new fix within the UI and provider.
kpatl1 Jul 22, 2025
1bf8187
removed code from SASContentAdapter and added it to UI and Service la…
kpatl1 Jul 23, 2025
b466d7c
DCO Remediation Commit for Kishan Patel <[email protected]>
kpatl1 Jul 23, 2025
30b81b7
Merge branch 'main' into kibpat_VSExt_1136
kpatl1 Jul 23, 2025
6593c67
Merge branch 'main' of https://github.com/sassoftware/vscode-sas-exte…
kpatl1 Jul 23, 2025
49edc94
Merge branch 'kibpat_VSExt_1136' of https://github.com/sassoftware/vs…
kpatl1 Jul 23, 2025
5ef4032
changed to BFS with conccurrrent HTTP requests.
kpatl1 Jul 24, 2025
3195f1d
changes to reduce HTTP requests. needs fixing for depth > 1
kpatl1 Jul 28, 2025
9166f84
needs upward traversal fix
kpatl1 Jul 28, 2025
63fb7d7
DCO Remediation Commit for Kishan Patel <[email protected]>
kpatl1 Jul 28, 2025
5da1aa5
debug
kpatl1 Jul 29, 2025
0d116e8
Update client/src/components/ContentNavigator/ContentDataProvider.ts
kpatl1 Aug 11, 2025
0bb9429
Update client/src/components/ContentNavigator/ContentDataProvider.ts
kpatl1 Aug 11, 2025
3d4cc59
Update client/src/components/ContentNavigator/ContentDataProvider.ts
kpatl1 Aug 11, 2025
2c3e6e2
debug statment removal
Aug 11, 2025
ed1cf13
DCO Remediation Commit for Kishan Patel <[email protected]>
Aug 11, 2025
9ce3d05
Merge branch 'main' into kibpat_VSExt_1136
kpatl1 Aug 11, 2025
b8dbf88
prettier formatting.
Aug 11, 2025
0cba768
linting
Aug 11, 2025
e10ae07
feat(ContentNavigator): resolved comments
Aug 15, 2025
b35aae3
Update index.ts
kpatl1 Aug 15, 2025
bfdc1a6
feat(ContentNavigator): remove changes from package lock
kpatl1 Aug 15, 2025
d0c8028
Merge branch 'main' into kibpat_VSExt_1136
Aug 15, 2025
123b7e6
DCO Remediation Commit for Kishan Patel <[email protected]>
Aug 15, 2025
7a9bd50
DCO Remediation Commit for Kishan Patel <[email protected]>
Aug 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 96 additions & 1 deletion client/src/components/ContentNavigator/ContentDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { basename, join } from "path";
import { promisify } from "util";

import { profileConfig } from "../../commands/profile";
import { getResourceId } from "../../connection/rest/util";
import { SubscriptionProvider } from "../SubscriptionProvider";
import { ViyaProfile } from "../profile";
import { ContentModel } from "./ContentModel";
Expand Down Expand Up @@ -79,6 +80,8 @@ class ContentDataProvider
public dropMimeTypes: string[];
public dragMimeTypes: string[];

private uriToParentMap = new Map<string, string>();

get treeView(): TreeView<ContentItem> {
return this._treeView;
}
Expand Down Expand Up @@ -214,6 +217,11 @@ class ContentDataProvider
const isContainer = getIsContainer(item);
const uri = await this.model.getUri(item, false);

// Cache the URI to parent mapping
if (item.parentFolderUri) {
this.uriToParentMap.set(item.uri, item.parentFolderUri);
}

return {
collapsibleState: isContainer
? TreeItemCollapsibleState.Collapsed
Expand Down Expand Up @@ -505,6 +513,94 @@ class ContentDataProvider
}
}

public async checkFolderDirty(resource: ContentItem): Promise<boolean> {
if (!resource.vscUri) {
return false;
}

const targetFolderUri = resource.uri;

const dirtyFiles = workspace.textDocuments
.filter((doc) => {
if (!doc.isDirty) {
return false;
}

const scheme = doc.uri.scheme;
return (
scheme === "sasContent" ||
scheme === "sasServer" ||
scheme === "sasContentReadOnly" ||
scheme === "sasServerReadOnly"
);
})
.map((doc) => doc.uri);

if (dirtyFiles.length === 0) {
return false;
}

for (const dirtyFileUri of dirtyFiles) {
if (
await this.isDescendantOf(getResourceId(dirtyFileUri), targetFolderUri)
) {
return true;
}
}

return false;
}

private async isDescendantOf(
fileUri: string,
ancestorFolderUri: string,
): Promise<boolean> {
let currentParentUri = this.uriToParentMap.get(fileUri);

if (!currentParentUri) {
try {
const item = await this.model.getResourceByUri(Uri.parse(fileUri));
currentParentUri = item.parentFolderUri;
if (currentParentUri) {
this.uriToParentMap.set(fileUri, currentParentUri);
}
} catch {
return false;
}
}

let depth = 0;
while (currentParentUri || depth >= 10) {
if (currentParentUri === ancestorFolderUri) {
return true;
}

const nextParentUri = this.uriToParentMap.get(currentParentUri);

if (nextParentUri) {
currentParentUri = nextParentUri;
} else {
try {
const parentItem = await this.model.getResourceByUri(
Uri.parse(currentParentUri),
);
currentParentUri = parentItem.parentFolderUri;
if (currentParentUri) {
this.uriToParentMap.set(
currentParentUri,
parentItem.parentFolderUri,
);
}
} catch {
break;
}
}
depth++;
}

return false;
}

public async downloadContentItems(
folderUri: Uri,
selections: ContentItem[],
Expand Down Expand Up @@ -759,6 +855,5 @@ const closeFileIfOpen = (item: ContentItem): Promise<Uri[]> | boolean => {
.catch(reject);
});
}

return true;
};
4 changes: 4 additions & 0 deletions client/src/components/ContentNavigator/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ export const Messages = {
AddFileToMyFolderSuccess: l10n.t("File added to my folder."),
AddToFavoritesError: l10n.t("The item could not be added to My Favorites."),
DeleteButtonLabel: l10n.t("Delete"),
MoveToRecycleBinLabel: l10n.t("Move to Recycle Bin"),
DeleteWarningMessage: l10n.t(
'Are you sure you want to permanently delete the item "{name}"?',
),
RecycleDirtyFolderWarning: l10n.t(
"This folder contains unsaved files, are you sure you want to delete?",
),
EmptyRecycleBinError: l10n.t("Unable to empty the recycle bin."),
EmptyRecycleBinWarningMessage: l10n.t(
"Are you sure you want to permanently delete all the items? You cannot undo this action.",
Expand Down
60 changes: 38 additions & 22 deletions client/src/components/ContentNavigator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,30 +107,47 @@ class ContentNavigator implements SubscriptionProvider {
return;
}
const isContainer = getIsContainer(resource);
const hasUnsavedFiles = isContainer
? await this.contentDataProvider.checkFolderDirty(resource)
: isContainer;
const moveToRecycleBin =
this.contentDataProvider.canRecycleResource(resource);

if (
!moveToRecycleBin &&
!(await window.showWarningMessage(
l10n.t(Messages.DeleteWarningMessage, {
name: resource.name,
}),
{ modal: true },
Messages.DeleteButtonLabel,
))
) {
return;
}
const deleteResult = moveToRecycleBin
? await this.contentDataProvider.recycleResource(resource)
: await this.contentDataProvider.deleteResource(resource);
if (!deleteResult) {
window.showErrorMessage(
isContainer
? Messages.FolderDeletionError
: Messages.FileDeletionError,
);
if (resource.contextValue.includes("delete")) {
if (
!moveToRecycleBin &&
!(await window.showWarningMessage(
l10n.t(Messages.DeleteWarningMessage, {
name: resource.name,
}),
{ modal: true },
Messages.DeleteButtonLabel,
))
) {
return;
} else if (moveToRecycleBin && hasUnsavedFiles) {
if (
!(await window.showWarningMessage(
l10n.t(Messages.RecycleDirtyFolderWarning, {
name: resource.name,
}),
{ modal: true },
Messages.MoveToRecycleBinLabel,
))
) {
return;
}
}
const deleteResult = moveToRecycleBin
? await this.contentDataProvider.recycleResource(resource)
: await this.contentDataProvider.deleteResource(resource);
if (!deleteResult) {
window.showErrorMessage(
isContainer
? Messages.FolderDeletionError
: Messages.FileDeletionError,
);
}
}
},
);
Expand Down Expand Up @@ -523,5 +540,4 @@ class ContentNavigator implements SubscriptionProvider {
}
}
}

export default ContentNavigator;
Loading