diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index 2b5ff142d3..1b3f12ce94 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ### New features and enhancements - Update Zowe SDKs to `8.8.4` to get the latest enhancements from Imperative and the z/OS Files SDK. [#3306](https://github.com/zowe/zowe-explorer-vscode/pull/3306) +- Added individual user settings for MVS, TSO, and Unix commands. [#3079](https://github.com/zowe/zowe-explorer-vscode/pull/3079) - Added new `searchDataSets` API to provide the ability to search all data sets and PDS members that match a pattern for a string. [#3306](https://github.com/zowe/zowe-explorer-vscode/pull/3306) - Added support for extenders to obtain an updated Session that will includes VS Code proxy settings values if set, `getProfileSessionWithVscProxy`. [#3010](https://github.com/zowe/zowe-explorer-vscode/issues/3010) - Added support for VS Code proxy settings with zosmf profile types. [#3010](https://github.com/zowe/zowe-explorer-vscode/issues/3010) diff --git a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts index 2c17a08d0f..f57eaba031 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts @@ -66,13 +66,17 @@ async function expectUnixCommandApiWithSshSession( sshobj: zosuss.SshSession ): Promise { spy.mockClear().mockResolvedValue(undefined); - spy.mockImplementation((sshobject: zosuss.SshSession, command: string, cwd: string, callback: (data: string) => void) => { - callback("test"); - }); + spy.mockImplementation( + (_sshobject: zosuss.SshSession, _command: string, _cwd: string, callback: (data: string) => void, _cleanStdout?: boolean) => { + callback("test"); + } + ); const spywhenpathnotspecified = jest.spyOn(zosuss.Shell, "executeSsh"); - spywhenpathnotspecified.mockImplementation((sshobject: zosuss.SshSession, command: string, callback: (data: string) => void) => { - callback("test"); - }); + spywhenpathnotspecified.mockImplementation( + (_sshobject: zosuss.SshSession, _command: string, callback: (data: string) => void, _cleanStdout?: boolean) => { + callback("test"); + } + ); await apiInstance[name as string](sshobj, ...args, true, () => {}); await apiInstance[name as string](sshobj, ...args, false, () => {}); expect(spy).toHaveBeenCalled(); @@ -671,6 +675,12 @@ describe("ZosmfCommandApi", () => { args: ["command", { account: "ACCT#" }], transform: (args) => [args[1].account, ...args], }, + { + name: "issueTsoCmdWithParms", + spy: jest.spyOn(zostso.IssueTso, "issueTsoCmd"), + args: ["command"], + transform: (args) => [...args, { addressSpaceOptions: undefined }], + }, { name: "issueMvsCommand", spy: jest.spyOn(zosconsole.IssueCommand, "issue"), diff --git a/packages/zowe-explorer-api/src/extend/MainframeInteraction.ts b/packages/zowe-explorer-api/src/extend/MainframeInteraction.ts index e059792068..c2ed156824 100644 --- a/packages/zowe-explorer-api/src/extend/MainframeInteraction.ts +++ b/packages/zowe-explorer-api/src/extend/MainframeInteraction.ts @@ -505,9 +505,20 @@ export namespace MainframeInteraction { * @param {zostso.IStartTsoParms} parms * @returns {Promise} * @memberof ICommand + * @deprecated Please use `issueTsoCmdWithParms` */ issueTsoCommandWithParms?(command: string, parms?: zostso.IStartTsoParms): Promise; + /** + * Issues a TSO Command without the need for account information + * + * @param {string} command + * @param {zostso.IStartTsoParms} parms + * @returns {Promise} + * @memberof ICommand + */ + issueTsoCmdWithParms?(command: string, parms?: zostso.IStartTsoParms): Promise; + /** * Issues a MVS Command and returns a Console Command API response. * diff --git a/packages/zowe-explorer-api/src/profiles/UserSettings.ts b/packages/zowe-explorer-api/src/profiles/UserSettings.ts index 535f705302..b2b8aca033 100644 --- a/packages/zowe-explorer-api/src/profiles/UserSettings.ts +++ b/packages/zowe-explorer-api/src/profiles/UserSettings.ts @@ -16,5 +16,7 @@ export enum PersistenceSchemaEnum { Dataset = "zowe.ds.history", USS = "zowe.uss.history", Job = "zowe.jobs.history", - Commands = "zowe.commands.history", + MvsCommands = "zowe.commands.mvs.history", + TsoCommands = "zowe.commands.tso.history", + UssCommands = "zowe.commands.uss.history", } diff --git a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts index 3a36043383..c4cab4d47c 100644 --- a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts +++ b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts @@ -457,6 +457,10 @@ export namespace ZoweExplorerZosmf { return zostso.IssueTso.issueTsoCommand(this.getSession(), parms.account, command, parms); } + public issueTsoCmdWithParms(command: string, parms: zostso.IStartTsoParms): Promise { + return zostso.IssueTso.issueTsoCmd(this.getSession(), command, { addressSpaceOptions: parms }); + } + public issueMvsCommand(command: string, consoleName?: string): Promise { return zosconsole.IssueCommand.issue(this.getSession(), { command, consoleName, processResponses: true }); } @@ -464,13 +468,24 @@ export namespace ZoweExplorerZosmf { public async issueUnixCommand(command: string, cwd: string, sshSession: zosuss.SshSession): Promise { let stdout = ""; if (cwd) { - await zosuss.Shell.executeSshCwd(sshSession, command, '"' + cwd + '"', (data: string) => { - stdout += data; - }); + await zosuss.Shell.executeSshCwd( + sshSession, + command, + '"' + cwd + '"', + (data: string) => { + stdout += data; + }, + true + ); } else { - await zosuss.Shell.executeSsh(sshSession, command, (data: string) => { - stdout += data; - }); + await zosuss.Shell.executeSsh( + sshSession, + command, + (data: string) => { + stdout += data; + }, + true + ); } return stdout; } diff --git a/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts b/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts index 65e4e2bb14..2071f6a501 100644 --- a/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts +++ b/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts @@ -12,7 +12,6 @@ import * as vscode from "vscode"; import * as imperative from "@zowe/imperative"; import { IZoweTreeNode } from "./IZoweTreeNode"; -import type { BaseProvider } from "../fs/BaseProvider"; /** * Common implementation of functions and methods associated with the @@ -98,9 +97,9 @@ export class ZoweTreeNode extends vscode.TreeItem { /** * Sets the imperative.IProfileLoaded profile for this node to the one chosen in parameters. * - * @param {imperative.IProfileLoaded} The profile you will set the node to use + * @param {imperative.IProfileLoaded} aProfile The profile you will set the node to use */ - public setProfileToChoice(aProfile: imperative.IProfileLoaded, fsProvider?: BaseProvider): void { + public setProfileToChoice(aProfile: imperative.IProfileLoaded): void { // Don't reassign profile if its already defined, as we want to keep the reference valid for other nodes and filesystems this.profile = Object.assign(this.profile ?? {}, aProfile); } diff --git a/packages/zowe-explorer-ftp-extension/CHANGELOG.md b/packages/zowe-explorer-ftp-extension/CHANGELOG.md index 7680359d4a..99b2941e23 100644 --- a/packages/zowe-explorer-ftp-extension/CHANGELOG.md +++ b/packages/zowe-explorer-ftp-extension/CHANGELOG.md @@ -4,10 +4,10 @@ All notable changes to the "zowe-explorer-ftp-extension" extension will be docum ### New features and enhancements -- Updated Zowe SDKs to `8.8.4` for technical currency. [#3306](https://github.com/zowe/zowe-explorer-vscode/pull/3306) - ### Bug fixes +- Updated Zowe SDKs to `8.8.4` for technical currency. [#3306](https://github.com/zowe/zowe-explorer-vscode/pull/3306) + ## `3.0.3` ### Bug fixes diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 8b7b228d97..608649d459 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Updated Zowe SDKs to `8.8.4` for technical currency. [#3306](https://github.com/zowe/zowe-explorer-vscode/pull/3306) - Added expired JSON web token detection for profiles in each tree view (Data Sets, USS, Jobs). When a user performs a search on a profile, they are prompted to log in if their token expired. [#3175](https://github.com/zowe/zowe-explorer-vscode/issues/3175) +- Added integrated terminals for z/OS Unix, TSO, and MVS commands which can be enabled via the `Zowe › Commands: Use Integrated Terminals` setting. [#3079](https://github.com/zowe/zowe-explorer-vscode/pull/3079) - Add a data set or USS resource to a virtual workspace with the new "Add to Workspace" context menu option. [#3265](https://github.com/zowe/zowe-explorer-vscode/issues/3265) - Power users and developers can now build links to efficiently open mainframe resources in Zowe Explorer. Use the **Copy External Link** option in the context menu to get the URL for a data set or USS resource, or create a link in the format `vscode://Zowe.vscode-extension-for-zowe?`. For more information on building resource URIs, see the [FileSystemProvider wiki article](https://github.com/zowe/zowe-explorer-vscode/wiki/FileSystemProvider#file-paths-vs-uris). [#3271](https://github.com/zowe/zowe-explorer-vscode/pull/3271) - Adopted support for VS Code proxy settings with zosmf profile types. [#3010](https://github.com/zowe/zowe-explorer-vscode/issues/3010) diff --git a/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts b/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts index d0b35edce7..2f80bc4ec2 100644 --- a/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts +++ b/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts @@ -150,6 +150,7 @@ export interface ICommandArguments { export interface IImperativeError { msg: string; errorCode?: number; + causeErrors?: any; additionalDetails?: string; } @@ -236,13 +237,20 @@ export class ConfigUtils { } export class ImperativeError extends Error { private msg: string; - constructor(public mDetails: IImperativeError) { + public causeErrors: any; + public additionalDetails: any; + constructor(private mDetails: IImperativeError) { super(); this.msg = mDetails.msg; + this.causeErrors = this.mDetails.causeErrors; + this.additionalDetails = this.mDetails.additionalDetails; } public get message() { return this.msg; } + public get details() { + return this.mDetails; + } } export class ProfInfoErr extends ImperativeError { @@ -359,6 +367,7 @@ export class TextUtils { public static prettyJson(object: any, options?: any, color?: boolean, append?: string): string { return JSON.stringify(object); } + public static chalk = jest.requireActual("chalk"); } export namespace SessConstants { diff --git a/packages/zowe-explorer/__tests__/__unit__/commands/MvsCommandHandler.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/commands/MvsCommandHandler.unit.test.ts index 2867def4a4..25b09ae5b0 100644 --- a/packages/zowe-explorer/__tests__/__unit__/commands/MvsCommandHandler.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/commands/MvsCommandHandler.unit.test.ts @@ -19,6 +19,7 @@ import { ProfileManagement } from "../../../src/management/ProfileManagement"; import { FilterDescriptor, FilterItem } from "../../../src/management/FilterManagement"; import { ZoweLogger } from "../../../src/tools/ZoweLogger"; import { MvsCommandHandler } from "../../../src/commands/MvsCommandHandler"; +import { SettingsConfig } from "../../../src/configuration/SettingsConfig"; jest.mock("Session"); @@ -28,6 +29,7 @@ describe("mvsCommandActions unit testing", () => { const showInformationMessage = jest.fn(); const showQuickPick = jest.fn(); const createQuickPick = jest.fn(); + const createTerminal = jest.fn(); const getConfiguration = jest.fn(); const createOutputChannel = jest.fn(); @@ -94,10 +96,7 @@ describe("mvsCommandActions unit testing", () => { }); const withProgress = jest.fn().mockImplementation((progLocation, callback) => { - return { - success: true, - commandResponse: callback(), - }; + return callback(); }); const session = new imperative.Session({ @@ -129,6 +128,7 @@ describe("mvsCommandActions unit testing", () => { Object.defineProperty(vscode.window, "showInformationMessage", { value: showInformationMessage }); Object.defineProperty(vscode.window, "showQuickPick", { value: showQuickPick }); Object.defineProperty(vscode.window, "createQuickPick", { value: createQuickPick }); + Object.defineProperty(vscode.window, "createTerminal", { value: createTerminal }); Object.defineProperty(vscode.window, "createOutputChannel", { value: createOutputChannel }); Object.defineProperty(vscode, "ProgressLocation", { value: ProgressLocation }); Object.defineProperty(vscode.window, "withProgress", { value: withProgress }); @@ -141,13 +141,20 @@ describe("mvsCommandActions unit testing", () => { }), }); + beforeEach(() => { + jest.spyOn(SettingsConfig, "getDirectValue").mockReturnValue(false); + }); + afterEach(() => { + (MvsCommandHandler as any).instance = undefined; jest.clearAllMocks(); }); const apiRegisterInstance = ZoweExplorerApiRegister.getInstance(); - const mvsActions = MvsCommandHandler.getInstance(); const profilesForValidation = { status: "active", name: "fake" }; + const getMvsActions = () => { + return MvsCommandHandler.getInstance(); + }; it("tests the issueMvsCommand function", async () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { @@ -180,16 +187,16 @@ describe("mvsCommandActions unit testing", () => { showInputBox.mockReturnValueOnce("/d iplinfo1"); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue("iplinfo1" as any); + jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue({ commandResponse: "iplinfo1" } as any); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(1); expect(appendLine.mock.calls.length).toBe(2); @@ -223,16 +230,19 @@ describe("mvsCommandActions unit testing", () => { apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem2)); - jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue("iplinfo0" as any); + jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue({ commandResponse: "iplinfo0" } as any); + + const actions = getMvsActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; - await mvsActions.issueMvsCommand(); + await actions.issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(0); expect(appendLine.mock.calls.length).toBe(2); @@ -267,16 +277,16 @@ describe("mvsCommandActions unit testing", () => { getCommandApiMock.mockReturnValue(mockCommandApi); apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue("iplinfo3" as any); + jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValue({ commandResponse: "iplinfo3" } as any); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(1); expect(showErrorMessage.mock.calls.length).toBe(1); @@ -309,7 +319,10 @@ describe("mvsCommandActions unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(undefined)); - await mvsActions.issueMvsCommand(); + const actions = getMvsActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showInputBox.mock.calls.length).toBe(0); @@ -317,13 +330,12 @@ describe("mvsCommandActions unit testing", () => { expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("No selection made. Operation cancelled."); + expect(showInformationMessage.mock.calls.length).toBe(0); }); - it("tests the issueMvsCommand function user escapes the command box", async () => { + it("tests the issueMvsCommand function user escapes the MVS command box", async () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { value: jest.fn(() => { return { @@ -349,18 +361,17 @@ describe("mvsCommandActions unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("No command entered."); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueMvsCommand function user starts typing a value in quick pick", async () => { @@ -406,14 +417,17 @@ describe("mvsCommandActions unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await mvsActions.issueMvsCommand(); + const actions = getMvsActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(0); }); @@ -450,14 +464,14 @@ describe("mvsCommandActions unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValueOnce({ commandResponse: "fake response" } as any); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(1); }); @@ -492,14 +506,14 @@ describe("mvsCommandActions unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValueOnce({ commandResponse: "fake response" } as any); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(1); }); @@ -523,7 +537,7 @@ describe("mvsCommandActions unit testing", () => { showQuickPick.mockReturnValueOnce("firstName"); showInputBox.mockReturnValueOnce("fake"); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showErrorMessage.mock.calls.length).toBe(1); }); @@ -544,10 +558,9 @@ describe("mvsCommandActions unit testing", () => { showQuickPick.mockReturnValueOnce(undefined); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueMvsCommand function from a session", async () => { @@ -564,7 +577,7 @@ describe("mvsCommandActions unit testing", () => { }), }); - jest.spyOn(mvsActions, "checkCurrentProfile").mockReturnValue(undefined as any); + jest.spyOn(getMvsActions(), "checkCurrentProfile").mockReturnValue(undefined as any); const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne); const getCommandApiMock = jest.fn(); @@ -574,7 +587,7 @@ describe("mvsCommandActions unit testing", () => { showInputBox.mockReturnValueOnce("/d iplinfo1"); jest.spyOn(mockCommandApi, "issueMvsCommand").mockReturnValueOnce({ commandResponse: "fake response" } as any); - await mvsActions.issueMvsCommand(session, null as any, testNode); + await getMvsActions().issueMvsCommand(session, null as any, testNode); expect(showInputBox.mock.calls.length).toBe(1); expect(showInformationMessage.mock.calls.length).toBe(0); @@ -608,14 +621,14 @@ describe("mvsCommandActions unit testing", () => { throw testError; }); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, - placeHolder: "Select the profile to use to submit the command", + placeHolder: "Select the profile to use to submit the MVS command", }); expect(showInputBox.mock.calls.length).toBe(0); expect(showErrorMessage.mock.calls.length).toBe(1); @@ -641,7 +654,7 @@ describe("mvsCommandActions unit testing", () => { value: jest.fn().mockReturnValue([]), configurable: true, }); - await mvsActions.issueMvsCommand(); + await getMvsActions().issueMvsCommand(); expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available"); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/commands/TsoCommandHandler.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/commands/TsoCommandHandler.unit.test.ts index bca89468fd..53e9fd831a 100644 --- a/packages/zowe-explorer/__tests__/__unit__/commands/TsoCommandHandler.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/commands/TsoCommandHandler.unit.test.ts @@ -18,6 +18,7 @@ import { ProfileManagement } from "../../../src/management/ProfileManagement"; import { ZoweLocalStorage } from "../../../src/tools/ZoweLocalStorage"; import { ZoweDatasetNode } from "../../../src/trees/dataset/ZoweDatasetNode"; import { TsoCommandHandler } from "../../../src/commands/TsoCommandHandler"; +import { SettingsConfig } from "../../../src/configuration/SettingsConfig"; jest.mock("Session"); @@ -27,6 +28,7 @@ describe("TsoCommandHandler unit testing", () => { const showInformationMessage = jest.fn(); const showQuickPick = jest.fn(); const createQuickPick = jest.fn(); + const createTerminal = jest.fn(); const getConfiguration = jest.fn(); const createOutputChannel = jest.fn(); @@ -87,10 +89,7 @@ describe("TsoCommandHandler unit testing", () => { }); const withProgress = jest.fn().mockImplementation((progLocation, callback) => { - return { - success: true, - commandResponse: callback(), - }; + return callback(); }); const session = new imperative.Session({ @@ -122,6 +121,7 @@ describe("TsoCommandHandler unit testing", () => { Object.defineProperty(vscode.window, "showInformationMessage", { value: showInformationMessage }); Object.defineProperty(vscode.window, "showQuickPick", { value: showQuickPick }); Object.defineProperty(vscode.window, "createQuickPick", { value: createQuickPick }); + Object.defineProperty(vscode.window, "createTerminal", { value: createTerminal }); Object.defineProperty(vscode.window, "createOutputChannel", { value: createOutputChannel }); Object.defineProperty(vscode, "ProgressLocation", { value: ProgressLocation }); Object.defineProperty(vscode.window, "withProgress", { value: withProgress }); @@ -138,19 +138,28 @@ describe("TsoCommandHandler unit testing", () => { }), }); + beforeEach(() => { + jest.spyOn(SettingsConfig, "getDirectValue").mockReturnValue(false); + }); + afterEach(() => { + (TsoCommandHandler as any).instance = undefined; jest.clearAllMocks(); }); const apiRegisterInstance = ZoweExplorerApiRegister.getInstance(); - const tsoActions = TsoCommandHandler.getInstance(); const profilesForValidation = { status: "active", name: "fake" }; - Object.defineProperty(tsoActions, "getTsoParams", { - value: jest.fn(() => { - return "acctNum"; - }), - }); + const getTsoActions = () => { + const tsoActions = TsoCommandHandler.getInstance(); + Object.defineProperty(tsoActions, "getTsoParams", { + value: jest.fn(() => { + return "acctNum"; + }), + configurable: true, + }); + return tsoActions; + }; it("tests the issueTsoCommand function", async () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { @@ -180,9 +189,9 @@ describe("TsoCommandHandler unit testing", () => { getCommandApiMock.mockReturnValue(mockCommandApi); apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); showInputBox.mockReturnValueOnce("/d iplinfo1"); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo1" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo1" } as any); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -222,9 +231,12 @@ describe("TsoCommandHandler unit testing", () => { getCommandApiMock.mockReturnValue(mockCommandApi); apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem2)); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo0" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo0" } as any); + + const actions = getTsoActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; - await tsoActions.issueTsoCommand(); + await actions.issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -266,9 +278,9 @@ describe("TsoCommandHandler unit testing", () => { getCommandApiMock.mockReturnValue(mockCommandApi); apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo3" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo3" } as any); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -308,7 +320,10 @@ describe("TsoCommandHandler unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(undefined)); - await tsoActions.issueTsoCommand(); + const actions = getTsoActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showInputBox.mock.calls.length).toBe(0); @@ -318,8 +333,6 @@ describe("TsoCommandHandler unit testing", () => { ignoreFocusOut: true, placeHolder: "Select the profile to use to submit the TSO command", }); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("No selection made. Operation cancelled."); }); it("tests the issueTsoCommand function user escapes the command box", async () => { @@ -348,7 +361,7 @@ describe("TsoCommandHandler unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -358,8 +371,7 @@ describe("TsoCommandHandler unit testing", () => { placeHolder: "Select the profile to use to submit the TSO command", }); expect(showInputBox.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("No command entered."); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueTsoCommand function user starts typing a value in quick pick", async () => { @@ -405,7 +417,10 @@ describe("TsoCommandHandler unit testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await tsoActions.issueTsoCommand(); + const actions = getTsoActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -447,9 +462,9 @@ describe("TsoCommandHandler unit testing", () => { apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo" } as any); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -489,9 +504,9 @@ describe("TsoCommandHandler unit testing", () => { getCommandApiMock.mockReturnValue(mockCommandApi); apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo5" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo5" } as any); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -522,7 +537,7 @@ describe("TsoCommandHandler unit testing", () => { showQuickPick.mockReturnValueOnce("firstName"); showInputBox.mockReturnValueOnce("fake"); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showErrorMessage.mock.calls.length).toBe(1); }); @@ -543,10 +558,9 @@ describe("TsoCommandHandler unit testing", () => { showQuickPick.mockReturnValueOnce(undefined); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueTsoCommand function from a session", async () => { @@ -563,7 +577,7 @@ describe("TsoCommandHandler unit testing", () => { }), }); - jest.spyOn(tsoActions, "checkCurrentProfile").mockReturnValue(undefined as any); + jest.spyOn(getTsoActions(), "checkCurrentProfile").mockReturnValue(undefined as any); const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne); const getCommandApiMock = jest.fn(); @@ -571,9 +585,9 @@ describe("TsoCommandHandler unit testing", () => { apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance); showInputBox.mockReturnValueOnce("/d iplinfo1"); - jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue("iplinfo1" as any); + jest.spyOn(mockCommandApi, "issueTsoCommandWithParms").mockReturnValue({ commandResponse: "iplinfo1" } as any); - await tsoActions.issueTsoCommand(session, null as any, testNode); + await getTsoActions().issueTsoCommand(session, null as any, testNode); expect(showInputBox.mock.calls.length).toBe(1); expect(showInformationMessage.mock.calls.length).toBe(0); @@ -607,7 +621,7 @@ describe("TsoCommandHandler unit testing", () => { throw testError; }); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showQuickPick.mock.calls.length).toBe(1); expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); @@ -621,23 +635,6 @@ describe("TsoCommandHandler unit testing", () => { expect(showErrorMessage.mock.calls[0][0]).toContain(testError.message); }); - it("tests the selectTsoProfile function", async () => { - showQuickPick.mockReturnValueOnce("test1" as any); - - await expect( - (tsoActions as any).selectTsoProfile([ - { - name: "test1", - }, - { - name: "test2", - }, - ]) - ).resolves.toEqual({ - name: "test1", - }); - }); - it("tests the issueTsoCommand function no profiles error", async () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { value: jest.fn(() => { @@ -657,7 +654,7 @@ describe("TsoCommandHandler unit testing", () => { value: jest.fn().mockReturnValue([]), configurable: true, }); - await tsoActions.issueTsoCommand(); + await getTsoActions().issueTsoCommand(); expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available"); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/commands/UnixCommandHandler.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/commands/UnixCommandHandler.unit.test.ts index 1eddd873be..c046cd925f 100644 --- a/packages/zowe-explorer/__tests__/__unit__/commands/UnixCommandHandler.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/commands/UnixCommandHandler.unit.test.ts @@ -22,6 +22,8 @@ import { ZoweLogger } from "../../../src/tools/ZoweLogger"; import { ZoweDatasetNode } from "../../../src/trees/dataset/ZoweDatasetNode"; import { UnixCommandHandler } from "../../../src/commands/UnixCommandHandler"; import { Constants } from "../../../src/configuration/Constants"; +import { Definitions } from "../../../src/configuration/Definitions"; +import { SettingsConfig } from "../../../src/configuration/SettingsConfig"; jest.mock("Session"); @@ -31,6 +33,7 @@ describe("UnixCommand Actions Unit Testing", () => { const showInformationMessage = jest.fn(); const showQuickPick = jest.fn(); const createQuickPick = jest.fn(); + const createTerminal = jest.fn(); const getConfiguration = jest.fn(); const createOutputChannel = jest.fn(); @@ -53,8 +56,8 @@ describe("UnixCommand Actions Unit Testing", () => { Object.defineProperty(profileLoader.Profiles, "createInstance", { value: jest.fn(() => { return { - allProfiles: [{ name: "firstName" }, { name: "secondName" }], - defaultProfile: { name: "firstName" }, + allProfiles: [{ name: "firstProfile" }, { name: "secondProfile" }], + defaultProfile: { name: "firstProfile" }, }; }), }); @@ -129,14 +132,14 @@ describe("UnixCommand Actions Unit Testing", () => { profile: { host: "host.com", port: 123, - user: "testuser", + user: "testUser", }, message: "", failNotFound: false, } as imperative.IProfileLoaded, ]; - const profilefromConfig = { + const profileFromConfig = { isDefaultProfile: false, profLoc: { osLoc: ["/user/configpath"] }, }; @@ -146,11 +149,12 @@ describe("UnixCommand Actions Unit Testing", () => { Object.defineProperty(vscode.window, "showInformationMessage", { value: showInformationMessage }); Object.defineProperty(vscode.window, "showQuickPick", { value: showQuickPick }); Object.defineProperty(vscode.window, "createQuickPick", { value: createQuickPick }); + Object.defineProperty(vscode.window, "createTerminal", { value: createTerminal }); Object.defineProperty(vscode.window, "createOutputChannel", { value: createOutputChannel }); Object.defineProperty(vscode, "ProgressLocation", { value: ProgressLocation }); Object.defineProperty(vscode.window, "withProgress", { value: withProgress }); Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", { - value: jest.fn().mockReturnValue(["firstName", "secondName"]), + value: jest.fn().mockReturnValue(["firstProfile", "secondProfile"]), configurable: true, }); @@ -180,12 +184,13 @@ describe("UnixCommand Actions Unit Testing", () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { value: jest.fn(() => { return { - allProfiles: [{ name: "firstName", profile: { user: "firstName", password: "pass" } }, { name: "secondName" }], - defaultProfile: { name: "firstName" }, + allProfiles: [{ name: "firstProfile", profile: { user: "testUser", password: "testPass" } }, { name: "secondProfile" }], + defaultProfile: { name: "firstProfile" }, zosmfProfile: mockLoadNamedProfile, checkCurrentProfile: jest.fn(() => { return profilesForValidation; }), + profileValidationHelper: jest.fn().mockReturnValue("active"), //.mockImplementation((prof, fun) => fun(prof)), validateProfiles: jest.fn(), getBaseProfile: jest.fn(), validProfile: Validation.ValidationType.VALID, @@ -196,14 +201,19 @@ describe("UnixCommand Actions Unit Testing", () => { return ["entered"]; }), getProfileFromConfig: jest.fn(() => { - return profilefromConfig; + return profileFromConfig; }), openConfigFile: jest.fn(), }; }), }); + beforeEach(() => { + jest.spyOn(SettingsConfig, "getDirectValue").mockReturnValue(false); + }); + afterEach(() => { + (UnixCommandHandler as any).instance = undefined; jest.clearAllMocks(); }); @@ -214,8 +224,10 @@ describe("UnixCommand Actions Unit Testing", () => { }); const apiRegisterInstance = ZoweExplorerApiRegister.getInstance(); - const unixActions = UnixCommandHandler.getInstance(); const profilesForValidation = { status: "active", name: "fake" }; + const getUnixActions = () => { + return UnixCommandHandler.getInstance(); + }; it("test the issueUnixCommand function", async () => { const mockUssApi = await apiRegisterInstance.getUssApi(profileOne); @@ -224,7 +236,7 @@ describe("UnixCommand Actions Unit Testing", () => { apiRegisterInstance.getUssApi = getUssApiMock.bind(apiRegisterInstance); jest.spyOn(mockUssApi, "getSession").mockReturnValue(session); - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne); const getCommandApiMock = jest.fn(); @@ -242,10 +254,10 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValue("iplinfo1" as any); - await unixActions.issueUnixCommand(); + await getUnixActions().issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, @@ -254,22 +266,22 @@ describe("UnixCommand Actions Unit Testing", () => { expect(showInputBox.mock.calls.length).toBe(2); expect(appendLine.mock.calls.length).toBe(2); - expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ /d iplinfo1"); + expect(appendLine.mock.calls[0][0]).toBe("> testUser@firstProfile:/u/directorypath $ /d iplinfo1"); expect(appendLine.mock.calls[1][0]["commandResponse"]).toBe("iplinfo1"); expect(showInformationMessage.mock.calls.length).toBe(0); }); - it("tests the selectSshProfile function with quickpick", async () => { + it("tests the selectServiceProfile function with quickpick", async () => { showQuickPick.mockReturnValueOnce("test1" as any); await expect( - (unixActions as any).selectSshProfile([ + getUnixActions().selectServiceProfile([ { name: "test1", }, { name: "test2", }, - ]) + ] as any) ).resolves.toEqual({ name: "test1", }); @@ -282,7 +294,7 @@ describe("UnixCommand Actions Unit Testing", () => { apiRegisterInstance.getUssApi = getUssApiMock.bind(apiRegisterInstance); jest.spyOn(mockUssApi, "getSession").mockReturnValue(session); - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne); const getCommandApiMock = jest.fn(); @@ -299,10 +311,13 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem2)); jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValue("iplinfo0" as any); - await unixActions.issueUnixCommand(); + const actions = getUnixActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, @@ -310,13 +325,13 @@ describe("UnixCommand Actions Unit Testing", () => { }); expect(showInputBox.mock.calls.length).toBe(1); expect(appendLine.mock.calls.length).toBe(2); - expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ /d iplinfo0"); + expect(appendLine.mock.calls[0][0]).toBe("> testUser@firstProfile:/u/directorypath $ /d iplinfo0"); expect(appendLine.mock.calls[1][0]["commandResponse"]).toBe("iplinfo0"); expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueUnixCommand function user escapes the quick pick box", async () => { - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce("/u/directorypath"); const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne); @@ -331,22 +346,24 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(undefined)); - await unixActions.issueUnixCommand(); + const actions = getUnixActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, placeHolder: "Select the profile to use to submit the Unix command", }); expect(showInputBox.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueUnixCommand function user escapes the commandbox", async () => { - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce("/directorypath"); showInputBox.mockReturnValueOnce(undefined); @@ -362,22 +379,21 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await unixActions.issueUnixCommand(); + await getUnixActions().issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, placeHolder: "Select the profile to use to submit the Unix command", }); expect(showInputBox.mock.calls.length).toBe(2); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueUnixCommand function - issueUnixCommand throws an error", async () => { - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce("/u/directorypath"); showInputBox.mockReturnValueOnce("/d iplinfo3"); withProgress.mockRejectedValueOnce(Error("fake testError")); @@ -395,10 +411,10 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValue("iplinfo3" as any); - await unixActions.issueUnixCommand(); + await getUnixActions().issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, @@ -414,12 +430,12 @@ describe("UnixCommand Actions Unit Testing", () => { value: jest.fn().mockReturnValueOnce({ profile: { user: "testuser", password: "testpassword" } } as any), configurable: true, }); - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce(""); showInputBox.mockReturnValue("/d iplinfo0"); - await unixActions.issueUnixCommand(); + await getUnixActions().issueUnixCommand(); expect(showInformationMessage.mock.calls[0][0]).toEqual("Redirecting to Home Directory"); }); @@ -429,13 +445,12 @@ describe("UnixCommand Actions Unit Testing", () => { value: jest.fn().mockReturnValueOnce({ profile: { user: "testuser", password: "testpassword" } } as any), configurable: true, }); - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce(undefined); - await unixActions.issueUnixCommand(); - - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + await getUnixActions().issueUnixCommand(); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueUnixCommand function user starts typing a value in quick pick", async () => { @@ -456,7 +471,7 @@ describe("UnixCommand Actions Unit Testing", () => { }), }); - showQuickPick.mockReturnValueOnce("firstName"); + showQuickPick.mockReturnValueOnce("firstProfile"); showInputBox.mockReturnValueOnce("/u/directorypath"); showInputBox.mockReturnValueOnce(undefined); @@ -472,10 +487,13 @@ describe("UnixCommand Actions Unit Testing", () => { jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem)); - await unixActions.issueUnixCommand(); + const actions = getUnixActions(); + (actions.history as any).mSearchHistory = [qpItem2.label]; + + await actions.issueUnixCommand(); expect(showQuickPick.mock.calls.length).toBe(1); - expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]); + expect(showQuickPick.mock.calls[0][0]).toEqual(["firstProfile", "secondProfile"]); expect(showQuickPick.mock.calls[0][1]).toEqual({ canPickMany: false, ignoreFocusOut: true, @@ -486,7 +504,7 @@ describe("UnixCommand Actions Unit Testing", () => { it("tests the issueUnixCommand function user does not select a profile", async () => { Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", { - value: jest.fn().mockReturnValue(["firstName"]), + value: jest.fn().mockReturnValue(["firstProfile"]), configurable: true, }); Object.defineProperty(profInstance, "getDefaultProfile", { @@ -495,10 +513,9 @@ describe("UnixCommand Actions Unit Testing", () => { }); showQuickPick.mockReturnValueOnce(undefined); - await unixActions.issueUnixCommand(); + await getUnixActions().issueUnixCommand(); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("tests the issueUnixCommand function no profiles error", async () => { @@ -510,13 +527,13 @@ describe("UnixCommand Actions Unit Testing", () => { value: jest.fn().mockReturnValueOnce({ profile: { user: "testuser", password: "testpassword" } } as any), configurable: true, }); - await unixActions.issueUnixCommand(); - expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available."); + await getUnixActions().issueUnixCommand(); + expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available"); }); it("ssh profile not found", async () => { fetchSshProfiles = []; - await (unixActions as any).getSshProfile(); + await (getUnixActions() as any).getSshProfile(); expect(showErrorMessage.mock.calls.length).toBe(1); expect(showErrorMessage.mock.calls[0][0]).toEqual("No SSH profile found. Please create an SSH profile."); }); @@ -541,55 +558,55 @@ describe("UnixCommand Actions Unit Testing", () => { value: jest.fn().mockReturnValueOnce({ profile: { user: "testuser", password: "testpassword" } } as any), configurable: true, }); - await (unixActions as any).getSshProfile(); + ((await getUnixActions()) as any).getSshProfile(); }); - it("the shh profile doesnot have port or host or both", async () => { + it("the shh profile does not have port or host or both", async () => { fetchSshProfiles = [ { name: "ssh", type: "ssh", profile: { host: "host.com", - user: "testuser", + user: "testUser", }, message: "", failNotFound: false, } as imperative.IProfileLoaded, ]; - await (unixActions as any).getSshProfile(); + await (getUnixActions() as any).getSshProfile(); expect(showErrorMessage.mock.calls[0][0]).toEqual("SSH profile missing connection details. Please update."); }); - it("tests the selectSshProfile function-1", async () => { + it("tests the selectServiceProfile function-1", async () => { await expect( - (unixActions as any).selectSshProfile([ + getUnixActions().selectServiceProfile([ { name: "test1", }, - ]) + ] as any) ).resolves.toEqual({ name: "test1", }); }); - it("tests the selectSshProfile function when user escapes", async () => { + it("tests the selectServiceProfile function when user escapes", async () => { showQuickPick.mockReturnValueOnce(undefined); await expect( - (unixActions as any).selectSshProfile([ + getUnixActions().selectServiceProfile([ { name: "test1", }, { name: "test2", }, - ]) + ] as any) ).resolves.toBe(undefined); Object.defineProperty(profInstance, "getDefaultProfile", { value: jest.fn().mockReturnValueOnce({ profile: { user: "testuser", password: "testpassword" } } as any), configurable: true, }); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); it("getCommand API is not implemented", async () => { @@ -600,8 +617,8 @@ describe("UnixCommand Actions Unit Testing", () => { }; }), }); - await unixActions.issueUnixCommand(testNode, null as any); - expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing commands is not supported for this profile type, undefined."); + await getUnixActions().issueUnixCommand(testNode, null as any); + expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing commands is not supported for this profile type, zosmf."); }); it("issueUnixCommand API is not yet implemented", async () => { @@ -616,13 +633,13 @@ describe("UnixCommand Actions Unit Testing", () => { }; }), }); - await unixActions.issueUnixCommand(testNode, null as any); + await getUnixActions().issueUnixCommand(testNode, null as any); expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing UNIX commands is not supported for this profile type, zosmf."); }); it("tests the issueUnixCommand function user does not select a profile - userSelectProfile", async () => { Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", { - value: jest.fn().mockReturnValue(["firstName"]), + value: jest.fn().mockReturnValue(["firstProfile"]), configurable: true, }); Object.defineProperty(profInstance, "getDefaultProfile", { @@ -631,9 +648,8 @@ describe("UnixCommand Actions Unit Testing", () => { }); showQuickPick.mockReturnValueOnce(undefined); - await (unixActions as any).userSelectProfile(); + await getUnixActions().selectNodeProfile(Definitions.Trees.USS); - expect(showInformationMessage.mock.calls.length).toBe(1); - expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation cancelled"); + expect(showInformationMessage.mock.calls.length).toBe(0); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/commands/ZoweCommandProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/commands/ZoweCommandProvider.unit.test.ts index 54916f2a3e..98c1da9b6d 100644 --- a/packages/zowe-explorer/__tests__/__unit__/commands/ZoweCommandProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/commands/ZoweCommandProvider.unit.test.ts @@ -10,7 +10,7 @@ */ import * as vscode from "vscode"; -import { ProfilesCache, ZoweExplorerApiType, ZoweTreeNode } from "@zowe/zowe-explorer-api"; +import { Gui, imperative, ProfilesCache, ZoweTreeNode } from "@zowe/zowe-explorer-api"; import { createIProfile, createISession } from "../../__mocks__/mockCreators/shared"; import { ZoweCommandProvider } from "../../../src/commands/ZoweCommandProvider"; import { Profiles } from "../../../src/configuration/Profiles"; @@ -18,6 +18,9 @@ import { ZoweDatasetNode } from "../../../src/trees/dataset/ZoweDatasetNode"; import { SharedContext } from "../../../src/trees/shared/SharedContext"; import { createIJobFile } from "../../__mocks__/mockCreators/jobs"; import { AuthUtils } from "../../../src/utils/AuthUtils"; +import { ZoweLogger } from "../../../src/tools/ZoweLogger"; +import { SettingsConfig } from "../../../src/configuration/SettingsConfig"; +import { Constants } from "../../../src/configuration/Constants"; const globalMocks = { testSession: createISession(), @@ -25,7 +28,7 @@ const globalMocks = { mockIJobFile: createIJobFile(), }; describe("ZoweCommandProvider Unit Tests", () => { - describe("ZoweCommandProvider Unit Tests - function refreshElement", () => { + describe("function refreshElement", () => { it("should refresh the tree data", () => { const testNode = new (ZoweTreeNode as any)("test", vscode.TreeItemCollapsibleState.None, undefined); Object.defineProperty(ZoweCommandProvider.prototype, "mOnDidChangeTreeData", { @@ -37,51 +40,139 @@ describe("ZoweCommandProvider Unit Tests", () => { expect(ZoweCommandProvider.prototype.refreshElement(testNode)).toEqual(undefined); }); }); -}); + describe("function checkCurrentProfile", () => { + const testNode: any = new ZoweDatasetNode({ + label: "test", + collapsibleState: vscode.TreeItemCollapsibleState.None, + session: globalMocks.testSession, + }); + testNode.setProfileToChoice(globalMocks.testProfile); + testNode.contextValue = "session server"; -describe("ZoweCommandProvider Unit Tests - function checkCurrentProfile", () => { - const testNode: any = new ZoweDatasetNode({ - label: "test", - collapsibleState: vscode.TreeItemCollapsibleState.None, - session: globalMocks.testSession, - }); - testNode.setProfileToChoice(globalMocks.testProfile); - testNode.contextValue = "session server"; - - beforeEach(async () => { - jest.spyOn(ProfilesCache.prototype, "refresh").mockImplementation(); - const profilesInstance = await Profiles.createInstance(undefined as any); - Object.defineProperty(profilesInstance, "log", { - value: { - error: jest.fn(), - }, + beforeEach(async () => { + jest.spyOn(ProfilesCache.prototype, "refresh").mockImplementation(); + const profilesInstance = await Profiles.createInstance(undefined as any); + Object.defineProperty(profilesInstance, "log", { + value: { + error: jest.fn(), + }, + }); + jest.spyOn(ZoweCommandProvider.prototype, "refresh").mockImplementationOnce(() => {}); + jest.spyOn(SharedContext, "isSessionNotFav").mockReturnValue(true); }); - jest.spyOn(ZoweCommandProvider.prototype, "refresh").mockImplementationOnce(() => {}); - jest.spyOn(SharedContext, "isSessionNotFav").mockReturnValue(true); - }); - it("should check current profile and perform the case when status is 'active'", async () => { - const profileStatus = { name: "test", status: "active" }; + it("should check current profile and perform the case when status is 'active'", async () => { + const profileStatus = { name: "test", status: "active" }; - jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); - await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); - }); - it("should check current profile and perform the case when status is 'unverified'", async () => { - const profileStatus = { name: "test", status: "unverified" }; + jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); + await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); + }); + it("should check current profile and perform the case when status is 'unverified'", async () => { + const profileStatus = { name: "test", status: "unverified" }; - jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); - await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); + jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); + await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); + }); + it("should check current profile and perform the case when status is 'inactive'", async () => { + Object.defineProperty(ZoweCommandProvider, "mOnDidChangeTreeData", { + value: { + debug: jest.fn(), + }, + configurable: true, + }); + const profileStatus = { name: "test", status: "inactive" }; + jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); + const profileInactive = jest.spyOn(Profiles.getInstance(), "showProfileInactiveMsg").mockImplementation(); + await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); + expect(profileInactive).toHaveBeenCalledWith(globalMocks.testProfile.name); + }); }); - it("should check current profile and perform the case when status is 'inactive'", async () => { - Object.defineProperty(ZoweCommandProvider, "mOnDidChangeTreeData", { - value: { - debug: jest.fn(), - }, - configurable: true, + + describe("integrated terminals", () => { + beforeEach(() => { + // Simulate that the setting is enabled : ) + jest.spyOn(SettingsConfig, "getDirectValue").mockImplementation( + (setting) => setting === Constants.SETTINGS_COMMANDS_INTEGRATED_TERMINALS + ); + }); + + describe("function issueCommand", () => { + const testError = new imperative.ImperativeError({ + msg: "test-msg", + causeErrors: "test-causeErrors", + additionalDetails: "test-additionalDetails", + }); + const mockCmdProvider: any = { + useIntegratedTerminals: true, + terminalName: "test-terminal", + pseudoTerminal: {}, + formatCommandLine: (cmd: string) => "test-" + cmd, + history: { getSearchHistory: () => ["old-cmd-01", "old-cmd-02"], addSearchHistory: jest.fn() }, + runCommand: jest.fn().mockRejectedValue(testError), + }; + const testProfile: any = { name: "test", profile: { user: "firstName", password: "12345" } }; + + it("should not create a terminal if the profile or the command is undefined", async () => { + const createTerminal = jest.fn().mockReturnValue({ show: jest.fn() }); + Object.defineProperty(vscode.window, "createTerminal", { value: createTerminal, configurable: true }); + await ZoweCommandProvider.prototype.issueCommand.call(mockCmdProvider, null, "test"); + await ZoweCommandProvider.prototype.issueCommand.call(mockCmdProvider, undefined, "test"); + await ZoweCommandProvider.prototype.issueCommand.call(mockCmdProvider, testProfile, null); + await ZoweCommandProvider.prototype.issueCommand.call(mockCmdProvider, testProfile, undefined); + expect(createTerminal).not.toHaveBeenCalled(); + }); + + it("should create an integrated terminal", async () => { + const createTerminal = jest.fn().mockReturnValue({ show: jest.fn() }); + Object.defineProperty(vscode.window, "createTerminal", { value: createTerminal, configurable: true }); + + await ZoweCommandProvider.prototype.issueCommand.call(mockCmdProvider, testProfile, "test"); + expect(createTerminal).toHaveBeenCalled(); + + // Test errorHandling on callback + const pty = createTerminal.mock.calls[0][0].pty; + const errorOutput = await pty.processCmd(); + + expect(errorOutput).toContain("Unable to perform this operation due to the following problem."); + expect(errorOutput).toContain("test-msg"); + expect(errorOutput).toContain("Response From Service"); + expect(errorOutput).toContain("test-causeErrors"); + expect(errorOutput).toContain("Diagnostic Information"); + expect(errorOutput).toContain("test-additionalDetails"); + }); + }); + + describe("function selectServiceProfile", () => { + it("should select the specified profile", async () => { + const mockCmdProvider: any = { + dialogs: { selectProfile: "select profile" }, + }; + + jest.spyOn(Gui, "showQuickPick").mockResolvedValue("prof02" as any); + const selected = await ZoweCommandProvider.prototype.selectServiceProfile.call( + mockCmdProvider, + [{ name: "prof01" }, { name: "prof02" }], + "test" + ); + + expect(selected).toEqual({ name: "prof02" }); + }); + it("should handle operation cancelled", async () => { + const mockCmdProvider: any = { + dialogs: { selectProfile: "select profile" }, + operationCancelled: "Operation cancelled", + }; + + jest.spyOn(Gui, "showQuickPick").mockResolvedValue(undefined); + const spyMessage = jest.spyOn(ZoweLogger, "info").mockImplementation(jest.fn()); + const selected = await ZoweCommandProvider.prototype.selectServiceProfile.call( + mockCmdProvider, + [{ name: "prof01" }, { name: "prof02" }], + "test" + ); + + expect(spyMessage).toHaveBeenCalledWith(mockCmdProvider.operationCancelled); + expect(selected).toBeUndefined(); + }); }); - const profileStatus = { name: "test", status: "inactive" }; - jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValue(profileStatus); - const showProfileInactiveMsg = jest.spyOn(Profiles.getInstance(), "showProfileInactiveMsg").mockImplementation(); - await expect(ZoweCommandProvider.prototype.checkCurrentProfile(testNode)).resolves.toEqual(profileStatus); - expect(showProfileInactiveMsg).toHaveBeenCalled(); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/configuration/SettingsConfig.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/configuration/SettingsConfig.unit.test.ts index 8a05be1271..2b94a2fe35 100644 --- a/packages/zowe-explorer/__tests__/__unit__/configuration/SettingsConfig.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/configuration/SettingsConfig.unit.test.ts @@ -48,7 +48,7 @@ describe("SettingsConfig Unit Tests", () => { const setValueSpy = jest.spyOn(ZoweLocalStorage, "setValue"); const promptReloadSpy = jest.spyOn(SettingsConfig as any, "promptReload"); await (SettingsConfig as any).migrateToLocalStorage(); - expect(setValueSpy).toHaveBeenCalledTimes(6); + expect(setValueSpy).toHaveBeenCalledTimes(8); expect(promptReloadSpy).toHaveBeenCalledTimes(1); }); it("should successfully migrate to local storage and update data set templates to new setting", async () => { @@ -83,7 +83,7 @@ describe("SettingsConfig Unit Tests", () => { const promptReloadSpy = jest.spyOn(SettingsConfig as any, "promptReload"); await (SettingsConfig as any).migrateToLocalStorage(); expect(templateSpy).toHaveBeenCalledTimes(1); - expect(setValueSpy).toHaveBeenCalledTimes(6); + expect(setValueSpy).toHaveBeenCalledTimes(8); expect(promptReloadSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/tools/ZoweTerminal.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/tools/ZoweTerminal.unit.test.ts new file mode 100644 index 0000000000..514b0e4c19 --- /dev/null +++ b/packages/zowe-explorer/__tests__/__unit__/tools/ZoweTerminal.unit.test.ts @@ -0,0 +1,88 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +import { ZoweTerminal } from "../../../src/tools/ZoweTerminal"; + +describe("ZoweTerminal Unit Tests", () => { + it("should not change the keys", () => { + expect(ZoweTerminal.Keys).toMatchSnapshot(); + }); + + it("should handle ctrl_c to cancel a running command", async () => { + const spyCb = jest.fn().mockImplementation(async (cmd: string) => Promise.resolve("test-output")); + const signalSpy = jest.fn().mockImplementation((_event, cb) => cb()); + const iTerm = new ZoweTerminal("test", spyCb, { abort: jest.fn(), signal: { addEventListener: signalSpy } } as any, { history: ["old"] }); + (iTerm as any).command = "open"; + iTerm.open(); + + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + (iTerm as any).isCommandRunning = true; + await iTerm.handleInput(ZoweTerminal.Keys.CTRL_D); + expect(spyCb).toHaveBeenCalledWith("open"); + spyCb.mockClear(); + + expect((iTerm as any).mHistory as string[]).toEqual(["old", "open"]); + }); + + it("should send the entered command to the callback function", async () => { + const spyCb = jest.fn().mockImplementation(async (cmd: string) => Promise.resolve("test-output")); + const iTerm = new ZoweTerminal("test", spyCb, { signal: { addEventListener: jest.fn() } } as any, { history: ["old"] }); + iTerm.open(); + + await iTerm.handleInput("testABC"); + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + expect(spyCb).toHaveBeenCalledWith("testABC"); + spyCb.mockClear(); + + await iTerm.handleInput(ZoweTerminal.Keys.HOME); // |testABC + await iTerm.handleInput(ZoweTerminal.Keys.END); // testABC| + await iTerm.handleInput(ZoweTerminal.Keys.CMD_LEFT); // |testABC + await iTerm.handleInput(ZoweTerminal.Keys.CMD_RIGHT); // testABC| + await iTerm.handleInput(ZoweTerminal.Keys.UP); // testABC| + await iTerm.handleInput(ZoweTerminal.Keys.UP); // old| + await iTerm.handleInput(ZoweTerminal.Keys.DOWN); // testABC| + await iTerm.handleInput(ZoweTerminal.Keys.LEFT); // testAB|C + await iTerm.handleInput(ZoweTerminal.Keys.LEFT); // testA|BC + await iTerm.handleInput(ZoweTerminal.Keys.BACKSPACE); // test|BC + await iTerm.handleInput(ZoweTerminal.Keys.RIGHT); // testB|C + await iTerm.handleInput(ZoweTerminal.Keys.BACKSPACE); // test|C + // handle multiple characters in sequence (CPU delay / copy+paste) + await iTerm.handleInput("1A"); // test1A|C + await iTerm.handleInput(ZoweTerminal.Keys.BACKSPACE); // test1|C + // Handle double byte characters + await iTerm.handleInput("🙏🙏"); // test1🙏🙏|C + await iTerm.handleInput(ZoweTerminal.Keys.BACKSPACE); // test1🙏|C + // Handle unicode "Hello" + await iTerm.handleInput("\u0048\u0065\u006C\u006C\u006F"); // test1🙏Hello|C + await iTerm.handleInput(ZoweTerminal.Keys.DEL); // test1🙏Hello| + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + expect(spyCb).toHaveBeenCalledWith("test1🙏Hello"); + spyCb.mockClear(); + + (iTerm as any).command = ""; + await iTerm.handleInput(ZoweTerminal.Keys.INSERT); // do nothing + await iTerm.handleInput(ZoweTerminal.Keys.TAB); // do nothing + await iTerm.handleInput("\x1b[1;A"); // Shift+Up // do nothing + await iTerm.handleInput("\x1b[3;A"); // fn+option+shift+up // do nothing + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + await iTerm.handleInput(ZoweTerminal.Keys.UP); // "test1🙏Hello" + await iTerm.handleInput(ZoweTerminal.Keys.CTRL_C); // Clear the terminal + await iTerm.handleInput(ZoweTerminal.Keys.CTRL_C); // Present the "(to exit ...)" message + await iTerm.handleInput(ZoweTerminal.Keys.CTRL_C); // close + await iTerm.handleInput(ZoweTerminal.Keys.CTRL_D); // close + await iTerm.handleInput(":clear"); + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + await iTerm.handleInput(":exit"); + await iTerm.handleInput(ZoweTerminal.Keys.ENTER); + + expect((iTerm as any).mHistory as string[]).toEqual(["old", "testABC", "test1🙏Hello", ":clear", ":exit"]); + }); +}); diff --git a/packages/zowe-explorer/__tests__/__unit__/tools/__snapshots__/ZoweTerminal.unit.test.ts.snap b/packages/zowe-explorer/__tests__/__unit__/tools/__snapshots__/ZoweTerminal.unit.test.ts.snap new file mode 100644 index 0000000000..af6e6bd61b --- /dev/null +++ b/packages/zowe-explorer/__tests__/__unit__/tools/__snapshots__/ZoweTerminal.unit.test.ts.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ZoweTerminal Unit Tests should not change the keys 1`] = ` +{ + "BACKSPACE": "", + "CLEAR_ALL": "", + "CLEAR_LINE": " +", + "CMD_BACKSPACE": "", + "CMD_LEFT": "", + "CMD_RIGHT": "", + "CTRL_BACKSPACE": "", + "CTRL_C": "", + "CTRL_D": "", + "DEL": "[3~", + "DOWN": "", + "EMPTY_LINE": "> ", + "END": "", + "ENTER": " +", + "HOME": "", + "INSERT": "[2~", + "LEFT": "", + "NEW_LINE": " +", + "OPT_BACKSPACE": "", + "OPT_CMD_BACKSPACE": "", + "OPT_LEFT": "b", + "OPT_RIGHT": "f", + "PAGE_DOWN": "[6~", + "PAGE_UP": "[5~", + "RIGHT": "", + "TAB": " ", + "UP": "", + "hasModKey": [Function], +} +`; diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedHistoryView.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedHistoryView.unit.test.ts index 7c66eb7aa4..2ce726bc9b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedHistoryView.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedHistoryView.unit.test.ts @@ -98,7 +98,7 @@ describe("HistoryView Unit Tests", () => { { test: "test" } as any ); expect(historyView["treeProviders"]).toEqual({ test: "test" }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", uss: "search", jobs: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", uss: "search", jobs: "search", cmds: "mvs" }); }); }); @@ -125,11 +125,13 @@ describe("HistoryView Unit Tests", () => { ds: [], uss: [], jobs: [], + cmds: { mvs: [], tso: [], uss: [] }, tab: undefined, selection: { ds: "search", jobs: "search", uss: "search", + cmds: "mvs", }, }); }); @@ -148,7 +150,7 @@ describe("HistoryView Unit Tests", () => { const blockMocks = createBlockMocks(globalMocks); const historyView = await initializeHistoryViewMock(blockMocks, globalMocks); await historyView["onDidReceiveMessage"]({ command: "update-selection", attrs: { type: "uss", selection: "favorites" } }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "favorites" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "favorites", cmds: "mvs" }); }); it("should handle the case where 'add-item' is the command sent", async () => { @@ -159,7 +161,7 @@ describe("HistoryView Unit Tests", () => { const addSearchHistorySpy = jest.spyOn(historyView["treeProviders"].uss, "addSearchHistory"); jest.spyOn(historyView as any, "refreshView").mockImplementation(); await historyView["onDidReceiveMessage"]({ command: "add-item", attrs: { type: "uss" } }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(addSearchHistorySpy).toHaveBeenCalledWith("test"); }); @@ -173,7 +175,7 @@ describe("HistoryView Unit Tests", () => { command: "remove-item", attrs: { type: "ds", selection: "search", selectedItems: { test: "test1" } }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(removeSearchHistorySpy).toHaveBeenCalledWith("test"); }); @@ -187,7 +189,7 @@ describe("HistoryView Unit Tests", () => { command: "remove-item", attrs: { type: "ds", selection: "fileHistory", selectedItems: { test: "test1" } }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(removeFileHistorySpy).toHaveBeenCalledWith("test"); }); @@ -201,7 +203,7 @@ describe("HistoryView Unit Tests", () => { command: "remove-item", attrs: { type: "ds", selection: "favorites", selectedItems: { test: "test1" } }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(showMessageSpy).toHaveBeenCalledTimes(1); }); @@ -217,7 +219,7 @@ describe("HistoryView Unit Tests", () => { command: "remove-item", attrs: { type: "uss", selection: "encodingHistory", selectedItems: { test: "test" } }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(removeEncodingHistorySpy).toHaveBeenCalledWith("zowe.encodingHistory", []); }); @@ -233,7 +235,7 @@ describe("HistoryView Unit Tests", () => { command: "clear-all", attrs: { type: "ds", selection: "search" }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(resetSearchHistorySpy).toHaveBeenCalledTimes(1); }); @@ -249,7 +251,7 @@ describe("HistoryView Unit Tests", () => { command: "clear-all", attrs: { type: "ds", selection: "fileHistory" }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(resetFileHistorySpy).toHaveBeenCalledTimes(1); }); @@ -264,7 +266,7 @@ describe("HistoryView Unit Tests", () => { command: "clear-all", attrs: { type: "ds", selection: "favorites" }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(showMessageSpy).toHaveBeenCalledTimes(2); }); @@ -280,7 +282,7 @@ describe("HistoryView Unit Tests", () => { command: "clear-all", attrs: { type: "ds", selection: "encodingHistory" }, }); - expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search" }); + expect(historyView["currentSelection"]).toEqual({ ds: "search", jobs: "search", uss: "search", cmds: "mvs" }); expect(resetEncodingHistorySpy).toHaveBeenCalledTimes(2); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts index d7794961a5..fb2dea3a70 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts @@ -51,6 +51,7 @@ describe("Test src/shared/extension", () => { _: { _: "_" }, }; const profileMocks = { deleteProfile: jest.fn(), disableValidation: jest.fn(), enableValidation: jest.fn(), refresh: jest.fn() }; + const cmdProviders = { mvs: { issueMvsCommand: jest.fn() }, tso: { issueTsoCommand: jest.fn() }, uss: { issueUnixCommand: jest.fn() } }; const treeProvider = { addFavorite: jest.fn(), deleteSession: jest.fn(), @@ -79,7 +80,7 @@ describe("Test src/shared/extension", () => { }, { name: "zowe.editHistory", - mock: [{ spy: jest.spyOn(SharedHistoryView, "SharedHistoryView"), arg: [test.context, test.value.providers] }], + mock: [{ spy: jest.spyOn(SharedHistoryView, "SharedHistoryView"), arg: [test.context, test.value.providers, cmdProviders] }], }, { name: "zowe.promptCredentials", @@ -254,21 +255,21 @@ describe("Test src/shared/extension", () => { }, { name: "zowe.issueTsoCmd:1", - mock: [{ spy: jest.spyOn(TsoCommandHandler, "getInstance"), arg: [], ret: { issueTsoCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(TsoCommandHandler, "getInstance"), arg: [], ret: cmdProviders.tso }], }, { name: "zowe.issueTsoCmd:2", parm: [], - mock: [{ spy: jest.spyOn(TsoCommandHandler, "getInstance"), arg: [], ret: { issueTsoCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(TsoCommandHandler, "getInstance"), arg: [], ret: cmdProviders.tso }], }, { name: "zowe.issueMvsCmd:1", - mock: [{ spy: jest.spyOn(MvsCommandHandler, "getInstance"), arg: [], ret: { issueMvsCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(MvsCommandHandler, "getInstance"), arg: [], ret: cmdProviders.mvs }], }, { name: "zowe.issueMvsCmd:2", parm: [], - mock: [{ spy: jest.spyOn(MvsCommandHandler, "getInstance"), arg: [], ret: { issueMvsCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(MvsCommandHandler, "getInstance"), arg: [], ret: cmdProviders.mvs }], }, { name: "zowe.selectForCompare", @@ -288,12 +289,12 @@ describe("Test src/shared/extension", () => { }, { name: "zowe.issueUnixCmd:1", - mock: [{ spy: jest.spyOn(UnixCommandHandler, "getInstance"), arg: [], ret: { issueUnixCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(UnixCommandHandler, "getInstance"), arg: [], ret: cmdProviders.uss }], }, { name: "zowe.issueUnixCmd:2", parm: [], - mock: [{ spy: jest.spyOn(UnixCommandHandler, "getInstance"), arg: [], ret: { issueUnixCommand: jest.fn() } }], + mock: [{ spy: jest.spyOn(UnixCommandHandler, "getInstance"), arg: [], ret: cmdProviders.uss }], }, ]; diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index 19c344f0bc..65f78fee34 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -199,6 +199,42 @@ "Profile auth error": "Profile auth error", "Profile is not authenticated, please log in to continue": "Profile is not authenticated, please log in to continue", "Retrieving response from USS list API": "Retrieving response from USS list API", + "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", + "Failed to move {0}/File path": { + "message": "Failed to move {0}", + "comment": [ + "File path" + ] + }, + "Profile does not exist for this file.": "Profile does not exist for this file.", + "Saving USS file...": "Saving USS file...", + "Failed to rename {0}/File path": { + "message": "Failed to rename {0}", + "comment": [ + "File path" + ] + }, + "Failed to delete {0}/File name": { + "message": "Failed to delete {0}", + "comment": [ + "File name" + ] + }, + "No error details given": "No error details given", + "Error fetching destination {0} for paste action: {1}/USS pathError message": { + "message": "Error fetching destination {0} for paste action: {1}", + "comment": [ + "USS path", + "Error message" + ] + }, + "Failed to copy {0} to {1}/Source pathDestination path": { + "message": "Failed to copy {0} to {1}", + "comment": [ + "Source path", + "Destination path" + ] + }, "Downloaded: {0}/Download time": { "message": "Downloaded: {0}", "comment": [ @@ -262,42 +298,6 @@ "initializeUSSFavorites.error.buttonRemove": "initializeUSSFavorites.error.buttonRemove", "File does not exist. It may have been deleted.": "File does not exist. It may have been deleted.", "Pulling from Mainframe...": "Pulling from Mainframe...", - "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", - "Failed to move {0}/File path": { - "message": "Failed to move {0}", - "comment": [ - "File path" - ] - }, - "Profile does not exist for this file.": "Profile does not exist for this file.", - "Saving USS file...": "Saving USS file...", - "Failed to rename {0}/File path": { - "message": "Failed to rename {0}", - "comment": [ - "File path" - ] - }, - "Failed to delete {0}/File name": { - "message": "Failed to delete {0}", - "comment": [ - "File name" - ] - }, - "No error details given": "No error details given", - "Error fetching destination {0} for paste action: {1}/USS pathError message": { - "message": "Error fetching destination {0} for paste action: {1}", - "comment": [ - "USS path", - "Error message" - ] - }, - "Failed to copy {0} to {1}/Source pathDestination path": { - "message": "Failed to copy {0} to {1}", - "comment": [ - "Source path", - "Destination path" - ] - }, "{0} location/Node type": { "message": "{0} location", "comment": [ @@ -907,6 +907,18 @@ ] }, "Error encountered while activating and initializing logger": "Error encountered while activating and initializing logger", + "Insufficient read permissions for {0} in local storage./Local storage key": { + "message": "Insufficient read permissions for {0} in local storage.", + "comment": [ + "Local storage key" + ] + }, + "Insufficient write permissions for {0} in local storage./Local storage key": { + "message": "Insufficient write permissions for {0} in local storage.", + "comment": [ + "Local storage key" + ] + }, "Add Credentials": "Add Credentials", "Add username and password for basic authentication": "Add username and password for basic authentication", "Update stored username and password": "Update stored username and password", @@ -1113,7 +1125,16 @@ "All jobs": "All jobs", "Ascending": "Ascending", "Descending": "Descending", - "Create a new Unix command": "Create a new Unix command", + "Unable to perform this operation due to the following problem.": "Unable to perform this operation due to the following problem.", + "Response From Service": "Response From Service", + "Diagnostic Information": "Diagnostic Information", + "Welcome to the integrated terminal for: {0}/Terminal Name": { + "message": "Welcome to the integrated terminal for: {0}", + "comment": [ + "Terminal Name" + ] + }, + "Profile is invalid": "Profile is invalid", "Issuing commands is not supported for this profile type, {0}./Profile type": { "message": "Issuing commands is not supported for this profile type, {0}.", "comment": [ @@ -1127,22 +1148,13 @@ ] }, "Error preparing SSH connection for issuing UNIX commands, please check SSH profile for correctness.": "Error preparing SSH connection for issuing UNIX commands, please check SSH profile for correctness.", - "No SSH profile found. Please create an SSH profile.": "No SSH profile found. Please create an SSH profile.", - "SSH profile missing connection details. Please update.": "SSH profile missing connection details. Please update.", - "No profiles available.": "No profiles available.", "Redirecting to Home Directory": "Redirecting to Home Directory", - "Zowe Unix Command": "Zowe Unix Command", - "An SSH profile will be used for issuing UNIX commands with the profile {0}.": "An SSH profile will be used for issuing UNIX commands with the profile {0}.", - "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.": "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.", - "Enter the path of the directory in order to execute the command": "Enter the path of the directory in order to execute the command", - "Not implemented yet for profile of type: {0}/Profile type": { - "message": "Not implemented yet for profile of type: {0}", - "comment": [ - "Profile type" - ] - }, - "Select the SSH Profile.": "Select the SSH Profile.", + "SSH profile missing connection details. Please update.": "SSH profile missing connection details. Please update.", + "No SSH profile found. Please create an SSH profile.": "No SSH profile found. Please create an SSH profile.", + "Unix command submitted.": "Unix command submitted.", + "$(plus) Create a new Unix command": "$(plus) Create a new Unix command", "Select the profile to use to submit the Unix command": "Select the profile to use to submit the Unix command", + "Enter or update the Unix command": "Enter or update the Unix command", "Select a Unix command to run against {0} (An option to edit will follow)/Current work directory": { "message": "Select a Unix command to run against {0} (An option to edit will follow)", "comment": [ @@ -1155,12 +1167,20 @@ "Current work directory" ] }, - "Enter or update the Unix command": "Enter or update the Unix command", - "Unix command submitted.": "Unix command submitted.", + "Zowe Unix Command": "Zowe Unix Command", + "An SSH profile will be used for issuing UNIX commands with the profile {0}.": "An SSH profile will be used for issuing UNIX commands with the profile {0}.", + "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.": "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.", + "Enter the path of the directory in order to execute the command": "Enter the path of the directory in order to execute the command", + "Not implemented yet for profile of type: {0}/Profile type": { + "message": "Not implemented yet for profile of type: {0}", + "comment": [ + "Profile type" + ] + }, + "TSO command submitted.": "TSO command submitted.", "Create a new TSO command": "Create a new TSO command", - "Zowe TSO Command": "Zowe TSO Command", "Select the profile to use to submit the TSO command": "Select the profile to use to submit the TSO command", - "Profile is invalid": "Profile is invalid", + "Enter or update the TSO command": "Enter or update the TSO command", "Select a TSO command to run against {0} (An option to edit will follow)/Host name": { "message": "Select a TSO command to run against {0} (An option to edit will follow)", "comment": [ @@ -1173,17 +1193,13 @@ "Host name" ] }, - "Enter or update the TSO command": "Enter or update the TSO command", - "No command entered.": "No command entered.", - "TSO command submitted.": "TSO command submitted.", - "No account number was supplied.": "No account number was supplied.", - "Select the TSO profile to use for account number.": "Select the TSO profile to use for account number.", + "Zowe TSO Command": "Zowe TSO Command", "Account Number": "Account Number", "Enter the account number for the TSO connection.": "Enter the account number for the TSO connection.", - "Operation cancelled.": "Operation cancelled.", + "MVS command submitted.": "MVS command submitted.", "Create a new MVS command": "Create a new MVS command", - "Zowe MVS Command": "Zowe MVS Command", - "Select the profile to use to submit the command": "Select the profile to use to submit the command", + "Select the profile to use to submit the MVS command": "Select the profile to use to submit the MVS command", + "Enter or update the MVS command": "Enter or update the MVS command", "Select an MVS command to run against {0} (An option to edit will follow)/Host name": { "message": "Select an MVS command to run against {0} (An option to edit will follow)", "comment": [ @@ -1196,6 +1212,5 @@ "Host name" ] }, - "Enter or update the MVS command": "Enter or update the MVS command", - "MVS command submitted.": "MVS command submitted." + "Zowe MVS Command": "Zowe MVS Command" } \ No newline at end of file diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index 48f053d259..a9b6b4a0b7 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -314,8 +314,20 @@ "zowe.cliLoggerSetting.presented": { "User has been asked to sync Zowe Explorer log setting with Zowe CL's environment variable.": "" }, - "zowe.commands.history": { - "Toggle if Commands persist locally": "" + "zowe.commands.mvs.history": { + "Toggle if MVS Commands persist locally": "" + }, + "zowe.commands.tso.history": { + "Toggle if TSO Commands persist locally": "" + }, + "zowe.commands.uss.history": { + "Toggle if Unix Commands persist locally": "" + }, + "zowe.commands.alwaysEdit": { + "Allow editing of commands before submitting": "" + }, + "zowe.commands.useIntegratedTerminals": { + "Allow commands to be executed using integrated terminals": "" }, "zowe.automaticProfileValidation": { "Allow automatic validation of profiles.": "" @@ -541,6 +553,15 @@ "Profile auth error": "", "Profile is not authenticated, please log in to continue": "", "Retrieving response from USS list API": "", + "The 'move' function is not implemented for this USS API.": "", + "Failed to move {0}": "", + "Profile does not exist for this file.": "", + "Saving USS file...": "", + "Failed to rename {0}": "", + "Failed to delete {0}": "", + "No error details given": "", + "Error fetching destination {0} for paste action: {1}": "", + "Failed to copy {0} to {1}": "", "Downloaded: {0}": "", "Encoding: {0}": "", "Binary": "", @@ -568,15 +589,6 @@ "initializeUSSFavorites.error.buttonRemove": "", "File does not exist. It may have been deleted.": "", "Pulling from Mainframe...": "", - "The 'move' function is not implemented for this USS API.": "", - "Failed to move {0}": "", - "Profile does not exist for this file.": "", - "Saving USS file...": "", - "Failed to rename {0}": "", - "Failed to delete {0}": "", - "No error details given": "", - "Error fetching destination {0} for paste action: {1}": "", - "Failed to copy {0} to {1}": "", "{0} location": "", "Choose a location to create the {0}": "", "Name of file or directory": "", @@ -816,6 +828,8 @@ "Percent Complete": "", "Failed to upload changes for {0}: {1}": "", "Error encountered while activating and initializing logger": "", + "Insufficient read permissions for {0} in local storage.": "", + "Insufficient write permissions for {0} in local storage.": "", "Add Credentials": "", "Add username and password for basic authentication": "", "Update stored username and password": "", @@ -914,43 +928,41 @@ "All jobs": "", "Ascending": "", "Descending": "", - "Create a new Unix command": "", + "Unable to perform this operation due to the following problem.": "", + "Response From Service": "", + "Diagnostic Information": "", + "Welcome to the integrated terminal for: {0}": "", + "Profile is invalid": "", "Issuing commands is not supported for this profile type, {0}.": "", "Issuing UNIX commands is not supported for this profile type, {0}.": "", "Error preparing SSH connection for issuing UNIX commands, please check SSH profile for correctness.": "", - "No SSH profile found. Please create an SSH profile.": "", - "SSH profile missing connection details. Please update.": "", - "No profiles available.": "", "Redirecting to Home Directory": "", + "SSH profile missing connection details. Please update.": "", + "No SSH profile found. Please create an SSH profile.": "", + "Unix command submitted.": "", + "$(plus) Create a new Unix command": "", + "Select the profile to use to submit the Unix command": "", + "Enter or update the Unix command": "", + "Select a Unix command to run against {0} (An option to edit will follow)": "", + "Select a Unix command to run immediately against {0}": "", "Zowe Unix Command": "", "An SSH profile will be used for issuing UNIX commands with the profile {0}.": "", "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.": "", "Enter the path of the directory in order to execute the command": "", - "Select the SSH Profile.": "", - "Select the profile to use to submit the Unix command": "", - "Select a Unix command to run against {0} (An option to edit will follow)": "", - "Select a Unix command to run immediately against {0}": "", - "Enter or update the Unix command": "", - "Unix command submitted.": "", + "TSO command submitted.": "", "Create a new TSO command": "", - "Zowe TSO Command": "", "Select the profile to use to submit the TSO command": "", - "Profile is invalid": "", + "Enter or update the TSO command": "", "Select a TSO command to run against {0} (An option to edit will follow)": "", "Select a TSO command to run immediately against {0}": "", - "Enter or update the TSO command": "", - "No command entered.": "", - "TSO command submitted.": "", - "No account number was supplied.": "", - "Select the TSO profile to use for account number.": "", + "Zowe TSO Command": "", "Account Number": "", "Enter the account number for the TSO connection.": "", - "Operation cancelled.": "", + "MVS command submitted.": "", "Create a new MVS command": "", - "Zowe MVS Command": "", - "Select the profile to use to submit the command": "", + "Select the profile to use to submit the MVS command": "", + "Enter or update the MVS command": "", "Select an MVS command to run against {0} (An option to edit will follow)": "", "Select an MVS command to run immediately against {0}": "", - "Enter or update the MVS command": "", - "MVS command submitted.": "" + "Zowe MVS Command": "" } diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index 9310a620f8..176a06043a 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -1751,18 +1751,40 @@ "description": "%zowe.jobs.confirmSubmission%", "scope": "window" }, + "zowe.commands.useIntegratedTerminals": { + "type": "boolean", + "default": false, + "description": "%zowe.commands.useIntegratedTerminals%", + "scope": "window" + }, "zowe.commands.alwaysEdit": { "type": "boolean", "default": true, - "description": "Allow editing of commands before submitting", + "description": "%zowe.commands.alwaysEdit%", "scope": "window" }, - "zowe.commands.history": { + "zowe.commands.mvs.history": { + "default": { + "persistence": true, + "history": [] + }, + "description": "%zowe.commands.mvs.history%", + "scope": "application" + }, + "zowe.commands.tso.history": { + "default": { + "persistence": true, + "history": [] + }, + "description": "%zowe.commands.tso.history%", + "scope": "application" + }, + "zowe.commands.uss.history": { "default": { "persistence": true, "history": [] }, - "description": "%zowe.commands.history%", + "description": "%zowe.commands.uss.history%", "scope": "application" }, "zowe.automaticProfileValidation": { diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index 3c5142cf04..ac35706d78 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -104,7 +104,11 @@ "zowe.logger.error.description": "Messages about serious problem occurrences.", "zowe.logger.fatal.description": "Messages about catastraphic error events.", "zowe.cliLoggerSetting.presented": "User has been asked to sync Zowe Explorer log setting with Zowe CL's environment variable.", - "zowe.commands.history": "Toggle if Commands persist locally", + "zowe.commands.mvs.history": "Toggle if MVS Commands persist locally", + "zowe.commands.tso.history": "Toggle if TSO Commands persist locally", + "zowe.commands.uss.history": "Toggle if Unix Commands persist locally", + "zowe.commands.alwaysEdit": "Allow editing of commands before submitting", + "zowe.commands.useIntegratedTerminals": "Allow commands to be executed using integrated terminals", "zowe.automaticProfileValidation": "Allow automatic validation of profiles.", "zowe.pollInterval.info": "Default interval (in milliseconds) when polling spool files.", "zowe.security.checkForCustomCredentialManagers": "Check for any installed VS Code extensions for handling credentials when activating Zowe Explorer", diff --git a/packages/zowe-explorer/src/commands/MvsCommandHandler.ts b/packages/zowe-explorer/src/commands/MvsCommandHandler.ts index cccb993c35..0296f5e817 100644 --- a/packages/zowe-explorer/src/commands/MvsCommandHandler.ts +++ b/packages/zowe-explorer/src/commands/MvsCommandHandler.ts @@ -10,17 +10,15 @@ */ import * as vscode from "vscode"; -import { Validation, imperative, IZoweTreeNode, Gui, ZoweExplorerApiType } from "@zowe/zowe-explorer-api"; -import { ZoweCommandProvider } from "./ZoweCommandProvider"; +import { Validation, imperative, IZoweTreeNode, Gui, PersistenceSchemaEnum, ZoweExplorerApiType } from "@zowe/zowe-explorer-api"; +import { ICommandProviderDialogs, ZoweCommandProvider } from "./ZoweCommandProvider"; import { ZoweLogger } from "../tools/ZoweLogger"; -import { Profiles } from "../configuration/Profiles"; import { ZoweExplorerApiRegister } from "../extending/ZoweExplorerApiRegister"; -import { ProfileManagement } from "../management/ProfileManagement"; -import { Constants } from "../configuration/Constants"; -import { SettingsConfig } from "../configuration/SettingsConfig"; -import { FilterDescriptor, FilterItem } from "../management/FilterManagement"; import { AuthUtils } from "../utils/AuthUtils"; import { Definitions } from "../configuration/Definitions"; +import { ZowePersistentFilters } from "../tools/ZowePersistentFilters"; +import { SettingsConfig } from "../configuration/SettingsConfig"; +import { Constants } from "../configuration/Constants"; /** * Provides a class that manages submitting a command on the server @@ -42,13 +40,32 @@ export class MvsCommandHandler extends ZoweCommandProvider { return this.instance; } - private static readonly defaultDialogText: string = `$(plus) ${vscode.l10n.t("Create a new MVS command")}`; + public readonly controller: AbortController = new AbortController(); + public readonly dialogs: ICommandProviderDialogs = { + commandSubmitted: vscode.l10n.t("MVS command submitted."), + defaultText: `$(plus) ${vscode.l10n.t("Create a new MVS command")}`, + selectProfile: vscode.l10n.t("Select the profile to use to submit the MVS command"), + searchCommand: vscode.l10n.t("Enter or update the MVS command"), + writeCommand: (options) => + vscode.l10n.t({ + message: "Select an MVS command to run against {0} (An option to edit will follow)", + args: options, + comment: ["Host name"], + }), + selectCommand: (options) => + vscode.l10n.t({ + message: "Select an MVS command to run immediately against {0}", + args: options, + comment: ["Host name"], + }), + }; + + public history: ZowePersistentFilters; private static instance: MvsCommandHandler; - public outputChannel: vscode.OutputChannel; public constructor() { - super(); - this.outputChannel = Gui.createOutputChannel(vscode.l10n.t("Zowe MVS Command")); + super(vscode.l10n.t("Zowe MVS Command")); + this.history = new ZowePersistentFilters(PersistenceSchemaEnum.MvsCommands, ZoweCommandProvider.totalFilters); } /** @@ -59,7 +76,6 @@ export class MvsCommandHandler extends ZoweCommandProvider { */ public async issueMvsCommand(session?: imperative.Session, command?: string, node?: IZoweTreeNode): Promise { ZoweLogger.trace("MvsCommandHandler.issueMvsCommand called."); - const profiles = Profiles.getInstance(); let profile: imperative.IProfileLoaded; if (node) { await this.checkCurrentProfile(node); @@ -71,49 +87,26 @@ export class MvsCommandHandler extends ZoweCommandProvider { } } if (!session) { - const allProfiles = profiles.allProfiles; - const profileNamesList = ProfileManagement.getRegisteredProfileNameList(Definitions.Trees.MVS); - if (profileNamesList.length) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: vscode.l10n.t("Select the profile to use to submit the command"), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(vscode.l10n.t("Operation cancelled")); - return; - } - profile = allProfiles.filter((temprofile) => temprofile.name === sesName)[0]; - if (!node) { - await profiles.checkCurrentProfile(profile); - } - if (profiles.validProfile !== Validation.ValidationType.INVALID) { - session = ZoweExplorerApiRegister.getMvsApi(profile).getSession(); - } else { - Gui.errorMessage(vscode.l10n.t("Profile is invalid")); - return; - } - } else { - Gui.showMessage(vscode.l10n.t("No profiles available")); + profile = await this.selectNodeProfile(Definitions.Trees.MVS); + if (!profile) { return; } } else { profile = node.getProfile(); } try { - if (profiles.validProfile !== Validation.ValidationType.INVALID) { + if (this.profileInstance.validProfile !== Validation.ValidationType.INVALID) { const commandApi = ZoweExplorerApiRegister.getInstance().getCommandApi(profile); if (commandApi) { - let command1: string = command; - if (!command) { - command1 = await this.getQuickPick(session && session.ISession ? session.ISession.hostname : "unknown"); + const iTerms = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_INTEGRATED_TERMINALS); + if (!command && !iTerms) { + command = await this.getQuickPick([session && session.ISession ? session.ISession.hostname : "unknown"]); } - await this.issueCommand(profile, command1); - } else { - Gui.errorMessage(vscode.l10n.t("Profile is invalid")); - return; + await this.issueCommand(profile, command ?? ""); } + } else { + Gui.errorMessage(vscode.l10n.t("Profile is invalid")); + return; } } catch (error) { if (error.toString().includes("non-existing")) { @@ -131,92 +124,18 @@ export class MvsCommandHandler extends ZoweCommandProvider { } } - private async getQuickPick(hostname: string): Promise { - ZoweLogger.trace("MvsCommandHandler.getQuickPick called."); - let response = ""; - const alwaysEdit: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_ALWAYS_EDIT); - if (this.history.getSearchHistory().length > 0) { - const createPick = new FilterDescriptor(MvsCommandHandler.defaultDialogText); - const items: vscode.QuickPickItem[] = this.history.getSearchHistory().map((element) => new FilterItem({ text: element })); - const quickpick = Gui.createQuickPick(); - quickpick.placeholder = alwaysEdit - ? vscode.l10n.t({ - message: "Select an MVS command to run against {0} (An option to edit will follow)", - args: [hostname], - comment: ["Host name"], - }) - : vscode.l10n.t({ - message: "Select an MVS command to run immediately against {0}", - args: [hostname], - comment: ["Host name"], - }); - - quickpick.items = [createPick, ...items]; - quickpick.ignoreFocusOut = true; - quickpick.show(); - const choice = await Gui.resolveQuickPick(quickpick); - quickpick.hide(); - if (!choice) { - Gui.showMessage(vscode.l10n.t("No selection made. Operation cancelled.")); - return; - } - if (choice instanceof FilterDescriptor) { - if (quickpick.value) { - response = quickpick.value; - } - } else { - response = choice.label; - } - } - if (!response || alwaysEdit) { - // manually entering a search - const options2: vscode.InputBoxOptions = { - prompt: vscode.l10n.t("Enter or update the MVS command"), - value: response, - valueSelection: response ? [response.length, response.length] : undefined, - }; - // get user input - response = await Gui.showInputBox(options2); - if (!response) { - Gui.showMessage(vscode.l10n.t("No command entered.")); - return; - } + public formatCommandLine(command: string): string { + if (command.startsWith("/")) { + command = command.substring(1); } - return response; + return `> ${command}`; } - /** - * Allow the user to submit an MVS Console command to the selected server. Response is written - * to the output channel. - * @param session The Session object - * @param command the command string - */ - private async issueCommand(profile: imperative.IProfileLoaded, command: string): Promise { - ZoweLogger.trace("MvsCommandHandler.issueCommand called."); - try { - if (command) { - // If the user has started their command with a / then remove it - if (command.startsWith("/")) { - command = command.substring(1); - } - this.outputChannel.appendLine(`> ${command}`); - const submitResponse = await Gui.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t("MVS command submitted."), - }, - () => { - return ZoweExplorerApiRegister.getCommandApi(profile).issueMvsCommand(command, profile.profile?.consoleName); - } - ); - if (submitResponse.success) { - this.outputChannel.appendLine(submitResponse.commandResponse); - this.outputChannel.show(true); - } - } - } catch (error) { - await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile }); + public async runCommand(profile: imperative.IProfileLoaded, command: string): Promise { + if (command.startsWith("/")) { + command = command.substring(1); } - this.history.addSearchHistory(command); + const response = await ZoweExplorerApiRegister.getCommandApi(profile).issueMvsCommand(command, profile.profile?.consoleName); + return response.commandResponse; } } diff --git a/packages/zowe-explorer/src/commands/TsoCommandHandler.ts b/packages/zowe-explorer/src/commands/TsoCommandHandler.ts index 56c8815da9..766aed15b8 100644 --- a/packages/zowe-explorer/src/commands/TsoCommandHandler.ts +++ b/packages/zowe-explorer/src/commands/TsoCommandHandler.ts @@ -11,17 +11,15 @@ import * as vscode from "vscode"; import * as zostso from "@zowe/zos-tso-for-zowe-sdk"; -import { Gui, Validation, imperative, IZoweTreeNode, ZoweExplorerApiType } from "@zowe/zowe-explorer-api"; -import { ZoweCommandProvider } from "./ZoweCommandProvider"; +import { Gui, Validation, imperative, IZoweTreeNode, PersistenceSchemaEnum, ZoweExplorerApiType } from "@zowe/zowe-explorer-api"; +import { ICommandProviderDialogs, ZoweCommandProvider } from "./ZoweCommandProvider"; import { ZoweLogger } from "../tools/ZoweLogger"; -import { Profiles } from "../configuration/Profiles"; import { ZoweExplorerApiRegister } from "../extending/ZoweExplorerApiRegister"; -import { ProfileManagement } from "../management/ProfileManagement"; -import { Constants } from "../configuration/Constants"; -import { SettingsConfig } from "../configuration/SettingsConfig"; -import { FilterDescriptor, FilterItem } from "../management/FilterManagement"; import { AuthUtils } from "../utils/AuthUtils"; import { Definitions } from "../configuration/Definitions"; +import { ZowePersistentFilters } from "../tools/ZowePersistentFilters"; +import { SettingsConfig } from "../configuration/SettingsConfig"; +import { Constants } from "../configuration/Constants"; /** * Provides a class that manages submitting a TSO command on the server @@ -43,13 +41,34 @@ export class TsoCommandHandler extends ZoweCommandProvider { return this.instance; } - private static readonly defaultDialogText: string = `$(plus) ${vscode.l10n.t("Create a new TSO command")}`; + public history: ZowePersistentFilters; private static instance: TsoCommandHandler; - public outputChannel: vscode.OutputChannel; + + public readonly controller: AbortController = new AbortController(); + public readonly dialogs: ICommandProviderDialogs = { + commandSubmitted: vscode.l10n.t("TSO command submitted."), + defaultText: `$(plus) ${vscode.l10n.t("Create a new TSO command")}`, + selectProfile: vscode.l10n.t("Select the profile to use to submit the TSO command"), + searchCommand: vscode.l10n.t("Enter or update the TSO command"), + writeCommand: (options) => + vscode.l10n.t({ + message: "Select a TSO command to run against {0} (An option to edit will follow)", + args: options, + comment: ["Host name"], + }), + selectCommand: (options) => + vscode.l10n.t({ + message: "Select a TSO command to run immediately against {0}", + args: options, + comment: ["Host name"], + }), + }; + + public tsoParams: zostso.IStartTsoParms; public constructor() { - super(); - this.outputChannel = Gui.createOutputChannel(vscode.l10n.t("Zowe TSO Command")); + super(vscode.l10n.t("Zowe TSO Command")); + this.history = new ZowePersistentFilters(PersistenceSchemaEnum.TsoCommands, ZoweCommandProvider.totalFilters); } /** @@ -60,7 +79,6 @@ export class TsoCommandHandler extends ZoweCommandProvider { */ public async issueTsoCommand(session?: imperative.Session, command?: string, node?: IZoweTreeNode): Promise { ZoweLogger.trace("TsoCommandHandler.issueTsoCommand called."); - const profiles = Profiles.getInstance(); let profile: imperative.IProfileLoaded; if (node) { await this.checkCurrentProfile(node); @@ -72,56 +90,32 @@ export class TsoCommandHandler extends ZoweCommandProvider { } } if (!session) { - const profileNamesList = ProfileManagement.getRegisteredProfileNameList(Definitions.Trees.MVS); - if (profileNamesList.length > 0) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: vscode.l10n.t("Select the profile to use to submit the TSO command"), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(vscode.l10n.t("Operation cancelled")); - return; - } - const allProfiles = profiles.allProfiles; - profile = allProfiles.filter((temprofile) => temprofile.name === sesName)[0]; - if (!node) { - await profiles.checkCurrentProfile(profile); - } - if (profiles.validProfile !== Validation.ValidationType.INVALID) { - session = ZoweExplorerApiRegister.getMvsApi(profile).getSession(); - } else { - Gui.errorMessage(vscode.l10n.t("Profile is invalid")); - return; - } - } else { - Gui.showMessage(vscode.l10n.t("No profiles available")); + profile = await this.selectNodeProfile(Definitions.Trees.MVS); + if (!profile) { return; } } else { profile = node.getProfile(); } try { - if (profiles.validProfile !== Validation.ValidationType.INVALID) { + if (this.profileInstance.validProfile !== Validation.ValidationType.INVALID) { const commandApi = ZoweExplorerApiRegister.getInstance().getCommandApi(profile); if (commandApi) { - let tsoParams: zostso.IStartTsoParms; if (profile.type === "zosmf") { - tsoParams = await this.getTsoParams(); - if (!tsoParams) { + this.tsoParams = await this.getTsoParams(); + if (!this.tsoParams) { return; } } - let command1: string = command; - if (!command) { - command1 = await this.getQuickPick(session && session.ISession ? session.ISession.hostname : "unknown"); + const iTerms = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_INTEGRATED_TERMINALS); + if (!command && !iTerms) { + command = await this.getQuickPick([session && session.ISession ? session.ISession.hostname : "unknown"]); } - await this.issueCommand(command1, profile, tsoParams); - } else { - Gui.errorMessage(vscode.l10n.t("Profile is invalid")); - return; + await this.issueCommand(profile, command ?? ""); } + } else { + Gui.errorMessage(vscode.l10n.t("Profile is invalid")); + return; } } catch (error) { if (error.toString().includes("non-existing")) { @@ -139,125 +133,19 @@ export class TsoCommandHandler extends ZoweCommandProvider { } } - private async getQuickPick(hostname: string): Promise { - ZoweLogger.trace("TsoCommandHandler.getQuickPick called."); - let response = ""; - const alwaysEdit: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_ALWAYS_EDIT); - if (this.history.getSearchHistory().length > 0) { - const createPick = new FilterDescriptor(TsoCommandHandler.defaultDialogText); - const items: vscode.QuickPickItem[] = this.history.getSearchHistory().map((element) => new FilterItem({ text: element })); - const quickpick = Gui.createQuickPick(); - quickpick.placeholder = alwaysEdit - ? vscode.l10n.t({ - message: "Select a TSO command to run against {0} (An option to edit will follow)", - args: [hostname], - comment: ["Host name"], - }) - : vscode.l10n.t({ - message: "Select a TSO command to run immediately against {0}", - args: [hostname], - comment: ["Host name"], - }); - - quickpick.items = [createPick, ...items]; - quickpick.ignoreFocusOut = true; - quickpick.show(); - const choice = await Gui.resolveQuickPick(quickpick); - quickpick.hide(); - if (!choice) { - Gui.showMessage(vscode.l10n.t("No selection made. Operation cancelled.")); - return; - } - if (choice instanceof FilterDescriptor) { - if (quickpick.value) { - response = quickpick.value; - } - } else { - response = choice.label; - } - } - if (!response || alwaysEdit) { - // manually entering a search - const options2: vscode.InputBoxOptions = { - prompt: vscode.l10n.t("Enter or update the TSO command"), - value: response, - valueSelection: response ? [response.length, response.length] : undefined, - }; - // get user input - response = await Gui.showInputBox(options2); - if (!response) { - Gui.showMessage(vscode.l10n.t("No command entered.")); - return; - } - } - return response; - } - /** - * Allow the user to submit an TSO command to the selected server. Response is written - * to the output channel. - * @param command the command string - * @param profile profile to be used - * @param tsoParams parameters (from TSO profile, when used) - */ - private async issueCommand(command: string, profile: imperative.IProfileLoaded, tsoParams?: zostso.IStartTsoParms): Promise { - ZoweLogger.trace("TsoCommandHandler.issueCommand called."); - try { - if (command) { - // If the user has started their command with a / then remove it - if (command.startsWith("/")) { - command = command.substring(1); - } - this.outputChannel.appendLine(`> ${command}`); - const submitResponse = await Gui.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t("TSO command submitted."), - }, - () => { - return ZoweExplorerApiRegister.getCommandApi(profile).issueTsoCommandWithParms(command, tsoParams); - } - ); - if (submitResponse.success) { - this.outputChannel.appendLine(submitResponse.commandResponse); - this.outputChannel.show(true); - } - } - this.history.addSearchHistory(command); - } catch (error) { - if (error.toString().includes("account number")) { - const message = vscode.l10n.t("No account number was supplied."); - ZoweLogger.error(message); - Gui.errorMessage(message); - } else { - await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile }); - } + public formatCommandLine(command: string): string { + if (command.startsWith("/")) { + command = command.substring(1); } + return `> ${command}`; } - private async selectTsoProfile(tsoProfiles: imperative.IProfileLoaded[] = []): Promise { - ZoweLogger.trace("TsoCommandHandler.selectTsoProfile called."); - let tsoProfile: imperative.IProfileLoaded; - if (tsoProfiles.length > 1) { - const tsoProfileNamesList = tsoProfiles.map((temprofile) => { - return temprofile.name; - }); - if (tsoProfileNamesList.length) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: vscode.l10n.t("Select the TSO profile to use for account number."), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(tsoProfileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(vscode.l10n.t("Operation cancelled")); - return; - } - tsoProfile = tsoProfiles.filter((temprofile) => temprofile.name === sesName)[0]; - } - } else if (tsoProfiles.length > 0) { - tsoProfile = tsoProfiles[0]; + public async runCommand(profile: imperative.IProfileLoaded, command: string): Promise { + if (command.startsWith("/")) { + command = command.substring(1); } - return tsoProfile; + const response = await ZoweExplorerApiRegister.getCommandApi(profile).issueTsoCommandWithParms(command, this.tsoParams); + return response.commandResponse; } /** @@ -267,7 +155,7 @@ export class TsoCommandHandler extends ZoweCommandProvider { */ private async getTsoParams(): Promise { ZoweLogger.trace("TsoCommandHandler.getTsoParams called."); - const profileInfo = await Profiles.getInstance().getProfileInfo(); + const profileInfo = await this.profileInstance.getProfileInfo(); let tsoParms: zostso.IStartTsoParms = {}; // Keys in the IStartTsoParms interface @@ -276,7 +164,7 @@ export class TsoCommandHandler extends ZoweCommandProvider { const profiles = profileInfo.getAllProfiles("tso"); let tsoProfile: imperative.IProfileLoaded; if (profiles.length > 0) { - tsoProfile = await this.selectTsoProfile(profiles.map((p) => imperative.ProfileInfo.profAttrsToProfLoaded(p))); + tsoProfile = await this.selectServiceProfile(profiles.map((p) => imperative.ProfileInfo.profAttrsToProfLoaded(p))); if (tsoProfile != null) { const prof = profileInfo.mergeArgsForProfile(tsoProfile.profile as imperative.IProfAttrs); iStartTso.forEach((p) => (tsoProfile.profile[p] = prof.knownArgs.find((a) => a.argName === p)?.argValue)); @@ -301,7 +189,7 @@ export class TsoCommandHandler extends ZoweCommandProvider { }; tsoParms.account = await Gui.showInputBox(InputBoxOptions); if (!tsoParms.account) { - Gui.showMessage(vscode.l10n.t("Operation cancelled.")); + ZoweLogger.info(this.operationCancelled); return; } } diff --git a/packages/zowe-explorer/src/commands/UnixCommandHandler.ts b/packages/zowe-explorer/src/commands/UnixCommandHandler.ts index 51726100e5..472f4badfc 100644 --- a/packages/zowe-explorer/src/commands/UnixCommandHandler.ts +++ b/packages/zowe-explorer/src/commands/UnixCommandHandler.ts @@ -11,17 +11,15 @@ import * as vscode from "vscode"; import * as zosuss from "@zowe/zos-uss-for-zowe-sdk"; -import { ZoweCommandProvider } from "./ZoweCommandProvider"; -import { Gui, IZoweTreeNode, ZoweExplorerApiType, imperative } from "@zowe/zowe-explorer-api"; -import { Profiles } from "../configuration/Profiles"; +import { ICommandProviderDialogs, ZoweCommandProvider } from "./ZoweCommandProvider"; +import { Gui, IZoweTreeNode, PersistenceSchemaEnum, Validation, ZoweExplorerApiType, imperative } from "@zowe/zowe-explorer-api"; import { ZoweExplorerApiRegister } from "../extending/ZoweExplorerApiRegister"; import { ZoweLogger } from "../tools/ZoweLogger"; -import { ProfileManagement } from "../management/ProfileManagement"; -import { Constants } from "../configuration/Constants"; -import { SettingsConfig } from "../configuration/SettingsConfig"; -import { FilterDescriptor, FilterItem } from "../management/FilterManagement"; import { AuthUtils } from "../utils/AuthUtils"; import { Definitions } from "../configuration/Definitions"; +import { ZowePersistentFilters } from "../tools/ZowePersistentFilters"; +import { SettingsConfig } from "../configuration/SettingsConfig"; +import { Constants } from "../configuration/Constants"; /** * Provides a class that manages submitting a Unix command on the server @@ -43,219 +41,194 @@ export class UnixCommandHandler extends ZoweCommandProvider { return this.instance; } - private static readonly defaultDialogText: string = `$(plus) ${vscode.l10n.t("Create a new Unix command")}`; + public history: ZowePersistentFilters; private static instance: UnixCommandHandler; - private serviceProf: imperative.IProfileLoaded = undefined; + private nodeProfile: imperative.IProfileLoaded = undefined; private unixCmdMsgs = { - opCancelledMsg: vscode.l10n.t("Operation cancelled"), - issueCmdNotSupportedMsg: vscode.l10n.t({ - message: "Issuing commands is not supported for this profile type, {0}.", - args: [this.serviceProf?.type], - comment: ["Profile type"], - }), - issueUnixCmdNotSupportedMsg: vscode.l10n.t({ - message: "Issuing UNIX commands is not supported for this profile type, {0}.", - args: [this.serviceProf?.type], - comment: ["Profile type"], - }), + issueCmdNotSupportedMsg: (profileType: string) => + vscode.l10n.t({ + message: "Issuing commands is not supported for this profile type, {0}.", + args: [profileType], + comment: ["Profile type"], + }), + issueUnixCmdNotSupportedMsg: (profileType: string) => + vscode.l10n.t({ + message: "Issuing UNIX commands is not supported for this profile type, {0}.", + args: [profileType], + comment: ["Profile type"], + }), sshSessionErrorMsg: vscode.l10n.t("Error preparing SSH connection for issuing UNIX commands, please check SSH profile for correctness."), - sshProfNotFoundMsg: vscode.l10n.t("No SSH profile found. Please create an SSH profile."), - sshProfMissingInfoMsg: vscode.l10n.t("SSH profile missing connection details. Please update."), - noProfilesAvailableMsg: vscode.l10n.t("No profiles available."), cwdRedirectingMsg: vscode.l10n.t("Redirecting to Home Directory"), + sshProfMissingInfoMsg: vscode.l10n.t("SSH profile missing connection details. Please update."), + sshProfNotFoundMsg: vscode.l10n.t("No SSH profile found. Please create an SSH profile."), }; - public profileInstance = Profiles.getInstance(); - public outputChannel: vscode.OutputChannel; + public sshCwd: string; public sshSession: zosuss.SshSession; public sshProfile: imperative.IProfileLoaded; public isSshRequiredForProf: boolean = false; + public readonly controller: AbortController = new AbortController(); + public readonly dialogs: ICommandProviderDialogs = { + commandSubmitted: vscode.l10n.t("Unix command submitted."), + defaultText: vscode.l10n.t("$(plus) Create a new Unix command"), + selectProfile: vscode.l10n.t("Select the profile to use to submit the Unix command"), + searchCommand: vscode.l10n.t("Enter or update the Unix command"), + writeCommand: (options) => + vscode.l10n.t({ + message: "Select a Unix command to run against {0} (An option to edit will follow)", + args: options, + comment: ["Current work directory"], + }), + selectCommand: (options) => + vscode.l10n.t({ + message: "Select a Unix command to run immediately against {0}", + args: options, + comment: ["Current work directory"], + }), + }; + public constructor() { - super(); - this.outputChannel = Gui.createOutputChannel(vscode.l10n.t("Zowe Unix Command")); + super(vscode.l10n.t("Zowe Unix Command")); + this.history = new ZowePersistentFilters(PersistenceSchemaEnum.UssCommands, ZoweCommandProvider.totalFilters); } public async issueUnixCommand(node?: IZoweTreeNode, command?: string): Promise { - let cwd: string; + ZoweLogger.trace("UnixCommandHandler.issueUnixCommand called."); + if (node) { - this.serviceProf = node.getProfile(); - cwd = node.fullPath; + await this.checkCurrentProfile(node); + this.nodeProfile = node.getProfile(); + this.sshCwd = node.fullPath; } - if (!this.serviceProf) { - this.serviceProf = await this.userSelectProfile(); - if (!this.serviceProf) { + if (!this.nodeProfile) { + this.nodeProfile = await this.selectNodeProfile(Definitions.Trees.USS); + if (!this.nodeProfile) { return; } } try { // check for availability of all needed ZE APIs for issuing UNIX commands - const commandApi = ZoweExplorerApiRegister.getInstance().getCommandApi(this.serviceProf); + const commandApi = ZoweExplorerApiRegister.getInstance().getCommandApi(this.nodeProfile); if (!commandApi) { - ZoweLogger.error(this.unixCmdMsgs.issueCmdNotSupportedMsg); - Gui.errorMessage(this.unixCmdMsgs.issueCmdNotSupportedMsg); + ZoweLogger.error(this.unixCmdMsgs.issueCmdNotSupportedMsg(this.nodeProfile.type)); + Gui.errorMessage(this.unixCmdMsgs.issueCmdNotSupportedMsg(this.nodeProfile.type)); return; } - if (!ZoweExplorerApiRegister.getCommandApi(this.serviceProf).issueUnixCommand) { - ZoweLogger.error(this.unixCmdMsgs.issueUnixCmdNotSupportedMsg); - Gui.errorMessage( - vscode.l10n.t({ - message: "Issuing UNIX commands is not supported for this profile type, {0}.", - args: [this.serviceProf?.type], - comment: ["Profile type"], - }) - ); - this.serviceProf = undefined; + if (!ZoweExplorerApiRegister.getCommandApi(this.nodeProfile).issueUnixCommand) { + ZoweLogger.error(this.unixCmdMsgs.issueUnixCmdNotSupportedMsg(this.nodeProfile.type)); + Gui.errorMessage(this.unixCmdMsgs.issueUnixCmdNotSupportedMsg(this.nodeProfile.type)); + this.nodeProfile = undefined; return; } try { - this.isSshRequiredForProf = ZoweExplorerApiRegister.getCommandApi(this.serviceProf).sshProfileRequired(); + this.isSshRequiredForProf = ZoweExplorerApiRegister.getCommandApi(this.nodeProfile).sshProfileRequired(); ZoweLogger.info( - vscode.l10n.t("An SSH profile will be used for issuing UNIX commands with the profile {0}.", [this.serviceProf?.name]) + vscode.l10n.t("An SSH profile will be used for issuing UNIX commands with the profile {0}.", [this.nodeProfile?.name]) ); } catch (e) { // error would be due to missing API, assuming SSH profile not required ZoweLogger.warn( vscode.l10n.t( "Error checking if SSH profile type required for issuing UNIX commands, setting requirement to false for profile {0}.", - [this.serviceProf?.name] + [this.nodeProfile?.name] ) ); } - await this.profileInstance.checkCurrentProfile(this.serviceProf); + + await this.profileInstance.checkCurrentProfile(this.nodeProfile); + if (this.profileInstance.validProfile === Validation.ValidationType.INVALID) { + Gui.errorMessage(vscode.l10n.t("Profile is invalid")); + return; + } if (this.isSshRequiredForProf) { - await this.setsshSession(); - if (!this.sshSession) { - this.serviceProf = undefined; + await this.getSshProfile(); + if (!this.sshProfile) { + return; + } + const cmdArgs: imperative.ICommandArguments = this.getSshCmdArgs(this.sshProfile.profile); + // create the SSH session + const sshSessCfg = zosuss.SshSession.createSshSessCfgFromArgs(cmdArgs); + imperative.ConnectionPropsForSessCfg.resolveSessCfgProps(sshSessCfg, cmdArgs); + this.sshSession = new zosuss.SshSession(sshSessCfg); + + const profileStatus = await this.profileInstance.profileValidationHelper( + this.sshProfile, + async (prof: imperative.IProfileLoaded, type: string): Promise => { + if (type !== "ssh" || prof.profile.host !== this.sshSession.ISshSession.hostname) return "unverified"; + return (await zosuss.Shell.isConnectionValid(this.sshSession)) ? "active" : "inactive"; + } + ); + + if (!this.sshSession || profileStatus !== "active") { + this.nodeProfile = undefined; ZoweLogger.error(this.unixCmdMsgs.sshSessionErrorMsg); Gui.errorMessage(this.unixCmdMsgs.sshSessionErrorMsg); return; } } - if (!cwd) { + if (!this.sshCwd) { const options: vscode.InputBoxOptions = { prompt: vscode.l10n.t("Enter the path of the directory in order to execute the command"), }; - cwd = await Gui.showInputBox(options); - if (cwd === "") { + this.sshCwd = await Gui.showInputBox(options); + if (this.sshCwd === "") { ZoweLogger.info(this.unixCmdMsgs.cwdRedirectingMsg); Gui.showMessage(this.unixCmdMsgs.cwdRedirectingMsg); } - if (cwd == undefined) { - this.serviceProf = undefined; - ZoweLogger.info(this.unixCmdMsgs.opCancelledMsg); - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); + if (this.sshCwd == undefined) { + this.nodeProfile = undefined; + ZoweLogger.info(this.operationCancelled); return; } } - if (!command) { - command = await this.getQuickPick(cwd); + const iTerms = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_INTEGRATED_TERMINALS); + if (!command && !iTerms) { + command = await this.getQuickPick([this.sshCwd]); } - await this.issueCommand(this.serviceProf, command, cwd); + await this.issueCommand(this.nodeProfile, command ?? ""); } catch (error) { if (error.toString().includes("non-existing")) { ZoweLogger.error(error); Gui.errorMessage( vscode.l10n.t({ message: "Not implemented yet for profile of type: {0}", - args: [this.serviceProf.type], + args: [this.nodeProfile.type], comment: ["Profile type"], }) ); } else { - await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile: this.serviceProf }); + await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile: this.nodeProfile }); } } } - public checkForSshRequiredForAllTypes(allProfiles: imperative.IProfileLoaded[]): boolean { - const sshReqArray: boolean[] = []; - try { - allProfiles.forEach((p) => { - sshReqArray.push(ZoweExplorerApiRegister.getCommandApi(p).sshProfileRequired()); - }); - sshReqArray.every((v) => v === true); - } catch (error) { - return false; - } - } - - public async setsshSession(): Promise { - ZoweLogger.trace("UnixCommandHandler.setsshSession called."); - await this.getSshProfile(); - if (this.sshProfile) { - const cmdArgs: imperative.ICommandArguments = this.getSshCmdArgs(this.sshProfile.profile); - // create the ssh session - const sshSessCfg = zosuss.SshSession.createSshSessCfgFromArgs(cmdArgs); - imperative.ConnectionPropsForSessCfg.resolveSessCfgProps(sshSessCfg, cmdArgs); - this.sshSession = new zosuss.SshSession(sshSessCfg); - } else { - ZoweLogger.info(this.unixCmdMsgs.opCancelledMsg); - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); - } - } - - private async selectSshProfile(sshProfiles: imperative.IProfileLoaded[] = []): Promise { - ZoweLogger.trace("UnixCommandHandler.selectSshProfile called."); - let sshProfile: imperative.IProfileLoaded; - if (sshProfiles.length > 1) { - const sshProfileNamesList = sshProfiles.map((temprofile) => { - return temprofile.name; - }); - if (sshProfileNamesList.length) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: vscode.l10n.t("Select the SSH Profile."), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(sshProfileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); - return; - } - - sshProfile = sshProfiles.filter((temprofile) => temprofile.name === sesName)[0]; - } - } else if (sshProfiles.length > 0) { - sshProfile = sshProfiles[0]; - } - return sshProfile; - } - private async getSshProfile(): Promise { - ZoweLogger.trace("UnixCommandHandler.getsshProfile called."); const profiles = await this.profileInstance.fetchAllProfilesByType("ssh"); if (!profiles.length) { ZoweLogger.error(this.unixCmdMsgs.sshProfNotFoundMsg); Gui.errorMessage(this.unixCmdMsgs.sshProfNotFoundMsg); return; } - if (profiles.length > 0) { - this.sshProfile = await this.selectSshProfile(profiles); - if (!this.sshProfile) { - return; - } - if (!(this.sshProfile.profile.host && this.sshProfile.profile.port)) { - const currentProfile = await this.profileInstance.getProfileFromConfig(this.sshProfile.name); - const filePath = currentProfile.profLoc.osLoc[0]; - await this.profileInstance.openConfigFile(filePath); - ZoweLogger.error(this.unixCmdMsgs.sshProfMissingInfoMsg); - Gui.errorMessage(this.unixCmdMsgs.sshProfMissingInfoMsg); + this.sshProfile = await this.selectServiceProfile(profiles); + if (!this.sshProfile) { + return; + } + if (!(this.sshProfile.profile.host && this.sshProfile.profile.port)) { + const currentProfile = await this.profileInstance.getProfileFromConfig(this.sshProfile.name); + const filePath = currentProfile.profLoc.osLoc?.[0]; + await this.profileInstance.openConfigFile(filePath); + ZoweLogger.error(this.unixCmdMsgs.sshProfMissingInfoMsg); + Gui.errorMessage(this.unixCmdMsgs.sshProfMissingInfoMsg); + return; + } + if (!(this.sshProfile.profile.user || this.sshProfile.profile.password) && !this.sshProfile.profile.privateKey) { + const prompted = await this.profileInstance.promptCredentials(this.sshProfile); + if (!prompted) { return; } - const baseProfile = Constants.PROFILES_CACHE.getDefaultProfile("base"); - if (baseProfile?.profile?.user && baseProfile?.profile?.password) { - this.sshProfile.profile.user = baseProfile.profile.user; - this.sshProfile.profile.password = baseProfile.profile.password; - } - if (!(this.sshProfile.profile.user || this.sshProfile.profile.password) && !this.sshProfile.profile.privateKey) { - const prompted = await this.profileInstance.promptCredentials(this.sshProfile); - if (!prompted) { - return; - } - } } } @@ -271,114 +244,19 @@ export class UnixCommandHandler extends ZoweCommandProvider { return cmdArgs; } - private async userSelectProfile(): Promise { - const allProfiles = this.profileInstance.allProfiles; - const sshReq = this.checkForSshRequiredForAllTypes(allProfiles); - const profileNamesList = ProfileManagement.getRegisteredProfileNameList(Definitions.Trees.USS); - if (profileNamesList.length) { - if (!sshReq) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: vscode.l10n.t("Select the profile to use to submit the Unix command"), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); - return; - } - return allProfiles.find((temprofile) => temprofile.name === sesName); - } + public formatCommandLine(command: string, profile?: imperative.IProfileLoaded): string { + const prof = this.nodeProfile ?? profile; + const user: string = prof?.profile.user; + if (prof) { + return `> ${user}@${prof.name}:${this.sshCwd ?? "~"} $ ${command}`; } else { - ZoweLogger.info(this.unixCmdMsgs.noProfilesAvailableMsg); - Gui.showMessage(this.unixCmdMsgs.noProfilesAvailableMsg); + return `> ${user}:${this.sshCwd ?? "~"} $ ${command}`; } } - private async getQuickPick(cwd: string): Promise { - ZoweLogger.trace("UnixCommandHandler.getQuickPick called."); - let response = ""; - const alwaysEdit: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_ALWAYS_EDIT); - if (this.history.getSearchHistory().length > 0) { - const createPick = new FilterDescriptor(UnixCommandHandler.defaultDialogText); - const items: vscode.QuickPickItem[] = this.history.getSearchHistory().map((element) => new FilterItem({ text: element })); - const quickpick = Gui.createQuickPick(); - quickpick.placeholder = alwaysEdit - ? vscode.l10n.t({ - message: "Select a Unix command to run against {0} (An option to edit will follow)", - args: [cwd], - comment: ["Current work directory"], - }) - : vscode.l10n.t({ - message: "Select a Unix command to run immediately against {0}", - args: [cwd], - comment: ["Current work directory"], - }); - - quickpick.items = [createPick, ...items]; - quickpick.ignoreFocusOut = true; - quickpick.show(); - const choice = await Gui.resolveQuickPick(quickpick); - quickpick.hide(); - if (!choice) { - this.serviceProf = undefined; - ZoweLogger.info(this.unixCmdMsgs.opCancelledMsg); - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); - return; - } - if (choice instanceof FilterDescriptor) { - if (quickpick.value) { - response = quickpick.value; - } - } else { - response = choice.label; - } - } - if (!response || alwaysEdit) { - const options2: vscode.InputBoxOptions = { - prompt: vscode.l10n.t("Enter or update the Unix command"), - value: response, - valueSelection: response ? [response.length, response.length] : undefined, - }; - // get user input - response = await Gui.showInputBox(options2); - if (!response) { - this.serviceProf = undefined; - ZoweLogger.info(this.unixCmdMsgs.opCancelledMsg); - Gui.showMessage(this.unixCmdMsgs.opCancelledMsg); - return; - } - } - return response; - } - - private async issueCommand(profile: imperative.IProfileLoaded, command: string, cwd: string): Promise { - ZoweLogger.trace("UnixCommandHandler.issueCommand called."); - try { - if (command) { - const user: string = profile.profile.user; - if (this.sshProfile) { - this.outputChannel.appendLine(`> ${user}@${this.sshProfile.name}:${cwd ? cwd : "~"}$ ${command}`); - } else { - this.outputChannel.appendLine(`> ${user}:${cwd ? cwd : "~"}$ ${command}`); - } - const submitResponse = await Gui.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t("Unix command submitted."), - }, - () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return ZoweExplorerApiRegister.getCommandApi(profile).issueUnixCommand(command, cwd, this.sshSession); - } - ); - this.outputChannel.appendLine(submitResponse); - this.outputChannel.show(true); - this.history.addSearchHistory(command); - this.serviceProf = undefined; - } - } catch (error) { - await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile }); - } + public runCommand(profile: imperative.IProfileLoaded, command: string): Promise { + // Clear path and selected profile for follow up commands from the command palette + this.nodeProfile = undefined; + return ZoweExplorerApiRegister.getCommandApi(profile).issueUnixCommand(command, this.sshCwd, this.sshSession); } } diff --git a/packages/zowe-explorer/src/commands/ZoweCommandProvider.ts b/packages/zowe-explorer/src/commands/ZoweCommandProvider.ts index 1ad5f86d9f..77655d92b1 100644 --- a/packages/zowe-explorer/src/commands/ZoweCommandProvider.ts +++ b/packages/zowe-explorer/src/commands/ZoweCommandProvider.ts @@ -10,7 +10,7 @@ */ import * as vscode from "vscode"; -import { IZoweTreeNode, PersistenceSchemaEnum, Validation } from "@zowe/zowe-explorer-api"; +import { Gui, imperative, IZoweTreeNode, Validation, ZoweExplorerApiType } from "@zowe/zowe-explorer-api"; import { ZowePersistentFilters } from "../tools/ZowePersistentFilters"; import { ZoweLogger } from "../tools/ZoweLogger"; import { SharedContext } from "../trees/shared/SharedContext"; @@ -18,18 +18,225 @@ import { Profiles } from "../configuration/Profiles"; import { Constants } from "../configuration/Constants"; import { IconGenerator } from "../icons/IconGenerator"; import { IconUtils } from "../icons/IconUtils"; +import { AuthUtils } from "../utils/AuthUtils"; +import { ProfileManagement } from "../management/ProfileManagement"; +import { Definitions } from "../configuration/Definitions"; +import { SettingsConfig } from "../configuration/SettingsConfig"; +import { FilterDescriptor, FilterItem } from "../management/FilterManagement"; +import { ZoweTerminal } from "../tools/ZoweTerminal"; -export class ZoweCommandProvider { - // eslint-disable-next-line no-magic-numbers - private static readonly totalFilters: number = 10; +export interface ICommandProviderDialogs { + commandSubmitted: string; + searchCommand: string; + selectCommand: (args: string[]) => string; + writeCommand: (args: string[]) => string; + defaultText: string; + selectProfile: string; +} - public history: ZowePersistentFilters; +export abstract class ZoweCommandProvider { + // eslint-disable-next-line no-magic-numbers + public static readonly totalFilters: number = 10; + protected readonly operationCancelled: string = vscode.l10n.t("Operation cancelled"); + public profileInstance: Profiles; + public abstract history: ZowePersistentFilters; // Event Emitters used to notify subscribers that the refresh event has fired public mOnDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); public readonly onDidChangeTreeData: vscode.Event = this.mOnDidChangeTreeData.event; - public constructor() { - this.history = new ZowePersistentFilters(PersistenceSchemaEnum.Commands, ZoweCommandProvider.totalFilters); + public abstract dialogs: ICommandProviderDialogs; + public outputChannel: vscode.OutputChannel; + public terminal: vscode.Terminal; + public pseudoTerminal: ZoweTerminal; + + public constructor(protected terminalName: string) { + this.profileInstance = Profiles.getInstance(); + } + + public abstract formatCommandLine(command: string, profile: imperative.IProfileLoaded): string; + public abstract runCommand(profile: imperative.IProfileLoaded, command: string): Promise; + public abstract controller: AbortController; + + public async issueCommand(profile: imperative.IProfileLoaded, command: string): Promise { + ZoweLogger.trace("ZoweCommandProvider.issueCommand called."); + if (profile == null || command == null) { + return; + } + try { + const iTerms = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_INTEGRATED_TERMINALS); + if (iTerms) { + this.pseudoTerminal = new ZoweTerminal( + this.terminalName, + async (command: string): Promise => { + try { + // We need to await the response, otherwise we can't catch errors thrown + this.history.addSearchHistory(command); + return await this.runCommand(profile, command); + } catch (error) { + if (error instanceof imperative.ImperativeError) { + let formattedError = ""; + formattedError += imperative.TextUtils.chalk.red( + vscode.l10n.t("Unable to perform this operation due to the following problem.") + "\n" + ); + + formattedError += imperative.TextUtils.chalk.red( + error.message.replace(/Rest API failure with HTTP\(S\) status \d\d\d\n/, "") + ); + + const responseTitle = vscode.l10n.t("Response From Service") + "\n"; + if (error.causeErrors) { + try { + const causeErrorsJson = JSON.parse(error.causeErrors); + formattedError += "\n" + imperative.TextUtils.chalk.bold.yellow(responseTitle); + formattedError += imperative.TextUtils.prettyJson(causeErrorsJson, undefined, false, ""); + } catch (parseErr) { + // causeErrors was not JSON. + const causeErrString: string = error.causeErrors.toString(); + if (causeErrString.length > 0) { + // output the text value of causeErrors + formattedError += "\n" + imperative.TextUtils.chalk.bold.yellow(responseTitle); + formattedError += causeErrString; + } + } + } + + const diagInfo: string = error.details.additionalDetails; + if (diagInfo?.length > 0) { + formattedError += imperative.TextUtils.chalk.bold.yellow(`\n${vscode.l10n.t("Diagnostic Information")}\n`); + formattedError += diagInfo; + } + + return formattedError; + } + return error.message; + } + }, + this.controller, + { + message: vscode.l10n.t({ + message: "Welcome to the integrated terminal for: {0}", + args: [this.terminalName], + comment: ["Terminal Name"], + }), + history: [...this.history.getSearchHistory()].reverse(), + startup: command, + formatCommandLine: (cmd: string) => this.formatCommandLine(cmd, profile), + } + ); + this.terminal = vscode.window.createTerminal({ name: `(${profile.name}) ${this.terminalName}`, pty: this.pseudoTerminal }); + this.terminal.show(); + } else { + this.outputChannel ??= Gui.createOutputChannel(this.terminalName); + this.outputChannel.appendLine(this.formatCommandLine(command, profile)); + const response = await Gui.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: this.dialogs.commandSubmitted, + }, + () => { + return this.runCommand(profile, command); + } + ); + this.outputChannel.appendLine(response); + this.outputChannel.show(true); + this.history.addSearchHistory(command); + } + } catch (error) { + await AuthUtils.errorHandling(error, { apiType: ZoweExplorerApiType.Command, profile }); + } + } + + public async selectNodeProfile(cmdTree: Definitions.Trees): Promise { + ZoweLogger.trace("ZoweCommandProvider.selectNodeProfile called."); + + const profileNamesList = ProfileManagement.getRegisteredProfileNameList(cmdTree); + if (profileNamesList.length > 0) { + const quickPickOptions: vscode.QuickPickOptions = { + placeHolder: this.dialogs.selectProfile, + ignoreFocusOut: true, + canPickMany: false, + }; + const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); + if (sesName === undefined) { + return; + } + const profile = this.profileInstance.allProfiles.find((tempProfile) => tempProfile.name === sesName); + await this.profileInstance.checkCurrentProfile(profile); + if (this.profileInstance.validProfile === Validation.ValidationType.INVALID) { + Gui.errorMessage(vscode.l10n.t("Profile is invalid")); + return; + } + return profile; + } else { + Gui.showMessage(vscode.l10n.t("No profiles available")); + } + } + + public async selectServiceProfile(profiles: imperative.IProfileLoaded[] = []): Promise { + ZoweLogger.trace("ZoweCommandProvider.selectServiceProfile called."); + let profile: imperative.IProfileLoaded; + if (profiles.length > 1) { + const profileNamesList = profiles.map((tempProfile) => { + return tempProfile.name; + }); + const quickPickOptions: vscode.QuickPickOptions = { + placeHolder: this.dialogs.selectProfile, + ignoreFocusOut: true, + canPickMany: false, + }; + const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); + if (sesName === undefined) { + ZoweLogger.info(this.operationCancelled); + return; + } + profile = profiles.filter((tempProfile) => tempProfile.name === sesName)[0]; + } else if (profiles.length > 0) { + profile = profiles[0]; + } + return profile; + } + + public async getQuickPick(dialogOptions: string[]): Promise { + ZoweLogger.trace("ZoweCommandProvider.getQuickPick called."); + let response = ""; + const alwaysEdit: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_COMMANDS_ALWAYS_EDIT); + if (this.history.getSearchHistory().length > 0) { + const createPick = new FilterDescriptor(this.dialogs.defaultText); + const items: vscode.QuickPickItem[] = this.history.getSearchHistory().map((element) => new FilterItem({ text: element })); + const quickpick = Gui.createQuickPick(); + quickpick.placeholder = alwaysEdit ? this.dialogs.writeCommand(dialogOptions) : this.dialogs.selectCommand(dialogOptions); + quickpick.items = [createPick, ...items]; + quickpick.ignoreFocusOut = true; + quickpick.show(); + const choice = await Gui.resolveQuickPick(quickpick); + quickpick.hide(); + if (!choice) { + ZoweLogger.info(this.operationCancelled); + return; + } + if (choice instanceof FilterDescriptor) { + if (quickpick.value) { + response = quickpick.value; + } + } else { + response = choice.label; + } + } + if (!response || alwaysEdit) { + // manually entering a search + const options2: vscode.InputBoxOptions = { + prompt: this.dialogs.searchCommand, + value: response, + valueSelection: response ? [response.length, response.length] : undefined, + }; + // get user input + response = await Gui.showInputBox(options2); + if (!response) { + ZoweLogger.info(this.operationCancelled); + return; + } + } + return response; } /** diff --git a/packages/zowe-explorer/src/configuration/Constants.ts b/packages/zowe-explorer/src/configuration/Constants.ts index e55309adfb..581259ae97 100644 --- a/packages/zowe-explorer/src/configuration/Constants.ts +++ b/packages/zowe-explorer/src/configuration/Constants.ts @@ -70,6 +70,7 @@ export class Constants { public static readonly SETTINGS_DS_DEFAULT_EXTENDED = "zowe.ds.default.extended"; public static readonly SETTINGS_DS_DEFAULT_PS = "zowe.ds.default.ps"; public static readonly SETTINGS_DS_TEMPLATES = "zowe.ds.templates"; + public static readonly SETTINGS_COMMANDS_INTEGRATED_TERMINALS = "zowe.commands.useIntegratedTerminals"; public static readonly SETTINGS_COMMANDS_ALWAYS_EDIT = "zowe.commands.alwaysEdit"; public static readonly SETTINGS_AUTOMATIC_PROFILE_VALIDATION = "zowe.automaticProfileValidation"; public static readonly SETTINGS_SECURE_CREDENTIALS_ENABLED = "zowe.security.secureCredentialsEnabled"; @@ -95,7 +96,9 @@ export class Constants { "Zowe-Default-Datasets-PDS": Constants.SETTINGS_DS_DEFAULT_PDS, "Zowe-Default-Datasets-Extended": Constants.SETTINGS_DS_DEFAULT_EXTENDED, "Zowe-Default-Datasets-PS": Constants.SETTINGS_DS_DEFAULT_PS, - "Zowe Commands: History": PersistenceSchemaEnum.Commands, + "Zowe MVS Commands: History": PersistenceSchemaEnum.MvsCommands, + "Zowe TSO Commands: History": PersistenceSchemaEnum.TsoCommands, + "Zowe Unix Commands: History": PersistenceSchemaEnum.UssCommands, "Zowe Commands: Always edit": Constants.SETTINGS_COMMANDS_ALWAYS_EDIT, "Zowe-Automatic-Validation": Constants.SETTINGS_AUTOMATIC_PROFILE_VALIDATION, "Zowe-DS-Persistent": PersistenceSchemaEnum.Dataset, @@ -263,5 +266,6 @@ export class Constants { DS: "ds-panel-tab", USS: "uss-panel-tab", JOBS: "jobs-panel-tab", + CMDS: "cmds-panel-tab", }; } diff --git a/packages/zowe-explorer/src/configuration/Definitions.ts b/packages/zowe-explorer/src/configuration/Definitions.ts index 46eb078898..c69a668987 100644 --- a/packages/zowe-explorer/src/configuration/Definitions.ts +++ b/packages/zowe-explorer/src/configuration/Definitions.ts @@ -15,6 +15,9 @@ import { Types, IZoweTreeNode, imperative, ZosEncoding, IZoweTree } from "@zowe/ import type { DatasetTree } from "../trees/dataset/DatasetTree"; import type { JobTree } from "../trees/job/JobTree"; import type { USSTree } from "../trees/uss/USSTree"; +import type { MvsCommandHandler } from "../commands/MvsCommandHandler"; +import type { TsoCommandHandler } from "../commands/TsoCommandHandler"; +import type { UnixCommandHandler } from "../commands/UnixCommandHandler"; export namespace Definitions { export type LocalFileInfo = { @@ -79,6 +82,11 @@ export namespace Definitions { uss: Types.IZoweUSSTreeType; job: Types.IZoweJobTreeType; } + export interface IZoweCommandProviders { + mvs: MvsCommandHandler; + tso: TsoCommandHandler; + uss: UnixCommandHandler; + } export interface IZoweJobTreeOpts extends IZoweTreeOpts { job?: zosJobs.IJob; } diff --git a/packages/zowe-explorer/src/configuration/Profiles.ts b/packages/zowe-explorer/src/configuration/Profiles.ts index b2f9ce5a29..f05c9739d8 100644 --- a/packages/zowe-explorer/src/configuration/Profiles.ts +++ b/packages/zowe-explorer/src/configuration/Profiles.ts @@ -683,6 +683,33 @@ export class Profiles extends ProfilesCache { await this.openConfigFile(filePath); } + public async profileValidationHelper(theProfile: imperative.IProfileLoaded, getStatus: (...args: unknown[]) => Promise) { + return Gui.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t({ + message: `Validating {0} Profile.`, + args: [theProfile.name], + comment: [`The profile name`], + }), + cancellable: true, + }, + async (progress, token) => { + token.onCancellationRequested(() => { + // will be returned as undefined + Gui.showMessage( + vscode.l10n.t({ + message: `Validating {0} was cancelled.`, + args: [theProfile.name], + comment: [`The profile name`], + }) + ); + }); + return getStatus(theProfile, theProfile.type); + } + ); + } + public async validateProfiles(theProfile: imperative.IProfileLoaded): Promise { ZoweLogger.trace("Profiles.validateProfiles called."); let filteredProfile: Validation.IValidationProfile; @@ -703,30 +730,7 @@ export class Profiles extends ProfilesCache { if (filteredProfile === undefined) { try { if (getSessStatus.getStatus) { - profileStatus = await Gui.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t({ - message: `Validating {0} Profile.`, - args: [theProfile.name], - comment: [`The profile name`], - }), - cancellable: true, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - // will be returned as undefined - Gui.showMessage( - vscode.l10n.t({ - message: `Validating {0} was cancelled.`, - args: [theProfile.name], - comment: [`The profile name`], - }) - ); - }); - return getSessStatus.getStatus(theProfile, theProfile.type); - } - ); + profileStatus = await this.profileValidationHelper(theProfile, getSessStatus.getStatus.bind(getSessStatus)); } else { profileStatus = "unverified"; } diff --git a/packages/zowe-explorer/src/configuration/SettingsConfig.ts b/packages/zowe-explorer/src/configuration/SettingsConfig.ts index 1380139fcd..8ad1c2b1b3 100644 --- a/packages/zowe-explorer/src/configuration/SettingsConfig.ts +++ b/packages/zowe-explorer/src/configuration/SettingsConfig.ts @@ -188,7 +188,9 @@ export class SettingsConfig { PersistenceSchemaEnum.Dataset, PersistenceSchemaEnum.USS, PersistenceSchemaEnum.Job, - PersistenceSchemaEnum.Commands, + PersistenceSchemaEnum.MvsCommands, + PersistenceSchemaEnum.TsoCommands, + PersistenceSchemaEnum.UssCommands, Definitions.LocalStorageKey.CLI_LOGGER_SETTING_PRESENTED, ]; diff --git a/packages/zowe-explorer/src/tools/ZoweLocalStorage.ts b/packages/zowe-explorer/src/tools/ZoweLocalStorage.ts index 74e159594a..062c2725a0 100644 --- a/packages/zowe-explorer/src/tools/ZoweLocalStorage.ts +++ b/packages/zowe-explorer/src/tools/ZoweLocalStorage.ts @@ -68,7 +68,9 @@ export class LocalStorageAccess extends ZoweLocalStorage { [PersistenceSchemaEnum.Dataset]: StorageAccessLevel.Read | StorageAccessLevel.Write, [PersistenceSchemaEnum.USS]: StorageAccessLevel.Read | StorageAccessLevel.Write, [PersistenceSchemaEnum.Job]: StorageAccessLevel.Read | StorageAccessLevel.Write, - [PersistenceSchemaEnum.Commands]: StorageAccessLevel.Read | StorageAccessLevel.Write, + [PersistenceSchemaEnum.MvsCommands]: StorageAccessLevel.Read | StorageAccessLevel.Write, + [PersistenceSchemaEnum.TsoCommands]: StorageAccessLevel.Read | StorageAccessLevel.Write, + [PersistenceSchemaEnum.UssCommands]: StorageAccessLevel.Read | StorageAccessLevel.Write, [Definitions.LocalStorageKey.V1_MIGRATION_STATUS]: StorageAccessLevel.None, }; diff --git a/packages/zowe-explorer/src/tools/ZoweTerminal.ts b/packages/zowe-explorer/src/tools/ZoweTerminal.ts new file mode 100644 index 0000000000..90a7ed2cff --- /dev/null +++ b/packages/zowe-explorer/src/tools/ZoweTerminal.ts @@ -0,0 +1,303 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +import { imperative } from "@zowe/zowe-explorer-api"; +import * as vscode from "vscode"; + +export class ZoweTerminal implements vscode.Pseudoterminal { + public static readonly mTermX = ">"; + public static readonly invalidChar = "�"; + public static readonly Keys = { + EMPTY_LINE: `${this.mTermX} `, + CLEAR_ALL: "\x1b[2J\x1b[3J\x1b[;H", + CLEAR_LINE: `\x1b[2K\r`, + END: "\x1b[F", + HOME: "\x1b[H", + CMD_LEFT: "\x01", // MacOS HOME + CTRL_C: "\x03", + CTRL_D: "\x04", + CMD_RIGHT: "\x05", // MacOS END + CTRL_BACKSPACE: "\x08", + TAB: "\x09", + CMD_BACKSPACE: "\x15", + OPT_BACKSPACE: "\x17", + OPT_CMD_BACKSPACE: "\x1b\x7F", + BACKSPACE: "\x7f", + INSERT: "\x1b[2~", + DEL: "\x1b[3~", + PAGE_UP: "\x1b[5~", + PAGE_DOWN: "\x1b[6~", + ENTER: "\r", + NEW_LINE: "\r\n", + OPT_LEFT: "\x1bb", + OPT_RIGHT: "\x1bf", + UP: "\x1b[A", + DOWN: "\x1b[B", + RIGHT: "\x1b[C", + LEFT: "\x1b[D", + hasModKey: (key: string): boolean => { + if (key.startsWith("\x1b[1;") || key.startsWith("\x1b[3;")) return true; + return false; + }, + }; + + public constructor( + terminalName: string, + private processCmd: (cmd: string) => Promise, + private controller: AbortController, + options?: { startup?: string; message?: string; history?: string[]; formatCommandLine?: (cmd: string) => string } + ) { + this.mTerminalName = terminalName; + this.mMessage = options?.message ?? this.mTerminalName; + this.mHistory = options?.history ?? []; + this.historyIndex = this.mHistory.length; + this.command = options?.startup ?? ""; + this.charArrayCmd = []; + this.cursorPosition = this.charArrayCmd.length; + this.formatCommandLine = options?.formatCommandLine ?? ((cmd: string) => `${ZoweTerminal.Keys.EMPTY_LINE}${cmd}`); + this.chalk = imperative.TextUtils.chalk; + } + + private charArrayCmd: string[]; + private mMessage: string; + protected mTerminalName: string = ""; + protected mHistory: string[]; + private historyIndex: number; + private isCommandRunning = false; + private pressedCtrlC = false; + private chalk; + + private writeEmitter = new vscode.EventEmitter(); + protected write(text: string) { + this.writeEmitter.fire(text); + } + protected writeLine(text: string) { + this.write(text); + this.write(ZoweTerminal.Keys.NEW_LINE); + this.writeCmd(); + } + protected clearLine() { + this.write(ZoweTerminal.Keys.CLEAR_LINE); + } + protected writeCmd(cmd?: string) { + this.write(this.formatCommandLine ? this.formatCommandLine(cmd ?? this.command) : cmd ?? this.command); + } + protected refreshCmd() { + this.command = this.sanitizeInput(this.command); + this.pressedCtrlC = false; + if (!this.charArrayCmd.length || this.charArrayCmd.join("") !== this.command) { + this.charArrayCmd = Array.from(this.command); + } + this.clearLine(); + this.writeCmd(); + if (this.charArrayCmd.length > this.cursorPosition) { + const getPos = (char: string) => { + if (char === ZoweTerminal.invalidChar) return 1; + const charBytes = Buffer.from(char).length; + return charBytes > 2 ? 2 : 1; + }; + const offset = this.charArrayCmd.slice(this.cursorPosition).reduce((total, curr) => total + getPos(curr), 0); + [...Array(offset)].map(() => this.write(ZoweTerminal.Keys.LEFT)); + } + } + protected clear() { + this.write(ZoweTerminal.Keys.CLEAR_ALL); + this.writeLine(this.chalk.dim.italic(this.mMessage)); + } + + protected command: string; + protected formatCommandLine: (cmd: string) => string; + protected cursorPosition: number; + + public onDidWrite: vscode.Event = this.writeEmitter.event; + + private closeEmitter = new vscode.EventEmitter(); + public onDidClose?: vscode.Event = this.closeEmitter.event; + + public open(_initialDimensions?: vscode.TerminalDimensions | undefined): void { + this.writeLine(this.chalk.dim.italic(this.mMessage)); + if (this.command.length > 0) { + this.handleInput(ZoweTerminal.Keys.ENTER); + } + } + + public close(): void { + this.closeEmitter.fire(); + } + + private navigateHistory(offset: number): void { + this.historyIndex = Math.max(0, Math.min(this.mHistory.length, this.historyIndex + offset)); + this.command = this.mHistory[this.historyIndex] ?? ""; + this.charArrayCmd = Array.from(this.command); + this.cursorPosition = this.charArrayCmd.length; + this.refreshCmd(); + } + + private moveCursor(offset: number): void { + this.cursorPosition = Math.max(0, Math.min(this.charArrayCmd.length, this.cursorPosition + offset)); + this.refreshCmd(); + } + + private moveCursorTo(position: number): void { + this.cursorPosition = position < 0 ? 0 : Math.min(this.charArrayCmd.length, position); + this.refreshCmd(); + } + + private isPrintable(char: string): boolean { + const codePoint = char.codePointAt(0); + if (codePoint === undefined) return false; + if (codePoint >= 0x20 && codePoint <= 0x7e) return true; + if (codePoint >= 0xa0 && codePoint <= 0xd7ff) return true; // Control characters + if (codePoint >= 0xe000 && codePoint <= 0xfffd) return true; // Private use area + if (codePoint >= 0x10000 && codePoint <= 0x10ffff) return true; // Supplemental planes + return false; + } + + private sanitizeInput(input: string): string { + return Array.from(input) + .map((char) => (this.isPrintable(char) ? char : ZoweTerminal.invalidChar)) + .join(""); + } + + private deleteCharacter(offset: number): void { + const deleteIndex = this.cursorPosition + offset; + + if (deleteIndex >= 0 && deleteIndex < this.charArrayCmd.length) { + this.charArrayCmd.splice(deleteIndex, 1); + this.command = this.charArrayCmd.join(""); + + if (offset === -1) { + this.cursorPosition--; + this.write(ZoweTerminal.Keys.LEFT); + } else if (offset === 0) { + this.write(ZoweTerminal.Keys.DEL); + } + this.refreshCmd(); + } + } + + private async handleEnter() { + this.write(ZoweTerminal.Keys.NEW_LINE); + const cmd = this.command; + this.command = ""; + this.charArrayCmd = []; + if (cmd.length === 0) { + this.writeCmd(); + return; + } + + if (cmd[0] === ":") { + if (cmd === ":clear") { + this.clear(); + } else if (cmd === ":exit") { + this.close(); + } + } else { + this.isCommandRunning = true; + + const output = await Promise.race([ + this.processCmd(cmd), + new Promise((resolve, _reject) => { + this.controller.signal.addEventListener("abort", () => { + this.isCommandRunning = false; + resolve(null); + }); + if (!this.isCommandRunning) resolve(null); + }), + ]); + this.isCommandRunning = false; + if (output === null) { + this.writeLine(this.chalk.italic.red("Operation cancelled!")); + } else { + this.writeLine(output.trim().split("\n").join("\r\n")); + } + } + this.mHistory.push(cmd); + this.historyIndex = this.mHistory.length; + this.cursorPosition = 0; + } + + // Handle input from the terminal + public async handleInput(data: string): Promise { + // console.log("data", data, Buffer.from(data)); + if (this.isCommandRunning) { + if ([ZoweTerminal.Keys.CTRL_C, ZoweTerminal.Keys.CTRL_D].includes(data)) this.controller.abort(); + if (data === ZoweTerminal.Keys.CTRL_D) this.close(); + else this.pressedCtrlC = true; + return; + } + if (ZoweTerminal.Keys.hasModKey(data)) return; + switch (data) { + case ZoweTerminal.Keys.CTRL_C: + if (this.pressedCtrlC) this.close(); + if (this.command.length > 0) { + this.command = ""; + this.handleEnter(); + } else { + this.writeLine(this.chalk.italic("(To exit, press Ctrl+C again or Ctrl+D or type :exit)")); + this.pressedCtrlC = true; + } + break; + case ZoweTerminal.Keys.CTRL_D: + this.close(); + break; + case ZoweTerminal.Keys.UP: + case ZoweTerminal.Keys.PAGE_UP: + this.navigateHistory(-1); + break; + case ZoweTerminal.Keys.DOWN: + case ZoweTerminal.Keys.PAGE_DOWN: + this.navigateHistory(1); + break; + case ZoweTerminal.Keys.LEFT: + case ZoweTerminal.Keys.OPT_LEFT: + this.moveCursor(-1); + break; + case ZoweTerminal.Keys.RIGHT: + case ZoweTerminal.Keys.OPT_RIGHT: + this.moveCursor(1); + break; + case ZoweTerminal.Keys.HOME: + case ZoweTerminal.Keys.CMD_LEFT: + this.moveCursorTo(0); + break; + case ZoweTerminal.Keys.END: + case ZoweTerminal.Keys.CMD_RIGHT: + this.moveCursorTo(this.charArrayCmd.length); + break; + case ZoweTerminal.Keys.DEL: + this.deleteCharacter(0); + break; + case ZoweTerminal.Keys.BACKSPACE: + case ZoweTerminal.Keys.CTRL_BACKSPACE: + case ZoweTerminal.Keys.CMD_BACKSPACE: + case ZoweTerminal.Keys.OPT_BACKSPACE: + case ZoweTerminal.Keys.OPT_CMD_BACKSPACE: + this.deleteCharacter(-1); + break; + case ZoweTerminal.Keys.ENTER: + await this.handleEnter(); + break; + case ZoweTerminal.Keys.TAB: + case ZoweTerminal.Keys.INSERT: + // Do nothing + break; + default: { + const charArray = this.charArrayCmd; + this.command = charArray.slice(0, Math.max(0, this.cursorPosition)).join("") + data + charArray.slice(this.cursorPosition).join(""); + this.charArrayCmd = Array.from(this.command); + this.cursorPosition = Math.min(this.charArrayCmd.length, this.cursorPosition + Array.from(data).length); + this.write(data); + this.refreshCmd(); + } + } + } +} diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts index 94ea260283..7eb68b07db 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts @@ -192,7 +192,7 @@ export class DatasetInit { await datasetProvider.onDidChangeConfiguration(e); }) ); - + context.subscriptions.push( vscode.commands.registerCommand("zowe.ds.pdsSearchFor", async (node: IZoweDatasetTreeNode) => DatasetActions.search(context, node)) ); diff --git a/packages/zowe-explorer/src/trees/shared/SharedHistoryView.ts b/packages/zowe-explorer/src/trees/shared/SharedHistoryView.ts index 1133cbb676..07fcabcd7b 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedHistoryView.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedHistoryView.ts @@ -21,17 +21,19 @@ import * as fs from "fs"; export class SharedHistoryView extends WebView { private treeProviders: Definitions.IZoweProviders; + private cmdProviders: Definitions.IZoweCommandProviders; private currentTab: string; private currentSelection: { [type: string]: string }; - public constructor(context: ExtensionContext, treeProviders: Definitions.IZoweProviders) { + public constructor(context: ExtensionContext, treeProviders: Definitions.IZoweProviders, cmdProviders?: Definitions.IZoweCommandProviders) { const label = "Edit History"; super(label, "edit-history", context, { onDidReceiveMessage: (message: object) => this.onDidReceiveMessage(message), retainContext: true, }); this.treeProviders = treeProviders; - this.currentSelection = { ds: "search", uss: "search", jobs: "search" }; + this.cmdProviders = cmdProviders; + this.currentSelection = { ds: "search", uss: "search", jobs: "search", cmds: "mvs" }; } protected async onDidReceiveMessage(message: any): Promise { @@ -44,6 +46,11 @@ export class SharedHistoryView extends WebView { ds: this.getHistoryData("ds"), uss: this.getHistoryData("uss"), jobs: this.getHistoryData("job"), + cmds: { + mvs: this.cmdProviders?.mvs.history.getSearchHistory() ?? [], + tso: this.cmdProviders?.tso.history.getSearchHistory() ?? [], + uss: this.cmdProviders?.uss.history.getSearchHistory() ?? [], + }, tab: this.currentTab, selection: this.currentSelection, }); @@ -203,6 +210,11 @@ export class SharedHistoryView extends WebView { ds: this.getHistoryData("ds"), uss: this.getHistoryData("uss"), jobs: this.getHistoryData("job"), + cmds: { + mvs: this.cmdProviders?.mvs.history.getSearchHistory() ?? [], + tso: this.cmdProviders?.tso.history.getSearchHistory() ?? [], + uss: this.cmdProviders?.uss.history.getSearchHistory() ?? [], + }, tab: this.currentTab, selection: this.currentSelection, }); diff --git a/packages/zowe-explorer/src/trees/shared/SharedInit.ts b/packages/zowe-explorer/src/trees/shared/SharedInit.ts index 539aae7800..86af848a9f 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedInit.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedInit.ts @@ -77,10 +77,16 @@ export class SharedInit { // Contribute the "Zowe Resources" view as a WebviewView panel in Zowe Explorer. context.subscriptions.push(vscode.window.registerWebviewViewProvider("zowe-resources", TableViewProvider.getInstance())); + const commandProviders: Definitions.IZoweCommandProviders = { + mvs: MvsCommandHandler.getInstance(), + tso: TsoCommandHandler.getInstance(), + uss: UnixCommandHandler.getInstance(), + }; + // Webview for editing persistent items on Zowe Explorer context.subscriptions.push( vscode.commands.registerCommand("zowe.editHistory", () => { - return new SharedHistoryView(context, providers); + return new SharedHistoryView(context, providers, commandProviders); }) ); @@ -234,27 +240,27 @@ export class SharedInit { context.subscriptions.push( vscode.commands.registerCommand("zowe.issueTsoCmd", async (node?, command?) => { if (node) { - await TsoCommandHandler.getInstance().issueTsoCommand(node.session, command, node); + await commandProviders.tso.issueTsoCommand(node.session, command, node); } else { - await TsoCommandHandler.getInstance().issueTsoCommand(); + await commandProviders.tso.issueTsoCommand(); } }) ); context.subscriptions.push( vscode.commands.registerCommand("zowe.issueUnixCmd", async (node?, command?) => { if (node) { - await UnixCommandHandler.getInstance().issueUnixCommand(node, command); + await commandProviders.uss.issueUnixCommand(node, command); } else { - await UnixCommandHandler.getInstance().issueUnixCommand(); + await commandProviders.uss.issueUnixCommand(); } }) ); context.subscriptions.push( vscode.commands.registerCommand("zowe.issueMvsCmd", async (node?, command?) => { if (node) { - await MvsCommandHandler.getInstance().issueMvsCommand(node.session, command, node); + await commandProviders.mvs.issueMvsCommand(node.session, command, node); } else { - await MvsCommandHandler.getInstance().issueMvsCommand(); + await commandProviders.mvs.issueMvsCommand(); } }) ); diff --git a/packages/zowe-explorer/src/trees/uss/USSActions.ts b/packages/zowe-explorer/src/trees/uss/USSActions.ts index 976f966484..4c344a24d3 100644 --- a/packages/zowe-explorer/src/trees/uss/USSActions.ts +++ b/packages/zowe-explorer/src/trees/uss/USSActions.ts @@ -374,7 +374,6 @@ export class USSActions { */ public static async pasteUss(ussFileProvider: Types.IZoweUSSTreeType, node: IZoweUSSTreeNode): Promise { ZoweLogger.trace("uss.actions.pasteUss called."); - /* eslint-disable-next-line deprecation/deprecation */ if (node.pasteUssTree == null) { await Gui.infoMessage(vscode.l10n.t("The paste operation is not supported for this node.")); return; @@ -385,7 +384,6 @@ export class USSActions { title: vscode.l10n.t("Pasting files..."), }, async () => { - /* eslint-disable-next-line deprecation/deprecation */ await node.pasteUssTree(); } ); diff --git a/packages/zowe-explorer/src/utils/AuthUtils.ts b/packages/zowe-explorer/src/utils/AuthUtils.ts index 523f574697..990a06b0dc 100644 --- a/packages/zowe-explorer/src/utils/AuthUtils.ts +++ b/packages/zowe-explorer/src/utils/AuthUtils.ts @@ -64,8 +64,8 @@ export class AuthUtils { ) { const correlation = ErrorCorrelator.getInstance().correlateError(ZoweExplorerApiType.All, err, { templateArgs: { - profileName: profile.name - } + profileName: profile.name, + }, }); void AuthUtils.promptForAuthentication(err, profile, correlation).catch( (error) => error instanceof Error && ZoweLogger.error(error.message) diff --git a/packages/zowe-explorer/src/webviews/src/edit-history/App.tsx b/packages/zowe-explorer/src/webviews/src/edit-history/App.tsx index a28fe785ae..c8c044fa89 100644 --- a/packages/zowe-explorer/src/webviews/src/edit-history/App.tsx +++ b/packages/zowe-explorer/src/webviews/src/edit-history/App.tsx @@ -60,9 +60,13 @@ export function App(): JSXInternal.Element {

