Skip to content

Commit

Permalink
Refactor undo-redo action for editors (#13963)
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew authored Aug 6, 2024
1 parent effb3ec commit 9e91241
Show file tree
Hide file tree
Showing 22 changed files with 367 additions and 118 deletions.
14 changes: 11 additions & 3 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { UserWorkingDirectoryProvider } from './user-working-directory-provider'
import { UNTITLED_SCHEME, UntitledResourceResolver } from '../common';
import { LanguageQuickPickService } from './i18n/language-quick-pick-service';
import { SidebarMenu } from './shell/sidebar-menu-widget';
import { UndoRedoHandlerService } from './undo-redo-handler';

export namespace CommonMenus {

Expand Down Expand Up @@ -443,6 +444,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
@inject(UntitledResourceResolver)
protected readonly untitledResourceResolver: UntitledResourceResolver;

@inject(UndoRedoHandlerService)
protected readonly undoRedoHandlerService: UndoRedoHandlerService;

protected pinnedKey: ContextKey<boolean>;
protected inputFocus: ContextKey<boolean>;

Expand Down Expand Up @@ -814,10 +818,14 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
}));

commandRegistry.registerCommand(CommonCommands.UNDO, {
execute: () => document.execCommand('undo')
execute: () => {
this.undoRedoHandlerService.undo();
}
});
commandRegistry.registerCommand(CommonCommands.REDO, {
execute: () => document.execCommand('redo')
execute: () => {
this.undoRedoHandlerService.redo();
}
});
commandRegistry.registerCommand(CommonCommands.SELECT_ALL, {
execute: () => document.execCommand('selectAll')
Expand Down Expand Up @@ -1080,7 +1088,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
},
{
command: CommonCommands.REDO.id,
keybinding: 'ctrlcmd+shift+z'
keybinding: isOSX ? 'ctrlcmd+shift+z' : 'ctrlcmd+y'
},
{
command: CommonCommands.SELECT_ALL.id,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ import { LanguageIconLabelProvider } from './language-icon-provider';
import { bindTreePreferences } from './tree';
import { OpenWithService } from './open-with-service';
import { ViewColumnService } from './shell/view-column-service';
import { DomInputUndoRedoHandler, UndoRedoHandler, UndoRedoHandlerService } from './undo-redo-handler';

export { bindResourceProvider, bindMessageService, bindPreferenceService };

Expand Down Expand Up @@ -466,4 +467,9 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
bind(SecondaryWindowHandler).toSelf().inSingletonScope();

bind(ViewColumnService).toSelf().inSingletonScope();

bind(UndoRedoHandlerService).toSelf().inSingletonScope();
bindContributionProvider(bind, UndoRedoHandler);
bind(DomInputUndoRedoHandler).toSelf().inSingletonScope();
bind(UndoRedoHandler).toService(DomInputUndoRedoHandler);
});
1 change: 1 addition & 0 deletions packages/core/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ export * from './decoration-style';
export * from './styling-service';
export * from './hover-service';
export * from './saveable-service';
export * from './undo-redo-handler';
85 changes: 85 additions & 0 deletions packages/core/src/browser/undo-redo-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// *****************************************************************************
// Copyright (C) 2024 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 { inject, injectable, named, postConstruct } from 'inversify';
import { ContributionProvider } from '../common';

export const UndoRedoHandler = Symbol('UndoRedoHandler');

export interface UndoRedoHandler<T> {
priority: number;
select(): T | undefined;
undo(item: T): void;
redo(item: T): void;
}

@injectable()
export class UndoRedoHandlerService {

@inject(ContributionProvider) @named(UndoRedoHandler)
protected readonly provider: ContributionProvider<UndoRedoHandler<unknown>>;

protected handlers: UndoRedoHandler<unknown>[];

@postConstruct()
protected init(): void {
this.handlers = this.provider.getContributions().sort((a, b) => b.priority - a.priority);
}

undo(): void {
for (const handler of this.handlers) {
const selection = handler.select();
if (selection) {
handler.undo(selection);
return;
}
}
}

redo(): void {
for (const handler of this.handlers) {
const selection = handler.select();
if (selection) {
handler.redo(selection);
return;
}
}
}

}

