Skip to content

Commit f7efa5a

Browse files
authored
Active editor context keys with appropriate events (#9418)
* Active editor context keys with appropriate events * Fix formatting * Oops * Fix tests
1 parent e1fcb7b commit f7efa5a

12 files changed

Lines changed: 105 additions & 5 deletions

src/client/datascience/activation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { debounceAsync, swallowExceptions } from '../common/utils/decorators';
1212
import { IInterpreterService } from '../interpreter/contracts';
1313
import { sendTelemetryEvent } from '../telemetry';
1414
import { PythonDaemonModule, Telemetry } from './constants';
15+
import { ActiveEditorContextService } from './context/activeEditorContext';
1516
import { INotebookEditor, INotebookEditorProvider } from './types';
1617

1718
@injectable()
@@ -21,11 +22,13 @@ export class Activation implements IExtensionSingleActivationService {
2122
@inject(INotebookEditorProvider) private readonly notebookProvider: INotebookEditorProvider,
2223
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
2324
@inject(IPythonExecutionFactory) private readonly factory: IPythonExecutionFactory,
24-
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry
25+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
26+
@inject(ActiveEditorContextService) private readonly contextService: ActiveEditorContextService
2527
) {}
2628
public async activate(): Promise<void> {
2729
this.disposables.push(this.notebookProvider.onDidOpenNotebookEditor(this.onDidOpenNotebookEditor, this));
2830
this.disposables.push(this.interpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter, this));
31+
await this.contextService.activate();
2932
}
3033

