From e89023f86a1e94cd60714238973c3a5eda28a67c Mon Sep 17 00:00:00 2001 From: Itsanexpriment Date: Sat, 30 May 2026 08:38:46 +0300 Subject: [PATCH 1/3] add multi-part extension action support and extract availability check to standalone function --- src/ui/actions.ts | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) 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[] = []; From 83460341512636f41bc6e493f2e471ca2559df9a Mon Sep 17 00:00:00 2001 From: Itsanexpriment Date: Sat, 30 May 2026 08:42:01 +0300 Subject: [PATCH 2/3] reuse action availability check to remove duplication --- src/ui/views/environment/actions.ts | 24 ++++++--------------- src/ui/views/environment/environmentView.ts | 4 ++-- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/ui/views/environment/actions.ts b/src/ui/views/environment/actions.ts index e8f744227..8b9ec1c0b 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 { uriToActionTarget, isActionAvailable } from "../../actions"; type ActionContext = { canRun?: boolean @@ -81,24 +80,15 @@ 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(); - 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) - }; + const workspace = vscode.workspace.getWorkspaceFolder(uri); + actionTarget = [uriToActionTarget(uri, workspace, connection)]; } - const canRunOnEditor = (actionItem: ActionItem) => activeEditorContext !== 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)); - - (await this.getAllActionItems()).forEach(item => item.setContext({ canRun: canRunOnEditor(item) })); + (await this.getAllActionItems()).forEach(item => item.setContext({ canRun: !!actionTarget && isActionAvailable(item.action, uri?.scheme || "", actionTarget) })); this.refresh(); } @@ -163,7 +153,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; } From 2d8306d973d26c8d2971e55dac290966e8247aff Mon Sep 17 00:00:00 2001 From: Yuval Neumann Date: Fri, 5 Jun 2026 21:36:03 +0300 Subject: [PATCH 3/3] Restore workspace check in activeEditorChanged --- src/ui/views/environment/actions.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ui/views/environment/actions.ts b/src/ui/views/environment/actions.ts index 8b9ec1c0b..350f4b528 100644 --- a/src/ui/views/environment/actions.ts +++ b/src/ui/views/environment/actions.ts @@ -5,7 +5,7 @@ import { instance } from "../../../instantiate"; import { Action, ActionType } from "../../../typings"; import { VscodeTools } from "../../Tools"; import { EnvironmentItem } from "./environmentItem"; -import { uriToActionTarget, isActionAvailable } from "../../actions"; +import { targetMatchesExtensions, uriToActionTarget } from "../../actions"; type ActionContext = { canRun?: boolean @@ -80,15 +80,30 @@ 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)]; + + actionTarget = uriToActionTarget(uri, workspace, connection); + + activeEditorContext = { + scheme: uri.scheme, + protected: actionTarget.protected, + workspace + }; } - (await this.getAllActionItems()).forEach(item => item.setContext({ canRun: !!actionTarget && isActionAvailable(item.action, uri?.scheme || "", actionTarget) })); + const canRunOnEditor = (actionItem: ActionItem) => activeEditorContext !== undefined && + actionTarget !== undefined && + activeEditorContext.scheme === actionItem.action.type && + activeEditorContext.workspace === actionItem.workspace && + (actionItem.action.runOnProtected || !activeEditorContext.protected) && + targetMatchesExtensions(actionTarget, actionItem.action.extensions); + + (await this.getAllActionItems()).forEach(item => item.setContext({ canRun: canRunOnEditor(item) })); this.refresh(); }