From a6b5f2d5b36e5baccb2463e33c60ceb5dc804d27 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Fri, 23 Feb 2024 22:15:14 +0100 Subject: [PATCH 01/14] Added support for Delete hotkey in browsers Signed-off-by: Seb Julliand --- package.json | 126 +++++++------- src/api/IBMiContent.ts | 2 +- src/extension.ts | 13 +- src/locale/ids/da.ts | 4 +- src/locale/ids/en.ts | 4 +- src/locale/ids/fr.ts | 4 +- src/views/ConnectionBrowser.ts | 292 ++++++++++++++++++--------------- src/views/ifsBrowser.ts | 99 +++++------ src/views/objectBrowser.ts | 148 ++++++++++------- 9 files changed, 368 insertions(+), 324 deletions(-) diff --git a/package.json b/package.json index 483931b6a..39ca55ab2 100644 --- a/package.json +++ b/package.json @@ -1224,13 +1224,6 @@ "category": "IBM i", "icon": "$(files)" }, - { - "command": "code-for-ibmi.deleteMember", - "enablement": "code-for-ibmi:connected", - "title": "Delete...", - "category": "IBM i", - "icon": "$(symbol-file)" - }, { "command": "code-for-ibmi.updateMemberText", "enablement": "code-for-ibmi:connected", @@ -1435,7 +1428,7 @@ "icon": "$(filter)" }, { - "command": "code-for-ibmi.deleteFilter", + "command": "code-for-ibmi.objectBrowser.delete", "enablement": "code-for-ibmi:connected", "title": "Delete...", "category": "IBM i", @@ -1523,13 +1516,6 @@ "category": "IBM i", "icon": "$(symbol-file)" }, - { - "command": "code-for-ibmi.deleteObject", - "enablement": "code-for-ibmi:connected", - "title": "Delete...", - "category": "IBM i", - "icon": "$(symbol-file)" - }, { "command": "code-for-ibmi.renameObject", "enablement": "code-for-ibmi:connected", @@ -1655,6 +1641,21 @@ "key": "ctrl+r", "mac": "cmd+r", "when": "editorLangId == cl" + }, + { + "command": "code-for-ibmi.deleteConnection", + "key": "delete", + "when": "focusedView === connectionBrowser && listHasSelectionOrFocus" + }, + { + "command": "code-for-ibmi.deleteIFS", + "key": "delete", + "when": "focusedView === ifsBrowser && listHasSelectionOrFocus" + }, + { + "command": "code-for-ibmi.objectBrowser.delete", + "key": "delete", + "when": "focusedView === objectBrowser && listHasSelectionOrFocus" } ], "viewsContainers": { @@ -1909,10 +1910,6 @@ "command": "code-for-ibmi.moveFilterToBottom", "when": "never" }, - { - "command": "code-for-ibmi.deleteFilter", - "when": "never" - }, { "command": "code-for-ibmi.createMember", "when": "never" @@ -1921,10 +1918,6 @@ "command": "code-for-ibmi.copyMember", "when": "never" }, - { - "command": "code-for-ibmi.deleteMember", - "when": "never" - }, { "command": "code-for-ibmi.updateMemberText", "when": "never" @@ -1953,10 +1946,6 @@ "command": "code-for-ibmi.copyObject", "when": "never" }, - { - "command": "code-for-ibmi.deleteObject", - "when": "never" - }, { "command": "code-for-ibmi.renameObject", "when": "never" @@ -2170,7 +2159,7 @@ "view/item/context": [ { "command": "code-for-ibmi.renameConnection", - "when": "view == connectionBrowser && viewItem == server", + "when": "view == connectionBrowser && !listMultiSelection && viewItem == server", "group": "2_delete@1" }, { @@ -2180,22 +2169,22 @@ }, { "command": "code-for-ibmi.showAdditionalSettings", - "when": "view == connectionBrowser && viewItem == server", + "when": "view == connectionBrowser && !listMultiSelection && viewItem == server", "group": "1_manage@1" }, { "command": "code-for-ibmi.showLoginSettings", - "when": "view == connectionBrowser && viewItem == server", + "when": "view == connectionBrowser && !listMultiSelection && viewItem == server", "group": "1_manage@2" }, { "command": "code-for-ibmi.connectTo", - "when": "view == connectionBrowser && viewItem == server", + "when": "view == connectionBrowser && !listMultiSelection && viewItem == server", "group": "inline" }, { "command": "code-for-ibmi.connectToAndReload", - "when": "view == connectionBrowser && viewItem == server", + "when": "view == connectionBrowser && !listMultiSelection && viewItem == server", "group": "3_connect@1" }, { @@ -2274,7 +2263,7 @@ "group": "4_filters@2" }, { - "command": "code-for-ibmi.deleteFilter", + "command": "code-for-ibmi.objectBrowser.delete", "when": "view == objectBrowser && viewItem =~ /^filter.*$/", "group": "4_filters@3" }, @@ -2318,10 +2307,15 @@ "when": "view == objectBrowser && viewItem =~ /^SPF.*$/", "group": "4_sourceFileStuff@1" }, + { + "command": "code-for-ibmi.objectBrowser.delete", + "when": "view == objectBrowser && viewItem == SPF", + "group": "5_sourceFileStuff@1" + }, { "submenu": "code-for-ibmi.sortMembers", "when": "view == objectBrowser && (viewItem =~ /^SPF/ || viewItem =~ /^member/)", - "group": "5_sourceFileStuff@1" + "group": "6_sourceFileStuff@1" }, { "submenu": "code-for-ibmi.openMember", @@ -2349,7 +2343,7 @@ "group": "2_memberStuff@2" }, { - "command": "code-for-ibmi.deleteMember", + "command": "code-for-ibmi.objectBrowser.delete", "when": "view == objectBrowser && viewItem == member", "group": "2_memberStuff@4" }, @@ -2393,26 +2387,26 @@ "when": "view == ifsBrowser && !listMultiSelection && viewItem == streamfile", "group": "1_workspace@1" }, - { - "command": "code-for-ibmi.createStreamfile", - "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^directory.*$/", - "group": "2_ifsStuff@1" - }, - { - "command": "code-for-ibmi.createDirectory", - "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^directory.*$/", - "group": "2_ifsStuff@2" - }, - { - "command": "code-for-ibmi.copyIFS", - "when": "view == ifsBrowser && !listMultiSelection && !(viewItem =~ /^.*_protected$/)", - "group": "2_ifsStuff@3" - }, - { - "command": "code-for-ibmi.moveIFS", - "when": "view == ifsBrowser && !listMultiSelection && !(viewItem =~ /^.*_protected$/)", - "group": "2_ifsStuff@4" - }, + { + "command": "code-for-ibmi.createStreamfile", + "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^directory.*$/", + "group": "2_ifsStuff@1" + }, + { + "command": "code-for-ibmi.createDirectory", + "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^directory.*$/", + "group": "2_ifsStuff@2" + }, + { + "command": "code-for-ibmi.copyIFS", + "when": "view == ifsBrowser && !listMultiSelection && !(viewItem =~ /^.*_protected$/)", + "group": "2_ifsStuff@3" + }, + { + "command": "code-for-ibmi.moveIFS", + "when": "view == ifsBrowser && !listMultiSelection && !(viewItem =~ /^.*_protected$/)", + "group": "2_ifsStuff@4" + }, { "command": "code-for-ibmi.deleteIFS", "when": "view == ifsBrowser && !(viewItem =~ /^.*_protected$/)", @@ -2438,11 +2432,11 @@ "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^directory.*$/", "group": "3_ifsStuff@3" }, - { - "command": "code-for-ibmi.createStreamfile", - "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^shortcut.*$/", - "group": "2_ifsStuff@1" - }, + { + "command": "code-for-ibmi.createStreamfile", + "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^shortcut.*$/", + "group": "2_ifsStuff@1" + }, { "command": "code-for-ibmi.createDirectory", "when": "view == ifsBrowser && !listMultiSelection && viewItem =~ /^shortcut.*$/", @@ -2519,21 +2513,19 @@ "group": "1_objActions@2" }, { - "command": "code-for-ibmi.deleteObject", - - "when": "view == objectBrowser && (viewItem =~ /^object/ || viewItem == SPF)", + "command": "code-for-ibmi.objectBrowser.delete", + "when": "view == objectBrowser && viewItem =~ /^object/", "group": "1_objActions@5" }, { "command": "code-for-ibmi.renameObject", - "when": "view == objectBrowser && (viewItem =~ /^object/ || viewItem == SPF)", + "when": "view == objectBrowser && viewItem =~ /^object/", "group": "1_objActions@3" }, { "command": "code-for-ibmi.moveObject", - "when": "view == objectBrowser && (viewItem =~ /^object(?!.lib)/ || viewItem == SPF)", + "when": "view == objectBrowser && viewItem =~ /^object(?!.lib)/", "group": "1_objActions@4" - }, { "command": "code-for-ibmi.createSourceFile", @@ -2604,4 +2596,4 @@ "HalcyonTechLtd.vscode-ibmi-walkthroughs", "vscode.git" ] -} +} \ No newline at end of file diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index c94f7c7b1..4106bc339 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -506,7 +506,7 @@ export default class IBMiContent { let createOBJLIST; if (sourceFilesOnly) { //DSPFD only - createOBJLIST =`select PHFILE as NAME, ` + + createOBJLIST = `select PHFILE as NAME, ` + `'*FILE' as TYPE, ` + `PHFILA as ATTRIBUTE, ` + `PHTXT as TEXT, ` + diff --git a/src/extension.ts b/src/extension.ts index 9e8045d83..b857eaa9d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,12 +21,13 @@ import { updateLocale } from "./locale"; import * as Sandbox from "./sandbox"; import { initialise } from "./testing"; import { CodeForIBMi, ConnectionData } from "./typings"; -import { ObjectBrowserProvider } from "./views/ConnectionBrowser"; +import { initializeConnectionBrowser } from "./views/ConnectionBrowser"; import { LibraryListProvider } from "./views/LibraryListView"; import { ProfilesView } from "./views/ProfilesView"; import { HelpView } from "./views/helpView"; import { initializeIFSBrowser } from "./views/ifsBrowser"; import { initializeObjectBrowser } from "./views/objectBrowser"; +import { SettingsUI } from "./webviews/settings"; export async function activate(context: ExtensionContext): Promise { // Use the console to output diagnostic information (console.log) and errors (console.error) @@ -41,15 +42,13 @@ export async function activate(context: ExtensionContext): Promise GlobalStorage.get().setLastConnections(lastConnections); commands.executeCommand(`setContext`, `code-for-ibmi:hasPreviousConnection`, lastConnections.length > 0); }; - + + SettingsUI.init(context); + initializeConnectionBrowser(context); initializeObjectBrowser(context) initializeIFSBrowser(context); - context.subscriptions.push( - window.registerTreeDataProvider( - `connectionBrowser`, - new ObjectBrowserProvider(context) - ), + context.subscriptions.push( window.registerTreeDataProvider( `helpView`, new HelpView() diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 57b0637c2..6d8d20684 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -55,6 +55,7 @@ export const da: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `Ingen parametre til forbindelsen '{0}' blev fundet`, 'connectionBrowser.renameConnection.error': `Fejl ved omdøbning af forbindelsen '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Er du sikker på at du vil slette forbindelsen '{0}'?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Er du sikker på at du vil disse forbindelser?`, 'connectionBrowser.ServerItem.tooltip': ` (forrige forbindelse)`, 'connectionBrowser.ServerItem.title': `Forbind`, // helpView: @@ -90,7 +91,7 @@ export const da: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'Hvad ønsker du at uploade?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Ikke tilladt at slette valgte mapper fra IFS Browser.\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Er du sikker på at du vil slette {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?\n{1}`, 'ifsBrowser.deleteIFS.deletionPrompt': `Når du har slettet mappen, kan den ikke retableres.\nVenligst tast \"{0}\" for at bekræfte sletning.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Tryk \'Escape\' for at afbryde)`, 'ifsBrowser.deleteIFS.infoMessage': `Slettede {0}.`, @@ -191,7 +192,6 @@ export const da: Locale = { 'objectBrowser.copyObject.infoMessage': `Kopierede objekt {0} {1} til {2}.`, 'objectBrowser.copyObject.infoMessage2': `Kopierede objekt {0} {1} til {2}. Opfrisk object browser.`, 'objectBrowser.copyObject.errorMessage4': `Fejl ved kopiering af objekt {0}! {1}`, - 'objectBrowser.deleteObject.warningMessage': `Er du sikker på at du vil slette {0} {1}?`, 'objectBrowser.deleteObject.infoMessage': `Slettede {0} {1}.`, 'objectBrowser.deleteObject.errorMessage': `Fejl ved sletning af objekt! {0}`, 'objectBrowser.deleteObject.progress':'Sletter objekt {0} {1}...', diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index 1c2472a1b..c55bda3f5 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -55,6 +55,7 @@ export const en: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `No parameters for connection '{0}' was found`, 'connectionBrowser.renameConnection.error': `Error renaming connection '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Are you sure you want to delete the connection '{0}'?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Are you sure you want to delete these connections?`, 'connectionBrowser.ServerItem.tooltip': ` (previous connection)`, 'connectionBrowser.ServerItem.title': `Connect`, // helpView: @@ -90,7 +91,7 @@ export const en: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'What do you want to upload?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Unable to delete protected directories from the IFS Browser!\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Are you sure you want to delete {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Are you sure you want to delete the {0} selected files?`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Are you sure you want to delete the {0} selected files?\n{1}`, 'ifsBrowser.deleteIFS.deletionPrompt': `Once you delete the directory, it cannot be restored.\nPlease type \"{0}\" to confirm deletion.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Press \'Escape\' to cancel)`, 'ifsBrowser.deleteIFS.infoMessage': `Deleted {0}.`, @@ -191,7 +192,6 @@ export const en: Locale = { 'objectBrowser.copyObject.infoMessage': `Copied object {0} {1} to {2}.`, 'objectBrowser.copyObject.infoMessage2': `Copied object {0} {1} to {2}. Refresh object browser.`, 'objectBrowser.copyObject.errorMessage4': `Error copying object {0}! {1}`, - 'objectBrowser.deleteObject.warningMessage': `Are you sure you want to delete {0} {1}?`, 'objectBrowser.deleteObject.infoMessage': `Deleted {0} {1}.`, 'objectBrowser.deleteObject.errorMessage': `Error deleting object! {0}`, 'objectBrowser.deleteObject.progress':'Deleting object {0} {1}...', diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 8a52440ca..99170658c 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -55,6 +55,7 @@ export const fr: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `Aucun paramètre pour la connexion '{0}' n'a été trouvé`, 'connectionBrowser.renameConnection.error': `Erreur lors du renommage de la connexion '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Êtes vous sûr de vouloir supprimer la connexion '{0}'?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Êtes vous sûr de vouloir supprimer ces connexions?`, 'connectionBrowser.ServerItem.tooltip': ` (précédente connexion)`, 'connectionBrowser.ServerItem.title': `Se Connecter`, // helpView: @@ -90,7 +91,7 @@ export const fr: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'Que souhaitez-vous envoyer?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Impossible de supprimer les répertoires protégés depuis l'Explorateur IFS!\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Êtes-vous sûr de vouloir suprimer {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Êtes-vous sûr de vouloir suprimer les {0} fichiers sélectionnés?`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Êtes-vous sûr de vouloir suprimer les {0} fichiers sélectionnés?\n{1}`, 'ifsBrowser.deleteIFS.deletionPrompt': `La suppression du répertoire est irréversible!\nEntrez \"{0}\" pour confirmer la suppression.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Appuyer sur \'Escape\' pour annuler)`, 'ifsBrowser.deleteIFS.infoMessage': `{0} supprimé.`, @@ -191,7 +192,6 @@ export const fr: Locale = { 'objectBrowser.copyObject.infoMessage': `Objet {0} {1} copié vers {2}.`, 'objectBrowser.copyObject.infoMessage2': `Objet {0} {1} copié vers {2}. Rafraîchissement de l'explorateur d'objet.`, 'objectBrowser.copyObject.errorMessage4': `Erreur lors de la copie de l'objet {0}! {1}`, - 'objectBrowser.deleteObject.warningMessage': `Êtes-vous sûr de vouloir supprimer {0} {1}?`, 'objectBrowser.deleteObject.infoMessage': `{0} {1} supprimé.`, 'objectBrowser.deleteObject.errorMessage': `Erreur lors de la suppression de l'objet! {0}`, 'objectBrowser.deleteObject.progress':`Suppression de l'objet {0} {1}...`, diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 936b785d8..fd0518bc6 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -6,146 +6,158 @@ import { GlobalStorage } from '../api/Storage'; import { instance } from '../instantiate'; import { t } from "../locale"; import { Login } from '../webviews/login'; -import { SettingsUI } from '../webviews/settings'; -export class ObjectBrowserProvider { - private _attemptingConnection: boolean; - private readonly _emitter: vscode.EventEmitter; - readonly onDidChangeTreeData: vscode.Event; - - constructor(context: vscode.ExtensionContext) { - this._attemptingConnection = false; - this._emitter = new vscode.EventEmitter(); - this.onDidChangeTreeData = this._emitter.event; - - SettingsUI.init(context); - - context.subscriptions.push( - vscode.commands.registerCommand(`code-for-ibmi.connect`, () => { - if (!this._attemptingConnection) { - Login.show(context); +export function initializeConnectionBrowser(context: vscode.ExtensionContext) { + const connectionBrowser = new ConnectionBrowser(); + const connectionTreeViewer = vscode.window.createTreeView( + `connectionBrowser`, { + treeDataProvider: connectionBrowser, + showCollapseAll: false, + canSelectMany: true + }); + + context.subscriptions.push( + connectionTreeViewer, + vscode.commands.registerCommand(`code-for-ibmi.connect`, () => { + if (!connectionBrowser.attemptingConnection) { + Login.show(context); + } + }), + + vscode.commands.registerCommand(`code-for-ibmi.connectToPrevious`, async () => { + const lastConnection = GlobalStorage.get().getLastConnections()?.[0]; + if (lastConnection) { + return await vscode.commands.executeCommand(`code-for-ibmi.connectTo`, lastConnection.name); + } + }), + + vscode.commands.registerCommand(`code-for-ibmi.connectTo`, async (name?: string | Server, reloadServerSettings?: boolean) => { + if (!connectionBrowser.attemptingConnection) { + connectionBrowser.attemptingConnection = true; + + if (!name) { + const lastConnections = GlobalStorage.get().getLastConnections() || []; + if (lastConnections && lastConnections.length) { + name = (await vscode.window.showQuickPick([{ kind: vscode.QuickPickItemKind.Separator, label: t(`connectionBrowser.connectTo.lastConnection`) }, + ...lastConnections.map(lc => ({ label: lc.name, description: t(`connectionBrowser.connectTo.lastUsed`, new Date(lc.timestamp).toLocaleString()) }))], + { title: t(`connectionBrowser.connectTo.title`) } + ))?.label; + } } - }), - vscode.commands.registerCommand(`code-for-ibmi.connectToPrevious`, async () => { - const lastConnection = GlobalStorage.get().getLastConnections()?.[0]; - if (lastConnection) { - return await vscode.commands.executeCommand(`code-for-ibmi.connectTo`, lastConnection.name); + switch (typeof name) { + case `string`: // Name of connection object + await Login.LoginToPrevious(name, context, reloadServerSettings); + break; + case `object`: // A Server object + await Login.LoginToPrevious(name.name, context, reloadServerSettings); + break; + default: + vscode.window.showErrorMessage(t(`connectionBrowser.connectTo.error`)); + break; } - }), - - vscode.commands.registerCommand(`code-for-ibmi.connectTo`, async (name?: string | Server, reloadServerSettings?: boolean) => { - if (!this._attemptingConnection) { - this._attemptingConnection = true; - - if (!name) { - const lastConnections = GlobalStorage.get().getLastConnections() || []; - if (lastConnections && lastConnections.length) { - name = (await vscode.window.showQuickPick([{ kind: vscode.QuickPickItemKind.Separator, label: t(`connectionBrowser.connectTo.lastConnection`) }, - ...lastConnections.map(lc => ({ label: lc.name, description: t(`connectionBrowser.connectTo.lastUsed`, new Date(lc.timestamp).toLocaleString()) }))], - { title: t(`connectionBrowser.connectTo.title`) } - ))?.label; - } - } - switch (typeof name) { - case `string`: // Name of connection object - await Login.LoginToPrevious(name, context, reloadServerSettings); - break; - case `object`: // A Server object - await Login.LoginToPrevious(name.name, context, reloadServerSettings); - break; - default: - vscode.window.showErrorMessage(t(`connectionBrowser.connectTo.error`)); - break; + connectionBrowser.attemptingConnection = false; + } + }), + + vscode.commands.registerCommand(`code-for-ibmi.connectToAndReload`, async (server: Server) => { + if (!connectionBrowser.attemptingConnection && server) { + const reloadServerSettings = true; + vscode.commands.executeCommand(`code-for-ibmi.connectTo`, server.name, reloadServerSettings); + } + }), + + vscode.commands.registerCommand(`code-for-ibmi.refreshConnections`, () => { + connectionBrowser.refresh(); + }), + + vscode.commands.registerCommand(`code-for-ibmi.renameConnection`, async (server: Server) => { + if (!connectionBrowser.attemptingConnection && server) { + const existingConnections = GlobalConfiguration.get(`connections`) || []; + const newName = await vscode.window.showInputBox({ + prompt: t(`connectionBrowser.renameConnection.prompt`, server.name), + value: server.name, + validateInput: newName => { + if (newName === server.name) { + return t(`connectionBrowser.renameConnection.invalid.input`); + } else if (existingConnections.findIndex(item => item.name === newName) !== -1) { + return t(`connectionBrowser.renameConnection.alreadyExists`, newName); + } } - - this._attemptingConnection = false; - } - }), - - vscode.commands.registerCommand(`code-for-ibmi.connectToAndReload`, async (server: Server) => { - if (!this._attemptingConnection && server) { - const reloadServerSettings = true; - vscode.commands.executeCommand(`code-for-ibmi.connectTo`, server.name, reloadServerSettings); - } - }), - - vscode.commands.registerCommand(`code-for-ibmi.refreshConnections`, () => { - this.refresh(); - }), - - vscode.commands.registerCommand(`code-for-ibmi.renameConnection`, async (server: Server) => { - if (!this._attemptingConnection && server) { - const existingConnections = GlobalConfiguration.get(`connections`) || []; - const newName = await vscode.window.showInputBox({ - prompt: t(`connectionBrowser.renameConnection.prompt`, server.name), - value: server.name, - validateInput: newName => { - if (newName === server.name) { - return t(`connectionBrowser.renameConnection.invalid.input`); - } else if (existingConnections.findIndex(item => item.name === newName) !== -1) { - return t(`connectionBrowser.renameConnection.alreadyExists`, newName); - } + }); + + if (newName) { + try { + let index; + // First rename the connection details + const connections = GlobalConfiguration.get(`connections`) || []; + index = connections.findIndex(connection => connection.name === server.name); + if (index === -1) throw (t(`connectionBrowser.renameConnection.noConnectionFound`, server.name)); + connections[index].name = newName; + + // Then rename the connection settings + const connectionSettings = GlobalConfiguration.get(`connectionSettings`) || []; + index = connectionSettings.findIndex(connection => connection.name === server.name); + if (index === -1) throw (t(`connectionBrowser.renameConnection.noConnParmsFound`, server.name)); + connectionSettings[index].name = newName; + + // Then get the cached connection settings + const cachedConnectionSettings = GlobalStorage.get().getServerSettingsCache(server.name); + + // Then get the password key + const secret = await context.secrets.get(`${server.name}_password`); + + // No errors - update the settings. + await GlobalConfiguration.set(`connectionSettings`, connectionSettings); + await GlobalConfiguration.set(`connections`, connections); + if (cachedConnectionSettings) { + GlobalStorage.get().setServerSettingsCache(newName, cachedConnectionSettings); + GlobalStorage.get().deleteServerSettingsCache(server.name); } - }); - - if (newName) { - try { - let index; - // First rename the connection details - const connections = GlobalConfiguration.get(`connections`) || []; - index = connections.findIndex(connection => connection.name === server.name); - if (index === -1) throw(t(`connectionBrowser.renameConnection.noConnectionFound`, server.name)); - connections[index].name = newName; - - // Then rename the connection settings - const connectionSettings = GlobalConfiguration.get(`connectionSettings`) || []; - index = connectionSettings.findIndex(connection => connection.name === server.name); - if (index === -1) throw(t(`connectionBrowser.renameConnection.noConnParmsFound`, server.name)); - connectionSettings[index].name = newName; - - // Then get the cached connection settings - const cachedConnectionSettings = GlobalStorage.get().getServerSettingsCache(server.name); - - // Then get the password key - const secret = await context.secrets.get(`${server.name}_password`); - - // No errors - update the settings. - await GlobalConfiguration.set(`connectionSettings`, connectionSettings); - await GlobalConfiguration.set(`connections`, connections); - if(cachedConnectionSettings) { - GlobalStorage.get().setServerSettingsCache(newName, cachedConnectionSettings); - GlobalStorage.get().deleteServerSettingsCache(server.name); - } - if (secret) { - await context.secrets.store(`${newName}_password`, secret); - await context.secrets.delete(`${server.name}_password`); - } - - this.refresh(); - } catch (e: any) { - vscode.window.showErrorMessage( - t(`connectionBrowser.renameConnection.error`, server.name, e.message || String(e))); + if (secret) { + await context.secrets.store(`${newName}_password`, secret); + await context.secrets.delete(`${server.name}_password`); } + + connectionBrowser.refresh(); + } catch (e: any) { + vscode.window.showErrorMessage( + t(`connectionBrowser.renameConnection.error`, server.name, e.message || String(e))); } } - }), - - vscode.commands.registerCommand(`code-for-ibmi.sortConnections`, async () => { - const connections = GlobalConfiguration.get(`connections`) || []; - connections.sort((conn1, conn2) => conn1.name.localeCompare(conn2.name)); - await GlobalConfiguration.set(`connections`, connections); - this.refresh(); - }), - - vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, (server: Server) => { - if (!this._attemptingConnection && server) { - vscode.window.showWarningMessage( - t(`connectionBrowser.deleteConnection.warning`, server.name), - t(`Yes`), t(`No`) - ).then(async (value) => { - if (value === t(`Yes`)) { + } + }), + + vscode.commands.registerCommand(`code-for-ibmi.sortConnections`, async () => { + const connections = GlobalConfiguration.get(`connections`) || []; + connections.sort((conn1, conn2) => conn1.name.localeCompare(conn2.name)); + await GlobalConfiguration.set(`connections`, connections); + connectionBrowser.refresh(); + }), + + vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, (single: Server, servers?: Server[]) => { + const toBeDeleted: Server[] = []; + if (servers) { + toBeDeleted.push(...servers); + } + else if (single) { + toBeDeleted.push(single); + } + else { + toBeDeleted.push(...connectionTreeViewer.selection); + } + + if (!connectionBrowser.attemptingConnection && toBeDeleted.length) { + const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`); + const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(server => `- ${server.name}`).join("\n"); + vscode.window.showWarningMessage( + message, + { modal: true, detail }, + t(`Yes`) + ).then(async (value) => { + if (value) { + for await (const server of toBeDeleted) { // First remove the connection details const connections = GlobalConfiguration.get(`connections`) || []; const newConnections = connections.filter(connection => connection.name !== server.name); @@ -161,14 +173,22 @@ export class ObjectBrowserProvider { // Then remove the password await context.secrets.delete(`${server.name}_password`); - - this.refresh(); } - }); - } - }) - ); + connectionBrowser.refresh(); + } + }); + } + }) + ); +} + +class ConnectionBrowser implements vscode.TreeDataProvider { + public attemptingConnection: boolean = false; + private readonly _emitter: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._emitter.event; + + constructor() { instance.onEvent("disconnected", () => this.refresh()) } diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 81727945c..c607292e7 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -162,7 +162,7 @@ class IFSDirectoryItem extends IFSItem { if (content) { try { const showHidden = instance.getConfig()?.showHiddenFiles; - const filterIFSFile = (file:IFSFile, type: "directory" | "streamfile") => file.type === type && (showHidden || !file.name.startsWith(`.`) || alwaysShow(file.name)); + const filterIFSFile = (file: IFSFile, type: "directory" | "streamfile") => file.type === type && (showHidden || !file.name.startsWith(`.`) || alwaysShow(file.name)); const objects = await content.getFileList(this.path, this.sort, handleFileListErrors); const directories = objects.filter(f => filterIFSFile(f, "directory")); const streamFiles = objects.filter(f => filterIFSFile(f, "streamfile")); @@ -546,58 +546,65 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { const connection = instance.getConnection(); const config = instance.getConfig(); if (connection && config) { - items = items || [singleItem]; - if (!items.find(n => isProtected(n.path))) { - let deletionConfirmed = false; - const proceed = items.length > 1 ? - await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length), t(`Yes`), t(`Cancel`)) === t(`Yes`) : - await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path), t(`Yes`), t(`Cancel`)) === t(`Yes`); - if (proceed) { - for (const item of items) { - if ((GlobalConfiguration.get(`safeDeleteMode`)) && item.contextValue === `directory`) { //Check if path is directory - const dirName = path.basename(item.path) //Get the name of the directory to be deleted - - const deletionPrompt = t(`ifsBrowser.deleteIFS.deletionPrompt`, dirName); - const input = await vscode.window.showInputBox({ - placeHolder: dirName, - prompt: deletionPrompt, - validateInput: text => { - return (text === dirName) ? null : deletionPrompt + t(`ifsBrowser.deleteIFS.deletionPrompt2`); - } - }); - deletionConfirmed = (input === dirName); - } - else { - // If deleting a file rather than a directory, skip the name entry - deletionConfirmed = true; - } + if (items || singleItem) { + items = items || [singleItem] + } + else { + items = ifsTreeViewer.selection.filter(selected => selected instanceof IFSItem) as IFSItem[]; + } + if (items && items.length) { + if (!items.find(n => isProtected(n.path))) { + let deletionConfirmed = false; + const proceed = items.length > 1 ? + await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length, items.map(i => `- ${i.path}`).join("\n")), { modal: true }, t(`Yes`), t(`Cancel`)) === t(`Yes`) : + await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path), { modal: true }, t(`Yes`), t(`Cancel`)) === t(`Yes`); + if (proceed) { + for (const item of items) { + if ((GlobalConfiguration.get(`safeDeleteMode`)) && item.contextValue === `directory`) { //Check if path is directory + const dirName = path.basename(item.path) //Get the name of the directory to be deleted + + const deletionPrompt = t(`ifsBrowser.deleteIFS.deletionPrompt`, dirName); + const input = await vscode.window.showInputBox({ + placeHolder: dirName, + prompt: deletionPrompt, + validateInput: text => { + return (text === dirName) ? null : deletionPrompt + t(`ifsBrowser.deleteIFS.deletionPrompt2`); + } + }); + deletionConfirmed = (input === dirName); + } + else { + // If deleting a file rather than a directory, skip the name entry + deletionConfirmed = true; + } - if (deletionConfirmed) { - try { - const removeResult = await connection.sendCommand({ command: `rm -rf ${Tools.escapePath(item.path)}` }) - if (removeResult.code === 0) { - vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.infoMessage`, item.path)); + if (deletionConfirmed) { + try { + const removeResult = await connection.sendCommand({ command: `rm -rf ${Tools.escapePath(item.path)}` }) + if (removeResult.code === 0) { + vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.infoMessage`, item.path)); + } + else { + throw removeResult.stderr; + } + } catch (e) { + vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.errorMessage`, e)); } - else { - throw removeResult.stderr; - } - } catch (e) { - vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.errorMessage`, e)); + } + else { + vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.cancelled`)); } } - else { - vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.cancelled`)); + if (GlobalConfiguration.get(`autoRefresh`)) { + items.map(item => item.parent) + .filter(Tools.distinct) + .forEach(async parent => parent?.refresh?.()); } } - if (GlobalConfiguration.get(`autoRefresh`)) { - items.map(item => item.parent) - .filter(Tools.distinct) - .forEach(async parent => parent?.refresh?.()); - } } - } - else { - vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.dirNotAllowed`, items.filter(n => isProtected(n.path)).map(n => n.path).join(`\n`))); + else { + vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.dirNotAllowed`, items.filter(n => isProtected(n.path)).map(n => n.path).join(`\n`))); + } } } }), diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index f73e78aa7..fa80fcc21 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -65,6 +65,8 @@ class ObjectBrowserItem extends BrowserItem { reveal(options?: FocusOptions) { return vscode.commands.executeCommand(`code-for-ibmi.revealInObjectBrowser`, this, options); } + + delete?(): Promise; } class ObjectBrowser implements vscode.TreeDataProvider { @@ -181,6 +183,22 @@ class ObjectBrowserFilterItem extends ObjectBrowserItem { }); } } + + async delete() { + const config = getConfig(); + const filter = this.filter; + vscode.window.showWarningMessage(t(`objectBrowser.deleteFilter.infoMessage`, filter.name), { modal: true }, t(`Yes`), t(`No`)).then(async (value) => { + if (value === t(`Yes`)) { + const index = config.objectFilters.findIndex(f => f.name === filter.name); + + if (index > -1) { + config.objectFilters.splice(index, 1); + await ConnectionConfiguration.update(config); + vscode.commands.executeCommand(`code-for-ibmi.refreshObjectBrowser`); + } + } + }); + } } class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements ObjectItem { @@ -273,6 +291,12 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O } } } + + async delete() { + if (await deleteObject(this.path, this.object)) { + this.parent?.refresh?.(); + } + } } class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { @@ -314,6 +338,12 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { objectFilter.library = this.object.name; return await listObjects(this, objectFilter); } + + async delete() { + if (await deleteObject(this.path, this.object)) { + this.parent?.refresh?.(); + } + } } class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { @@ -344,6 +374,26 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { arguments: [this, (readonly ? "browse" : undefined) as DefaultOpenMode] }; } + + async delete() { + const result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteMember.warningMessage`, this.path), { modal: true }, t(`Yes`), t(`Cancel`)); + if (result === t(`Yes`)) { + const connection = getConnection(); + const { library, file, name } = connection.parserMemberPath(this.path); + + const removeResult = await connection.runCommand({ + command: `RMVM FILE(${library}/${file}) MBR(${name})`, + noLibList: true + }); + + if (removeResult.code === 0) { + vscode.window.showInformationMessage(t(`objectBrowser.deleteMember.infoMessage`, this.path)); + this.parent?.refresh?.(); + } else { + vscode.window.showErrorMessage(t(`objectBrowser.deleteMember.errorMessage`, removeResult.stderr)); + } + } + } } class ObjectBrowserMemberItemDragAndDrop implements vscode.TreeDragAndDropController { @@ -452,19 +502,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { }), vscode.commands.registerCommand(`code-for-ibmi.deleteFilter`, async (node: FilteredItem) => { - const config = getConfig(); - const filter = node.filter; - vscode.window.showInformationMessage(t(`objectBrowser.deleteFilter.infoMessage`, filter.name), t(`Yes`), t(`No`)).then(async (value) => { - if (value === t(`Yes`)) { - const index = config.objectFilters.findIndex(f => f.name === filter.name); - - if (index > -1) { - config.objectFilters.splice(index, 1); - await ConnectionConfiguration.update(config); - objectBrowser.refresh(); - } - } - }); + }), vscode.commands.registerCommand(`code-for-ibmi.moveFilterUp`, (node: ObjectBrowserFilterItem) => objectBrowser.moveFilterInList(node, `UP`)), @@ -618,30 +656,6 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } } }), - vscode.commands.registerCommand(`code-for-ibmi.deleteMember`, async (node: ObjectBrowserMemberItem) => { - let result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteMember.warningMessage`, node.path), t(`Yes`), t(`Cancel`)); - - if (result === t(`Yes`)) { - const connection = getConnection(); - const { library, file, name } = connection.parserMemberPath(node.path); - - const removeResult = await connection.runCommand({ - command: `RMVM FILE(${library}/${file}) MBR(${name})`, - noLibList: true - }); - - if (removeResult.code === 0) { - vscode.window.showInformationMessage(t(`objectBrowser.deleteMember.infoMessage`, node.path)); - - objectBrowser.refresh(node.parent); - - } else { - vscode.window.showErrorMessage(t(`objectBrowser.deleteMember.errorMessage`, removeResult.stderr)); - } - - //Not sure how to remove the item from the list. Must refresh - but that might be slow? - } - }), vscode.commands.registerCommand(`code-for-ibmi.updateMemberText`, async (node: ObjectBrowserMemberItem) => { const connection = getConnection(); const { library, file, name, basename } = connection.parserMemberPath(node.path); @@ -1033,30 +1047,6 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } while (newPath && !newPathOK) }), - vscode.commands.registerCommand(`code-for-ibmi.deleteObject`, async (node: ObjectBrowserObjectItem | ObjectBrowserSourcePhysicalFileItem) => { - let result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteObject.warningMessage`, node.path, node.object.type.toUpperCase()), t(`Yes`), t(`Cancel`)); - - if (result === t(`Yes`)) { - const connection = getConnection(); - await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t("objectBrowser.deleteObject.progress", node.path, node.object.type.toUpperCase()) } - , async (progress) => { - // TODO: Progress message about deleting! - const deleteResult = await connection.runCommand({ - command: `DLTOBJ OBJ(${node.path}) OBJTYPE(${node.object.type})`, - noLibList: true - }); - - if (deleteResult.code === 0) { - vscode.window.showInformationMessage(t(`objectBrowser.deleteObject.infoMessage`, node.path, node.object.type.toUpperCase())); - objectBrowser.refresh(node.parent); - } else { - vscode.window.showErrorMessage(t(`objectBrowser.deleteObject.errorMessage`, deleteResult.stderr)); - } - } - ); - } - }), - vscode.commands.registerCommand(`code-for-ibmi.renameObject`, async (node: ObjectBrowserObjectItem) => { let [, newObject] = node.path.split(`/`); let newObjectOK; @@ -1129,6 +1119,16 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { }); } } while (newLibrary && !newLibraryOK) + }), + vscode.commands.registerCommand("code-for-ibmi.objectBrowser.delete", async (node?: ObjectBrowserItem) => { + if (!node && objectTreeViewer.selection.length) { + const item = objectTreeViewer.selection.at(0); + if (item instanceof ObjectBrowserItem) { + node = item; + } + } + + node?.delete?.(); }) ); } @@ -1265,3 +1265,29 @@ async function listObjects(item: ObjectBrowserFilterItem, filter?: ConnectionCon return object.sourceFile ? new ObjectBrowserSourcePhysicalFileItem(item, object) : new ObjectBrowserObjectItem(item, object); }); } + +async function deleteObject(path: string, object: IBMiObject) { + const result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteObject.warningMessage`, path, object.type.toUpperCase()), { modal: true }, t(`Yes`), t(`Cancel`)); + + if (result === t(`Yes`)) { + const connection = getConnection(); + await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t("objectBrowser.deleteObject.progress", path, object.type.toUpperCase()) } + , async () => { + // TODO: Progress message about deleting! + const deleteResult = await connection.runCommand({ + command: `DLTOBJ OBJ(${path}) OBJTYPE(${object.type})`, + noLibList: true + }); + + if (deleteResult.code === 0) { + vscode.window.showInformationMessage(t(`objectBrowser.deleteObject.infoMessage`, path, object.type.toUpperCase())); + return true; + } else { + vscode.window.showErrorMessage(t(`objectBrowser.deleteObject.errorMessage`, deleteResult.stderr)); + } + } + ); + } + + return false; +} \ No newline at end of file From 2edc3e72e2dffad6c2d5520e0ff02c4a64e568be Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Fri, 23 Feb 2024 23:08:57 +0100 Subject: [PATCH 02/14] Delete action's single server is optional Signed-off-by: Seb Julliand --- src/views/ConnectionBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index fd0518bc6..9e3c266d8 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -136,7 +136,7 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { connectionBrowser.refresh(); }), - vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, (single: Server, servers?: Server[]) => { + vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, (single?: Server, servers?: Server[]) => { const toBeDeleted: Server[] = []; if (servers) { toBeDeleted.push(...servers); From de7d1e9ceb1e5fa819f8d750287a8872441186be Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Sat, 24 Feb 2024 00:26:02 +0100 Subject: [PATCH 03/14] "Delete confirmation" dialog consistency Signed-off-by: Seb Julliand --- src/views/ConnectionBrowser.ts | 14 ++++---------- src/views/ifsBrowser.ts | 9 ++++----- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 9e3c266d8..4744289d0 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -136,7 +136,7 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { connectionBrowser.refresh(); }), - vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, (single?: Server, servers?: Server[]) => { + vscode.commands.registerCommand(`code-for-ibmi.deleteConnection`, async (single?: Server, servers?: Server[]) => { const toBeDeleted: Server[] = []; if (servers) { toBeDeleted.push(...servers); @@ -151,13 +151,8 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { if (!connectionBrowser.attemptingConnection && toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`); const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(server => `- ${server.name}`).join("\n"); - vscode.window.showWarningMessage( - message, - { modal: true, detail }, - t(`Yes`) - ).then(async (value) => { - if (value) { - for await (const server of toBeDeleted) { + if(await vscode.window.showWarningMessage( message, { modal: true, detail }, t(`Yes`))){ + for (const server of toBeDeleted) { // First remove the connection details const connections = GlobalConfiguration.get(`connections`) || []; const newConnections = connections.filter(connection => connection.name !== server.name); @@ -176,8 +171,7 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { } connectionBrowser.refresh(); - } - }); + } } }) ); diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index c607292e7..97b59d5b4 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -9,7 +9,7 @@ import { GlobalStorage } from "../api/Storage"; import { Tools } from "../api/Tools"; import { instance, setSearchResults } from "../instantiate"; import { t } from "../locale"; -import { BrowserItem, BrowserItemParameters, CommandResult, FocusOptions, IFSFile, IFS_BROWSER_MIMETYPE, OBJECT_BROWSER_MIMETYPE, WithPath } from "../typings"; +import { BrowserItem, BrowserItemParameters, FocusOptions, IFSFile, IFS_BROWSER_MIMETYPE, OBJECT_BROWSER_MIMETYPE, WithPath } from "../typings"; const URI_LIST_MIMETYPE = "text/uri-list"; const URI_LIST_SEPARATOR = "\r\n"; @@ -555,10 +555,9 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { if (items && items.length) { if (!items.find(n => isProtected(n.path))) { let deletionConfirmed = false; - const proceed = items.length > 1 ? - await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length, items.map(i => `- ${i.path}`).join("\n")), { modal: true }, t(`Yes`), t(`Cancel`)) === t(`Yes`) : - await vscode.window.showWarningMessage(t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path), { modal: true }, t(`Yes`), t(`Cancel`)) === t(`Yes`); - if (proceed) { + const message = items.length === 1 ? t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path) : t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length); + const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); + if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { for (const item of items) { if ((GlobalConfiguration.get(`safeDeleteMode`)) && item.contextValue === `directory`) { //Check if path is directory const dirName = path.basename(item.path) //Get the name of the directory to be deleted From 443537df9d700654f33fcfe350ec17447d3c947f Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Sat, 24 Feb 2024 00:26:23 +0100 Subject: [PATCH 04/14] Reworked Object Browser deletion process Signed-off-by: Seb Julliand --- src/locale/ids/da.ts | 10 +-- src/locale/ids/en.ts | 10 +-- src/locale/ids/fr.ts | 10 +-- src/views/objectBrowser.ts | 171 +++++++++++++++++++++++-------------- 4 files changed, 118 insertions(+), 83 deletions(-) diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 6d8d20684..1ba4bd95f 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -91,7 +91,7 @@ export const da: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'Hvad ønsker du at uploade?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Ikke tilladt at slette valgte mapper fra IFS Browser.\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Er du sikker på at du vil slette {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?\n{1}`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?`, 'ifsBrowser.deleteIFS.deletionPrompt': `Når du har slettet mappen, kan den ikke retableres.\nVenligst tast \"{0}\" for at bekræfte sletning.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Tryk \'Escape\' for at afbryde)`, 'ifsBrowser.deleteIFS.infoMessage': `Slettede {0}.`, @@ -141,7 +141,9 @@ export const da: Locale = { 'LibraryListView.cleanupLibraryList.removedLibs': `De følgende biblioteker blev fjernet fra den opdaterede biblioteksliste, da de ikke er gyldige: {0}`, 'LibraryListView.cleanupLibraryList.validated': `Bibliotekslisten blev valideret uden fejl.`, // objectBrowser: - 'objectBrowser.deleteFilter.infoMessage': `Slet filter '{0}'?`, + 'objectBrowser.delete.confirm':'Are you sure you want to delete {0}?', + 'objectBrowser.delete.multiple.confirm':'Are you sure you want to delete the selected elements?', + 'objectBrowser.delete.progress':'Deleting', 'objectBrowser.createMember.prompt': `Navn på nyt member (member.ext)`, 'objectBrowser.createMember.progressTitle': `Opretter member {0}...`, 'objectBrowser.createMember.errorMessage': `Fejl ved oprettelse af member {0}: {1}`, @@ -151,8 +153,6 @@ export const da: Locale = { 'objectBrowser.copyMember.overwrite': `Vil du overskrive member {0}?`, 'objectBrowser.copyMember.errorMessage2': `Member {0} eksisterer allerede!`, 'objectBrowser.copyMember.errorMessage3': `Fejl ved oprettelse af member {0}: {1}`, - 'objectBrowser.deleteMember.warningMessage': `Er du sikker på at du vil slette member {0}?`, - 'objectBrowser.deleteMember.infoMessage': `Slettede member {0}.`, 'objectBrowser.deleteMember.errorMessage': `Fejl ved sletning af member! {0}`, 'objectBrowser.updateMemberText.prompt': `Opdater beskrivelse af member {0}, *BLANK for ingen beskrivelse`, 'objectBrowser.updateMemberText.errorMessage': `Fejl ved ændring af member beskrivelse! {0}`, @@ -192,9 +192,7 @@ export const da: Locale = { 'objectBrowser.copyObject.infoMessage': `Kopierede objekt {0} {1} til {2}.`, 'objectBrowser.copyObject.infoMessage2': `Kopierede objekt {0} {1} til {2}. Opfrisk object browser.`, 'objectBrowser.copyObject.errorMessage4': `Fejl ved kopiering af objekt {0}! {1}`, - 'objectBrowser.deleteObject.infoMessage': `Slettede {0} {1}.`, 'objectBrowser.deleteObject.errorMessage': `Fejl ved sletning af objekt! {0}`, - 'objectBrowser.deleteObject.progress':'Sletter objekt {0} {1}...', 'objectBrowser.renameObject.prompt': `Omdøb objekt`, 'objectBrowser.renameObject.errorMessage': `Objektnavn må være 10 tegn eller mindre.`, 'objectBrowser.renameObject.infoMessage': `Omdøbte objekt {0} {1} til {2}.`, diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index c55bda3f5..fba2e2166 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -91,7 +91,7 @@ export const en: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'What do you want to upload?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Unable to delete protected directories from the IFS Browser!\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Are you sure you want to delete {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Are you sure you want to delete the {0} selected files?\n{1}`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Are you sure you want to delete the {0} selected files?`, 'ifsBrowser.deleteIFS.deletionPrompt': `Once you delete the directory, it cannot be restored.\nPlease type \"{0}\" to confirm deletion.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Press \'Escape\' to cancel)`, 'ifsBrowser.deleteIFS.infoMessage': `Deleted {0}.`, @@ -141,7 +141,9 @@ export const en: Locale = { 'LibraryListView.cleanupLibraryList.removedLibs': `The following libraries were removed from the updated library list as they are invalid: {0}`, 'LibraryListView.cleanupLibraryList.validated': `Library list were validated without any errors.`, // objectBrowser: - 'objectBrowser.deleteFilter.infoMessage': `Delete filter '{0}'?`, + 'objectBrowser.delete.confirm':'Are you sure you want to delete {0}?', + 'objectBrowser.delete.multiple.confirm':'Are you sure you want to delete the selected elements?', + 'objectBrowser.delete.progress':'Deleting', 'objectBrowser.createMember.prompt': `Name of new source member (member.ext)`, 'objectBrowser.createMember.progressTitle': `Creating member {0}...`, 'objectBrowser.createMember.errorMessage': `Error creating member {0}: {1}`, @@ -151,8 +153,6 @@ export const en: Locale = { 'objectBrowser.copyMember.overwrite': `Are you sure you want overwrite member {0}?`, 'objectBrowser.copyMember.errorMessage2': `Member {0} already exists!`, 'objectBrowser.copyMember.errorMessage3': `Error creating member {0}: {1}`, - 'objectBrowser.deleteMember.warningMessage': `Are you sure you want to delete {0}?`, - 'objectBrowser.deleteMember.infoMessage': `Deleted {0}.`, 'objectBrowser.deleteMember.errorMessage': `Error deleting member! {0}`, 'objectBrowser.updateMemberText.prompt': `Change member description for {0}, *BLANK for no description`, 'objectBrowser.updateMemberText.errorMessage': `Error changing member description! {0}`, @@ -192,9 +192,7 @@ export const en: Locale = { 'objectBrowser.copyObject.infoMessage': `Copied object {0} {1} to {2}.`, 'objectBrowser.copyObject.infoMessage2': `Copied object {0} {1} to {2}. Refresh object browser.`, 'objectBrowser.copyObject.errorMessage4': `Error copying object {0}! {1}`, - 'objectBrowser.deleteObject.infoMessage': `Deleted {0} {1}.`, 'objectBrowser.deleteObject.errorMessage': `Error deleting object! {0}`, - 'objectBrowser.deleteObject.progress':'Deleting object {0} {1}...', 'objectBrowser.renameObject.prompt': `Rename object`, 'objectBrowser.renameObject.errorMessage': `Object name must be 10 chars or less.`, 'objectBrowser.renameObject.infoMessage': `Renamed object {0} {1} to {2}.`, diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 99170658c..280cc457e 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -91,7 +91,7 @@ export const fr: Locale = { 'ifsBrowser.uploadStreamfile.select.type.title':'Que souhaitez-vous envoyer?', 'ifsBrowser.deleteIFS.dirNotAllowed': `Impossible de supprimer les répertoires protégés depuis l'Explorateur IFS!\n{0}`, 'ifsBrowser.deleteIFS.warningMessage': `Êtes-vous sûr de vouloir suprimer {0}?`, - 'ifsBrowser.deleteIFS.multi.warningMessage': `Êtes-vous sûr de vouloir suprimer les {0} fichiers sélectionnés?\n{1}`, + 'ifsBrowser.deleteIFS.multi.warningMessage': `Êtes-vous sûr de vouloir suprimer les {0} fichiers sélectionnés?`, 'ifsBrowser.deleteIFS.deletionPrompt': `La suppression du répertoire est irréversible!\nEntrez \"{0}\" pour confirmer la suppression.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Appuyer sur \'Escape\' pour annuler)`, 'ifsBrowser.deleteIFS.infoMessage': `{0} supprimé.`, @@ -141,7 +141,9 @@ export const fr: Locale = { 'LibraryListView.cleanupLibraryList.removedLibs': `Les bibliothèques suivantes ont été retirées de la liste des bibliothèques mise à jour car elles sont invalides: {0}`, 'LibraryListView.cleanupLibraryList.validated': `La liste des bibliothèques a été validée avec succès.`, // objectBrowser: - 'objectBrowser.deleteFilter.infoMessage': `Supprimer le filtre '{0}'?`, + 'objectBrowser.delete.confirm':'Êtes-vous sûr de vouloir supprimer {0}?', + 'objectBrowser.delete.multiple.confirm':'Êtes-vous sûr de vouloir supprimer les éléments sélectionnés?', + 'objectBrowser.delete.progress':'Suppression', 'objectBrowser.createMember.prompt': `Nom du nouveau membre source (membre.ext)`, 'objectBrowser.createMember.progressTitle': `Creation du membre {0}...`, 'objectBrowser.createMember.errorMessage': `Erreur lors de la creation du membre {0}: {1}`, @@ -151,8 +153,6 @@ export const fr: Locale = { 'objectBrowser.copyMember.overwrite': `Êtes-vous sûr de vouloir écraser le membre {0}?`, 'objectBrowser.copyMember.errorMessage2': `Le membre {0} existe déjà!`, 'objectBrowser.copyMember.errorMessage3': `Erreur lors de la création du membre {0}: {1}`, - 'objectBrowser.deleteMember.warningMessage': `Êtes-vous sûr de vouloir supprimer {0}?`, - 'objectBrowser.deleteMember.infoMessage': `Membre {0} supprimé.`, 'objectBrowser.deleteMember.errorMessage': `Erreur lors de la suppression du membre! {0}`, 'objectBrowser.updateMemberText.prompt': `Changer la description du membre {0}, *BLANK pour effacer la description`, 'objectBrowser.updateMemberText.errorMessage': `Erreur lors de la mise à jour du texte du membre! {0}`, @@ -192,9 +192,7 @@ export const fr: Locale = { 'objectBrowser.copyObject.infoMessage': `Objet {0} {1} copié vers {2}.`, 'objectBrowser.copyObject.infoMessage2': `Objet {0} {1} copié vers {2}. Rafraîchissement de l'explorateur d'objet.`, 'objectBrowser.copyObject.errorMessage4': `Erreur lors de la copie de l'objet {0}! {1}`, - 'objectBrowser.deleteObject.infoMessage': `{0} {1} supprimé.`, 'objectBrowser.deleteObject.errorMessage': `Erreur lors de la suppression de l'objet! {0}`, - 'objectBrowser.deleteObject.progress':`Suppression de l'objet {0} {1}...`, 'objectBrowser.renameObject.prompt': `Renamer l'objet`, 'objectBrowser.renameObject.errorMessage': `Le nom de l'objet doit faire au maximum 10 caractères de long.`, 'objectBrowser.renameObject.infoMessage': `Objet {0} {1} renommé {2}.`, diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index fa80fcc21..3d679a7f4 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -53,7 +53,7 @@ function isProtected(filter: ConnectionConfiguration.ObjectFilters) { return filter.protected || getContent().isProtectedPath(filter.library); } -class ObjectBrowserItem extends BrowserItem { +abstract class ObjectBrowserItem extends BrowserItem { constructor(readonly filter: ConnectionConfiguration.ObjectFilters, label: string, params?: BrowserItemParameters) { super(label, params); } @@ -66,7 +66,8 @@ class ObjectBrowserItem extends BrowserItem { return vscode.commands.executeCommand(`code-for-ibmi.revealInObjectBrowser`, this, options); } - delete?(): Promise; + abstract toString(): string; + abstract delete(): Promise; } class ObjectBrowser implements vscode.TreeDataProvider { @@ -184,20 +185,22 @@ class ObjectBrowserFilterItem extends ObjectBrowserItem { } } + toString(): string { + return `${this.filter.name} (filter)`; + } + async delete() { const config = getConfig(); const filter = this.filter; - vscode.window.showWarningMessage(t(`objectBrowser.deleteFilter.infoMessage`, filter.name), { modal: true }, t(`Yes`), t(`No`)).then(async (value) => { - if (value === t(`Yes`)) { - const index = config.objectFilters.findIndex(f => f.name === filter.name); + const index = config.objectFilters.findIndex(f => f.name === filter.name); - if (index > -1) { - config.objectFilters.splice(index, 1); - await ConnectionConfiguration.update(config); - vscode.commands.executeCommand(`code-for-ibmi.refreshObjectBrowser`); - } - } - }); + if (index > -1) { + config.objectFilters.splice(index, 1); + await ConnectionConfiguration.update(config); + + } + + return true; } } @@ -292,10 +295,12 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O } } + toString(): string { + return `${this.path} (${this.object.type})`; + } + async delete() { - if (await deleteObject(this.path, this.object)) { - this.parent?.refresh?.(); - } + return deleteObject(this.object); } } @@ -339,10 +344,12 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { return await listObjects(this, objectFilter); } + toString(): string { + return `${this.path} (${this.object.type})`; + } + async delete() { - if (await deleteObject(this.path, this.object)) { - this.parent?.refresh?.(); - } + return deleteObject(this.object); } } @@ -375,24 +382,24 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { }; } + toString(): string { + return `${this.path} (${this.member.extension})`; + } + async delete() { - const result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteMember.warningMessage`, this.path), { modal: true }, t(`Yes`), t(`Cancel`)); - if (result === t(`Yes`)) { - const connection = getConnection(); - const { library, file, name } = connection.parserMemberPath(this.path); + const connection = getConnection(); + const { library, file, name } = connection.parserMemberPath(this.path); - const removeResult = await connection.runCommand({ - command: `RMVM FILE(${library}/${file}) MBR(${name})`, - noLibList: true - }); + const removeResult = await connection.runCommand({ + command: `RMVM FILE(${library}/${file}) MBR(${name})`, + noLibList: true + }); - if (removeResult.code === 0) { - vscode.window.showInformationMessage(t(`objectBrowser.deleteMember.infoMessage`, this.path)); - this.parent?.refresh?.(); - } else { - vscode.window.showErrorMessage(t(`objectBrowser.deleteMember.errorMessage`, removeResult.stderr)); - } + if (removeResult.code !== 0) { + vscode.window.showErrorMessage(t(`objectBrowser.deleteMember.errorMessage`, removeResult.stderr)); } + + return removeResult.code === 0; } } @@ -402,8 +409,8 @@ class ObjectBrowserMemberItemDragAndDrop implements vscode.TreeDragAndDropContro handleDrag(source: readonly ObjectBrowserMemberItem[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken) { dataTransfer.set(OBJECT_BROWSER_MIMETYPE, new vscode.DataTransferItem(source.filter(item => item.resourceUri?.scheme === `member`) - .map(item => item.resourceUri) - .join(URI_LIST_SEPARATOR))); + .map(item => item.resourceUri) + .join(URI_LIST_SEPARATOR))); } } @@ -501,10 +508,6 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { objectBrowser.refresh(); }), - vscode.commands.registerCommand(`code-for-ibmi.deleteFilter`, async (node: FilteredItem) => { - - }), - vscode.commands.registerCommand(`code-for-ibmi.moveFilterUp`, (node: ObjectBrowserFilterItem) => objectBrowser.moveFilterInList(node, `UP`)), vscode.commands.registerCommand(`code-for-ibmi.moveFilterDown`, (node: ObjectBrowserFilterItem) => objectBrowser.moveFilterInList(node, `DOWN`)), vscode.commands.registerCommand(`code-for-ibmi.moveFilterToTop`, (node: ObjectBrowserFilterItem) => objectBrowser.moveFilterInList(node, `TOP`)), @@ -1120,15 +1123,65 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } } while (newLibrary && !newLibraryOK) }), - vscode.commands.registerCommand("code-for-ibmi.objectBrowser.delete", async (node?: ObjectBrowserItem) => { - if (!node && objectTreeViewer.selection.length) { - const item = objectTreeViewer.selection.at(0); - if (item instanceof ObjectBrowserItem) { - node = item; + vscode.commands.registerCommand("code-for-ibmi.objectBrowser.delete", async (node?: ObjectBrowserItem, nodes?: ObjectBrowserItem[]) => { + const toBeDeleted: ObjectBrowserItem[] = []; + if (nodes) { + toBeDeleted.push(...nodes); + } + else if (node) { + toBeDeleted.push(node); + } + else { + for (const item of objectTreeViewer.selection) { + if (item instanceof ObjectBrowserItem) { + toBeDeleted.push(item); + item.label + } } } - node?.delete?.(); + if (toBeDeleted.length) { + const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm'); + const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(item => `- ${item.toString()}`).join("\n"); + if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { + const increment = Math.round(100 / toBeDeleted.length); + const toRefresh = new Set(); + let refreshBrowser = false; + await vscode.window.withProgress({ title: t("objectBrowser.delete.progress"), location: vscode.ProgressLocation.Notification }, async (task) => { + for (const item of toBeDeleted) { + task.report({ message: item.toString(), increment }); + await item.delete(); + + if(!item.parent){ + //No parent (a filter): the whole browser needs to be refreshed + refreshBrowser = true; + toRefresh.clear(); + } + + if(!refreshBrowser && item.parent){ + //Refresh the element's parent unless its own parent must be refreshed + let parent : BrowserItem | undefined = item.parent; + let found = false + while(!found && parent){ + found = toRefresh.has(parent); + parent = parent.parent + } + + if(!found){ + toRefresh.add(item.parent); + } + } + } + }); + + if(refreshBrowser){ + vscode.commands.executeCommand(`code-for-ibmi.refreshObjectBrowser`); + } + else{ + toRefresh.forEach(item => item.refresh?.()); + } + } + } }) ); } @@ -1266,28 +1319,16 @@ async function listObjects(item: ObjectBrowserFilterItem, filter?: ConnectionCon }); } -async function deleteObject(path: string, object: IBMiObject) { - const result = await vscode.window.showWarningMessage(t(`objectBrowser.deleteObject.warningMessage`, path, object.type.toUpperCase()), { modal: true }, t(`Yes`), t(`Cancel`)); - - if (result === t(`Yes`)) { - const connection = getConnection(); - await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t("objectBrowser.deleteObject.progress", path, object.type.toUpperCase()) } - , async () => { - // TODO: Progress message about deleting! - const deleteResult = await connection.runCommand({ - command: `DLTOBJ OBJ(${path}) OBJTYPE(${object.type})`, - noLibList: true - }); +async function deleteObject(object: IBMiObject) { + const connection = getConnection(); + const deleteResult = await connection.runCommand({ + command: `DLTOBJ OBJ(${object.library}/${object.name}) OBJTYPE(${object.type})`, + noLibList: true + }); - if (deleteResult.code === 0) { - vscode.window.showInformationMessage(t(`objectBrowser.deleteObject.infoMessage`, path, object.type.toUpperCase())); - return true; - } else { - vscode.window.showErrorMessage(t(`objectBrowser.deleteObject.errorMessage`, deleteResult.stderr)); - } - } - ); + if (deleteResult.code !== 0) { + vscode.window.showErrorMessage(t(`objectBrowser.deleteObject.errorMessage`, deleteResult.stderr)); } - return false; + return deleteResult.code === 0; } \ No newline at end of file From 567f7c693f84ef88b34998b1aee88889f75b825f Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Sat, 24 Feb 2024 00:57:07 +0100 Subject: [PATCH 05/14] Prevent deletion of protected items + fixed protected flag computation Signed-off-by: Seb Julliand --- src/views/objectBrowser.ts | 77 ++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 3d679a7f4..aebb9fd74 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -49,10 +49,6 @@ const objectIcons = { '': `circle-large-outline` } -function isProtected(filter: ConnectionConfiguration.ObjectFilters) { - return filter.protected || getContent().isProtectedPath(filter.library); -} - abstract class ObjectBrowserItem extends BrowserItem { constructor(readonly filter: ConnectionConfiguration.ObjectFilters, label: string, params?: BrowserItemParameters) { super(label, params); @@ -68,6 +64,7 @@ abstract class ObjectBrowserItem extends BrowserItem { abstract toString(): string; abstract delete(): Promise; + abstract isProtected(): boolean; } class ObjectBrowser implements vscode.TreeDataProvider { @@ -166,12 +163,16 @@ class CreateFilterItem extends BrowserItem { class ObjectBrowserFilterItem extends ObjectBrowserItem { constructor(filter: ConnectionConfiguration.ObjectFilters) { - super(filter, filter.name, { icon: isProtected(filter) ? `lock-small` : '', state: vscode.TreeItemCollapsibleState.Collapsed }); - this.contextValue = `filter${isProtected(filter) ? `_readonly` : ``}`; + super(filter, filter.name, { icon: filter.protected ? `lock-small` : '', state: vscode.TreeItemCollapsibleState.Collapsed }); + this.contextValue = `filter${this.isProtected() ? `_readonly` : ``}`; this.description = `${filter.library}/${filter.object}/${filter.member}.${filter.memberType || `*`} (${filter.types.join(`, `)})`; this.tooltip = ``; } + isProtected(): boolean { + return this.filter.protected; + } + async getChildren(): Promise { const libraryFilter = parseFilter(this.filter.library); if (libraryFilter.noFilter) { @@ -197,9 +198,9 @@ class ObjectBrowserFilterItem extends ObjectBrowserItem { if (index > -1) { config.objectFilters.splice(index, 1); await ConnectionConfiguration.update(config); - + } - + return true; } } @@ -212,7 +213,7 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O const type = object.type.startsWith(`*`) ? object.type.substring(1) : object.type; super(parent.filter, correctCase(object.name), { parent, icon: `file-directory`, state: vscode.TreeItemCollapsibleState.Collapsed }); - this.contextValue = `SPF${isProtected(this.filter) ? `_readonly` : ``}`; + this.contextValue = `SPF${this.isProtected() ? `_readonly` : ``}`; this.updateDescription(); this.path = [object.library, object.name].join(`/`); @@ -230,6 +231,10 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O }); } + isProtected(): boolean { + return this.filter.protected || getContent().isProtectedPath(this.object.library); + } + sortBy(sort: SortOptions) { if (this.sort.order !== sort.order) { this.sort.order = sort.order; @@ -316,7 +321,7 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { this.path = [object.library, object.name].join(`/`); this.updateDescription(); - this.contextValue = `object.${type.toLowerCase()}${object.attribute ? `.${object.attribute}` : ``}${isProtected(this.filter) ? `_readonly` : ``}`; + this.contextValue = `object.${type.toLowerCase()}${object.attribute ? `.${object.attribute}` : ``}${this.isProtected() ? `_readonly` : ``}`; this.tooltip = new vscode.MarkdownString(``); this.resourceUri = vscode.Uri.from({ @@ -334,6 +339,10 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { } } + isProtected(): boolean { + return this.filter.protected || getContent().isProtectedPath(this.object.library); + } + updateDescription() { this.description = this.object.text.trim() + (this.object.attribute ? ` (${this.object.attribute})` : ``); } @@ -356,9 +365,9 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { readonly path: string; readonly sortBy: (sort: SortOptions) => void; - + readonly readonly: boolean; constructor(parent: ObjectBrowserSourcePhysicalFileItem, readonly member: IBMiMember, writable: boolean) { - const readonly = isProtected(parent.filter) || !writable; + const readonly = !writable || parent.isProtected(); super(parent.filter, correctCase(`${member.name}.${member.extension}`), { icon: readonly ? `lock-small` : "", parent }); this.contextValue = `member${readonly ? `_readonly` : ``}`; this.description = member.text; @@ -380,6 +389,12 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { title: `Open Member`, arguments: [this, (readonly ? "browse" : undefined) as DefaultOpenMode] }; + + this.readonly = readonly; + } + + isProtected(): boolean { + return this.readonly; } toString(): string { @@ -1124,22 +1139,18 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } while (newLibrary && !newLibraryOK) }), vscode.commands.registerCommand("code-for-ibmi.objectBrowser.delete", async (node?: ObjectBrowserItem, nodes?: ObjectBrowserItem[]) => { - const toBeDeleted: ObjectBrowserItem[] = []; + const candidates: ObjectBrowserItem[] = []; if (nodes) { - toBeDeleted.push(...nodes); + candidates.push(...nodes); } else if (node) { - toBeDeleted.push(node); + candidates.push(node); } else { - for (const item of objectTreeViewer.selection) { - if (item instanceof ObjectBrowserItem) { - toBeDeleted.push(item); - item.label - } - } + candidates.push(...objectTreeViewer.selection.filter(i => i instanceof ObjectBrowserItem) as ObjectBrowserItem[]); } + const toBeDeleted = candidates.filter(item => !item.isProtected()); if (toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm'); const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(item => `- ${item.toString()}`).join("\n"); @@ -1147,39 +1158,39 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { const increment = Math.round(100 / toBeDeleted.length); const toRefresh = new Set(); let refreshBrowser = false; - await vscode.window.withProgress({ title: t("objectBrowser.delete.progress"), location: vscode.ProgressLocation.Notification }, async (task) => { + await vscode.window.withProgress({ title: t("objectBrowser.delete.progress"), location: vscode.ProgressLocation.Notification }, async (task) => { for (const item of toBeDeleted) { task.report({ message: item.toString(), increment }); await item.delete(); - - if(!item.parent){ + + if (!item.parent) { //No parent (a filter): the whole browser needs to be refreshed refreshBrowser = true; toRefresh.clear(); } - if(!refreshBrowser && item.parent){ + if (!refreshBrowser && item.parent) { //Refresh the element's parent unless its own parent must be refreshed - let parent : BrowserItem | undefined = item.parent; + let parent: BrowserItem | undefined = item.parent; let found = false - while(!found && parent){ + while (!found && parent) { found = toRefresh.has(parent); parent = parent.parent } - if(!found){ + if (!found) { toRefresh.add(item.parent); } } - } + } }); - if(refreshBrowser){ + if (refreshBrowser) { vscode.commands.executeCommand(`code-for-ibmi.refreshObjectBrowser`); } - else{ + else { toRefresh.forEach(item => item.refresh?.()); - } + } } } }) @@ -1265,7 +1276,7 @@ async function doSearchInSourceFile(searchTerm: string, path: string, filter: Co } }, timeoutInternal); - let results = await Search.searchMembers(instance, pathParts[0], pathParts[1], `${filter?.member || `*`}.MBR`, searchTerm, filter ? isProtected(filter) : false); + let results = await Search.searchMembers(instance, pathParts[0], pathParts[1], `${filter?.member || `*`}.MBR`, searchTerm, filter?.protected || content.isProtectedPath(pathParts[0])); // Filter search result by member type filter. if (results.length > 0 && filter?.member) { From e7a5c05f2aafd9ba9eb264656b0305bea4be68b9 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 24 Feb 2024 12:31:42 +0100 Subject: [PATCH 06/14] Translate new texts to Danish --- src/locale/ids/da.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 1ba4bd95f..75b81b3ea 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -55,7 +55,7 @@ export const da: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `Ingen parametre til forbindelsen '{0}' blev fundet`, 'connectionBrowser.renameConnection.error': `Fejl ved omdøbning af forbindelsen '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Er du sikker på at du vil slette forbindelsen '{0}'?`, - 'connectionBrowser.deleteConnection.multiple.warning': `Er du sikker på at du vil disse forbindelser?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Er du sikker på at du vil slette disse forbindelser?`, 'connectionBrowser.ServerItem.tooltip': ` (forrige forbindelse)`, 'connectionBrowser.ServerItem.title': `Forbind`, // helpView: @@ -141,9 +141,9 @@ export const da: Locale = { 'LibraryListView.cleanupLibraryList.removedLibs': `De følgende biblioteker blev fjernet fra den opdaterede biblioteksliste, da de ikke er gyldige: {0}`, 'LibraryListView.cleanupLibraryList.validated': `Bibliotekslisten blev valideret uden fejl.`, // objectBrowser: - 'objectBrowser.delete.confirm':'Are you sure you want to delete {0}?', - 'objectBrowser.delete.multiple.confirm':'Are you sure you want to delete the selected elements?', - 'objectBrowser.delete.progress':'Deleting', + 'objectBrowser.delete.confirm':'Er du sikker på at du vil slette {0}?', + 'objectBrowser.delete.multiple.confirm':'Er du sikker på at du vil slette de valgte elementer?', + 'objectBrowser.delete.progress':'Sletter', 'objectBrowser.createMember.prompt': `Navn på nyt member (member.ext)`, 'objectBrowser.createMember.progressTitle': `Opretter member {0}...`, 'objectBrowser.createMember.errorMessage': `Fejl ved oprettelse af member {0}: {1}`, From 85c0194322efe246ba0aa3a06efb7bca06788add Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Sun, 25 Feb 2024 14:38:13 +0100 Subject: [PATCH 07/14] Show the number of elements about to be deleted in confirm dialog Signed-off-by: Seb Julliand --- src/locale/ids/da.ts | 4 ++-- src/locale/ids/en.ts | 4 ++-- src/locale/ids/fr.ts | 4 ++-- src/views/ConnectionBrowser.ts | 40 +++++++++++++++++----------------- src/views/objectBrowser.ts | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 75b81b3ea..c67287f1a 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -55,7 +55,7 @@ export const da: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `Ingen parametre til forbindelsen '{0}' blev fundet`, 'connectionBrowser.renameConnection.error': `Fejl ved omdøbning af forbindelsen '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Er du sikker på at du vil slette forbindelsen '{0}'?`, - 'connectionBrowser.deleteConnection.multiple.warning': `Er du sikker på at du vil slette disse forbindelser?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Er du sikker på at du vil slette disse {0} forbindelser?`, 'connectionBrowser.ServerItem.tooltip': ` (forrige forbindelse)`, 'connectionBrowser.ServerItem.title': `Forbind`, // helpView: @@ -142,7 +142,7 @@ export const da: Locale = { 'LibraryListView.cleanupLibraryList.validated': `Bibliotekslisten blev valideret uden fejl.`, // objectBrowser: 'objectBrowser.delete.confirm':'Er du sikker på at du vil slette {0}?', - 'objectBrowser.delete.multiple.confirm':'Er du sikker på at du vil slette de valgte elementer?', + 'objectBrowser.delete.multiple.confirm':'Er du sikker på at du vil slette de {0} valgte elementer?', 'objectBrowser.delete.progress':'Sletter', 'objectBrowser.createMember.prompt': `Navn på nyt member (member.ext)`, 'objectBrowser.createMember.progressTitle': `Opretter member {0}...`, diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index fba2e2166..6a8439a3e 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -55,7 +55,7 @@ export const en: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `No parameters for connection '{0}' was found`, 'connectionBrowser.renameConnection.error': `Error renaming connection '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Are you sure you want to delete the connection '{0}'?`, - 'connectionBrowser.deleteConnection.multiple.warning': `Are you sure you want to delete these connections?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Are you sure you want to delete these {0} connections?`, 'connectionBrowser.ServerItem.tooltip': ` (previous connection)`, 'connectionBrowser.ServerItem.title': `Connect`, // helpView: @@ -142,7 +142,7 @@ export const en: Locale = { 'LibraryListView.cleanupLibraryList.validated': `Library list were validated without any errors.`, // objectBrowser: 'objectBrowser.delete.confirm':'Are you sure you want to delete {0}?', - 'objectBrowser.delete.multiple.confirm':'Are you sure you want to delete the selected elements?', + 'objectBrowser.delete.multiple.confirm':'Are you sure you want to delete these {0} elements?', 'objectBrowser.delete.progress':'Deleting', 'objectBrowser.createMember.prompt': `Name of new source member (member.ext)`, 'objectBrowser.createMember.progressTitle': `Creating member {0}...`, diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 280cc457e..bc183fefc 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -55,7 +55,7 @@ export const fr: Locale = { 'connectionBrowser.renameConnection.noConnParmsFound': `Aucun paramètre pour la connexion '{0}' n'a été trouvé`, 'connectionBrowser.renameConnection.error': `Erreur lors du renommage de la connexion '{0}'! {1}`, 'connectionBrowser.deleteConnection.warning': `Êtes vous sûr de vouloir supprimer la connexion '{0}'?`, - 'connectionBrowser.deleteConnection.multiple.warning': `Êtes vous sûr de vouloir supprimer ces connexions?`, + 'connectionBrowser.deleteConnection.multiple.warning': `Êtes vous sûr de vouloir supprimer ces {0} connexions?`, 'connectionBrowser.ServerItem.tooltip': ` (précédente connexion)`, 'connectionBrowser.ServerItem.title': `Se Connecter`, // helpView: @@ -142,7 +142,7 @@ export const fr: Locale = { 'LibraryListView.cleanupLibraryList.validated': `La liste des bibliothèques a été validée avec succès.`, // objectBrowser: 'objectBrowser.delete.confirm':'Êtes-vous sûr de vouloir supprimer {0}?', - 'objectBrowser.delete.multiple.confirm':'Êtes-vous sûr de vouloir supprimer les éléments sélectionnés?', + 'objectBrowser.delete.multiple.confirm':'Êtes-vous sûr de vouloir supprimer les {0} éléments sélectionnés?', 'objectBrowser.delete.progress':'Suppression', 'objectBrowser.createMember.prompt': `Nom du nouveau membre source (membre.ext)`, 'objectBrowser.createMember.progressTitle': `Creation du membre {0}...`, diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 4744289d0..942537c17 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -149,29 +149,29 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { } if (!connectionBrowser.attemptingConnection && toBeDeleted.length) { - const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`); + const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`, toBeDeleted.length); const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(server => `- ${server.name}`).join("\n"); - if(await vscode.window.showWarningMessage( message, { modal: true, detail }, t(`Yes`))){ - for (const server of toBeDeleted) { - // First remove the connection details - const connections = GlobalConfiguration.get(`connections`) || []; - const newConnections = connections.filter(connection => connection.name !== server.name); - await GlobalConfiguration.set(`connections`, newConnections); - - // Also remove the connection settings - const connectionSettings = GlobalConfiguration.get(`connectionSettings`) || []; - const newConnectionSettings = connectionSettings.filter(connection => connection.name !== server.name); - await GlobalConfiguration.set(`connectionSettings`, newConnectionSettings); - - // Also remove the cached connection settings - GlobalStorage.get().deleteServerSettingsCache(server.name); + if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { + for (const server of toBeDeleted) { + // First remove the connection details + const connections = GlobalConfiguration.get(`connections`) || []; + const newConnections = connections.filter(connection => connection.name !== server.name); + await GlobalConfiguration.set(`connections`, newConnections); - // Then remove the password - await context.secrets.delete(`${server.name}_password`); - } + // Also remove the connection settings + const connectionSettings = GlobalConfiguration.get(`connectionSettings`) || []; + const newConnectionSettings = connectionSettings.filter(connection => connection.name !== server.name); + await GlobalConfiguration.set(`connectionSettings`, newConnectionSettings); - connectionBrowser.refresh(); - } + // Also remove the cached connection settings + GlobalStorage.get().deleteServerSettingsCache(server.name); + + // Then remove the password + await context.secrets.delete(`${server.name}_password`); + } + + connectionBrowser.refresh(); + } } }) ); diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index aebb9fd74..329a71c7e 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1152,7 +1152,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { const toBeDeleted = candidates.filter(item => !item.isProtected()); if (toBeDeleted.length) { - const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm'); + const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm', toBeDeleted.length); const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(item => `- ${item.toString()}`).join("\n"); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { const increment = Math.round(100 / toBeDeleted.length); From 9e0a90ae330f0177273e8eeea4f1a73e8424a9d0 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 09:13:42 +0100 Subject: [PATCH 08/14] Fixed wrong isProtected call Signed-off-by: Seb Julliand --- src/views/objectBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 7f428236e..c95364de9 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -321,7 +321,7 @@ class ObjectBrowserObjectItem extends ObjectBrowserItem implements ObjectItem { this.path = [object.library, object.name].join(`/`); this.updateDescription(); - this.contextValue = `object.${type.toLowerCase()}${object.attribute ? `.${object.attribute}` : ``}${isProtected(this.filter) ? `_readonly` : ``}`; + this.contextValue = `object.${type.toLowerCase()}${object.attribute ? `.${object.attribute}` : ``}${this.isProtected() ? `_readonly` : ``}`; this.tooltip = new vscode.MarkdownString(Tools.generateTooltipHtmlTable(this.path, { type: object.type, attribute: object.attribute, From d0ac8ec5b7205892bdef0a4eca7d8a4285de440d Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 10:12:15 +0100 Subject: [PATCH 09/14] Optimized IFS delete process Signed-off-by: Seb Julliand --- src/locale/ids/da.ts | 2 +- src/locale/ids/en.ts | 2 +- src/locale/ids/fr.ts | 2 +- src/views/ifsBrowser.ts | 70 ++++++++++++++++++++++++++--------------- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index ac4a4aba8..8865c857c 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -97,7 +97,7 @@ export const da: Locale = { 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?`, 'ifsBrowser.deleteIFS.deletionPrompt': `Når du har slettet mappen, kan den ikke retableres.\nVenligst tast \"{0}\" for at bekræfte sletning.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Tryk \'Escape\' for at afbryde)`, - 'ifsBrowser.deleteIFS.infoMessage': `Slettede {0}.`, + 'ifsBrowser.deleteIFS.progress': `Deleting {0} element(s)...`, 'ifsBrowser.deleteIFS.errorMessage': `Fejl ved sletning af streamfile! {0}`, 'ifsBrowser.deleteIFS.cancelled': `Sletning afbrudt.`, 'ifsBrowser.deleteIFS.default.home.dir':'{0} var den aktuelle mappe; det er nu {1}.', diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index 14145de0e..86ef8f0cd 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -97,7 +97,7 @@ export const en: Locale = { 'ifsBrowser.deleteIFS.multi.warningMessage': `Are you sure you want to delete the {0} selected files?`, 'ifsBrowser.deleteIFS.deletionPrompt': `Once you delete the directory, it cannot be restored.\nPlease type \"{0}\" to confirm deletion.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Press \'Escape\' to cancel)`, - 'ifsBrowser.deleteIFS.infoMessage': `Deleted {0}.`, + 'ifsBrowser.deleteIFS.progress': `Deleting {0} element(s)...`, 'ifsBrowser.deleteIFS.errorMessage': `Error deleting streamfile! {0}`, 'ifsBrowser.deleteIFS.cancelled': `Deletion canceled.`, 'ifsBrowser.deleteIFS.default.home.dir':'{0} was the working directory; it is now {1}.', diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 0521af4c8..87d90a3e3 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -97,7 +97,7 @@ export const fr: Locale = { 'ifsBrowser.deleteIFS.multi.warningMessage': `Êtes-vous sûr de vouloir suprimer les {0} fichiers sélectionnés?`, 'ifsBrowser.deleteIFS.deletionPrompt': `La suppression du répertoire est irréversible!\nEntrez \"{0}\" pour confirmer la suppression.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Appuyer sur \'Escape\' pour annuler)`, - 'ifsBrowser.deleteIFS.infoMessage': `{0} supprimé.`, + 'ifsBrowser.deleteIFS.progress': `Suppression de {0} élément(s)...`, 'ifsBrowser.deleteIFS.errorMessage': `Erreur lors de la suppression du fichier! {0}`, 'ifsBrowser.deleteIFS.cancelled': `Suppression annulée.`, 'ifsBrowser.deleteIFS.default.home.dir':'{0} était le répertoire de travail; c\'est désormais {1}.', diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 97b59d5b4..f48c7db17 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -100,13 +100,13 @@ class IFSItem extends BrowserItem implements WithPath { constructor(readonly file: IFSFile, parameters: BrowserItemParameters) { super(file.name, parameters); this.path = file.path; - this.tooltip = new vscode.MarkdownString(Tools.generateTooltipHtmlTable(this.path, { + this.tooltip = new vscode.MarkdownString(Tools.generateTooltipHtmlTable(this.path, { size: file.size, modified: file.modified ? new Date(file.modified.getTime() - file.modified.getTimezoneOffset() * 60 * 1000).toISOString().slice(0, 19).replace(`T`, ` `) : ``, owner: file.owner ? file.owner.toUpperCase() : `` })); this.tooltip.supportHtml = true; - } + } sortBy(sort: SortOptions) { if (this.sort.order !== sort.order) { @@ -216,8 +216,8 @@ class IFSBrowserDragAndDrop implements vscode.TreeDragAndDropController if (ifsBrowserItems) { this.moveOrCopyItems(ifsBrowserItems.value as IFSItem[], toDirectory) } else if (objectBrowserItems) { - const memberUris = (await objectBrowserItems.asString()).split(URI_LIST_SEPARATOR).map(uri => vscode.Uri.parse(uri)); - this.copyMembers(memberUris, toDirectory) + const memberUris = (await objectBrowserItems.asString()).split(URI_LIST_SEPARATOR).map(uri => vscode.Uri.parse(uri)); + this.copyMembers(memberUris, toDirectory) } else { const explorerItems = dataTransfer.get(URI_LIST_MIMETYPE); @@ -286,7 +286,7 @@ class IFSBrowserDragAndDrop implements vscode.TreeDragAndDropController noLibList: true }); if (result.code !== 0) { - throw(t(`ifsBrowser.copyToStreamfile.failed`, toDirectory.path, result!.stderr)); + throw (t(`ifsBrowser.copyToStreamfile.failed`, toDirectory.path, result!.stderr)); } }; @@ -547,19 +547,21 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { const config = instance.getConfig(); if (connection && config) { if (items || singleItem) { - items = items || [singleItem] + items = (items || [singleItem]).filter(reduceIFSPath); } else { - items = ifsTreeViewer.selection.filter(selected => selected instanceof IFSItem) as IFSItem[]; + items = (ifsTreeViewer.selection.filter(selected => selected instanceof IFSItem) as IFSItem[]).filter(reduceIFSPath); } + if (items && items.length) { if (!items.find(n => isProtected(n.path))) { let deletionConfirmed = false; const message = items.length === 1 ? t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path) : t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length); const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { + const toBeDeleted: string[] = []; for (const item of items) { - if ((GlobalConfiguration.get(`safeDeleteMode`)) && item.contextValue === `directory`) { //Check if path is directory + if ((GlobalConfiguration.get(`safeDeleteMode`)) && item.file.type === `directory`) { //Check if path is directory const dirName = path.basename(item.path) //Get the name of the directory to be deleted const deletionPrompt = t(`ifsBrowser.deleteIFS.deletionPrompt`, dirName); @@ -574,32 +576,34 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { } else { // If deleting a file rather than a directory, skip the name entry + // Do not delete a file if one of its parent directory is going to be deleted deletionConfirmed = true; } if (deletionConfirmed) { - try { - const removeResult = await connection.sendCommand({ command: `rm -rf ${Tools.escapePath(item.path)}` }) - if (removeResult.code === 0) { - vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.infoMessage`, item.path)); - } - else { - throw removeResult.stderr; - } - } catch (e) { - vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.errorMessage`, e)); - } - } - else { - vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.cancelled`)); + toBeDeleted.push(item.path); } } - if (GlobalConfiguration.get(`autoRefresh`)) { - items.map(item => item.parent) - .filter(Tools.distinct) - .forEach(async parent => parent?.refresh?.()); + + try { + const removeResult = await vscode.window.withProgress({ title: t('ifsBrowser.deleteIFS.progress', toBeDeleted.length), location: vscode.ProgressLocation.Notification }, async () => { + return await connection.sendCommand({ command: `rm -rf ${toBeDeleted.map(path => Tools.escapePath(path)).join(" ")}` }); + }); + if (removeResult.code !== 0) { + throw removeResult.stderr; + } + if (GlobalConfiguration.get(`autoRefresh`)) { + items.map(item => item.parent) + .filter(Tools.distinct) + .forEach(async parent => parent?.refresh?.()); + } + } catch (e) { + vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.errorMessage`, e)); } } + else { + vscode.window.showInformationMessage(t(`ifsBrowser.deleteIFS.cancelled`)); + } } else { vscode.window.showErrorMessage(t(`ifsBrowser.deleteIFS.dirNotAllowed`, items.filter(n => isProtected(n.path)).map(n => n.path).join(`\n`))); @@ -813,4 +817,18 @@ async function showOpenDialog() { } }) } +} + +/** + * Filters the content of an IFSItem array to keep only: + * - Folders + * - Files whose parent folders are not in the array + */ +function reduceIFSPath(item: IFSItem, index: number, array: IFSItem[]) { + if (item.file.type === "directory") { + return true; + } + else { + return !array.filter(i => i.file.type === "directory").some(folder => item.file.path.startsWith(folder.file.path)); + } } \ No newline at end of file From ad724552096ea36d788e31f7211f64a4d1e95946 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 16:37:01 +0100 Subject: [PATCH 10/14] Updated danish translation Signed-off-by: Seb Julliand --- src/locale/ids/da.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 8865c857c..3267f5d8e 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -97,7 +97,7 @@ export const da: Locale = { 'ifsBrowser.deleteIFS.multi.warningMessage': `Er du sikker på at du vil slette de {0} valgte filer?`, 'ifsBrowser.deleteIFS.deletionPrompt': `Når du har slettet mappen, kan den ikke retableres.\nVenligst tast \"{0}\" for at bekræfte sletning.`, 'ifsBrowser.deleteIFS.deletionPrompt2': ` (Tryk \'Escape\' for at afbryde)`, - 'ifsBrowser.deleteIFS.progress': `Deleting {0} element(s)...`, + 'ifsBrowser.deleteIFS.progress': `Sletning af {0} element(er)...`, 'ifsBrowser.deleteIFS.errorMessage': `Fejl ved sletning af streamfile! {0}`, 'ifsBrowser.deleteIFS.cancelled': `Sletning afbrudt.`, 'ifsBrowser.deleteIFS.default.home.dir':'{0} var den aktuelle mappe; det er nu {1}.', From 0e07f4ae3bc2cd3220797f2303351030c08a350e Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 19:04:10 +0100 Subject: [PATCH 11/14] Fixed Member item's toString() Signed-off-by: Seb Julliand --- src/views/objectBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index c95364de9..09ff5cf45 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -408,7 +408,7 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { } toString(): string { - return `${this.path} (${this.member.extension})`; + return this.path; } async delete() { From ef18e83e4f7ebfcbaaaf7b0a378da345bbd7b818 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 21:54:16 +0100 Subject: [PATCH 12/14] Limit the number of elements shown in deletion confirm dialog Signed-off-by: Seb Julliand --- src/api/Tools.ts | 15 ++++++++++++--- src/locale/ids/da.ts | 1 + src/locale/ids/en.ts | 1 + src/locale/ids/fr.ts | 1 + src/views/ConnectionBrowser.ts | 3 ++- src/views/ifsBrowser.ts | 2 +- src/views/objectBrowser.ts | 2 +- 7 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/api/Tools.ts b/src/api/Tools.ts index 2e0578846..f237f6f27 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -2,9 +2,9 @@ import Crypto from 'crypto'; import { readFileSync } from "fs"; import path from "path"; import vscode from "vscode"; +import { t } from "../locale"; import { IBMiMessage, IBMiMessages, QsysPath } from '../typings'; import { API, GitExtension } from "./import/git"; -import { t } from "../locale"; export namespace Tools { export class SqlError extends Error { @@ -304,10 +304,19 @@ export namespace Tools { ).join("\n"); } - export function generateTooltipHtmlTable(header:string, rows: Record){ + export function generateTooltipHtmlTable(header: string, rows: Record) { return `` .concat(`${header ? `${header}` : ``}`) .concat(`${Object.entries(rows).map(([key, value]) => ``).join(``)}`) .concat(`
${t(key)}: ${value}
`); - } + } + + export function listAndTruncate(list: string[], max: number) { + return [ + ...list.slice(0, max), + ...(list.length > max ? [t('and.x.more', list.length - max)] : []) + ] + .map(item => `- ${item}`) + .join("\n"); + } } \ No newline at end of file diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 3267f5d8e..1ac8a63ec 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -33,6 +33,7 @@ export const da: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Oprettet af', + 'and.x.more':'Og {0} mere...', // Sandbox: 'sandbox.input.user.title': `Bruger for server`, 'sandbox.input.user.prompt': `Indtast brugernavn for {0}`, diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index 86ef8f0cd..a3336ddb8 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -33,6 +33,7 @@ export const en: Locale = { 'type': 'Type', 'attribute': 'Attribute', 'created_by': 'Created by', + 'and.x.more':'And {0} more...', // Sandbox: 'sandbox.input.user.title': `User for server`, 'sandbox.input.user.prompt': `Enter username for {0}`, diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 87d90a3e3..50599eaa0 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -33,6 +33,7 @@ export const fr: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Créé par', + 'and.x.more':'Et {0} de plus...', // Sandbox: 'sandbox.input.user.title': `Nom d'utilisateur`, 'sandbox.input.user.prompt': `Entrez le nom d'utilisateur pour {0}`, diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 942537c17..2a64174f9 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -3,6 +3,7 @@ import { ConnectionData, Server } from '../typings'; import { ConnectionConfiguration, GlobalConfiguration } from '../api/Configuration'; import { GlobalStorage } from '../api/Storage'; +import { Tools } from '../api/Tools'; import { instance } from '../instantiate'; import { t } from "../locale"; import { Login } from '../webviews/login'; @@ -150,7 +151,7 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { if (!connectionBrowser.attemptingConnection && toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`, toBeDeleted.length); - const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(server => `- ${server.name}`).join("\n"); + const detail = toBeDeleted.length === 1 ? undefined : Tools.listAndTruncate(toBeDeleted.map(server => server.name), 10); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { for (const server of toBeDeleted) { // First remove the connection details diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index f48c7db17..524f487f2 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -557,7 +557,7 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { if (!items.find(n => isProtected(n.path))) { let deletionConfirmed = false; const message = items.length === 1 ? t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path) : t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length); - const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); + const detail = items.length === 1 ? undefined : Tools.listAndTruncate(items.map(item => item.path), 10); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { const toBeDeleted: string[] = []; for (const item of items) { diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 09ff5cf45..46a8fc9a4 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1163,7 +1163,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { const toBeDeleted = candidates.filter(item => !item.isProtected()); if (toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm', toBeDeleted.length); - const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(item => `- ${item.toString()}`).join("\n"); + const detail = toBeDeleted.length === 1 ? undefined : Tools.listAndTruncate(toBeDeleted.map(item => item.toString()), 10); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { const increment = Math.round(100 / toBeDeleted.length); const toRefresh = new Set(); From 904121b04205dea4a21669d0048c998082664c66 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 21:54:43 +0100 Subject: [PATCH 13/14] Do not round deletion progress increment Signed-off-by: Seb Julliand --- src/views/objectBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 46a8fc9a4..6b9e68f2e 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1165,7 +1165,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm', toBeDeleted.length); const detail = toBeDeleted.length === 1 ? undefined : Tools.listAndTruncate(toBeDeleted.map(item => item.toString()), 10); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { - const increment = Math.round(100 / toBeDeleted.length); + const increment = 100 / toBeDeleted.length; const toRefresh = new Set(); let refreshBrowser = false; await vscode.window.withProgress({ title: t("objectBrowser.delete.progress"), location: vscode.ProgressLocation.Notification }, async (task) => { From 5c8f58783f39c4ad0f184303d52da91629a193e2 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Tue, 27 Feb 2024 22:06:07 +0100 Subject: [PATCH 14/14] Revert "Limit the number of elements shown in deletion confirm dialog" This reverts commit ef18e83e4f7ebfcbaaaf7b0a378da345bbd7b818. --- src/api/Tools.ts | 15 +++------------ src/locale/ids/da.ts | 1 - src/locale/ids/en.ts | 1 - src/locale/ids/fr.ts | 1 - src/views/ConnectionBrowser.ts | 3 +-- src/views/ifsBrowser.ts | 2 +- src/views/objectBrowser.ts | 2 +- 7 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/api/Tools.ts b/src/api/Tools.ts index f237f6f27..2e0578846 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -2,9 +2,9 @@ import Crypto from 'crypto'; import { readFileSync } from "fs"; import path from "path"; import vscode from "vscode"; -import { t } from "../locale"; import { IBMiMessage, IBMiMessages, QsysPath } from '../typings'; import { API, GitExtension } from "./import/git"; +import { t } from "../locale"; export namespace Tools { export class SqlError extends Error { @@ -304,19 +304,10 @@ export namespace Tools { ).join("\n"); } - export function generateTooltipHtmlTable(header: string, rows: Record) { + export function generateTooltipHtmlTable(header:string, rows: Record){ return `` .concat(`${header ? `${header}` : ``}`) .concat(`${Object.entries(rows).map(([key, value]) => ``).join(``)}`) .concat(`
${t(key)}: ${value}
`); - } - - export function listAndTruncate(list: string[], max: number) { - return [ - ...list.slice(0, max), - ...(list.length > max ? [t('and.x.more', list.length - max)] : []) - ] - .map(item => `- ${item}`) - .join("\n"); - } + } } \ No newline at end of file diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index 1ac8a63ec..3267f5d8e 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -33,7 +33,6 @@ export const da: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Oprettet af', - 'and.x.more':'Og {0} mere...', // Sandbox: 'sandbox.input.user.title': `Bruger for server`, 'sandbox.input.user.prompt': `Indtast brugernavn for {0}`, diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index a3336ddb8..86ef8f0cd 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -33,7 +33,6 @@ export const en: Locale = { 'type': 'Type', 'attribute': 'Attribute', 'created_by': 'Created by', - 'and.x.more':'And {0} more...', // Sandbox: 'sandbox.input.user.title': `User for server`, 'sandbox.input.user.prompt': `Enter username for {0}`, diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 50599eaa0..87d90a3e3 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -33,7 +33,6 @@ export const fr: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Créé par', - 'and.x.more':'Et {0} de plus...', // Sandbox: 'sandbox.input.user.title': `Nom d'utilisateur`, 'sandbox.input.user.prompt': `Entrez le nom d'utilisateur pour {0}`, diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 2a64174f9..942537c17 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -3,7 +3,6 @@ import { ConnectionData, Server } from '../typings'; import { ConnectionConfiguration, GlobalConfiguration } from '../api/Configuration'; import { GlobalStorage } from '../api/Storage'; -import { Tools } from '../api/Tools'; import { instance } from '../instantiate'; import { t } from "../locale"; import { Login } from '../webviews/login'; @@ -151,7 +150,7 @@ export function initializeConnectionBrowser(context: vscode.ExtensionContext) { if (!connectionBrowser.attemptingConnection && toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t(`connectionBrowser.deleteConnection.warning`, toBeDeleted[0].name) : t(`connectionBrowser.deleteConnection.multiple.warning`, toBeDeleted.length); - const detail = toBeDeleted.length === 1 ? undefined : Tools.listAndTruncate(toBeDeleted.map(server => server.name), 10); + const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(server => `- ${server.name}`).join("\n"); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { for (const server of toBeDeleted) { // First remove the connection details diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 524f487f2..f48c7db17 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -557,7 +557,7 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { if (!items.find(n => isProtected(n.path))) { let deletionConfirmed = false; const message = items.length === 1 ? t(`ifsBrowser.deleteIFS.warningMessage`, items[0].path) : t(`ifsBrowser.deleteIFS.multi.warningMessage`, items.length); - const detail = items.length === 1 ? undefined : Tools.listAndTruncate(items.map(item => item.path), 10); + const detail = items.length === 1 ? undefined : items.map(i => `- ${i.path}`).join("\n"); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { const toBeDeleted: string[] = []; for (const item of items) { diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 6b9e68f2e..3765b5baf 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1163,7 +1163,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { const toBeDeleted = candidates.filter(item => !item.isProtected()); if (toBeDeleted.length) { const message = toBeDeleted.length === 1 ? t('objectBrowser.delete.confirm', toBeDeleted[0].toString()) : t('objectBrowser.delete.multiple.confirm', toBeDeleted.length); - const detail = toBeDeleted.length === 1 ? undefined : Tools.listAndTruncate(toBeDeleted.map(item => item.toString()), 10); + const detail = toBeDeleted.length === 1 ? undefined : toBeDeleted.map(item => `- ${item.toString()}`).join("\n"); if (await vscode.window.showWarningMessage(message, { modal: true, detail }, t(`Yes`))) { const increment = 100 / toBeDeleted.length; const toRefresh = new Set();