Merge branch 'rebornix/notebook-keymap' into main
This commit is contained in:
commit
d4e09b1d97
|
@ -91,6 +91,8 @@ import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransfo
|
|||
import { editorOptionsRegistry } from 'vs/editor/common/config/editorOptions';
|
||||
import { NotebookExecutionService } from 'vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl';
|
||||
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
|
||||
import { INotebookKeymapService } from 'vs/workbench/contrib/notebook/common/notebookKeymapService';
|
||||
import { NotebookKeymapService } from 'vs/workbench/contrib/notebook/browser/notebookKeymapServiceImpl';
|
||||
|
||||
/*--------------------------------------------------------------------------------------------- */
|
||||
|
||||
|
@ -607,6 +609,7 @@ registerSingleton(INotebookEditorService, NotebookEditorWidgetService, true);
|
|||
registerSingleton(INotebookKernelService, NotebookKernelService, true);
|
||||
registerSingleton(INotebookExecutionService, NotebookExecutionService, true);
|
||||
registerSingleton(INotebookRendererMessagingService, NotebookRendererMessagingService, true);
|
||||
registerSingleton(INotebookKeymapService, NotebookKeymapService, true);
|
||||
|
||||
const schemas: IJSONSchemaMap = {};
|
||||
function isConfigurationPropertySchema(x: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema; }): x is IConfigurationPropertySchema {
|
||||
|
|
|
@ -92,9 +92,10 @@ export const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput';
|
|||
|
||||
// Hardcoding viewType/extension ID for now. TODO these should be replaced once we can
|
||||
// look them up in the marketplace dynamically.
|
||||
export const IPYNB_VIEW_TYPE = 'jupyter-notebook';
|
||||
export const JUPYTER_EXTENSION_ID = 'ms-toolsai.jupyter';
|
||||
export const KERNEL_EXTENSIONS = new Map<string, string>([
|
||||
['jupyter-notebook', JUPYTER_EXTENSION_ID],
|
||||
[IPYNB_VIEW_TYPE, JUPYTER_EXTENSION_ID],
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { getInstalledExtensions, IExtensionStatus } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
|
||||
import { INotebookKeymapService } from 'vs/workbench/contrib/notebook/common/notebookKeymapService';
|
||||
import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IExtensionIdentifier, IExtensionManagementService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
||||
|
||||
function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
|
||||
const extensionService = accessor.get(IExtensionManagementService);
|
||||
const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService);
|
||||
const onDidInstallExtensions = Event.chain(extensionService.onDidInstallExtensions)
|
||||
.filter(e => e.some(({ operation }) => operation === InstallOperation.Install))
|
||||
.map(e => e.map(({ identifier }) => identifier))
|
||||
.event;
|
||||
return Event.debounce<IExtensionIdentifier[], IExtensionIdentifier[]>(Event.any(
|
||||
Event.chain(Event.any(onDidInstallExtensions, Event.map(extensionService.onDidUninstallExtension, e => [e.identifier])))
|
||||
.event,
|
||||
Event.map(extensionEnablementService.onEnablementChanged, extensions => extensions.map(e => e.identifier))
|
||||
), (result: IExtensionIdentifier[] | undefined, identifiers: IExtensionIdentifier[]) => {
|
||||
result = result || (identifiers.length ? [identifiers[0]] : []);
|
||||
for (const identifier of identifiers) {
|
||||
if (result.some(l => !areSameExtensions(l, identifier))) {
|
||||
result.push(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
const hasRecommendedKeymapKey = 'hasRecommendedKeymap';
|
||||
|
||||
export class NotebookKeymapService extends Disposable implements INotebookKeymapService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private notebookKeymapMemento: Memento;
|
||||
private notebookKeymap: MementoObject;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.notebookKeymapMemento = new Memento('notebookKeymap', storageService);
|
||||
this.notebookKeymap = this.notebookKeymapMemento.getMemento(StorageScope.GLOBAL, StorageTarget.USER);
|
||||
|
||||
this._register(lifecycleService.onDidShutdown(() => this.dispose()));
|
||||
this._register(this.instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
|
||||
Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
|
||||
.then(undefined, onUnexpectedError);
|
||||
})));
|
||||
}
|
||||
|
||||
private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise<void> {
|
||||
return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
|
||||
const keymaps = extensions.filter(extension => isNotebookKeymapExtension(extension));
|
||||
const extension = keymaps.find(extension => areSameExtensions(extension.identifier, extensionIdentifier));
|
||||
if (extension && extension.globallyEnabled) {
|
||||
// there is already a keymap extension
|
||||
this.notebookKeymap[hasRecommendedKeymapKey] = true;
|
||||
this.notebookKeymapMemento.saveMemento();
|
||||
const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled);
|
||||
if (otherKeymaps.length) {
|
||||
return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {
|
||||
const onPrompt = (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.extensionEnablementService.setEnablement(oldKeymaps.map(keymap => keymap.local), EnablementState.DisabledGlobally);
|
||||
}
|
||||
};
|
||||
|
||||
this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),
|
||||
[{
|
||||
label: localize('yes', "Yes"),
|
||||
run: () => onPrompt(true)
|
||||
}, {
|
||||
label: localize('no', "No"),
|
||||
run: () => onPrompt(false)
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function isNotebookKeymapExtension(extension: IExtensionStatus): boolean {
|
||||
const keywords = extension.local.manifest.keywords;
|
||||
if (!keywords) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return keywords.indexOf('notebook-keymap') !== -1;
|
||||
}
|
|
@ -5,21 +5,21 @@
|
|||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import * as model from 'vs/editor/common/model';
|
||||
import { SearchParams } from 'vs/editor/common/model/textModelSearch';
|
||||
import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { CellKind, INotebookSearchOptions, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { CellEditState, CellFocusMode, CellViewModelStateChangeEvent, CursorAtBoundary, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { CellKind, INotebookCellStatusBarItem, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
|
||||
export abstract class BaseCellViewModel extends Disposable {
|
||||
|
||||
|
@ -109,7 +109,7 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
get editorAttached(): boolean {
|
||||
return !!this._textEditor;
|
||||
}
|
||||
private _cursorChangeListener: IDisposable | null = null;
|
||||
private _editorListeners: IDisposable[] = [];
|
||||
private _editorViewStates: editorCommon.ICodeEditorViewState | null = null;
|
||||
private _resolvedCellDecorations = new Map<string, INotebookCellDecorationOptions>();
|
||||
|
||||
|
@ -153,7 +153,8 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
private readonly _viewContext: ViewContext,
|
||||
private readonly _configurationService: IConfigurationService,
|
||||
private readonly _modelService: ITextModelService,
|
||||
private readonly _undoRedoService: IUndoRedoService
|
||||
private readonly _undoRedoService: IUndoRedoService,
|
||||
// private readonly _keymapService: INotebookKeymapService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -191,14 +192,21 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
return false;
|
||||
}
|
||||
|
||||
// private handleKeyDown(e: IKeyboardEvent) {
|
||||
// if (this.viewType === IPYNB_VIEW_TYPE && isWindows && e.ctrlKey && e.keyCode === KeyCode.Enter) {
|
||||
// this._keymapService.promptKeymapRecommendation();
|
||||
// }
|
||||
// }
|
||||
|
||||
attachTextEditor(editor: ICodeEditor) {
|
||||
if (!editor.hasModel()) {
|
||||
throw new Error('Invalid editor: model is missing');
|
||||
}
|
||||
|
||||
if (this._textEditor === editor) {
|
||||
if (this._cursorChangeListener === null) {
|
||||
this._cursorChangeListener = this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); });
|
||||
if (this._editorListeners.length === 0) {
|
||||
this._editorListeners.push(this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); }));
|
||||
// this._editorListeners.push(this._textEditor.onKeyDown(e => this.handleKeyDown(e)));
|
||||
this._onDidChangeState.fire({ selectionChanged: true });
|
||||
}
|
||||
return;
|
||||
|
@ -222,7 +230,8 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
}
|
||||
});
|
||||
|
||||
this._cursorChangeListener = this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); });
|
||||
this._editorListeners.push(this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); }));
|
||||
// this._editorListeners.push(this._textEditor.onKeyDown(e => this.handleKeyDown(e)));
|
||||
this._onDidChangeState.fire({ selectionChanged: true });
|
||||
this._onDidChangeEditorAttachState.fire();
|
||||
}
|
||||
|
@ -239,8 +248,8 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
});
|
||||
|
||||
this._textEditor = undefined;
|
||||
this._cursorChangeListener?.dispose();
|
||||
this._cursorChangeListener = null;
|
||||
this._editorListeners.forEach(e => e.dispose());
|
||||
this._editorListeners = [];
|
||||
this._onDidChangeEditorAttachState.fire();
|
||||
|
||||
if (this._textModelRef) {
|
||||
|
@ -542,6 +551,7 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
override dispose() {
|
||||
super.dispose();
|
||||
|
||||
this._editorListeners.forEach(e => e.dispose());
|
||||
this._undoRedoService.removeElements(this.uri);
|
||||
|
||||
if (this._textModelRef) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import { CellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/viewM
|
|||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { CellKind, INotebookSearchOptions, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookKeymapService } from 'vs/workbench/contrib/notebook/common/notebookKeymapService';
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { BaseCellViewModel } from './baseCellViewModel';
|
||||
|
||||
|
@ -105,7 +106,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
|||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@INotebookService private readonly _notebookService: INotebookService,
|
||||
@ITextModelService modelService: ITextModelService,
|
||||
@IUndoRedoService undoRedoService: IUndoRedoService
|
||||
@IUndoRedoService undoRedoService: IUndoRedoService,
|
||||
@INotebookKeymapService keymapService: INotebookKeymapService
|
||||
) {
|
||||
super(viewType, model, UUID.generateUuid(), viewContext, configurationService, modelService, undoRedoService);
|
||||
this._outputViewModels = this.model.outputs.map(output => new CellOutputViewModel(this, output, this._notebookService));
|
||||
|
|
|
@ -115,7 +115,7 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM
|
|||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITextModelService textModelService: ITextModelService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IUndoRedoService undoRedoService: IUndoRedoService
|
||||
@IUndoRedoService undoRedoService: IUndoRedoService,
|
||||
) {
|
||||
super(viewType, model, UUID.generateUuid(), viewContext, configurationService, textModelService, undoRedoService);
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const INotebookKeymapService = createDecorator<INotebookKeymapService>('notebookKeymapService');
|
||||
|
||||
export interface INotebookKeymapService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
Loading…
Reference in a new issue