@injectable()
export class DomInputUndoRedoHandler implements UndoRedoHandler<Element> {

priority = 1000;

select(): Element | undefined {
const element = document.activeElement;
if (element && ['input', 'textarea'].includes(element.tagName.toLowerCase())) {
return element;
}
return undefined;
}

undo(item: Element): void {
document.execCommand('undo');
}

redo(item: Element): void {
document.execCommand('redo');
}

}
6 changes: 3 additions & 3 deletions packages/monaco/src/browser/monaco-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/brow
export namespace MonacoCommands {

export const COMMON_ACTIONS = new Map<string, string>([
['undo', CommonCommands.UNDO.id],
['redo', CommonCommands.REDO.id],
['editor.action.selectAll', CommonCommands.SELECT_ALL.id],
['actions.find', CommonCommands.FIND.id],
['editor.action.startFindReplaceAction', CommonCommands.REPLACE.id],
Expand All @@ -47,7 +45,9 @@ export namespace MonacoCommands {
export const GO_TO_DEFINITION = 'editor.action.revealDefinition';

export const EXCLUDE_ACTIONS = new Set([
'editor.action.quickCommand'
'editor.action.quickCommand',
'undo',
'redo'
]);
}

Expand Down
8 changes: 8 additions & 0 deletions packages/monaco/src/browser/monaco-editor-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
);
}

undo(): void {
this.model.undo();
}

redo(): void {
this.model.redo();
}

dispose(): void {
this.toDispose.dispose();
}
Expand Down
9 changes: 8 additions & 1 deletion packages/monaco/src/browser/monaco-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { MenuContribution, CommandContribution, quickInputServicePath } from '@t
import {
FrontendApplicationContribution, KeybindingContribution,
PreferenceService, PreferenceSchemaProvider, createPreferenceProxy,
PreferenceScope, PreferenceChange, OVERRIDE_PROPERTY_PATTERN, QuickInputService, StylingParticipant, WebSocketConnectionProvider
PreferenceScope, PreferenceChange, OVERRIDE_PROPERTY_PATTERN, QuickInputService, StylingParticipant, WebSocketConnectionProvider,
UndoRedoHandler
} from '@theia/core/lib/browser';
import { TextEditorProvider, DiffNavigatorProvider, TextEditor } from '@theia/editor/lib/browser';
import { MonacoEditorProvider, MonacoEditorFactory } from './monaco-editor-provider';
Expand Down Expand Up @@ -73,6 +74,7 @@ import { ThemeService } from '@theia/core/lib/browser/theming';
import { ThemeServiceWithDB } from './monaco-indexed-db';
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { ActiveMonacoUndoRedoHandler, FocusedMonacoUndoRedoHandler } from './monaco-undo-redo-handler';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(MonacoThemingService).toSelf().inSingletonScope();
Expand Down Expand Up @@ -175,6 +177,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bind(MonacoIconRegistry).toSelf().inSingletonScope();
bind(IconRegistry).toService(MonacoIconRegistry);

bind(FocusedMonacoUndoRedoHandler).toSelf().inSingletonScope();
bind(ActiveMonacoUndoRedoHandler).toSelf().inSingletonScope();
bind(UndoRedoHandler).toService(FocusedMonacoUndoRedoHandler);
bind(UndoRedoHandler).toService(ActiveMonacoUndoRedoHandler);
});

export const MonacoConfigurationService = Symbol('MonacoConfigurationService');
Expand Down
64 changes: 64 additions & 0 deletions packages/monaco/src/browser/monaco-undo-redo-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// *****************************************************************************
// Copyright (C) 2024 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 { UndoRedoHandler } from '@theia/core/lib/browser';
import { injectable } from '@theia/core/shared/inversify';
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';

@injectable()
export abstract class AbstractMonacoUndoRedoHandler implements UndoRedoHandler<ICodeEditor> {
priority: number;
abstract select(): ICodeEditor | undefined;
undo(item: ICodeEditor): void {
item.trigger('MonacoUndoRedoHandler', 'undo', undefined);
}
redo(item: ICodeEditor): void {
item.trigger('MonacoUndoRedoHandler', 'redo', undefined);
}
}

@injectable()
export class FocusedMonacoUndoRedoHandler extends AbstractMonacoUndoRedoHandler {
override priority = 10000;

protected codeEditorService = StandaloneServices.get(ICodeEditorService);

override select(): ICodeEditor | undefined {
const focusedEditor = this.codeEditorService.getFocusedCodeEditor();
if (focusedEditor && focusedEditor.hasTextFocus()) {
return focusedEditor;
}
return undefined;
}
}

