Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@
"contributes": {
"taskDefinitions": [
{
"type": "ibmi"
"type": "ibmi",
"properties": {
"path": {
"type": "string",
"description": "The path to the target file being compiled"
}
}
}
],
"jsonValidation": [
Expand Down
134 changes: 71 additions & 63 deletions src/api/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,89 @@ import vscode, { l10n } from "vscode";
import IBMi from "./IBMi";
import { Action } from "./types";

export async function getActions(workspace?: vscode.WorkspaceFolder) {
return workspace ? await getLocalActions(workspace) : (IBMi.connectionManager.get<Action[]>(`actions`) || []);
}
export namespace ActionTools {
export async function getActions(workspace?: vscode.WorkspaceFolder) {
return workspace ? await getLocalActions(workspace) : getConnectionActions();
}

export async function updateAction(action: Action, workspace?: vscode.WorkspaceFolder, options?: { newName?: string, oldType?: string, delete?: boolean }) {
const actions = await getActions(workspace);
const type = options?.oldType || action.type;
const currentIndex = actions.findIndex(a => action.name === a.name && type === a.type);
async function getLocalActions(currentWorkspace: vscode.WorkspaceFolder) {
const actions: Action[] = [];

action.name = options?.newName || action.name;
if (currentWorkspace) {
const actionsFiles = await getLocalActionsFiles(currentWorkspace);

if (options?.delete) {
if (currentIndex >= 0) {
actions.splice(currentIndex, 1);
}
else {
throw new Error(l10n.t("Cannot find action {0} for delete operation", action.name));
}
}
else {
actions[currentIndex >= 0 ? currentIndex : actions.length] = action;
}
for (const file of actionsFiles) {
const actionsContent = await vscode.workspace.fs.readFile(file);
try {
const actionsJson: Action[] = JSON.parse(actionsContent.toString());

if (workspace) {
const actionsFile = (await getLocalActionsFiles(workspace)).at(0);
if (actionsFile) {
await vscode.workspace.fs.writeFile(actionsFile, Buffer.from(JSON.stringify(actions, undefined, 2), "utf-8"));
}
else {
throw new Error(l10n.t("No local actions file defined in workspace {0}", workspace.name));
// Maybe one day replace this with real schema validation
if (Array.isArray(actionsJson)) {
actionsJson.forEach((action, index) => {
if (
typeof action.name === `string` &&
typeof action.command === `string` &&
[`ile`, `pase`, `qsh`].includes(action.environment) &&
(!action.extensions || Array.isArray(action.extensions))
) {
actions.push({
...action,
type: `file`
});
} else {
throw new Error(`Invalid Action defined at index ${index}.`);
}
})
}
} catch (e: any) {
vscode.window.showErrorMessage(`Error parsing ${file.fsPath}: ${e.message}\n`);
}
};
}

return actions;
}
else {
await IBMi.connectionManager.set(`actions`, actions);

export function getConnectionActions() {
return IBMi.connectionManager.get<Action[]>(`actions`) || [];
}
}

export async function getLocalActionsFiles(workspace: vscode.WorkspaceFolder) {
return workspace ? await vscode.workspace.findFiles(new vscode.RelativePattern(workspace, `**/.vscode/actions.json`)) : [];
}
export async function getLocalActionsFiles(workspace: vscode.WorkspaceFolder) {
return workspace ?
await vscode.workspace.findFiles(new vscode.RelativePattern(workspace, `**/.vscode/actions.json`)) :
[];
}

async function getLocalActions(currentWorkspace: vscode.WorkspaceFolder) {
const actions: Action[] = [];
export async function updateAction(action: Action, workspace?: vscode.WorkspaceFolder, options?: { newName?: string, oldType?: string, delete?: boolean }) {
const actions = await getActions(workspace);
const type = options?.oldType || action.type;
const currentIndex = actions.findIndex(a => action.name === a.name && type === a.type);

if (currentWorkspace) {
const actionsFiles = await getLocalActionsFiles(currentWorkspace);
action.name = options?.newName || action.name;

for (const file of actionsFiles) {
const actionsContent = await vscode.workspace.fs.readFile(file);
try {
const actionsJson: Action[] = JSON.parse(actionsContent.toString());
if (options?.delete) {
if (currentIndex >= 0) {
actions.splice(currentIndex, 1);
}
else {
throw new Error(l10n.t("Cannot find action {0} for delete operation", action.name));
}
}
else {
actions[currentIndex >= 0 ? currentIndex : actions.length] = action;
}

// Maybe one day replace this with real schema validation
if (Array.isArray(actionsJson)) {
actionsJson.forEach((action, index) => {
if (
typeof action.name === `string` &&
typeof action.command === `string` &&
[`ile`, `pase`, `qsh`].includes(action.environment) &&
(!action.extensions || Array.isArray(action.extensions))
) {
actions.push({
...action,
type: `file`
});
} else {
throw new Error(`Invalid Action defined at index ${index}.`);
}
})
}
} catch (e: any) {
vscode.window.showErrorMessage(`Error parsing ${file.fsPath}: ${e.message}\n`);
if (workspace) {
const actionsFile = (await getLocalActionsFiles(workspace)).at(0);
if (actionsFile) {
await vscode.workspace.fs.writeFile(actionsFile, Buffer.from(JSON.stringify(actions, undefined, 2), "utf-8"));
}
};
else {
throw new Error(l10n.t("No local actions file defined in workspace {0}", workspace.name));
}
}
else {
await IBMi.connectionManager.set(`actions`, actions);
}
}

return actions;
}
12 changes: 12 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ export interface Action {
outputToFile?: string
}

export interface ActionResult {
success: boolean,
output: {
path: string,
processed: boolean,
hasRun: boolean,
executionOK: boolean,
output: string[]
}[],
message: string
}

export interface ConnectionData {
name: string;
host: string;
Expand Down
24 changes: 16 additions & 8 deletions src/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { commands, Disposable, env, l10n, TreeItem, Uri, window, WorkspaceFolder
import IBMi from "../api/IBMi";
import { Tools } from "../api/Tools";
import Instance from "../Instance";
import { Action, DeploymentMethod } from "../typings";
import { Action, ActionResult, DeploymentMethod } from "../typings";
import { runAction } from "../ui/actions";
import { refreshDiagnosticsFromServer } from "../ui/diagnostics";
import { BrowserItem } from "../ui/types";
Expand All @@ -13,6 +13,7 @@ type CommandOrigin = "editor" | "objectBrowser" | "ifsBrowser" | "commandPalette
export function registerActionsCommands(instance: Instance): Disposable[] {
return [
commands.registerCommand(`code-for-ibmi.runAction`, async (item: (CommandOrigin | TreeItem | BrowserItem | Uri), items?: (TreeItem | BrowserItem | Uri)[], action?: Action, method?: DeploymentMethod, workspaceFolder?: WorkspaceFolder) => {
let actionMessage: string;
if (!item && !items && !action && !method && !workspaceFolder) {
item = "commandPalette";
}
Expand Down Expand Up @@ -60,8 +61,9 @@ export function registerActionsCommands(instance: Instance): Disposable[] {
const scheme = uris[0]?.scheme;
if (scheme) {
if (!uris.every(uri => uri.scheme === scheme)) {
window.showWarningMessage(l10n.t("Actions can't be run on multiple items of different natures. ({0})", uris.map(uri => uri.scheme).filter(Tools.distinct).join(", ")));
return false;
actionMessage = l10n.t("Actions can't be run on multiple items of different natures. ({0})", uris.map(uri => uri.scheme).filter(Tools.distinct).join(", "));
window.showWarningMessage(actionMessage);
return { success: false, output: [], error: actionMessage };
}

const config = connection.getConfig();
Expand All @@ -72,7 +74,8 @@ export function registerActionsCommands(instance: Instance): Disposable[] {
if (config.autoSaveBeforeAction) {
await openedEditor.document.save();
} else {
const result = await window.showWarningMessage(`File ${path} must be saved to run Actions.`, `Save`, `Save automatically`, `Cancel`);
actionMessage = l10n.t(`File {0} must be saved to run Actions.`, path);
const result = await window.showWarningMessage(actionMessage, `Save`, `Save automatically`, `Cancel`);
switch (result) {
case `Save`:
await openedEditor.document.save();
Expand All @@ -85,7 +88,7 @@ export function registerActionsCommands(instance: Instance): Disposable[] {
break;

default:
return;
return { success: false, output: [], error: actionMessage }
}
}
}
Expand All @@ -94,14 +97,19 @@ export function registerActionsCommands(instance: Instance): Disposable[] {
if ([`member`, `streamfile`, `file`, 'object'].includes(scheme)) {
return await runAction(instance, uris, action, method, browserItems, workspaceFolder);
}

actionMessage = l10n.t("Unsupported file scheme: {0}. Must be 'member', 'streamfile', 'file', or 'object'", scheme);
return { success: false, output: [], error: actionMessage };
}

actionMessage = l10n.t("Failed to retrieve file scheme");
return { success: false, output: [], error: actionMessage };
}
else {
window.showErrorMessage('Please connect to an IBM i first');
actionMessage = l10n.t("Please connect to an IBM i first");
window.showErrorMessage(actionMessage);
return { success: false, output: [], error: actionMessage };
}

return false;
}),

commands.registerCommand(`code-for-ibmi.openErrors`, async (options: { qualifiedObject?: string, workspace?: WorkspaceFolder, keepDiagnostics?: boolean }) => {
Expand Down
6 changes: 3 additions & 3 deletions src/editors/actionEditor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import vscode, { l10n } from "vscode";
import { getActions, updateAction } from "../api/actions";
import { ActionTools } from "../api/actions";
import { Tools } from "../api/Tools";
import { Action, ActionEnvironment, ActionRefresh, ActionType } from "../typings";
import { CustomVariables } from "../ui/views/environment/customVariables";
Expand Down Expand Up @@ -157,11 +157,11 @@ async function save(targetAction: Action, actionData: ActionData, workspace?: vs
postDownload: postDownload.length ? postDownload : undefined
});
const typeChanged = (oldType !== targetAction.type);
if (typeChanged && (await getActions(workspace)).some(a => a.name === targetAction.name && a.type === targetAction.type)) {
if (typeChanged && (await ActionTools.getActions(workspace)).some(a => a.name === targetAction.name && a.type === targetAction.type)) {
throw new Error(l10n.t("The action '{0}' already exists for the '{1}' type", targetAction.name, targetAction.type!))
}

await updateAction(targetAction, workspace, { oldType });
await ActionTools.updateAction(targetAction, workspace, { oldType });
if (typeChanged) {
editedActions.delete({ name: targetAction.name, type: oldType });
editedActions.add({ name: targetAction.name, type: targetAction.type });
Expand Down
4 changes: 3 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { openURIHandler } from "./uri/handlers/open";
import { initializeSandbox, sandboxURIHandler } from "./uri/handlers/sandbox";
import { CustomUI } from "./webviews/CustomUI";
import { SettingsUI } from "./webviews/settings";
import { ActionTools } from "./api/actions";

export async function activate(context: ExtensionContext): Promise<CodeForIBMi> {
// Use the console to output diagnostic information (console.log) and errors (console.error)
Expand Down Expand Up @@ -136,9 +137,10 @@ export async function activate(context: ExtensionContext): Promise<CodeForIBMi>
instance,
customUI: () => new CustomUI(),
customEditor: (target, onSave, onClosed) => new CustomEditor(target, onSave, onClosed),
deployTools: DeployTools,
evfeventParser: parseErrors,
tools: VscodeTools,
deployTools: DeployTools,
actionTools: ActionTools,
componentRegistry: extensionComponentRegistry,
connectionManager: IBMi.connectionManager,
searchTools: SearchTools
Expand Down
12 changes: 8 additions & 4 deletions src/filesystems/local/deployTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import createIgnore, { Ignore } from 'ignore';
import path, { basename } from 'path';
import vscode, { l10n, Uri, WorkspaceFolder } from 'vscode';
import { getActions } from '../../api/actions';
import { ActionTools } from '../../api/actions';
import { DeploymentMethod } from '../../api/types';
import { instance } from '../../instantiate';
import { BrowserItem, DeploymentParameters } from '../../typings';
Expand All @@ -13,9 +13,10 @@ import { Deployment } from './deployment';
type ServerFileChanges = { uploads: Uri[], relativeRemoteDeletes: string[] };

export namespace DeployTools {
export async function launchActionsSetup(workspaceFolder?: WorkspaceFolder | BrowserItem) {
const chosenWorkspace = !workspaceFolder || workspaceFolder instanceof BrowserItem ? await Deployment.getWorkspaceFolder() : workspaceFolder;
export async function launchActionsSetup(workspaceFolder?: WorkspaceFolder | BrowserItem): Promise<boolean> {
let isSetupComplete: boolean = false;

const chosenWorkspace = !workspaceFolder || workspaceFolder instanceof BrowserItem ? await Deployment.getWorkspaceFolder() : workspaceFolder;
if (chosenWorkspace) {
const types = Object.entries(LocalLanguageActions).map(([type, actions]) => ({ label: type, actions }));

Expand All @@ -38,7 +39,7 @@ export namespace DeployTools {
actions.push(...newActions);
}
else if (action === append) {
const existingActions = await getActions(chosenWorkspace);
const existingActions = await ActionTools.getActions(chosenWorkspace);
const existingActionNames = existingActions.map(action => action.name);
//Change names of new actions
let toRename = [];
Expand All @@ -61,13 +62,16 @@ export namespace DeployTools {
Buffer.from(JSON.stringify(actions, null, 2), `utf-8`)
);
vscode.workspace.openTextDocument(localActionsUri).then(doc => vscode.window.showTextDocument(doc));
isSetupComplete = true;
} catch (e) {
console.log(e);
vscode.window.showErrorMessage(`Unable to create actions.json file.`);
}
}
}
}

return isSetupComplete;
}

export function getRemoteDeployDirectory(workspaceFolder: WorkspaceFolder): string | undefined {
Expand Down
6 changes: 3 additions & 3 deletions src/filesystems/local/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import { create as tarCreate, t as tarT } from 'tar';
import tmp from 'tmp';
import vscode, { Uri } from 'vscode';
import { getActions, getLocalActionsFiles } from '../../api/actions';
import { ActionTools } from '../../api/actions';
import IBMi from '../../api/IBMi';
import IBMiContent from '../../api/IBMiContent';
import { Tools } from '../../api/Tools';
Expand Down Expand Up @@ -103,7 +103,7 @@ export namespace Deployment {
});
}

getActions(workspace).then(result => {
ActionTools.getActions(workspace).then(result => {
if (result.length === 0) {
vscode.window.showInformationMessage(
`There are no local Actions defined for this project.`,
Expand Down Expand Up @@ -182,7 +182,7 @@ export namespace Deployment {
workspace = uri;
}

vscode.commands.executeCommand(`setContext`, `code-for-ibmi:hasLocalActions`, workspace ? (await getLocalActionsFiles(workspace)).length > 0 : false);
vscode.commands.executeCommand(`setContext`, `code-for-ibmi:hasLocalActions`, workspace ? (await ActionTools.getActions(workspace)).length > 0 : false);
};

watcher.onDidChange(uri => {
Expand Down
Loading
Loading