|
1 | 1 | import os from "os"; |
2 | 2 | import path, { dirname, extname } from "path"; |
3 | | -import vscode, { FileType, l10n, Uri, window } from "vscode"; |
| 3 | +import vscode, { CancellationToken, Event, FileDecoration, FileDecorationProvider, FileType, l10n, ProviderResult, ThemeColor, Uri, window } from "vscode"; |
4 | 4 |
|
5 | 5 | import { existsSync, mkdirSync, rmdirSync } from "fs"; |
6 | 6 | import IBMi from "../../api/IBMi"; |
@@ -187,6 +187,7 @@ class IFSShortcutItem extends IFSDirectoryItem { |
187 | 187 | this.contextValue = `shortcut${protectedDir ? `_protected` : ``}`; |
188 | 188 | this.iconPath = new vscode.ThemeIcon(protectedDir ? "lock-small" : "folder-library"); |
189 | 189 | this.tooltip = ``; |
| 190 | + this.resourceUri = Uri.parse(`shortcut:${shortcut}`); |
190 | 191 | } |
191 | 192 | } |
192 | 193 |
|
@@ -312,12 +313,17 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { |
312 | 313 | canSelectMany: true, |
313 | 314 | dragAndDropController: new IFSBrowserDragAndDrop() |
314 | 315 | }); |
| 316 | + const shortcutDecorationProvider = new ShortcutDecorationProvider(); |
315 | 317 |
|
316 | 318 | const getSelectedItems = <T>(node?: T | T[]) => node ? Array.isArray(node) ? node : [node] : ifsTreeViewer.selection as T[]; |
317 | 319 |
|
318 | 320 | context.subscriptions.push( |
319 | 321 | ifsTreeViewer, |
320 | | - vscode.commands.registerCommand(`code-for-ibmi.refreshIFSBrowser`, () => ifsBrowser.refresh()), |
| 322 | + window.registerFileDecorationProvider(shortcutDecorationProvider), |
| 323 | + vscode.commands.registerCommand(`code-for-ibmi.refreshIFSBrowser`, () => { |
| 324 | + ifsBrowser.refresh(); |
| 325 | + shortcutDecorationProvider.refresh(); |
| 326 | + }), |
321 | 327 | vscode.commands.registerCommand(`code-for-ibmi.refreshIFSBrowserItem`, (item?: BrowserItem) => ifsBrowser.refresh(item)), |
322 | 328 |
|
323 | 329 | vscode.commands.registerCommand(`code-for-ibmi.revealInIFSBrowser`, async (item: BrowserItem, options?: FocusOptions) => { |
@@ -443,6 +449,7 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { |
443 | 449 |
|
444 | 450 | if (IBMi.connectionManager.get(`autoRefresh`)) { |
445 | 451 | ifsBrowser.refresh(node); |
| 452 | + vscode.commands.executeCommand(`code-for-ibmi.refreshIFSBrowser`); |
446 | 453 | } |
447 | 454 |
|
448 | 455 | } catch (e: any) { |
@@ -542,92 +549,87 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { |
542 | 549 | } |
543 | 550 | }), |
544 | 551 |
|
545 | | - vscode.commands.registerCommand(`code-for-ibmi.deleteIFS`, async (singleItem: IFSItem | IFSShortcutItem, items?: IFSItem[] | IFSShortcutItem[]) => { |
| 552 | + vscode.commands.registerCommand(`code-for-ibmi.deleteIFS`, async (singleItem: IFSItem, items?: IFSItem[]) => { |
546 | 553 | const connection = instance.getConnection(); |
547 | 554 | if (connection) { |
548 | | - if (singleItem instanceof IFSShortcutItem || items instanceof IFSShortcutItem) { |
549 | | - const config = connection.getConfig(); |
550 | | - const shortcuts = config.ifsShortcuts; |
551 | | - const toBeRemoved = (items || [singleItem]).map(n => n.path); |
| 555 | + const config = connection.getConfig(); |
| 556 | + const shortcuts = config.ifsShortcuts; |
| 557 | + if (items || singleItem) { |
| 558 | + items = (items || [singleItem]).filter(reduceIFSPath); |
| 559 | + } |
| 560 | + else { |
| 561 | + items = getSelectedItems(singleItem).filter(reduceIFSPath); |
| 562 | + } |
552 | 563 |
|
553 | | - try { |
554 | | - if (toBeRemoved.length) { |
555 | | - config.ifsShortcuts = shortcuts.filter(path => !toBeRemoved.includes(path)); |
556 | | - await IBMi.connectionManager.update(config); |
557 | | - if (IBMi.connectionManager.get(`autoRefresh`)) { |
558 | | - ifsBrowser.refresh(); |
559 | | - } |
560 | | - } |
561 | | - } catch (e) { |
562 | | - console.log(e); |
563 | | - } |
564 | | - } else { |
565 | | - if (items || singleItem) { |
566 | | - items = (items || [singleItem]).filter(reduceIFSPath); |
567 | | - } |
568 | | - else { |
569 | | - items = getSelectedItems(singleItem).filter(reduceIFSPath); |
570 | | - } |
| 564 | + if (items && items.length) { |
| 565 | + if (!items.find(n => isProtected(n.path))) { |
| 566 | + let deletionConfirmed = false; |
| 567 | + const message = items.length === 1 ? l10n.t(`Are you sure you want to delete {0}?`, items[0].path) : l10n.t("Are you sure you want to delete the {0} selected files?", items.length); |
| 568 | + const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); |
| 569 | + if (await vscode.window.showWarningMessage(message, { modal: true, detail }, l10n.t(`Yes`))) { |
| 570 | + const toBeDeleted: string[] = []; |
| 571 | + for (const item of items) { |
| 572 | + if ((IBMi.connectionManager.get(`safeDeleteMode`)) && item.file.type === `directory`) { //Check if path is directory |
| 573 | + const dirName = path.basename(item.path) //Get the name of the directory to be deleted |
571 | 574 |
|
572 | | - if (items && items.length) { |
573 | | - if (!items.find(n => isProtected(n.path))) { |
574 | | - let deletionConfirmed = false; |
575 | | - const message = items.length === 1 ? l10n.t(`Are you sure you want to delete {0}?`, items[0].path) : l10n.t("Are you sure you want to delete the {0} selected files?", items.length); |
576 | | - const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); |
577 | | - if (await vscode.window.showWarningMessage(message, { modal: true, detail }, l10n.t(`Yes`))) { |
578 | | - const toBeDeleted: string[] = []; |
579 | | - for (const item of items) { |
580 | | - if ((IBMi.connectionManager.get(`safeDeleteMode`)) && item.file.type === `directory`) { //Check if path is directory |
581 | | - const dirName = path.basename(item.path) //Get the name of the directory to be deleted |
582 | | - |
583 | | - const deletionPrompt = l10n.t(`Once you delete the directory, it cannot be restored. |
| 575 | + const deletionPrompt = l10n.t(`Once you delete the directory, it cannot be restored. |
584 | 576 | Please type "{0}" to confirm deletion.`, dirName); |
585 | | - const input = await vscode.window.showInputBox({ |
586 | | - placeHolder: dirName, |
587 | | - prompt: deletionPrompt, |
588 | | - validateInput: text => { |
589 | | - return (text === dirName) ? null : deletionPrompt + l10n.t(` (Press "Escape" to cancel)`); |
590 | | - } |
591 | | - }); |
592 | | - deletionConfirmed = (input === dirName); |
593 | | - } |
594 | | - else { |
595 | | - // If deleting a file rather than a directory, skip the name entry |
596 | | - // Do not delete a file if one of its parent directory is going to be deleted |
597 | | - deletionConfirmed = true; |
598 | | - } |
| 577 | + const input = await vscode.window.showInputBox({ |
| 578 | + placeHolder: dirName, |
| 579 | + prompt: deletionPrompt, |
| 580 | + validateInput: text => { |
| 581 | + return (text === dirName) ? null : deletionPrompt + l10n.t(` (Press "Escape" to cancel)`); |
| 582 | + } |
| 583 | + }); |
| 584 | + deletionConfirmed = (input === dirName); |
| 585 | + } |
| 586 | + else { |
| 587 | + // If deleting a file rather than a directory, skip the name entry |
| 588 | + // Do not delete a file if one of its parent directory is going to be deleted |
| 589 | + deletionConfirmed = true; |
| 590 | + } |
599 | 591 |
|
600 | | - if (deletionConfirmed) { |
601 | | - toBeDeleted.push(item.path); |
602 | | - } |
| 592 | + if (deletionConfirmed) { |
| 593 | + toBeDeleted.push(item.path); |
603 | 594 | } |
| 595 | + } |
604 | 596 |
|
605 | | - try { |
606 | | - const removeResult = await vscode.window.withProgress({ title: l10n.t(`Deleting {0} element(s)...`, toBeDeleted.length), location: vscode.ProgressLocation.Notification }, async () => { |
607 | | - return await connection.sendCommand({ command: `rm -rf ${toBeDeleted.map(path => Tools.escapePath(path)).join(" ")}` }); |
608 | | - }); |
| 597 | + try { |
| 598 | + const removeResult = await vscode.window.withProgress({ title: l10n.t(`Deleting {0} element(s)...`, toBeDeleted.length), location: vscode.ProgressLocation.Notification }, async () => { |
| 599 | + return await connection.sendCommand({ command: `rm -rf ${toBeDeleted.map(path => Tools.escapePath(path)).join(" ")}` }); |
| 600 | + }); |
609 | 601 |
|
610 | | - if (removeResult.code !== 0) { |
611 | | - throw removeResult.stderr; |
612 | | - } |
613 | | - if (IBMi.connectionManager.get(`autoRefresh`)) { |
614 | | - items.map(item => item.parent) |
615 | | - .filter(Tools.distinct) |
616 | | - .forEach(async parent => parent?.refresh?.()); |
| 602 | + if (removeResult.code !== 0) { |
| 603 | + throw removeResult.stderr; |
| 604 | + } |
| 605 | + |
| 606 | + const deletedShortcuts = shortcuts.filter(path => toBeDeleted.includes(path)); |
| 607 | + if (deletedShortcuts.length) { |
| 608 | + const message = deletedShortcuts.length === 1 ? l10n.t(`Do you also want to remove the IFS shortcut to the folder {0}?`, deletedShortcuts[0]) : l10n.t("Do you also want to remove the IFS shortcuts to the folders {0}?", deletedShortcuts.length); |
| 609 | + const detail = deletedShortcuts.length === 1 ? undefined : deletedShortcuts.map(i => `- ${i}`).join("\n"); |
| 610 | + if (await vscode.window.showWarningMessage(message, { modal: true, detail }, l10n.t(`Yes`))) { |
| 611 | + config.ifsShortcuts = shortcuts.filter(path => !deletedShortcuts.includes(path)); |
617 | 612 | } |
618 | | - } catch (e: any) { |
619 | | - vscode.window.showErrorMessage(l10n.t(`Error deleting streamfile! {0}`, e)); |
620 | 613 | } |
621 | | - } |
622 | | - else { |
623 | | - vscode.window.showInformationMessage(l10n.t(`Deletion canceled.`)); |
| 614 | + |
| 615 | + if (IBMi.connectionManager.get(`autoRefresh`)) { |
| 616 | + items.map(item => item.parent) |
| 617 | + .filter(Tools.distinct) |
| 618 | + .forEach(async parent => parent?.refresh?.()); |
| 619 | + vscode.commands.executeCommand(`code-for-ibmi.refreshIFSBrowser`); |
| 620 | + } |
| 621 | + } catch (e: any) { |
| 622 | + vscode.window.showErrorMessage(l10n.t(`Error deleting streamfile! {0}`, e)); |
624 | 623 | } |
625 | 624 | } |
626 | 625 | else { |
627 | | - vscode.window.showErrorMessage(l10n.t(`Unable to delete protected directories from the IFS Browser! |
628 | | -{0}`, items.filter(n => isProtected(n.path)).map(n => n.path).join(`\n`))); |
| 626 | + vscode.window.showInformationMessage(l10n.t(`Deletion canceled.`)); |
629 | 627 | } |
630 | 628 | } |
| 629 | + else { |
| 630 | + vscode.window.showErrorMessage(l10n.t(`Unable to delete protected directories from the IFS Browser! |
| 631 | +{0}`, items.filter(n => isProtected(n.path)).map(n => n.path).join(`\n`))); |
| 632 | + } |
631 | 633 | } |
632 | 634 | } |
633 | 635 | }), |
@@ -1095,3 +1097,26 @@ async function showOpenDialog() { |
1095 | 1097 | function reduceIFSPath(item: IFSItem, index: number, array: IFSItem[]) { |
1096 | 1098 | return !array.filter(i => i.file.type === "directory" && i !== item).some(folder => item.file.path.startsWith(`${folder.file.path}/`)); |
1097 | 1099 | } |
| 1100 | +export class ShortcutDecorationProvider implements FileDecorationProvider { |
| 1101 | + private readonly _onDidChangeFileDecorations = new vscode.EventEmitter<vscode.Uri | vscode.Uri[] | undefined>(); |
| 1102 | + readonly onDidChangeFileDecorations = this._onDidChangeFileDecorations.event; |
| 1103 | + |
| 1104 | + provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult<FileDecoration> { |
| 1105 | + if (uri.scheme === 'shortcut') { |
| 1106 | + return instance.getConnection()?.getContent().isDirectory(uri.path).then(isFound => { |
| 1107 | + if (!isFound) { |
| 1108 | + return { |
| 1109 | + badge: '⚠', |
| 1110 | + color: new ThemeColor('errorForeground'), |
| 1111 | + tooltip: l10n.t(`Directory does not exist.`) |
| 1112 | + }; |
| 1113 | + } |
| 1114 | + return undefined; |
| 1115 | + }); |
| 1116 | + } |
| 1117 | + } |
| 1118 | + |
| 1119 | + refresh(uri?: vscode.Uri | vscode.Uri[]) { |
| 1120 | + this._onDidChangeFileDecorations.fire(uri); |
| 1121 | + } |
| 1122 | +} |
0 commit comments