1- import { Menu , type MenuItemConstructorOptions } from "electron" ;
1+ import { Menu , type MenuItemConstructorOptions , nativeImage } from "electron" ;
22import { createIpcService } from "../ipc/createIpcService.js" ;
33import type {
4+ ExternalAppContextMenuResult ,
45 FolderContextMenuResult ,
56 SplitContextMenuResult ,
67 TabContextMenuResult ,
78 TaskContextMenuResult ,
89} from "./contextMenu.types.js" ;
10+ import { externalAppsStore , getOrRefreshApps } from "./externalApps.js" ;
911
1012export type {
13+ ExternalAppContextMenuResult ,
1114 FolderContextMenuAction ,
1215 FolderContextMenuResult ,
1316 SplitContextMenuResult ,
@@ -18,12 +21,84 @@ export type {
1821 TaskContextMenuResult ,
1922} from "./contextMenu.types.js" ;
2023
24+ const ICON_SIZE = 16 ;
25+
26+ function showContextMenu < T > (
27+ template : MenuItemConstructorOptions [ ] ,
28+ defaultResult : T ,
29+ ) : Promise < T > {
30+ return new Promise ( ( resolve ) => {
31+ const menu = Menu . buildFromTemplate ( template ) ;
32+ menu . popup ( {
33+ callback : ( ) => resolve ( defaultResult ) ,
34+ } ) ;
35+ } ) ;
36+ }
37+
38+ async function buildExternalAppsMenuItems (
39+ _targetPath : string ,
40+ resolve : ( result : ExternalAppContextMenuResult ) => void ,
41+ ) : Promise < MenuItemConstructorOptions [ ] > {
42+ const apps = await getOrRefreshApps ( ) ;
43+ const prefs = externalAppsStore . get ( "externalAppsPrefs" ) ;
44+ const lastUsedAppId = prefs . lastUsedApp ;
45+
46+ // Handle no apps detected
47+ if ( apps . length === 0 ) {
48+ return [
49+ {
50+ label : "No external apps detected" ,
51+ enabled : false ,
52+ } ,
53+ ] ;
54+ }
55+
56+ // Find last used app or default to first
57+ const lastUsedApp = apps . find ( ( app ) => app . id === lastUsedAppId ) || apps [ 0 ] ;
58+
59+ const menuItems : MenuItemConstructorOptions [ ] = [
60+ {
61+ label : `Open in ${ lastUsedApp . name } ` ,
62+ click : ( ) =>
63+ resolve ( {
64+ action : { type : "open-in-app" , appId : lastUsedApp . id } ,
65+ } ) ,
66+ } ,
67+ {
68+ label : "Open in" ,
69+ submenu : apps . map ( ( app ) => ( {
70+ label : app . name ,
71+ icon : app . icon
72+ ? nativeImage
73+ . createFromDataURL ( app . icon )
74+ . resize ( { width : ICON_SIZE , height : ICON_SIZE } )
75+ : undefined ,
76+ click : ( ) =>
77+ resolve ( {
78+ action : { type : "open-in-app" , appId : app . id } ,
79+ } ) ,
80+ } ) ) ,
81+ } ,
82+ {
83+ label : "Copy Path" ,
84+ accelerator : "CmdOrCtrl+Shift+C" ,
85+ click : ( ) =>
86+ resolve ( {
87+ action : { type : "copy-path" } ,
88+ } ) ,
89+ } ,
90+ ] ;
91+
92+ return menuItems ;
93+ }
94+
2195export const showTaskContextMenuService = createIpcService ( {
2296 channel : "show-task-context-menu" ,
2397 handler : async (
2498 _event ,
2599 _taskId : string ,
26100 _taskTitle : string ,
101+ worktreePath ?: string ,
27102 ) : Promise < TaskContextMenuResult > => {
28103 return new Promise ( ( resolve ) => {
29104 const template : MenuItemConstructorOptions [ ] = [
@@ -42,10 +117,20 @@ export const showTaskContextMenuService = createIpcService({
42117 } ,
43118 ] ;
44119
45- const menu = Menu . buildFromTemplate ( template ) ;
46- menu . popup ( {
47- callback : ( ) => resolve ( { action : null } ) ,
48- } ) ;
120+ const setupMenu = async ( ) => {
121+ if ( worktreePath ) {
122+ template . push ( { type : "separator" } ) ;
123+ const externalAppsItems = await buildExternalAppsMenuItems (
124+ worktreePath ,
125+ resolve ,
126+ ) ;
127+ template . push ( ...externalAppsItems ) ;
128+ }
129+
130+ showContextMenu ( template , { action : null } ) . then ( resolve ) ;
131+ } ;
132+
133+ setupMenu ( ) ;
49134 } ) ;
50135 } ,
51136} ) ;
@@ -56,6 +141,7 @@ export const showFolderContextMenuService = createIpcService({
56141 _event ,
57142 _folderId : string ,
58143 _folderName : string ,
144+ folderPath ?: string ,
59145 ) : Promise < FolderContextMenuResult > => {
60146 return new Promise ( ( resolve ) => {
61147 const template : MenuItemConstructorOptions [ ] = [
@@ -65,17 +151,31 @@ export const showFolderContextMenuService = createIpcService({
65151 } ,
66152 ] ;
67153
68- const menu = Menu . buildFromTemplate ( template ) ;
69- menu . popup ( {
70- callback : ( ) => resolve ( { action : null } ) ,
71- } ) ;
154+ const setupMenu = async ( ) => {
155+ if ( folderPath ) {
156+ template . push ( { type : "separator" } ) ;
157+ const externalAppsItems = await buildExternalAppsMenuItems (
158+ folderPath ,
159+ resolve ,
160+ ) ;
161+ template . push ( ...externalAppsItems ) ;
162+ }
163+
164+ showContextMenu ( template , { action : null } ) . then ( resolve ) ;
165+ } ;
166+
167+ setupMenu ( ) ;
72168 } ) ;
73169 } ,
74170} ) ;
75171
76172export const showTabContextMenuService = createIpcService ( {
77173 channel : "show-tab-context-menu" ,
78- handler : async ( _event , canClose : boolean ) : Promise < TabContextMenuResult > => {
174+ handler : async (
175+ _event ,
176+ canClose : boolean ,
177+ filePath ?: string ,
178+ ) : Promise < TabContextMenuResult > => {
79179 return new Promise ( ( resolve ) => {
80180 const template : MenuItemConstructorOptions [ ] = [
81181 {
@@ -84,7 +184,6 @@ export const showTabContextMenuService = createIpcService({
84184 enabled : canClose ,
85185 click : ( ) => resolve ( { action : "close" } ) ,
86186 } ,
87- { type : "separator" } ,
88187 {
89188 label : "Close other tabs" ,
90189 click : ( ) => resolve ( { action : "close-others" } ) ,
@@ -95,10 +194,20 @@ export const showTabContextMenuService = createIpcService({
95194 } ,
96195 ] ;
97196
98- const menu = Menu . buildFromTemplate ( template ) ;
99- menu . popup ( {
100- callback : ( ) => resolve ( { action : null } ) ,
101- } ) ;
197+ const setupMenu = async ( ) => {
198+ if ( filePath ) {
199+ template . push ( { type : "separator" } ) ;
200+ const externalAppsItems = await buildExternalAppsMenuItems (
201+ filePath ,
202+ resolve ,
203+ ) ;
204+ template . push ( ...externalAppsItems ) ;
205+ }
206+
207+ showContextMenu ( template , { action : null } ) . then ( resolve ) ;
208+ } ;
209+
210+ setupMenu ( ) ;
102211 } ) ;
103212 } ,
104213} ) ;
@@ -126,10 +235,28 @@ export const showSplitContextMenuService = createIpcService({
126235 } ,
127236 ] ;
128237
129- const menu = Menu . buildFromTemplate ( template ) ;
130- menu . popup ( {
131- callback : ( ) => resolve ( { direction : null } ) ,
132- } ) ;
238+ showContextMenu ( template , { direction : null } ) . then ( resolve ) ;
239+ } ) ;
240+ } ,
241+ } ) ;
242+
243+ export const showFileContextMenuService = createIpcService ( {
244+ channel : "show-file-context-menu" ,
245+ handler : async (
246+ _event ,
247+ filePath : string ,
248+ ) : Promise < ExternalAppContextMenuResult > => {
249+ return new Promise ( ( resolve ) => {
250+ const setupMenu = async ( ) => {
251+ const externalAppsItems = await buildExternalAppsMenuItems (
252+ filePath ,
253+ resolve ,
254+ ) ;
255+
256+ showContextMenu ( externalAppsItems , { action : null } ) . then ( resolve ) ;
257+ } ;
258+
259+ setupMenu ( ) ;
133260 } ) ;
134261 } ,
135262} ) ;
0 commit comments