3134
private onDidOpenNotebookEditor(_: INotebookEditor) {

src/client/datascience/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ export namespace EditorContexts {
7979
export const HaveInteractiveCells = 'python.datascience.haveinteractivecells';
8080
export const HaveRedoableCells = 'python.datascience.haveredoablecells';
8181
export const HaveInteractive = 'python.datascience.haveinteractive';
82+
export const IsInteractive = 'python.datascience.isinteractive';
8283
export const OwnsSelection = 'python.datascience.ownsSelection';
8384
export const HaveNativeCells = 'python.datascience.havenativecells';
8485
export const HaveNativeRedoableCells = 'python.datascience.havenativeredoablecells';
8586
export const HaveNative = 'python.datascience.havenative';
87+
export const IsNative = 'python.datascience.isnative';
8688
export const HaveCellSelected = 'python.datascience.havecellselected';
8789
}
8890

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { IExtensionSingleActivationService } from '../../activation/types';
8+
import { ICommandManager } from '../../common/application/types';
9+
import { ContextKey } from '../../common/contextKey';
10+
import { IDisposable, IDisposableRegistry } from '../../common/types';
11+
import { EditorContexts } from '../constants';
12+
import { IInteractiveWindow, IInteractiveWindowProvider, INotebookEditor, INotebookEditorProvider } from '../types';
13+
14+
@injectable()
15+
export class ActiveEditorContextService implements IExtensionSingleActivationService, IDisposable {
16+
private readonly disposables: IDisposable[] = [];
17+
constructor(
18+
@inject(IInteractiveWindowProvider) private readonly interactiveProvider: IInteractiveWindowProvider,
19+
@inject(INotebookEditorProvider) private readonly notebookProvider: INotebookEditorProvider,
20+
@inject(ICommandManager) private readonly commandManager: ICommandManager,
21+
@inject(IDisposableRegistry) disposables: IDisposableRegistry
22+
) {
23+
disposables.push(this);
24+
}
25+
public dispose() {
26+
this.disposables.forEach(item => item.dispose());
27+
}
28+
public async activate(): Promise<void> {
29+
this.interactiveProvider.onDidChangeActiveInteractiveWindow(this.onDidChangeActiveInteractiveWindow, this, this.disposables);
30+
this.notebookProvider.onDidChangeActiveNotebookEditor(this.onDidChangeActiveNotebookEditor, this, this.disposables);
31+
}
32+
33+
private onDidChangeActiveInteractiveWindow(e?: IInteractiveWindow) {
34+
const interactiveContext = new ContextKey(EditorContexts.IsInteractive, this.commandManager);
35+
interactiveContext.set(!!e).ignoreErrors();
36+
}
37+
private onDidChangeActiveNotebookEditor(e?: INotebookEditor) {
38+
const interactiveContext = new ContextKey(EditorContexts.IsNative, this.commandManager);
39+
interactiveContext.set(!!e).ignoreErrors();
40+
}
41+
}

src/client/datascience/interactive-ipynb/nativeEditor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const NotebookTransferKey = 'notebook-transfered';
6363

6464
@injectable()
6565
export class NativeEditor extends InteractiveBase implements INotebookEditor {
66+
public get onDidChangeViewState(): Event<void> {
67+
return this._onDidChangeViewState.event;
68+
}
69+
private _onDidChangeViewState = new EventEmitter<void>();
6670
private closedEvent: EventEmitter<INotebookEditor> = new EventEmitter<INotebookEditor>();
6771
private executedEvent: EventEmitter<INotebookEditor> = new EventEmitter<INotebookEditor>();
6872
private modifiedEvent: EventEmitter<INotebookEditor> = new EventEmitter<INotebookEditor>();
@@ -472,6 +476,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
472476
// Update our contexts
473477
const interactiveContext = new ContextKey(EditorContexts.HaveNative, this.commandManager);
474478
interactiveContext.set(visible && active).catch();
479+
this._onDidChangeViewState.fire();
475480
}
476481

477482
protected async closeBecauseOfFailure(_exc: Error): Promise<void> {

src/client/datascience/interactive-ipynb/nativeEditorProvider.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { IDataScienceErrorHandler, INotebookEditor, INotebookEditorProvider, INo
1717

1818
@injectable()
1919
export class NativeEditorProvider implements INotebookEditorProvider, IAsyncDisposable {
20+
public get onDidChangeActiveNotebookEditor(): Event<INotebookEditor | undefined> {
21+
return this._onDidChangeActiveNotebookEditor.event;
22+
}
23+
private readonly _onDidChangeActiveNotebookEditor = new EventEmitter<INotebookEditor | undefined>();
2024
private activeEditors: Map<string, INotebookEditor> = new Map<string, INotebookEditor>();
2125
private executedEditors: Set<string> = new Set<string>();
2226
private _onDidOpenNotebookEditor = new EventEmitter<INotebookEditor>();
@@ -179,6 +183,10 @@ export class NativeEditorProvider implements INotebookEditorProvider, IAsyncDisp
179183
this.disposables.push(e.saved(this.onSavedEditor.bind(this, e.file.fsPath)));
180184
this.openedNotebookCount += 1;
181185
this._onDidOpenNotebookEditor.fire(e);
186+
this.disposables.push(e.onDidChangeViewState(this.onDidChangeViewState, this));
187+
}
188+
private onDidChangeViewState() {
189+
this._onDidChangeActiveNotebookEditor.fire(this.activeEditor);
182190
}
183191

184192
private onSavedEditor(oldPath: string, e: INotebookEditor) {

src/client/datascience/interactive-window/interactiveWindow.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ const historyReactDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', '
4040

4141
@injectable()
4242
export class InteractiveWindow extends InteractiveBase implements IInteractiveWindow {
43+
public get onDidChangeViewState(): Event<void> {
44+
return this._onDidChangeViewState.event;
45+
}
46+
private _onDidChangeViewState = new EventEmitter<void>();
47+
public get visible(): boolean {
48+
return this.viewState.visible;
49+
}
50+
public get active(): boolean {
51+
return this.viewState.active;
52+
}
4353
private closedEvent: EventEmitter<IInteractiveWindow> = new EventEmitter<IInteractiveWindow>();
4454
private waitingForExportCells: boolean = false;
4555
private trackedJupyterStart: boolean = false;
@@ -218,6 +228,10 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi
218228
public scrollToCell(id: string): void {
219229
this.postMessage(InteractiveWindowMessages.ScrollToCell, { id }).ignoreErrors();
220230
}
231+
protected async onViewStateChanged(visible: boolean, active: boolean) {
232+
super.onViewStateChanged(visible, active);
233+
this._onDidChangeViewState.fire();
234+
}
221235

222236
@captureTelemetry(Telemetry.SubmitCellThroughInput, undefined, false)
223237
// tslint:disable-next-line:no-any

src/client/datascience/interactive-window/interactiveWindowProvider.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ interface ISyncData {
2222

2323
@injectable()
2424
export class InteractiveWindowProvider implements IInteractiveWindowProvider, IAsyncDisposable {
25+
public get onDidChangeActiveInteractiveWindow(): Event<IInteractiveWindow | undefined> {
26+
return this._onDidChangeActiveInteractiveWindow.event;
27+
}
28+
private readonly _onDidChangeActiveInteractiveWindow = new EventEmitter<IInteractiveWindow | undefined>();
2529
private activeInteractiveWindow: IInteractiveWindow | undefined;
2630
private postOffice: PostOffice;
2731
private id: string;
@@ -108,9 +112,15 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA
108112
this.disposables.push(handler);
109113
this.activeInteractiveWindowExecuteHandler = this.activeInteractiveWindow.onExecutedCode(this.onInteractiveWindowExecute);
110114
this.disposables.push(this.activeInteractiveWindowExecuteHandler);
115+
this.disposables.push(this.activeInteractiveWindow.onDidChangeViewState(() => this.raiseOnDidChangeActiveInteractiveWindow()));
116+
this.raiseOnDidChangeActiveInteractiveWindow();
111117
return this.activeInteractiveWindow;
112118
}
113119

120+
private raiseOnDidChangeActiveInteractiveWindow() {
121+
const currentWindow = this.getActive();
122+
this._onDidChangeActiveInteractiveWindow.fire(currentWindow?.active && currentWindow.visible ? currentWindow : undefined);
123+
}
114124
private onPeerCountChanged(newCount: number) {
115125
// If we're losing peers, resolve all syncs
116126
if (newCount < this.postOffice.peerCount) {
@@ -159,6 +169,7 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA
159169
this.activeInteractiveWindowExecuteHandler = undefined;
160170
}
161171
}
172+
this.raiseOnDidChangeActiveInteractiveWindow();
162173
};
163174

164175
private async synchronizeCreate(): Promise<void> {

src/client/datascience/serviceRegistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CodeCssGenerator } from './codeCssGenerator';
88
import { CommandRegistry } from './commands/commandRegistry';
99
import { KernelSwitcherCommand } from './commands/kernelSwitcher';
1010
import { JupyterServerSelectorCommand } from './commands/serverSelector';
11+
import { ActiveEditorContextService } from './context/activeEditorContext';
1112
import { DataViewer } from './data-viewing/dataViewer';
1213
import { DataViewerProvider } from './data-viewing/dataViewerProvider';
1314
import { DataScience } from './datascience';
@@ -151,4 +152,5 @@ export function registerTypes(serviceManager: IServiceManager) {
151152
serviceManager.addSingleton<JupyterInterpreterConfigurationService>(JupyterInterpreterConfigurationService, JupyterInterpreterConfigurationService);
152153
serviceManager.addSingleton<JupyterInterpreterService>(JupyterInterpreterService, JupyterInterpreterService);
153154
serviceManager.addSingleton<JupyterInterpreterOldCacheStateStore>(JupyterInterpreterOldCacheStateStore, JupyterInterpreterOldCacheStateStore);
155+
serviceManager.addSingleton<ActiveEditorContextService>(ActiveEditorContextService, ActiveEditorContextService);
154156
}

src/client/datascience/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export interface INotebookExporter extends Disposable {
248248

249249
export const IInteractiveWindowProvider = Symbol('IInteractiveWindowProvider');
250250
export interface IInteractiveWindowProvider {
251+
readonly onDidChangeActiveInteractiveWindow: Event<IInteractiveWindow | undefined>;
251252
onExecutedCode: Event<string>;
252253
getActive(): IInteractiveWindow | undefined;
253254
getOrCreateActive(): Promise<IInteractiveWindow>;
@@ -274,6 +275,9 @@ export interface IInteractiveBase extends Disposable {
274275

275276
export const IInteractiveWindow = Symbol('IInteractiveWindow');
276277
export interface IInteractiveWindow extends IInteractiveBase {
278+
readonly onDidChangeViewState: Event<void>;
279+
readonly visible: boolean;
280+
readonly active: boolean;
277281
closed: Event<IInteractiveWindow>;
278282
addCode(code: string, file: string, line: number, editor?: TextEditor, runningStopWatch?: StopWatch): Promise<boolean>;
279283
addMessage(message: string): Promise<void>;
@@ -290,6 +294,7 @@ export interface INotebookEditorProvider {
290294
readonly activeEditor: INotebookEditor | undefined;
291295
readonly editors: INotebookEditor[];
292296
readonly onDidOpenNotebookEditor: Event<INotebookEditor>;
297+
readonly onDidChangeActiveNotebookEditor: Event<INotebookEditor | undefined>;
293298
open(file: Uri, contents: string): Promise<INotebookEditor>;
294299
show(file: Uri): Promise<INotebookEditor | undefined>;
295300
createNew(contents?: string): Promise<INotebookEditor>;
@@ -299,6 +304,7 @@ export interface INotebookEditorProvider {
299304
// For native editing, the INotebookEditor acts like a TextEditor and a TextDocument together
300305
export const INotebookEditor = Symbol('INotebookEditor');
301306
export interface INotebookEditor extends IInteractiveBase {
307+
readonly onDidChangeViewState: Event<void>;
302308
readonly closed: Event<INotebookEditor>;
303309
readonly executed: Event<INotebookEditor>;
304310
readonly modified: Event<INotebookEditor>;

src/test/datascience/activation.unit.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PythonExecutionFactory } from '../../client/common/process/pythonExecut
1010
import { IPythonExecutionFactory } from '../../client/common/process/types';
1111
import { Activation } from '../../client/datascience/activation';
1212
import { PythonDaemonModule } from '../../client/datascience/constants';
13+
import { ActiveEditorContextService } from '../../client/datascience/context/activeEditorContext';
1314
import { NativeEditor } from '../../client/datascience/interactive-ipynb/nativeEditor';
1415
import { NativeEditorProvider } from '../../client/datascience/interactive-ipynb/nativeEditorProvider';
1516
import { INotebookEditor, INotebookEditorProvider } from '../../client/datascience/types';
@@ -26,24 +27,25 @@ suite('Data Science - Activation', () => {
2627
let executionFactory: IPythonExecutionFactory;
2728
let openedEventEmitter: EventEmitter<INotebookEditor>;
2829
let interpreterEventEmitter: EventEmitter<void>;
29-
30+
let contextService: ActiveEditorContextService;
3031
setup(async () => {
3132
openedEventEmitter = new EventEmitter<INotebookEditor>();
3233
interpreterEventEmitter = new EventEmitter<void>();
3334

3435
notebookProvider = mock(NativeEditorProvider);
3536
interpreterService = mock(InterpreterService);
3637
executionFactory = mock(PythonExecutionFactory);
38+
contextService = mock(ActiveEditorContextService);
3739
when(notebookProvider.onDidOpenNotebookEditor).thenReturn(openedEventEmitter.event);
3840
when(interpreterService.onDidChangeInterpreter).thenReturn(interpreterEventEmitter.event);
3941
when(executionFactory.createDaemon(anything())).thenResolve();
40-
41-
activator = new Activation(instance(notebookProvider), instance(interpreterService), instance(executionFactory), []);
42+
when(contextService.activate()).thenResolve();
43+
activator = new Activation(instance(notebookProvider), instance(interpreterService), instance(executionFactory), [], instance(contextService));
4244
await activator.activate();
4345
});
4446

4547
async function testCreatingDaemonWhenOpeningANotebook() {
46-
const notebook = mock(NativeEditor);
48+
const notebook: INotebookEditor = mock(NativeEditor);
4749
const interpreter = ({ path: 'MY_PY' } as any) as PythonInterpreter;
4850

4951
when(interpreterService.getActiveInterpreter(undefined)).thenResolve(interpreter);

0 commit comments

Comments
 (0)