Skip to content

Commit d65f0eb

Browse files
authored
Add current cell highlight (microsoft#5219)
For #3542 <!-- If an item below does not apply to you, then go ahead and check it off as "done" and strikethrough the text, e.g.: - [x] ~Has unit tests & system/integration tests~ --> - [x] Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR) - [x] Title summarizes what is changing - [x] Has a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file (remember to thank yourself!) - [ ] Has sufficient logging. - [ ] Has telemetry for enhancements. - [ ] Unit tests & system/integration tests are added/updated - [ ] [Test plan](https://github.com/Microsoft/vscode-python/blob/master/.github/test_plan.md) is updated as appropriate - [ ] [`package-lock.json`](https://github.com/Microsoft/vscode-python/blob/master/package-lock.json) has been regenerated by running `npm install` (if dependencies have changed) - [ ] The wiki is updated with any design decisions/details.
1 parent 59d152d commit d65f0eb

File tree

8 files changed

+156
-4
lines changed

8 files changed

+156
-4
lines changed

news/1 Enhancements/3542.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tracking of 'current' cell in the editor.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,12 @@
14151415
"description": "Amount of time to wait for guest connections to verify they have the Python extension installed.",
14161416
"scope": "application"
14171417
},
1418+
"python.dataScience.decorateCells": {
1419+
"type": "boolean",
1420+
"default": true,
1421+
"description": "Draw a highlight behind the currently active cell.",
1422+
"scope": "resource"
1423+
},
14181424
"python.disableInstallationCheck": {
14191425
"type": "boolean",
14201426
"default": false,

src/client/common/application/documentManager.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
3-
4-
// tslint:disable:no-any unified-signatures
5-
63
import { injectable } from 'inversify';
7-
import { Event, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, window, workspace, WorkspaceEdit } from 'vscode';
4+
import {
5+
DecorationRenderOptions,
6+
Event,
7+
TextDocument,
8+
TextDocumentChangeEvent,
9+
TextDocumentShowOptions,
10+
TextEditor,
11+
TextEditorDecorationType,
12+
TextEditorOptionsChangeEvent,
13+
TextEditorSelectionChangeEvent,
14+
TextEditorViewColumnChangeEvent,
15+
Uri,
16+
ViewColumn,
17+
window,
18+
workspace,
19+
WorkspaceEdit
20+
} from 'vscode';
21+
822
import { IDocumentManager } from './types';
923

24+
// tslint:disable:no-any unified-signatures
25+
1026
@injectable()
1127
export class DocumentManager implements IDocumentManager {
1228
public get textDocuments(): TextDocument[] {
@@ -21,6 +37,9 @@ export class DocumentManager implements IDocumentManager {
2137
public get onDidChangeActiveTextEditor(): Event<TextEditor | undefined> {
2238
return window.onDidChangeActiveTextEditor;
2339
}
40+
public get onDidChangeTextDocument() : Event<TextDocumentChangeEvent> {
41+
return workspace.onDidChangeTextDocument;
42+
}
2443
public get onDidChangeVisibleTextEditors(): Event<TextEditor[]> {
2544
return window.onDidChangeVisibleTextEditors;
2645
}
@@ -57,4 +76,7 @@ export class DocumentManager implements IDocumentManager {
5776
public applyEdit(edit: WorkspaceEdit): Thenable<boolean> {
5877
return workspace.applyEdit(edit);
5978
}
79+
public createTextEditorDecorationType(options: DecorationRenderOptions): TextEditorDecorationType {
80+
return window.createTextEditorDecorationType(options);
81+
}
6082
}

src/client/common/application/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
DebugConsole,
1313
DebugSession,
1414
DebugSessionCustomEvent,
15+
DecorationRenderOptions,
1516
Disposable,
1617
DocumentSelector,
1718
Event,
@@ -33,8 +34,10 @@ import {
3334
Terminal,
3435
TerminalOptions,
3536
TextDocument,
37+
TextDocumentChangeEvent,
3638
TextDocumentShowOptions,
3739
TextEditor,
40+
TextEditorDecorationType,
3841
TextEditorEdit,
3942
TextEditorOptionsChangeEvent,
4043
TextEditorSelectionChangeEvent,
@@ -433,6 +436,13 @@ export interface IDocumentManager {
433436
*/
434437
readonly onDidChangeActiveTextEditor: Event<TextEditor | undefined>;
435438

439+
/**
440+
* An event that is emitted when a [text document](#TextDocument) is changed. This usually happens
441+
* when the [contents](#TextDocument.getText) changes but also when other things like the
442+
* [dirty](#TextDocument.isDirty)-state changes.
443+
*/
444+
readonly onDidChangeTextDocument: Event<TextDocumentChangeEvent>;
445+
436446
/**
437447
* An [event](#Event) which fires when the array of [visible editors](#window.visibleTextEditors)
438448
* has changed.
@@ -549,6 +559,15 @@ export interface IDocumentManager {
549559
* @return A thenable that resolves when the edit could be applied.
550560
*/
551561
applyEdit(edit: WorkspaceEdit): Thenable<boolean>;
562+
563+
/**
564+
* Create a TextEditorDecorationType that can be used to add decorations to text editors.
565+
*
566+
* @param options Rendering options for the decoration type.
567+
* @return A new decoration type instance.
568+
*/
569+
createTextEditorDecorationType(options: DecorationRenderOptions): TextEditorDecorationType;
570+
552571
}
553572

554573
export const IWorkspaceService = Symbol('IWorkspaceService');

src/client/common/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ export interface IDataScienceSettings {
305305
showJupyterVariableExplorer?: boolean;
306306
variableExplorerExclude?: string;
307307
liveShareConnectionTimeout?: number;
308+
decorateCells?: boolean;
308309
}
309310

310311
export const IConfigurationService = Symbol('IConfigurationService');
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import { inject, injectable } from 'inversify';
5+
import * as vscode from 'vscode';
6+
7+
import { IExtensionActivationService } from '../../activation/types';
8+
import { IDocumentManager } from '../../common/application/types';
9+
import { PYTHON_LANGUAGE } from '../../common/constants';
10+
import { IConfigurationService, IDisposable, IDisposableRegistry, Resource } from '../../common/types';
11+
import { generateCellRanges } from '../cellFactory';
12+
13+
@injectable()
14+
export class Decorator implements IExtensionActivationService, IDisposable {
15+
16+
private activeCellType: vscode.TextEditorDecorationType;
17+
private timer: NodeJS.Timer | undefined;
18+
19+
constructor(@inject(IDocumentManager) private documentManager: IDocumentManager,
20+
@inject(IDisposableRegistry) disposables: IDisposableRegistry,
21+
@inject(IConfigurationService) private configuration: IConfigurationService)
22+
{
23+
this.activeCellType = this.documentManager.createTextEditorDecorationType({
24+
backgroundColor: new vscode.ThemeColor('sideBarSectionHeader.background'),
25+
isWholeLine: true
26+
});
27+
disposables.push(this);
28+
disposables.push(this.configuration.getSettings().onDidChange(this.settingsChanged, this));
29+
disposables.push(this.documentManager.onDidChangeActiveTextEditor(this.changedEditor, this));
30+
disposables.push(this.documentManager.onDidChangeTextEditorSelection(this.changedSelection, this));
31+
disposables.push(this.documentManager.onDidChangeTextDocument(this.changedDocument, this));
32+
this.settingsChanged();
33+
}
34+
35+
public activate(_resource: Resource) : Promise<void> {
36+
// We don't need to do anything here as we already did all of our work in the
37+
// constructor.
38+
return Promise.resolve();
39+
}
40+
41+
public dispose() {
42+
if (this.timer) {
43+
clearTimeout(this.timer);
44+
}
45+
}
46+
47+
private settingsChanged() {
48+
if (this.documentManager.activeTextEditor) {
49+
this.triggerUpdate(this.documentManager.activeTextEditor);
50+
}
51+
}
52+
53+
private changedEditor(editor: vscode.TextEditor | undefined) {
54+
this.triggerUpdate(editor);
55+
}
56+
57+
private changedDocument(e: vscode.TextDocumentChangeEvent) {
58+
if (this.documentManager.activeTextEditor && e.document === this.documentManager.activeTextEditor.document) {
59+
this.triggerUpdate(this.documentManager.activeTextEditor);
60+
}
61+
}
62+
63+
private changedSelection(e: vscode.TextEditorSelectionChangeEvent) {
64+
if (e.textEditor && e.textEditor.selection.anchor) {
65+
this.triggerUpdate(e.textEditor);
66+
}
67+
}
68+
69+
private triggerUpdate(editor: vscode.TextEditor | undefined) {
70+
if (this.timer) {
71+
clearTimeout(this.timer);
72+
}
73+
this.timer = setTimeout(() => this.update(editor), 100);
74+
}
75+
76+
private update(editor: vscode.TextEditor | undefined) {
77+
if (editor && editor.document && editor.document.languageId === PYTHON_LANGUAGE) {
78+
const settings = this.configuration.getSettings().datascience;
79+
if (settings.decorateCells && settings.enabled) {
80+
// Find all of the cells
81+
const cells = generateCellRanges(editor.document, this.configuration.getSettings().datascience);
82+
const cellRanges = cells.map(c => c.range).filter(r => r.contains(editor.selection.anchor));
83+
editor.setDecorations(this.activeCellType, cellRanges);
84+
} else {
85+
editor.setDecorations(this.activeCellType, []);
86+
}
87+
}
88+
}
89+
}

src/client/datascience/serviceRegistry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
'use strict';
4+
import { IExtensionActivationService } from '../activation/types';
45
import { IServiceManager } from '../ioc/types';
56
import { CodeCssGenerator } from './codeCssGenerator';
67
import { DataViewer } from './data-viewing/dataViewer';
78
import { DataViewerProvider } from './data-viewing/dataViewerProvider';
89
import { DataScience } from './datascience';
910
import { DataScienceCodeLensProvider } from './editor-integration/codelensprovider';
1011
import { CodeWatcher } from './editor-integration/codewatcher';
12+
import { Decorator } from './editor-integration/decorator';
1113
import { History } from './history/history';
1214
import { HistoryCommandListener } from './history/historycommandlistener';
1315
import { HistoryProvider } from './history/historyProvider';
@@ -60,4 +62,5 @@ export function registerTypes(serviceManager: IServiceManager) {
6062
serviceManager.addSingleton<IThemeFinder>(IThemeFinder, ThemeFinder);
6163
serviceManager.addSingleton<IDataViewerProvider>(IDataViewerProvider, DataViewerProvider);
6264
serviceManager.add<IDataViewer>(IDataViewer, DataViewer);
65+
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, Decorator);
6366
}

src/test/datascience/mockDocumentManager.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
'use strict';
44
import * as TypeMoq from 'typemoq';
55
import {
6+
DecorationRenderOptions,
67
Event,
78
EventEmitter,
89
TextDocument,
10+
TextDocumentChangeEvent,
911
TextDocumentShowOptions,
1012
TextEditor,
13+
TextEditorDecorationType,
1114
TextEditorOptionsChangeEvent,
1215
TextEditorSelectionChangeEvent,
1316
TextEditorViewColumnChangeEvent,
@@ -42,9 +45,13 @@ export class MockDocumentManager implements IDocumentManager {
4245
private didChangeTextEditorViewColumnEmitter = new EventEmitter<TextEditorViewColumnChangeEvent>();
4346
private didCloseEmitter = new EventEmitter<TextDocument>();
4447
private didSaveEmitter = new EventEmitter<TextDocument>();
48+
private didChangeTextDocumentEmitter = new EventEmitter<TextDocumentChangeEvent>();
4549
public get onDidChangeActiveTextEditor(): Event<TextEditor> {
4650
return this.didChangeEmitter.event;
4751
}
52+
public get onDidChangeTextDocument(): Event<TextDocumentChangeEvent> {
53+
return this.didChangeTextDocumentEmitter.event;
54+
}
4855
public get onDidOpenTextDocument(): Event<TextDocument> {
4956
return this.didOpenEmitter.event;
5057
}
@@ -89,6 +96,10 @@ export class MockDocumentManager implements IDocumentManager {
8996
this.textDocuments.push(mockDoc.object);
9097
}
9198

99+
public createTextEditorDecorationType(_options: DecorationRenderOptions) : TextEditorDecorationType {
100+
throw new Error('Method not implemented');
101+
}
102+
92103
private get lastDocument() : TextDocument {
93104
if (this.textDocuments.length > 0) {
94105
return this.textDocuments[this.textDocuments.length - 1];

0 commit comments

Comments
 (0)