{l10n.t("Jobs")}

+ +

Zowe Commands

+
+ ); diff --git a/packages/zowe-explorer/src/webviews/src/edit-history/components/PersistentTable/PersistentDataPanel.tsx b/packages/zowe-explorer/src/webviews/src/edit-history/components/PersistentTable/PersistentDataPanel.tsx index b3bef8545b..3485252a49 100644 --- a/packages/zowe-explorer/src/webviews/src/edit-history/components/PersistentTable/PersistentDataPanel.tsx +++ b/packages/zowe-explorer/src/webviews/src/edit-history/components/PersistentTable/PersistentDataPanel.tsx @@ -75,6 +75,15 @@ export default function PersistentDataPanel({ type }: Readonly<{ type: Readonly< setPersistentProp(() => data[type][selection[type]]); }, [selection]); + if (type == "cmds") { + return ( + + +

Coming soon!

+
+
+ ); + } return ( diff --git a/packages/zowe-explorer/src/webviews/src/edit-history/types.ts b/packages/zowe-explorer/src/webviews/src/edit-history/types.ts index 77e6d08087..b4fcfd9198 100644 --- a/packages/zowe-explorer/src/webviews/src/edit-history/types.ts +++ b/packages/zowe-explorer/src/webviews/src/edit-history/types.ts @@ -13,6 +13,7 @@ export const panelId: { [key: string]: string } = { ds: "ds-panel-view", uss: "uss-panel-view", jobs: "jobs-panel-view", + cmds: "cmds-panel-view", }; export type DataPanelContextType = {