// ***************************************************************************** // Copyright (C) 2017 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { EditorManager } from './editor-manager'; import { TextEditor } from './editor'; import { injectable, inject, optional, named } from '@theia/core/shared/inversify'; import { StatusBarAlignment, StatusBar } from '@theia/core/lib/browser/status-bar/status-bar'; import { FrontendApplicationContribution, DiffUris, DockLayout, QuickInputService, KeybindingRegistry, KeybindingContribution, SHELL_TABBAR_CONTEXT_SPLIT, ApplicationShell, WidgetStatusBarContribution, Widget, OpenWithService } from '@theia/core/lib/browser'; import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { CommandHandler, DisposableCollection, MenuContribution, MenuModelRegistry, ContributionProvider, Prioritizeable } from '@theia/core'; import { EditorCommands } from './editor-command'; import { CommandRegistry, CommandContribution } from '@theia/core/lib/common'; import { SUPPORTED_ENCODINGS } from '@theia/core/lib/common/supported-encodings'; import { nls } from '@theia/core/lib/common/nls'; import { CurrentWidgetCommandAdapter } from '@theia/core/lib/browser/shell/current-widget-command-adapter'; import { EditorWidget } from './editor-widget'; import { EditorLanguageStatusService } from './language-status/editor-language-status-service'; import { QuickEditorService } from './quick-editor-service'; import { SplitEditorContribution } from './split-editor-contribution'; @injectable() export class EditorContribution implements FrontendApplicationContribution, CommandContribution, KeybindingContribution, MenuContribution, WidgetStatusBarContribution { @inject(EditorManager) protected readonly editorManager: EditorManager; @inject(OpenWithService) protected readonly openWithService: OpenWithService; @inject(EditorLanguageStatusService) protected readonly languageStatusService: EditorLanguageStatusService; @inject(ApplicationShell) protected readonly shell: ApplicationShell; @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService; @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; @inject(ContributionProvider) @named(SplitEditorContribution) protected readonly splitEditorContributions: ContributionProvider; onStart(): void { this.initEditorContextKeys(); this.openWithService.registerHandler({ id: 'default', label: this.editorManager.label, providerName: nls.localizeByDefault('Built-in'), canHandle: () => 100, // Higher priority than any other handler // so that the text editor always appears first in the quick pick getOrder: () => 10000, open: uri => this.editorManager.open(uri) }); } protected initEditorContextKeys(): void { const editorIsOpen = this.contextKeyService.createKey('editorIsOpen', false); const textCompareEditorVisible = this.contextKeyService.createKey('textCompareEditorVisible', false); const updateContextKeys = () => { const widgets = this.editorManager.all; editorIsOpen.set(!!widgets.length); textCompareEditorVisible.set(widgets.some(widget => DiffUris.isDiffUri(widget.editor.uri))); }; updateContextKeys(); for (const widget of this.editorManager.all) { widget.disposed.connect(updateContextKeys); } this.editorManager.onCreated(widget => { updateContextKeys(); widget.disposed.connect(updateContextKeys); }); } protected readonly toDisposeOnCurrentEditorChanged = new DisposableCollection(); canHandle(widget: Widget): widget is EditorWidget { return widget instanceof EditorWidget; } activate(statusBar: StatusBar, widget: EditorWidget): void { this.toDisposeOnCurrentEditorChanged.dispose(); const editor = widget.editor; this.updateLanguageStatus(statusBar, editor); this.updateEncodingStatus(statusBar, editor); this.setCursorPositionStatus(statusBar, editor); this.toDisposeOnCurrentEditorChanged.pushAll([ editor.onLanguageChanged(() => this.updateLanguageStatus(statusBar, editor)), editor.onEncodingChanged(() => this.updateEncodingStatus(statusBar, editor)), editor.onCursorPositionChanged(() => this.setCursorPositionStatus(statusBar, editor)) ]); } deactivate(statusBar: StatusBar): void { this.toDisposeOnCurrentEditorChanged.dispose(); this.updateLanguageStatus(statusBar, undefined); this.updateEncodingStatus(statusBar, undefined); this.setCursorPositionStatus(statusBar, undefined); } protected updateLanguageStatus(statusBar: StatusBar, editor: TextEditor | undefined): void { this.languageStatusService.updateLanguageStatus(editor); } protected updateEncodingStatus(statusBar: StatusBar, editor: TextEditor | undefined): void { if (!editor) { statusBar.removeElement('editor-status-encoding'); return; } statusBar.setElement('editor-status-encoding', { text: SUPPORTED_ENCODINGS[editor.getEncoding()].labelShort, alignment: StatusBarAlignment.RIGHT, priority: 10, command: EditorCommands.CHANGE_ENCODING.id, tooltip: nls.localizeByDefault('Select Encoding') }); } protected setCursorPositionStatus(statusBar: StatusBar, editor: TextEditor | undefined): void { if (!editor) { statusBar.removeElement('editor-status-cursor-position'); return; } const { cursor } = editor; statusBar.setElement('editor-status-cursor-position', { text: nls.localizeByDefault('Ln {0}, Col {1}', cursor.line + 1, editor.getVisibleColumn(cursor)), alignment: StatusBarAlignment.RIGHT, priority: 100, tooltip: EditorCommands.GOTO_LINE_COLUMN.label, command: EditorCommands.GOTO_LINE_COLUMN.id }); } registerCommands(commands: CommandRegistry): void { commands.registerCommand(EditorCommands.SHOW_ALL_OPENED_EDITORS, { execute: () => this.quickInputService?.open(QuickEditorService.PREFIX) }); const splitHandlerFactory = (splitMode: DockLayout.InsertMode): CommandHandler => new CurrentWidgetCommandAdapter(this.shell, { isEnabled: title => { if (!title?.owner) { return false; } return this.findSplitContribution(title.owner) !== undefined; }, execute: async title => { if (!title?.owner) { return; } const contribution = this.findSplitContribution(title.owner); if (contribution) { await contribution.split(title.owner, splitMode); } } }); commands.registerCommand(EditorCommands.SPLIT_EDITOR_HORIZONTAL, splitHandlerFactory('split-right')); commands.registerCommand(EditorCommands.SPLIT_EDITOR_VERTICAL, splitHandlerFactory('split-bottom')); commands.registerCommand(EditorCommands.SPLIT_EDITOR_RIGHT, splitHandlerFactory('split-right')); commands.registerCommand(EditorCommands.SPLIT_EDITOR_DOWN, splitHandlerFactory('split-bottom')); commands.registerCommand(EditorCommands.SPLIT_EDITOR_UP, splitHandlerFactory('split-top')); commands.registerCommand(EditorCommands.SPLIT_EDITOR_LEFT, splitHandlerFactory('split-left')); } protected findSplitContribution(widget: Widget): SplitEditorContribution | undefined { const prioritized = Prioritizeable.prioritizeAllSync( this.splitEditorContributions.getContributions(), contribution => contribution.canHandle(widget) ); return prioritized.length > 0 ? prioritized[0].value : undefined; } registerKeybindings(keybindings: KeybindingRegistry): void { keybindings.registerKeybinding({ command: EditorCommands.SHOW_ALL_OPENED_EDITORS.id, keybinding: 'ctrlcmd+k ctrlcmd+p' }); keybindings.registerKeybinding({ command: EditorCommands.SPLIT_EDITOR_HORIZONTAL.id, keybinding: 'ctrlcmd+\\', }); keybindings.registerKeybinding({ command: EditorCommands.SPLIT_EDITOR_VERTICAL.id, keybinding: 'ctrlcmd+k ctrlcmd+\\', }); } registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, { commandId: EditorCommands.SPLIT_EDITOR_UP.id, label: nls.localizeByDefault('Split Up'), order: '1', }); registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, { commandId: EditorCommands.SPLIT_EDITOR_DOWN.id, label: nls.localizeByDefault('Split Down'), order: '2', }); registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, { commandId: EditorCommands.SPLIT_EDITOR_LEFT.id, label: nls.localizeByDefault('Split Left'), order: '3', }); registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, { commandId: EditorCommands.SPLIT_EDITOR_RIGHT.id, label: nls.localizeByDefault('Split Right'), order: '4', }); } }