diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index aad8595efcd83..cc305bed2696d 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -41,20 +41,20 @@ test.describe('Theia Main Menu', () => { expect(await mainMenu.isOpen()).toBe(true); }); - test("should show the menu items 'New File' and 'New Folder'", async () => { + test("should show the menu items 'New Text File' and 'New Folder'", async () => { const mainMenu = await menuBar.openMenu('File'); const menuItems = await mainMenu.visibleMenuItems(); - expect(menuItems).toContain('New File...'); + expect(menuItems).toContain('New Text File'); expect(menuItems).toContain('New Folder...'); }); - test("should return menu item by name 'New File'", async () => { + test("should return menu item by name 'New Text File'", async () => { const mainMenu = await menuBar.openMenu('File'); - const menuItem = await mainMenu.menuItemByName('New File...'); + const menuItem = await mainMenu.menuItemByName('New Text File'); expect(menuItem).toBeDefined(); const label = await menuItem?.label(); - expect(label).toBe('New File...'); + expect(label).toBe('New Text File'); const shortCut = await menuItem?.shortCut(); expect(shortCut).toBe(OSUtil.isMacOS ? '⌥ N' : 'Alt+N'); @@ -65,7 +65,7 @@ test.describe('Theia Main Menu', () => { test('should detect whether menu item has submenu', async () => { const mainMenu = await menuBar.openMenu('File'); - const newFileItem = await mainMenu.menuItemByName('New File...'); + const newFileItem = await mainMenu.menuItemByName('New Text File'); const settingsItem = await mainMenu.menuItemByName('Preferences'); expect(await newFileItem?.hasSubmenu()).toBe(false); diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index c9c7cf350db39..2e95ea0a8eaa9 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -52,7 +52,7 @@ import { UTF8 } from '../common/encodings'; import { EnvVariablesServer } from '../common/env-variables'; import { AuthenticationService } from './authentication-service'; import { FormatType, Saveable, SaveOptions } from './saveable'; -import { QuickInputService, QuickPickItem, QuickPickItemOrSeparator } from './quick-input'; +import { QuickInputService, QuickPickItem, QuickPickItemOrSeparator, QuickPickSeparator } from './quick-input'; import { AsyncLocalizationProvider } from '../common/i18n/localization'; import { nls } from '../common/nls'; import { CurrentWidgetCommandAdapter } from './shell/current-widget-command-adapter'; @@ -69,6 +69,7 @@ import { LanguageQuickPickService } from './i18n/language-quick-pick-service'; export namespace CommonMenus { export const FILE = [...MAIN_MENU_BAR, '1_file']; + export const FILE_NEW_TEXT = [...FILE, '1_new_text']; export const FILE_NEW = [...FILE, '1_new']; export const FILE_OPEN = [...FILE, '2_open']; export const FILE_SAVE = [...FILE, '3_save']; @@ -79,6 +80,8 @@ export namespace CommonMenus { export const FILE_SETTINGS_SUBMENU_THEME = [...FILE_SETTINGS_SUBMENU, '2_settings_submenu_theme']; export const FILE_CLOSE = [...FILE, '6_close']; + export const FILE_NEW_CONTRIBUTIONS = 'file/newFile'; + export const EDIT = [...MAIN_MENU_BAR, '2_edit']; export const EDIT_UNDO = [...EDIT, '1_undo']; export const EDIT_CLIPBOARD = [...EDIT, '2_clipboard']; @@ -108,6 +111,7 @@ export namespace CommonCommands { export const FILE_CATEGORY = 'File'; export const VIEW_CATEGORY = 'View'; + export const CREATE_CATEGORY = 'Create'; export const PREFERENCES_CATEGORY = 'Preferences'; export const FILE_CATEGORY_KEY = nls.getDefaultKey(FILE_CATEGORY); export const VIEW_CATEGORY_KEY = nls.getDefaultKey(VIEW_CATEGORY); @@ -272,11 +276,16 @@ export namespace CommonCommands { category: VIEW_CATEGORY, label: 'Toggle Menu Bar' }); - export const NEW_UNTITLED_FILE = Command.toDefaultLocalizedCommand({ - id: 'workbench.action.files.newUntitledFile', + export const NEW_UNTITLED_TEXT_FILE = Command.toDefaultLocalizedCommand({ + id: 'workbench.action.files.newUntitledTextFile', category: FILE_CATEGORY, label: 'New Untitled Text File' }); + export const NEW_UNTITLED_FILE = Command.toDefaultLocalizedCommand({ + id: 'workbench.action.files.newUntitledFile', + category: CREATE_CATEGORY, + label: 'New File...' + }); export const SAVE = Command.toDefaultLocalizedCommand({ id: 'core.save', category: FILE_CATEGORY, @@ -371,6 +380,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; + @inject(StorageService) protected readonly storageService: StorageService; @@ -545,6 +557,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi registry.registerSubmenu(CommonMenus.VIEW, nls.localizeByDefault('View')); registry.registerSubmenu(CommonMenus.HELP, nls.localizeByDefault('Help')); + // For plugins contributing create new file commands/menu-actions + registry.registerIndependentSubmenu(CommonMenus.FILE_NEW_CONTRIBUTIONS, nls.localizeByDefault('New File...')); + registry.registerMenuAction(CommonMenus.FILE_SAVE, { commandId: CommonCommands.SAVE.id }); @@ -693,10 +708,16 @@ export class CommonFrontendContribution implements FrontendApplicationContributi registry.registerSubmenu(CommonMenus.VIEW_APPEARANCE_SUBMENU, nls.localizeByDefault('Appearance')); - registry.registerMenuAction(CommonMenus.FILE_NEW, { + registry.registerMenuAction(CommonMenus.FILE_NEW_TEXT, { + commandId: CommonCommands.NEW_UNTITLED_TEXT_FILE.id, + label: nls.localizeByDefault('New Text File'), + order: 'a' + }); + + registry.registerMenuAction(CommonMenus.FILE_NEW_TEXT, { commandId: CommonCommands.NEW_UNTITLED_FILE.id, label: nls.localizeByDefault('New File...'), - order: 'a' + order: 'a1' }); } @@ -941,13 +962,17 @@ export class CommonFrontendContribution implements FrontendApplicationContributi execute: () => this.toggleBreadcrumbs(), isToggled: () => this.isBreadcrumbsEnabled(), }); - commandRegistry.registerCommand(CommonCommands.NEW_UNTITLED_FILE, { + commandRegistry.registerCommand(CommonCommands.NEW_UNTITLED_TEXT_FILE, { execute: async () => { const untitledUri = this.untitledResourceResolver.createUntitledURI('', await this.workingDirProvider.getUserWorkingDir()); this.untitledResourceResolver.resolve(untitledUri); return open(this.openerService, untitledUri); } }); + commandRegistry.registerCommand(CommonCommands.NEW_UNTITLED_FILE, { + execute: async () => this.showNewFilePicker() + }); + for (const [index, ordinal] of this.getOrdinalNumbers().entries()) { commandRegistry.registerCommand({ id: `workbench.action.focus${ordinal}EditorGroup`, label: index === 0 ? nls.localizeByDefault('Focus First Editor Group') : '', category: nls.localize(CommonCommands.VIEW_CATEGORY_KEY, CommonCommands.VIEW_CATEGORY) }, { isEnabled: () => this.shell.mainAreaTabBars.length > index, @@ -1097,8 +1122,12 @@ export class CommonFrontendContribution implements FrontendApplicationContributi when: 'activeEditorIsPinned' }, { - command: CommonCommands.NEW_UNTITLED_FILE.id, + command: CommonCommands.NEW_UNTITLED_TEXT_FILE.id, keybinding: this.isElectron() ? 'ctrlcmd+n' : 'alt+n', + }, + { + command: CommonCommands.NEW_UNTITLED_FILE.id, + keybinding: 'ctrlcmd+alt+n' } ); for (const [index, ordinal] of this.getOrdinalNumbers().entries()) { @@ -1294,6 +1323,43 @@ export class CommonFrontendContribution implements FrontendApplicationContributi }); } + /** + * @todo https://github.com/eclipse-theia/theia/issues/12824 + */ + protected async showNewFilePicker(): Promise { + const newFileContributions = this.menuRegistry.getMenuNode(CommonMenus.FILE_NEW_CONTRIBUTIONS); // Add menus + const items: QuickPickItemOrSeparator[] = [ + { + label: nls.localizeByDefault('New Text File'), + execute: async () => this.commandRegistry.executeCommand(CommonCommands.NEW_UNTITLED_TEXT_FILE.id) + }, + ...newFileContributions.children + .flatMap(node => { + if (node.children && node.children.length > 0) { + return node.children; + } + return node; + }) + .filter(node => node.role || node.command) + .map(node => { + if (node.role) { + return { type: 'separator' } as QuickPickSeparator; + } + const command = this.commandRegistry.getCommand(node.command!); + return { + label: command!.label!, + execute: async () => this.commandRegistry.executeCommand(command!.id!) + }; + + }) + ]; + this.quickInputService.showQuickPick(items, { + title: nls.localizeByDefault('New File...'), + placeholder: nls.localizeByDefault('Select File Type or Enter File Name...'), + canSelectMany: false + }); + } + registerColors(colors: ColorRegistry): void { colors.register( // Base Colors should be aligned with https://code.visualstudio.com/api/references/theme-color#base-colors diff --git a/packages/core/src/browser/window-contribution.ts b/packages/core/src/browser/window-contribution.ts index 9dcfd1be0d091..14fd338fe8ab4 100644 --- a/packages/core/src/browser/window-contribution.ts +++ b/packages/core/src/browser/window-contribution.ts @@ -51,7 +51,7 @@ export class WindowContribution implements CommandContribution, KeybindingContri } registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(CommonMenus.FILE_NEW, { + registry.registerMenuAction(CommonMenus.FILE_NEW_TEXT, { commandId: WindowCommands.NEW_WINDOW.id, order: 'c' }); diff --git a/packages/workspace/src/browser/workspace-commands.ts b/packages/workspace/src/browser/workspace-commands.ts index 6f7264b584be8..2e7625f434485 100644 --- a/packages/workspace/src/browser/workspace-commands.ts +++ b/packages/workspace/src/browser/workspace-commands.ts @@ -153,7 +153,7 @@ export namespace WorkspaceCommands { export class FileMenuContribution implements MenuContribution { registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(CommonMenus.FILE_NEW, { + registry.registerMenuAction(CommonMenus.FILE_NEW_TEXT, { commandId: WorkspaceCommands.NEW_FOLDER.id, order: 'b' });