diff --git a/src/ui/actions.ts b/src/ui/actions.ts index c11dc1f20..cb9524b03 100644 --- a/src/ui/actions.ts +++ b/src/ui/actions.ts @@ -670,9 +670,8 @@ export async function getAllAvailableActions(targets: ActionTarget[], scheme: st }); // Then we get all the available Actions for the current context - const availableActions: AvailableAction[] = allActions.filter(action => action.type === scheme) - .filter(action => !action.extensions || action.extensions.every(e => !e) || targets.every(t => action.extensions!.includes(t.extension) || action.extensions!.includes(t.fragment)) || action.extensions.includes(`GLOBAL`)) - .filter(action => action.runOnProtected || !targets.some(t => t.protected)) + const availableActions: AvailableAction[] = allActions + .filter(action => isActionAvailable(action, scheme, targets)) .sort((a, b) => (actionUsed.get(b.name) || 0) - (actionUsed.get(a.name) || 0)) .map(action => ({ label: action.name, @@ -682,6 +681,41 @@ export async function getAllAvailableActions(targets: ActionTarget[], scheme: st return availableActions; } +export function isActionAvailable(action: Action, scheme: string, targets: ActionTarget[]): boolean { + if (action.type !== scheme) return false; + + // action isn't cleared to run on protected targets and some of them are + if (!action.runOnProtected && targets.some(t => t.protected)) return false; + + return targets.every((t) => targetMatchesExtensions(t, action.extensions)); +} + +export function targetMatchesExtensions(target: ActionTarget, extensions?: string[]): boolean { + // action has no extension requirements, or is global + if (!extensions || extensions.every(e => !e) || extensions.includes("GLOBAL")) return true; + + const targetExtParts = [target.extension.toUpperCase(), target.fragment.toUpperCase()]; + + for (const e of extensions) { + const ext = e.toUpperCase(); + const extDotCount = ext.split('.').length - 1; + + if (extDotCount === 0) { + // match on single extension (myfile.a) + if (targetExtParts.includes(ext)) return true; // match + } else { + // match on multi-part extension (myfile.a.b) + const parsed = path.parse(target.uri.path); + const targetFile = (parsed.name + parsed.ext).toUpperCase(); + const targetDotCount = targetFile.split('.').length - 1; + + if ((targetDotCount > extDotCount) && targetFile.endsWith(ext)) return true; // match + } + } + + return false; // no matches +} + function getObjectsFromJoblog(stderr: string): CommandObject[] | undefined { const objects: CommandObject[] = []; diff --git a/src/ui/views/environment/actions.ts b/src/ui/views/environment/actions.ts index e8f744227..350f4b528 100644 --- a/src/ui/views/environment/actions.ts +++ b/src/ui/views/environment/actions.ts @@ -1,12 +1,11 @@ -import { parse } from "path"; import { stringify } from "querystring"; import vscode, { l10n } from "vscode"; import { ActionTools } from "../../../api/actions"; -import { parseFSOptions } from "../../../filesystems/qsys/QSysFs"; import { instance } from "../../../instantiate"; import { Action, ActionType } from "../../../typings"; import { VscodeTools } from "../../Tools"; import { EnvironmentItem } from "./environmentItem"; +import { targetMatchesExtensions, uriToActionTarget } from "../../actions"; type ActionContext = { canRun?: boolean @@ -82,21 +81,27 @@ export class ActionsNode extends EnvironmentItem { async activeEditorChanged(editor?: vscode.TextEditor) { const uri = editor?.document.uri; let activeEditorContext = undefined; + let actionTarget = undefined; + if (uri) { const connection = instance.getConnection(); + const workspace = vscode.workspace.getWorkspaceFolder(uri); + + actionTarget = uriToActionTarget(uri, workspace, connection); + activeEditorContext = { scheme: uri.scheme, - extension: parse(uri.path).ext.substring(1).toLocaleUpperCase(), - protected: parseFSOptions(uri).readonly || connection?.getConfig()?.readOnlyMode || connection?.getContent().isProtectedPath(uri.path), - workspace: vscode.workspace.getWorkspaceFolder(uri) + protected: actionTarget.protected, + workspace }; } const canRunOnEditor = (actionItem: ActionItem) => activeEditorContext !== undefined && + actionTarget !== undefined && activeEditorContext.scheme === actionItem.action.type && activeEditorContext.workspace === actionItem.workspace && (actionItem.action.runOnProtected || !activeEditorContext.protected) && - (!actionItem.action.extensions?.length || actionItem.action.extensions.includes('GLOBAL') || actionItem.action.extensions.includes(activeEditorContext.extension)); + targetMatchesExtensions(actionTarget, actionItem.action.extensions); (await this.getAllActionItems()).forEach(item => item.setContext({ canRun: canRunOnEditor(item) })); this.refresh(); @@ -163,7 +168,7 @@ export class ActionItem extends EnvironmentItem { this.iconPath = new vscode.ThemeIcon("github-action", this.context.matched ? new vscode.ThemeColor(ActionItem.matchedColor) : undefined); this.description = this.context.matched ? l10n.t("search match") : undefined; - this.tooltip = `${ this.action.command }\nExtensions: ${this.action.extensions?.join(`, `)}`; + this.tooltip = `${this.action.command}\nExtensions: ${this.action.extensions?.join(`, `)}`; this.resourceUri = vscode.Uri.from({ scheme: ActionItem.context, authority: this.action.name, diff --git a/src/ui/views/environment/environmentView.ts b/src/ui/views/environment/environmentView.ts index e6010f514..43bde9cc3 100644 --- a/src/ui/views/environment/environmentView.ts +++ b/src/ui/views/environment/environmentView.ts @@ -9,7 +9,7 @@ import { editAction, isActionEdited } from '../../../editors/actionEditor'; import { editConnectionProfile, isProfileEdited } from '../../../editors/connectionProfileEditor'; import { instance } from '../../../instantiate'; import { Action, ActionEnvironment, BrowserItem, ConnectionProfile, CustomVariable, FocusOptions } from '../../../typings'; -import { uriToActionTarget } from '../../actions'; +import { uriToActionTarget, targetMatchesExtensions } from '../../actions'; import { ActionItem, Actions, ActionsNode, ActionTypeNode } from './actions'; import { ConnectionProfiles, ProfileItem, ProfilesNode } from './connectionProfiles'; import { CustomVariableItem, CustomVariables, CustomVariablesNode } from './customVariables'; @@ -142,7 +142,7 @@ export function initializeEnvironmentView(context: vscode.ExtensionContext) { } const actionTarget = uriToActionTarget(uri); - if (action.extensions && !action.extensions.includes('GLOBAL') && !action.extensions.includes(actionTarget.extension) && !action.extensions.includes(actionTarget.fragment)) { + if (!targetMatchesExtensions(actionTarget, action.extensions)) { vscode.window.showErrorMessage(l10n.t("This action cannot run on a file with the {0} extension.", actionTarget.extension), editActionLabel).then(edit => edit ? editAction() : ''); return; }