Skip to content

Commit c8287cd

Browse files
authored
claim ownership of python file if we create code cells for it (#16103)
* claim ownership of python file if we create code cells for it * remove parameter * update unit tests
1 parent 36e3c61 commit c8287cd

File tree

4 files changed

+96
-54
lines changed

4 files changed

+96
-54
lines changed

src/interactive-window/editor-integration/codelensprovider.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
3737
private totalGetCodeLensCalls: number = 0;
3838
private activeCodeWatchers: ICodeWatcher[] = [];
3939
private didChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
40+
private cachedOwnsSetting: boolean;
4041

4142
constructor(
4243
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
@@ -59,6 +60,10 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
5960
}
6061

6162
disposableRegistry.push(vscode.window.onDidChangeActiveTextEditor(() => this.onChangedActiveTextEditor()));
63+
const settings = this.configuration.getSettings(undefined);
64+
this.cachedOwnsSetting = settings.sendSelectionToInteractiveWindow;
65+
this.updateOwnerContextKey();
66+
disposableRegistry.push(vscode.workspace.onDidChangeConfiguration((e) => this.onSettingChanged(e)));
6267
this.onChangedActiveTextEditor();
6368
}
6469

@@ -73,9 +78,33 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
7378
// set the context to false so our command doesn't run for other files
7479
const hasCellsContext = new ContextKey(EditorContexts.HasCodeCells);
7580
hasCellsContext.set(false).catch((ex) => logger.warn('Failed to set jupyter.HasCodeCells context', ex));
81+
this.updateOwnerContextKey(false);
7682
}
7783
}
7884

