diff --git a/packages/navigator/src/browser/navigator-contribution.ts b/packages/navigator/src/browser/navigator-contribution.ts index d4dcc631dc6d7..1cd1cf425f1e2 100644 --- a/packages/navigator/src/browser/navigator-contribution.ts +++ b/packages/navigator/src/browser/navigator-contribution.ts @@ -21,7 +21,7 @@ import { OpenerService, FrontendApplicationContribution, FrontendApplication, CompositeTreeNode } from '@theia/core/lib/browser'; import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution'; -import { CommandRegistry, MenuModelRegistry, MenuPath, isOSX, Command, DisposableCollection } from '@theia/core/lib/common'; +import { CommandRegistry, MenuModelRegistry, MenuPath, isOSX, Command, DisposableCollection, Mutable } from '@theia/core/lib/common'; import { SHELL_TABBAR_CONTEXT_MENU } from '@theia/core/lib/browser'; import { WorkspaceCommands, WorkspaceService, WorkspacePreferences } from '@theia/workspace/lib/browser'; import { FILE_NAVIGATOR_ID, FileNavigatorWidget, EXPLORER_VIEW_CONTAINER_ID } from './navigator-widget'; @@ -30,7 +30,7 @@ import { NavigatorKeybindingContexts } from './navigator-keybinding-context'; import { FileNavigatorFilter } from './navigator-filter'; import { WorkspaceNode } from './navigator-tree'; import { NavigatorContextKeyService } from './navigator-context-key-service'; -import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { TabBarToolbarContribution, TabBarToolbarRegistry, TabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { FileSystemCommands } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution'; import { NavigatorDiff, NavigatorDiffCommands } from './navigator-diff'; import { UriSelection } from '@theia/core/lib/common/selection'; @@ -61,6 +61,13 @@ export namespace FileNavigatorCommands { }; } +/** + * Navigator `More Actions...` toolbar item groups. + */ +export namespace NavigatorMoreToolbarGroups { + export const NEW_OPEN = '1_navigator_new_open'; +} + export const NAVIGATOR_CONTEXT_MENU: MenuPath = ['navigator-context-menu']; /** @@ -95,6 +102,9 @@ export namespace NavigatorContextMenu { @injectable() export class FileNavigatorContribution extends AbstractViewContribution implements FrontendApplicationContribution, TabBarToolbarContribution { + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; + @inject(NavigatorContextKeyService) protected readonly contextKeyService: NavigatorContextKeyService; @@ -340,6 +350,35 @@ export class FileNavigatorContribution extends AbstractViewContribution) => { + const commandId = item.command; + const id = 'navigator.tabbar.toolbar.' + commandId; + const command = this.commandRegistry.getCommand(commandId); + this.commandRegistry.registerCommand({ id, iconClass: command && command.iconClass }, { + execute: (w, ...args) => w instanceof FileNavigatorWidget + && this.commandRegistry.executeCommand(commandId, ...args), + isEnabled: (w, ...args) => w instanceof FileNavigatorWidget + && this.commandRegistry.isEnabled(commandId, ...args), + isVisible: (w, ...args) => w instanceof FileNavigatorWidget + && this.commandRegistry.isVisible(commandId, ...args), + }); + item.command = id; + toolbarRegistry.registerItem(item); + }; + navigatorRegisterItem({ + id: WorkspaceCommands.NEW_FILE_ROOT.id, + command: WorkspaceCommands.NEW_FILE_ROOT.id, + tooltip: 'New File', + group: NavigatorMoreToolbarGroups.NEW_OPEN, + }); + navigatorRegisterItem({ + id: WorkspaceCommands.NEW_FOLDER_ROOT.id, + command: WorkspaceCommands.NEW_FOLDER_ROOT.id, + tooltip: 'New Folder', + group: NavigatorMoreToolbarGroups.NEW_OPEN, + }); } /** diff --git a/packages/workspace/src/browser/workspace-commands.ts b/packages/workspace/src/browser/workspace-commands.ts index 6397b97807c62..3d234942e0ffd 100644 --- a/packages/workspace/src/browser/workspace-commands.ts +++ b/packages/workspace/src/browser/workspace-commands.ts @@ -83,11 +83,17 @@ export namespace WorkspaceCommands { category: FILE_CATEGORY, label: 'New File' }; + export const NEW_FILE_ROOT: Command = { + id: 'file.newFile.root', + }; export const NEW_FOLDER: Command = { id: 'file.newFolder', category: FILE_CATEGORY, label: 'New Folder' }; + export const NEW_FOLDER_ROOT: Command = { + id: 'file.newFolder.root', + }; export const FILE_OPEN_WITH = (opener: OpenHandler): Command => ({ id: `file.openWith.${opener.id}` }); @@ -195,47 +201,21 @@ export class WorkspaceCommandContribution implements CommandContribution { } }); registry.registerCommand(WorkspaceCommands.NEW_FILE, this.newWorkspaceRootUriAwareCommandHandler({ - execute: uri => this.getDirectory(uri).then(parent => { - if (parent) { - const parentUri = new URI(parent.uri); - const { fileName, fileExtension } = this.getDefaultFileConfig(); - const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, parent, fileName, fileExtension); - - const dialog = new SingleTextInputDialog({ - title: 'New File', - initialValue: vacantChildUri.path.base, - validate: name => this.validateFileName(name, parent, true) - }); - - dialog.open().then(name => { - if (name) { - const fileUri = parentUri.resolve(name); - this.fileSystem.createFile(fileUri.toString()).then(() => { - open(this.openerService, fileUri); - }); - } - }); - } - }) + execute: uri => this.getDirectory(uri).then(parent => this.createNewFile(parent)) })); + registry.registerCommand(WorkspaceCommands.NEW_FILE_ROOT, { + isEnabled: () => !!this.workspaceService.workspace && !this.workspaceService.isMultiRootWorkspaceOpened, + isVisible: () => !!this.workspaceService.workspace && !this.workspaceService.isMultiRootWorkspaceOpened, + execute: () => this.createNewFile(this.workspaceService.workspace), + }); registry.registerCommand(WorkspaceCommands.NEW_FOLDER, this.newWorkspaceRootUriAwareCommandHandler({ - execute: uri => this.getDirectory(uri).then(parent => { - if (parent) { - const parentUri = new URI(parent.uri); - const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, parent, 'Untitled'); - const dialog = new SingleTextInputDialog({ - title: 'New Folder', - initialValue: vacantChildUri.path.base, - validate: name => this.validateFileName(name, parent, true) - }); - dialog.open().then(name => { - if (name) { - this.fileSystem.createFolder(parentUri.resolve(name).toString()); - } - }); - } - }) + execute: uri => this.getDirectory(uri).then(parent => this.createNewFolder(parent)) })); + registry.registerCommand(WorkspaceCommands.NEW_FOLDER_ROOT, { + isEnabled: () => !!this.workspaceService.workspace && !this.workspaceService.isMultiRootWorkspaceOpened, + isVisible: () => !!this.workspaceService.workspace && !this.workspaceService.isMultiRootWorkspaceOpened, + execute: () => this.createNewFolder(this.workspaceService.workspace), + }); registry.registerCommand(WorkspaceCommands.FILE_RENAME, this.newMultiUriAwareCommandHandler({ isEnabled: uris => uris.some(uri => !this.isWorkspaceRoot(uri)) && uris.length === 1, isVisible: uris => uris.some(uri => !this.isWorkspaceRoot(uri)) && uris.length === 1, @@ -323,6 +303,56 @@ export class WorkspaceCommandContribution implements CommandContribution { return new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, handler); } + /** + * Create a new file given a file stat with the default name and extension params. + * @param stat {FileStat} the given file stat. + */ + protected createNewFile(stat: FileStat | undefined): void { + if (!stat) { + return; + } + const parentUri = new URI(stat.uri); + const { fileName, fileExtension } = this.getDefaultFileConfig(); + const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, stat, fileName, fileExtension); + + const dialog = new SingleTextInputDialog({ + title: 'New File', + initialValue: vacantChildUri.path.base, + validate: name => this.validateFileName(name, stat, true) + }); + + dialog.open().then(name => { + if (name) { + const fileUri = parentUri.resolve(name); + this.fileSystem.createFile(fileUri.toString()).then(() => { + open(this.openerService, fileUri); + }); + } + }); + } + + /** + * Create a new folder given a file stat. + * @param stat {FileStat} the given file stat. + */ + protected createNewFolder(stat: FileStat | undefined): void { + if (!stat) { + return; + } + const parentUri = new URI(stat.uri); + const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, stat, 'Untitled'); + const dialog = new SingleTextInputDialog({ + title: 'New Folder', + initialValue: vacantChildUri.path.base, + validate: name => this.validateFileName(name, stat, true) + }); + dialog.open().then(name => { + if (name) { + this.fileSystem.createFolder(parentUri.resolve(name).toString()); + } + }); + } + /** * Returns an error message if the file name is invalid. Otherwise, an empty string. *