11import * as path from 'node:path' ;
22import * as vscode from 'vscode' ;
3- import { CommandsProvider , MAGEFORGE_COMMANDS , MageforgeCommand } from './commandsProvider' ;
3+ import {
4+ CommandsProvider ,
5+ getAvailableMageforgeCommands ,
6+ MAGEFORGE_COMMANDS ,
7+ MageforgeCommand ,
8+ } from './commandsProvider' ;
49import { ThemeTreeItem , ThemesProvider } from './themesProvider' ;
510import { WelcomeViewProvider } from './welcomeProvider' ;
611import { ChangelogViewProvider } from './changelogProvider' ;
@@ -67,6 +72,17 @@ export function activate(context: vscode.ExtensionContext) {
6772 commandsProvider . refresh ( ) ,
6873 ) ,
6974 vscode . commands . registerCommand ( 'mageforge.refreshThemes' , ( ) => themesProvider . refresh ( ) ) ,
75+ vscode . commands . registerCommand ( 'mageforge.addQuickAction' , ( ) => addQuickAction ( ) ) ,
76+ vscode . commands . registerCommand ( 'mageforge.removeQuickAction' , ( index : number ) =>
77+ removeQuickAction ( index ) ,
78+ ) ,
79+ vscode . commands . registerCommand ( 'mageforge.settingsQuickActions' , ( ) =>
80+ settingsQuickActions ( ) ,
81+ ) ,
82+ vscode . commands . registerCommand (
83+ 'mageforge.reorderQuickAction' ,
84+ ( fromIndex : number , toIndex : number ) => reorderQuickAction ( fromIndex , toIndex ) ,
85+ ) ,
7086 vscode . commands . registerCommand (
7187 'mageforge.template.overrideFile' ,
7288 async ( uri ?: vscode . Uri ) => {
@@ -90,6 +106,178 @@ export function activate(context: vscode.ExtensionContext) {
90106 ) ;
91107}
92108
109+ /**
110+ * Let the user pick a MageForge command that is not already a quick action and
111+ * append it to the `mageforge.quickActions` setting.
112+ */
113+ async function addQuickAction ( ) : Promise < void > {
114+ const magentoRoot = getMagentoRoot ( ) ;
115+ if ( ! magentoRoot ) {
116+ void vscode . window . showErrorMessage ( 'MageForge: No workspace folder open.' ) ;
117+ return ;
118+ }
119+
120+ const config = vscode . workspace . getConfiguration ( 'mageforge' ) ;
121+ const currentActions = config . get <
122+ { label : string ; command ?: string ; url ?: string ; icon : string } [ ]
123+ > ( 'quickActions' , [ ] ) ;
124+
125+ let availableCliCommands : string [ ] ;
126+ try {
127+ availableCliCommands = await getAvailableMageforgeCommands ( magentoRoot ) ;
128+ } catch {
129+ void vscode . window . showErrorMessage (
130+ 'MageForge: Could not load available commands. Is the MageForge CLI installed?' ,
131+ ) ;
132+ return ;
133+ }
134+
135+ const commandByCli = new Map ( MAGEFORGE_COMMANDS . map ( ( cmd ) => [ cmd . cliCommand , cmd ] ) ) ;
136+ const existingCommands = new Set (
137+ currentActions . filter ( ( a ) => a . command ) . map ( ( a ) => a . command ! ) ,
138+ ) ;
139+
140+ const choices = availableCliCommands
141+ . map ( ( cliCommand ) => commandByCli . get ( cliCommand ) )
142+ . filter ( ( cmd ) : cmd is MageforgeCommand => Boolean ( cmd ) )
143+ . filter ( ( cmd ) => ! existingCommands . has ( cmd . id ) )
144+ . map ( ( cmd ) => ( {
145+ label : cmd . label ,
146+ description : cmd . description ,
147+ mageforgeCommand : cmd ,
148+ } ) ) ;
149+
150+ if ( choices . length === 0 ) {
151+ void vscode . window . showInformationMessage (
152+ 'MageForge: All available commands are already quick actions.' ,
153+ ) ;
154+ return ;
155+ }
156+
157+ const picked = await vscode . window . showQuickPick ( choices , {
158+ placeHolder : 'Select a command to add as quick action' ,
159+ } ) ;
160+ if ( ! picked ) {
161+ return ;
162+ }
163+
164+ const icon = await pickQuickActionIcon ( picked . mageforgeCommand . label ) ;
165+ if ( ! icon ) {
166+ return ;
167+ }
168+
169+ const newAction = {
170+ label : picked . mageforgeCommand . label ,
171+ command : picked . mageforgeCommand . id ,
172+ icon,
173+ } ;
174+
175+ await config . update (
176+ 'quickActions' ,
177+ [ ...currentActions , newAction ] ,
178+ vscode . ConfigurationTarget . Workspace ,
179+ ) ;
180+ void vscode . window . showInformationMessage (
181+ `MageForge: Added "${ picked . mageforgeCommand . label } " to quick actions.` ,
182+ ) ;
183+ }
184+
185+ /**
186+ * Reorder quick actions by moving an item from one index to another.
187+ */
188+ async function reorderQuickAction ( fromIndex : number , toIndex : number ) : Promise < void > {
189+ const config = vscode . workspace . getConfiguration ( 'mageforge' ) ;
190+ const currentActions = config . get <
191+ { label : string ; command ?: string ; url ?: string ; icon : string } [ ]
192+ > ( 'quickActions' , [ ] ) ;
193+
194+ if (
195+ fromIndex < 0 ||
196+ fromIndex >= currentActions . length ||
197+ toIndex < 0 ||
198+ toIndex >= currentActions . length ||
199+ fromIndex === toIndex
200+ ) {
201+ return ;
202+ }
203+
204+ const updated = [ ...currentActions ] ;
205+ const [ moved ] = updated . splice ( fromIndex , 1 ) ;
206+ updated . splice ( toIndex , 0 , moved ) ;
207+
208+ await config . update ( 'quickActions' , updated , vscode . ConfigurationTarget . Workspace ) ;
209+ }
210+
211+ /**
212+ * Remove a quick action by its index in the settings array.
213+ */
214+ async function removeQuickAction ( index : number ) : Promise < void > {
215+ const config = vscode . workspace . getConfiguration ( 'mageforge' ) ;
216+ const currentActions = config . get <
217+ { label : string ; command ?: string ; url ?: string ; icon : string } [ ]
218+ > ( 'quickActions' , [ ] ) ;
219+
220+ if ( index < 0 || index >= currentActions . length ) {
221+ return ;
222+ }
223+
224+ const removed = currentActions [ index ] ;
225+ const updated = [ ...currentActions . slice ( 0 , index ) , ...currentActions . slice ( index + 1 ) ] ;
226+
227+ await config . update ( 'quickActions' , updated , vscode . ConfigurationTarget . Workspace ) ;
228+ void vscode . window . showInformationMessage (
229+ `MageForge: Removed "${ removed . label } " from quick actions.` ,
230+ ) ;
231+ }
232+
233+ /**
234+ * Open settings.json with the mageforge.quickActions key focused so the user
235+ * can reorder, rename and change icons manually.
236+ */
237+ async function settingsQuickActions ( ) : Promise < void > {
238+ await vscode . commands . executeCommand ( 'workbench.action.openSettings' , 'mageforge' ) ;
239+ }
240+
241+ /**
242+ * Let the user pick a Tabler icon for the new quick action.
243+ * Defaults to the command's suggested icon.
244+ */
245+ async function pickQuickActionIcon ( commandLabel : string ) : Promise < string | undefined > {
246+ const icons = [
247+ { label : 'Hammer' , icon : 'hammer' } ,
248+ { label : 'Eye' , icon : 'eye' } ,
249+ { label : 'Search' , icon : 'search' } ,
250+ { label : 'Checklist' , icon : 'checklist' } ,
251+ { label : 'Tools' , icon : 'tools' } ,
252+ { label : 'Trash' , icon : 'trash' } ,
253+ { label : 'List' , icon : 'list-unordered' } ,
254+ { label : 'Copy' , icon : 'copy' } ,
255+ { label : 'Cloud Download' , icon : 'cloud-download' } ,
256+ { label : 'Pulse' , icon : 'pulse' } ,
257+ { label : 'Info' , icon : 'info' } ,
258+ { label : 'Star' , icon : 'star' } ,
259+ { label : 'Bulb' , icon : 'bulb' } ,
260+ { label : 'Color' , icon : 'symbol-color' } ,
261+ { label : 'Refresh' , icon : 'refresh' } ,
262+ { label : 'Shield' , icon : 'shield' } ,
263+ { label : 'Package' , icon : 'package' } ,
264+ { label : 'Rocket' , icon : 'rocket' } ,
265+ { label : 'Settings' , icon : 'settings' } ,
266+ { label : 'Terminal' , icon : 'terminal' } ,
267+ ] ;
268+
269+ const choices = icons . map ( ( item ) => ( {
270+ label : `$(${ item . icon } ) ${ item . label } ` ,
271+ icon : item . icon ,
272+ } ) ) ;
273+
274+ const picked = await vscode . window . showQuickPick ( choices , {
275+ placeHolder : `Select an icon for "${ commandLabel } "` ,
276+ } ) ;
277+
278+ return picked ?. icon ;
279+ }
280+
93281/**
94282 * Shows an update notification and opens the changelog tab when the extension
95283 * version has changed since the last activation.
0 commit comments