@injectable()
export class ActiveMonacoUndoRedoHandler extends AbstractMonacoUndoRedoHandler {
override priority = 0;

protected codeEditorService = StandaloneServices.get(ICodeEditorService);

override select(): ICodeEditor | undefined {
const focusedEditor = this.codeEditorService.getActiveCodeEditor();
if (focusedEditor) {
focusedEditor.focus();
return focusedEditor;
}
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@

import { Command, CommandContribution, CommandHandler, CommandRegistry, CompoundMenuNodeRole, MenuContribution, MenuModelRegistry, nls, URI } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { ApplicationShell, codicon, CommonCommands, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
import { ApplicationShell, codicon, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
import { NotebookModel } from '../view-model/notebook-model';
import { NotebookService } from '../service/notebook-service';
import { CellEditType, CellKind, NotebookCommand } from '../../common';
import { NotebookKernelQuickPickService } from '../service/notebook-kernel-quick-pick-service';
import { NotebookExecutionService } from '../service/notebook-execution-service';
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { NOTEBOOK_CELL_CURSOR_FIRST_LINE, NOTEBOOK_CELL_CURSOR_LAST_LINE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS } from './notebook-context-keys';
import { NotebookClipboardService } from '../service/notebook-clipboard-service';
Expand Down Expand Up @@ -208,21 +207,6 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
}
);

commands.registerHandler(CommonCommands.UNDO.id, {
isEnabled: () => {
const widget = this.shell.activeWidget;
return widget instanceof NotebookEditorWidget && !Boolean(widget.model?.readOnly);
},
execute: () => (this.shell.activeWidget as NotebookEditorWidget).undo()
});
commands.registerHandler(CommonCommands.REDO.id, {
isEnabled: () => {
const widget = this.shell.activeWidget;
return widget instanceof NotebookEditorWidget && !Boolean(widget.model?.readOnly);
},
execute: () => (this.shell.activeWidget as NotebookEditorWidget).redo()
});

commands.registerCommand(NotebookCommands.CUT_SELECTED_CELL, this.editableCommandHandler(
() => {
const model = this.notebookEditorWidgetService.focusedEditor?.model;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// *****************************************************************************
// Copyright (C) 2024 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 { inject, injectable } from '@theia/core/shared/inversify';
import { ApplicationShell, UndoRedoHandler } from '@theia/core/lib/browser';
import { NotebookEditorWidget } from '../notebook-editor-widget';

@injectable()
export class NotebookUndoRedoHandler implements UndoRedoHandler<NotebookEditorWidget> {

@inject(ApplicationShell)
protected readonly applicationShell: ApplicationShell;

priority = 200;
select(): NotebookEditorWidget | undefined {
const current = this.applicationShell.currentWidget;
if (current instanceof NotebookEditorWidget) {
return current;
}
return undefined;
}
undo(item: NotebookEditorWidget): void {
item.undo();
}
redo(item: NotebookEditorWidget): void {
item.redo();
}
}
6 changes: 5 additions & 1 deletion packages/notebook/src/browser/notebook-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import '../../src/browser/style/index.css';

import { ContainerModule } from '@theia/core/shared/inversify';
import { FrontendApplicationContribution, KeybindingContribution, LabelProviderContribution, OpenHandler, WidgetFactory } from '@theia/core/lib/browser';
import { FrontendApplicationContribution, KeybindingContribution, LabelProviderContribution, OpenHandler, UndoRedoHandler, WidgetFactory } from '@theia/core/lib/browser';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { NotebookOpenHandler } from './notebook-open-handler';
import { CommandContribution, MenuContribution, ResourceResolver, } from '@theia/core';
Expand Down Expand Up @@ -46,6 +46,7 @@ import { NotebookOutputActionContribution } from './contributions/notebook-outpu
import { NotebookClipboardService } from './service/notebook-clipboard-service';
import { bindNotebookPreferences } from './contributions/notebook-preferences';
import { NotebookOptionsService } from './service/notebook-options';
import { NotebookUndoRedoHandler } from './contributions/notebook-undo-redo-handler';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotebookColorContribution).toSelf().inSingletonScope();
Expand Down Expand Up @@ -108,4 +109,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bindNotebookPreferences(bind);
bind(NotebookOptionsService).toSelf().inSingletonScope();

bind(NotebookUndoRedoHandler).toSelf().inSingletonScope();
bind(UndoRedoHandler).toService(NotebookUndoRedoHandler);
});
Loading

0 comments on commit 9e91241

Please sign in to comment.