85+
private onSettingChanged(e: vscode.ConfigurationChangeEvent) {
86+
if (e.affectsConfiguration('jupyter.interactiveWindow.textEditor.executeSelection')) {
87+
const settings = this.configuration.getSettings(undefined);
88+
this.cachedOwnsSetting = settings.sendSelectionToInteractiveWindow;
89+
this.updateOwnerContextKey();
90+
}
91+
}
92+
93+
private updateOwnerContextKey(hasCodeCells?: boolean) {
94+
const editorContext = new ContextKey(EditorContexts.OwnsSelection);
95+
if (this.cachedOwnsSetting) {
96+
editorContext.set(true).catch(noop);
97+
return;
98+
}
99+
100+
if (hasCodeCells === undefined) {
101+
const hasCellsContext = new ContextKey(EditorContexts.HasCodeCells);
102+
hasCodeCells = hasCellsContext.value ?? false;
103+
}
104+
105+
editorContext.set(hasCodeCells).catch(noop);
106+
}
107+
79108
public dispose() {
80109
// On shutdown send how long on average we spent parsing code lens
81110
if (this.totalGetCodeLensCalls > 0) {
@@ -135,9 +164,9 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
135164
// ask whenever a change occurs. Do this regardless of if we have code lens turned on or not as
136165
// shift+enter relies on this code context.
137166
const hasCellsContext = new ContextKey(EditorContexts.HasCodeCells);
138-
hasCellsContext
139-
.set(codeLenses && codeLenses.length > 0)
140-
.catch((ex) => logger.debug('Failed to set jupyter.HasCodeCells context', ex));
167+
const hasCodeCells = codeLenses && codeLenses.length > 0;
168+
hasCellsContext.set(hasCodeCells).catch((ex) => logger.debug('Failed to set jupyter.HasCodeCells context', ex));
169+
this.updateOwnerContextKey(hasCodeCells);
141170

142171
// Don't provide any code lenses if we have not enabled data science
143172
const settings = this.configuration.getSettings(document.uri);

src/interactive-window/editor-integration/codelensprovider.unit.test.ts

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { anything, when } from 'ts-mockito';
4+
import { anything, verify, when } from 'ts-mockito';
55
import * as TypeMoq from 'typemoq';
6-
import { CancellationTokenSource, Disposable, EventEmitter, TextDocument, Uri } from 'vscode';
6+
import { CancellationTokenSource, CodeLens, Disposable, EventEmitter, TextDocument, Uri, Range } from 'vscode';
77

88
import { IDebugService } from '../../platform/common/application/types';
99
import { IConfigurationService, IWatchableJupyterSettings } from '../../platform/common/types';
1010
import { DataScienceCodeLensProvider } from '../../interactive-window/editor-integration/codelensprovider';
1111
import { IServiceContainer } from '../../platform/ioc/types';
12-
import { ICodeWatcher, IDataScienceCodeLensProvider } from '../../interactive-window/editor-integration/types';
12+
import { ICodeWatcher } from '../../interactive-window/editor-integration/types';
1313
import { IDebugLocationTracker } from '../../notebooks/debugger/debuggingTypes';
1414
import { mockedVSCodeNamespaces } from '../../test/vscode-mock';
1515

1616
// eslint-disable-next-line
1717
suite('DataScienceCodeLensProvider Unit Tests', () => {
1818
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
1919
let configurationService: TypeMoq.IMock<IConfigurationService>;
20-
let codeLensProvider: IDataScienceCodeLensProvider;
2120
let pythonSettings: TypeMoq.IMock<IWatchableJupyterSettings>;
2221
let debugService: TypeMoq.IMock<IDebugService>;
2322
let debugLocationTracker: TypeMoq.IMock<IDebugLocationTracker>;
@@ -37,16 +36,17 @@ suite('DataScienceCodeLensProvider Unit Tests', () => {
3736
configurationService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
3837
when(mockedVSCodeNamespaces.commands.executeCommand(anything(), anything(), anything())).thenResolve();
3938
debugService.setup((d) => d.activeDebugSession).returns(() => undefined);
40-
codeLensProvider = new DataScienceCodeLensProvider(
39+
});
40+
41+
function provideCodeLensesForOneDoc(codeLenses: CodeLens[] = []) {
42+
const codeLensProvider = new DataScienceCodeLensProvider(
4143
serviceContainer.object,
4244
debugLocationTracker.object,
4345
configurationService.object,
4446
disposables,
4547
debugService.object
4648
);
47-
});
4849

49-
test('Initialize Code Lenses one document', async () => {
5050
// Create our document
5151
const document = TypeMoq.Mock.ofType<TextDocument>();
5252
const uri = Uri.file('test.py');
@@ -57,21 +57,35 @@ suite('DataScienceCodeLensProvider Unit Tests', () => {
5757
const targetCodeWatcher = TypeMoq.Mock.ofType<ICodeWatcher>();
5858
targetCodeWatcher
5959
.setup((tc) => tc.getCodeLenses())
60-
.returns(() => [])
60+
.returns(() => codeLenses)
6161
.verifiable(TypeMoq.Times.once());
6262
serviceContainer
6363
.setup((c) => c.get(TypeMoq.It.isValue(ICodeWatcher)))
6464
.returns(() => targetCodeWatcher.object)
6565
.verifiable(TypeMoq.Times.once());
6666
when(mockedVSCodeNamespaces.workspace.textDocuments).thenReturn([document.object]);
6767

68-
await codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
68+
codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
69+
70+
return targetCodeWatcher;
71+
}
72+
73+
test('Initialize Code Lenses one document', async () => {
74+
const targetCodeWatcher = provideCodeLensesForOneDoc();
6975

7076
targetCodeWatcher.verifyAll();
7177
serviceContainer.verifyAll();
7278
});
7379

7480
test('Initialize Code Lenses same doc called', async () => {
81+
const codeLensProvider = new DataScienceCodeLensProvider(
82+
serviceContainer.object,
83+
debugLocationTracker.object,
84+
configurationService.object,
85+
disposables,
86+
debugService.object
87+
);
88+
7589
// Create our document
7690
const document = TypeMoq.Mock.ofType<TextDocument>();
7791
const uri = Uri.file('test.py');
@@ -94,15 +108,23 @@ suite('DataScienceCodeLensProvider Unit Tests', () => {
94108
.verifiable(TypeMoq.Times.once());
95109
when(mockedVSCodeNamespaces.workspace.textDocuments).thenReturn([document.object]);
96110

97-
await codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
98-
await codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
111+
codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
112+
codeLensProvider.provideCodeLenses(document.object, tokenSource.token);
99113

100114
// getCodeLenses should be called twice, but getting the code watcher only once due to same doc
101115
targetCodeWatcher.verifyAll();
102116
serviceContainer.verifyAll();
103117
});
104118

105119
test('Initialize Code Lenses different documents', async () => {
120+
const codeLensProvider = new DataScienceCodeLensProvider(
121+
serviceContainer.object,
122+
debugLocationTracker.object,
123+
configurationService.object,
124+
disposables,
125+
debugService.object
126+
);
127+
106128
// Create our document
107129
const uri1 = Uri.file('test.py');
108130
const document1 = TypeMoq.Mock.ofType<TextDocument>();
@@ -134,13 +156,40 @@ suite('DataScienceCodeLensProvider Unit Tests', () => {
134156

135157
when(mockedVSCodeNamespaces.workspace.textDocuments).thenReturn([document1.object, document2.object]);
136158

137-
await codeLensProvider.provideCodeLenses(document1.object, tokenSource.token);
138-
await codeLensProvider.provideCodeLenses(document1.object, tokenSource.token);
139-
await codeLensProvider.provideCodeLenses(document2.object, tokenSource.token);
159+
codeLensProvider.provideCodeLenses(document1.object, tokenSource.token);
160+
codeLensProvider.provideCodeLenses(document1.object, tokenSource.token);
161+
codeLensProvider.provideCodeLenses(document2.object, tokenSource.token);
140162

141163
// service container get should be called three times as the names and versions don't match
142164
targetCodeWatcher1.verifyAll();
143165
targetCodeWatcher2.verifyAll();
144166
serviceContainer.verifyAll();
145167
});
168+
169+
test('Having code lenses will update context keys to true', async () => {
170+
pythonSettings.setup((p) => p.sendSelectionToInteractiveWindow).returns(() => true);
171+
172+
provideCodeLensesForOneDoc([new CodeLens({} as Range)]);
173+
174+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.ownsSelection', true)).atLeast(1);
175+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.hascodecells', true)).atLeast(1);
176+
});
177+
178+
test('Having no code lenses will set context keys to false', async () => {
179+
pythonSettings.setup((p) => p.sendSelectionToInteractiveWindow).returns(() => false);
180+
181+
provideCodeLensesForOneDoc([]);
182+
183+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.ownsSelection', false)).atLeast(1);
184+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.hascodecells', false)).atLeast(1);
185+
});
186+
187+
test('Having no code lenses but ownership setting true will set context keys correctly', async () => {
188+
pythonSettings.setup((p) => p.sendSelectionToInteractiveWindow).returns(() => true);
189+
190+
provideCodeLensesForOneDoc([]);
191+
192+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.ownsSelection', true)).atLeast(1);
193+
verify(mockedVSCodeNamespaces.commands.executeCommand('setContext', 'jupyter.hascodecells', false)).atLeast(1);
194+
});
146195
});

src/standalone/activation/globalActivation.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33

44
import { inject, injectable, multiInject, optional } from 'inversify';
55
import { ContextKey } from '../../platform/common/contextKey';
6-
import {
7-
IConfigurationService,
8-
IDataScienceCommandListener,
9-
IDisposable,
10-
IDisposableRegistry
11-
} from '../../platform/common/types';
6+
import { IDataScienceCommandListener, IDisposable, IDisposableRegistry } from '../../platform/common/types';
127
import { noop } from '../../platform/common/utils/misc';
138
import { EditorContexts } from '../../platform/common/constants';
149
import { IExtensionSyncActivationService } from '../../platform/activation/types';
@@ -25,7 +20,6 @@ export class GlobalActivation implements IExtensionSyncActivationService {
2520
private startTime: number = Date.now();
2621
constructor(
2722
@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry,
28-
@inject(IConfigurationService) private configuration: IConfigurationService,
2923
@inject(IRawNotebookSupportedService)
3024
@optional()
3125
private rawSupported: IRawNotebookSupportedService | undefined,
@@ -38,9 +32,6 @@ export class GlobalActivation implements IExtensionSyncActivationService {
3832
}
3933

4034
public activate() {
41-
// Set our initial settings and sign up for changes
42-
this.onSettingsChanged();
43-
this.changeHandler = this.configuration.getSettings(undefined).onDidChange(this.onSettingsChanged.bind(this));
4435
this.disposableRegistry.push(this);
4536

4637
// Figure out the ZMQ available context key
@@ -58,13 +49,6 @@ export class GlobalActivation implements IExtensionSyncActivationService {
5849
}
5950
}
6051

61-
private onSettingsChanged = () => {
62-
const settings = this.configuration.getSettings(undefined);
63-
const ownsSelection = settings.sendSelectionToInteractiveWindow;
64-
const editorContext = new ContextKey(EditorContexts.OwnsSelection);
65-
editorContext.set(ownsSelection).catch(noop);
66-
};
67-
6852
private computeZmqAvailable() {
6953
const zmqContext = new ContextKey(EditorContexts.ZmqAvailable);
7054
zmqContext.set(this.rawSupported ? this.rawSupported.isSupported : false).then(noop, noop);

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

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import { anything, instance, mock, when } from 'ts-mockito';
88
import { JupyterSettings } from '../../platform/common/configSettings';
99
import { ConfigurationService } from '../../platform/common/configuration/service.node';
1010
import { IConfigurationService, IWatchableJupyterSettings } from '../../platform/common/types';
11-
import { GlobalActivation } from '../../standalone/activation/globalActivation';
1211
import { RawNotebookSupportedService } from '../../kernels/raw/session/rawNotebookSupportedService.node';
1312
import { IRawNotebookSupportedService } from '../../kernels/raw/types';
1413
import { pruneCell } from '../../platform/common/utils';
1514

1615
/* eslint-disable */
1716
suite('Tests', () => {
18-
let dataScience: GlobalActivation;
1917
let configService: IConfigurationService;
2018
let settings: IWatchableJupyterSettings;
2119
let onDidChangeSettings: sinon.SinonStub;
@@ -25,30 +23,12 @@ suite('Tests', () => {
2523
settings = mock(JupyterSettings);
2624
rawNotebookSupported = mock(RawNotebookSupportedService);
2725

28-
dataScience = new GlobalActivation(
29-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
30-
[] as any,
31-
instance(configService),
32-
instance(rawNotebookSupported),
33-
[] as any
34-
);
35-
3626
onDidChangeSettings = sinon.stub();
3727
when(configService.getSettings(anything())).thenReturn(instance(settings));
3828
when(settings.onDidChange).thenReturn(onDidChangeSettings);
3929
when(rawNotebookSupported.isSupported).thenReturn(true);
4030
});
4131

42-
suite('Activate', () => {
43-
setup(async () => {
44-
await dataScience.activate();
45-
});
46-
47-
test('Should add handler for Settings Changed', async () => {
48-
assert.ok(onDidChangeSettings.calledOnce);
49-
});
50-
});
51-
5232
suite('Cell pruning', () => {
5333
test('Remove output and execution count from non code', () => {
5434
const cell: nbformat.ICell = {

0 commit comments

Comments
 (0)