Repository navigation
Fix code lens perf for interactive window #11142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
18d8968
f02d847
6be574a
82605b0
09c2b49
d17058b
1806922
7d5a6dc
916d77b
f1e7138
6c87fb1
18f7582
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,26 +4,37 @@ | |
| import { inject, injectable } from 'inversify'; | ||
| import { CodeLens, Command, Event, EventEmitter, Range, TextDocument, Uri } from 'vscode'; | ||
|
|
||
| import { IDocumentManager } from '../../common/application/types'; | ||
| import { traceWarning } from '../../common/logger'; | ||
| import { IFileSystem } from '../../common/platform/types'; | ||
| import { IConfigurationService, Resource } from '../../common/types'; | ||
| import * as localize from '../../common/utils/localize'; | ||
| import { noop } from '../../common/utils/misc'; | ||
| import { generateCellRangesFromDocument } from '../cellFactory'; | ||
| import { CodeLensCommands, Commands } from '../constants'; | ||
| import { generateCellRangesFromDocument, ICellRange } from '../cellFactory'; | ||
| import { CodeLensCommands, Commands, Identifiers } from '../constants'; | ||
| import { INotebookIdentity, InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes'; | ||
| import { | ||
| ICell, | ||
| ICellHashLogger, | ||
| ICellHashProvider, | ||
| ICodeLensFactory, | ||
| IFileHashes, | ||
| IInteractiveWindowListener, | ||
| INotebook, | ||
| INotebookExecutionLogger, | ||
| INotebookProvider | ||
| } from '../types'; | ||
|
|
||
| type CodeLensCacheData = { | ||
| cachedDocumentVersion: number | undefined; | ||
| cachedExecutionCount: number | undefined; | ||
| documentLenses: CodeLens[]; | ||
| cellRanges: ICellRange[]; | ||
| gotoCellLens: CodeLens[]; | ||
| }; | ||
|
|
||
| /** | ||
| * This class is a singleton that generates code lenses for any document the user opens. It listens | ||
| * to cells being execute so it can add 'goto' lenses on cells that have already been run. | ||
| */ | ||
| @injectable() | ||
| export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowListener { | ||
| private updateEvent: EventEmitter<void> = new EventEmitter<void>(); | ||
|
|
@@ -33,15 +44,21 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList | |
| // tslint:disable-next-line: no-any | ||
| payload: any; | ||
| }>(); | ||
| private cellExecutionCounts: Map<string, string> = new Map<string, string>(); | ||
| private cellExecutionCounts = new Map<string, string>(); | ||
| private documentExecutionCounts = new Map<string, number>(); | ||
| private hashProvider: ICellHashProvider | undefined; | ||
| private notebookIdentity: Uri | undefined; | ||
| private interactiveIdentity: Uri | undefined; // Once we have more than one interactive window, this logic won't work anymore | ||
| private codeLensCache = new Map<string, CodeLensCacheData>(); | ||
|
|
||
| constructor( | ||
| @inject(IConfigurationService) private configService: IConfigurationService, | ||
| @inject(INotebookProvider) private notebookProvider: INotebookProvider, | ||
| @inject(IFileSystem) private fileSystem: IFileSystem | ||
| ) {} | ||
| @inject(IFileSystem) private fileSystem: IFileSystem, | ||
| @inject(IDocumentManager) private documentManager: IDocumentManager | ||
| ) { | ||
| this.documentManager.onDidCloseTextDocument(this.onClosedDocument.bind(this)); | ||
| this.configService.getSettings(undefined).onDidChange(this.onChangedSettings.bind(this)); | ||
| } | ||
|
|
||
| public dispose(): void { | ||
| noop(); | ||
|
|
@@ -65,9 +82,15 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList | |
| case InteractiveWindowMessages.FinishCell: | ||
| const cell = payload as ICell; | ||
| if (cell && cell.data && cell.data.execution_count) { | ||
| this.cellExecutionCounts.set(cell.id, cell.data.execution_count.toString()); | ||
| if (cell.file && cell.file !== Identifiers.EmptyFileName) { | ||
| this.cellExecutionCounts.set(cell.id, cell.data.execution_count.toString()); | ||
| this.documentExecutionCounts.set( | ||
| cell.file.toLocaleLowerCase(), | ||
| parseInt(cell.data.execution_count.toString(), 10) | ||
| ); | ||
| this.updateEvent.fire(); | ||
| } | ||
| } | ||
| this.updateEvent.fire(); | ||
| break; | ||
|
|
||
| default: | ||
|
|
@@ -80,61 +103,122 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList | |
| } | ||
|
|
||
| public createCodeLenses(document: TextDocument): CodeLens[] { | ||
| const ranges = generateCellRangesFromDocument( | ||
| document, | ||
| this.configService.getSettings(document.uri).datascience | ||
| ); | ||
| const commands = this.enumerateCommands(document.uri); | ||
| const hashes = this.configService.getSettings(document.uri).datascience.addGotoCodeLenses | ||
| ? this.hashProvider | ||
| ? this.hashProvider.getHashes() | ||
| : [] | ||
| : []; | ||
| const codeLenses: CodeLens[] = []; | ||
| let firstCell = true; | ||
|
|
||
| ranges.forEach((range) => { | ||
| commands.forEach((c) => { | ||
| const codeLens = this.createCodeLens(document, range, c, firstCell); | ||
| // See if we have a cached version of the code lenses for this document | ||
| const key = document.fileName.toLocaleLowerCase(); | ||
| let cache = this.codeLensCache.get(key); | ||
| let needUpdate = false; | ||
|
|
||
| // If we don't have one, generate one | ||
| if (!cache) { | ||
| cache = { | ||
| cachedDocumentVersion: undefined, | ||
| cachedExecutionCount: undefined, | ||
| documentLenses: [], | ||
| cellRanges: [], | ||
| gotoCellLens: [] | ||
| }; | ||
| needUpdate = true; | ||
| this.codeLensCache.set(key, cache); | ||
| } | ||
|
|
||
| // If the document version doesn't match, our cell ranges are out of date | ||
| if (cache.cachedDocumentVersion !== document.version) { | ||
| cache.cellRanges = generateCellRangesFromDocument( | ||
| document, | ||
| this.configService.getSettings(document.uri).datascience | ||
| ); | ||
|
|
||
| // Because we have all new ranges, we need to recompute ALL of our code lenses. | ||
| cache.documentLenses = []; | ||
| cache.gotoCellLens = []; | ||
| cache.cachedDocumentVersion = document.version; | ||
| needUpdate = true; | ||
| } | ||
|
|
||
| // If the document execution count doesn't match, then our goto cell lens is out of date | ||
| if (cache.cachedExecutionCount !== this.documentExecutionCounts.get(key)) { | ||
| cache.gotoCellLens = []; | ||
| cache.cachedExecutionCount = this.documentExecutionCounts.get(key); | ||
| needUpdate = true; | ||
| } | ||
|
|
||
| // Generate our code lenses if necessary | ||
| if (cache.documentLenses.length === 0 && needUpdate && cache.cellRanges.length) { | ||
| // Enumerate the possible commands for the document based code lenses | ||
| const commands = needUpdate ? this.enumerateCommands(document.uri) : []; | ||
|
|
||
| // Then iterate over all of the cell ranges and generate code lenses for each possible | ||
| // commands | ||
| let firstCell = true; | ||
| cache.cellRanges.forEach((r) => { | ||
| commands.forEach((c) => { | ||
| const codeLens = this.createCodeLens(document, r, c, firstCell); | ||
| if (codeLens) { | ||
| cache?.documentLenses.push(codeLens); | ||
| } | ||
| }); | ||
| firstCell = false; | ||
| }); | ||
| } | ||
|
|
||
| // Generate the goto cell lense if necessary | ||
| if ( | ||
| needUpdate && | ||
| cache.gotoCellLens.length === 0 && | ||
| this.hashProvider && | ||
| cache.cellRanges.length && | ||
| this.configService.getSettings(document.uri).datascience.addGotoCodeLenses | ||
| ) { | ||
| const hashes = this.hashProvider.getHashes(); | ||
| cache.cellRanges.forEach((r) => { | ||
| const codeLens = this.createExecutionLens(document, r.range, hashes); | ||
| if (codeLens) { | ||
| codeLenses.push(codeLens); | ||
| cache?.gotoCellLens.push(codeLens); | ||
| } | ||
| }); | ||
| this.addExecutionCount(codeLenses, document, range.range, hashes); | ||
| firstCell = false; | ||
| }); | ||
| } | ||
|
|
||
| return codeLenses; | ||
| return [...cache?.documentLenses, ...cache?.gotoCellLens]; | ||
| } | ||
|
|
||
| private setIdentity(identity: INotebookIdentity) { | ||
| this.notebookIdentity = Uri.parse(identity.resource); | ||
| if (identity.type === 'interactive') { | ||
| this.interactiveIdentity = identity.resource; | ||
| } | ||
| } | ||
|
|
||
| private initCellHashProvider() { | ||
| this.getNotebook() | ||
| // Try getting our notebook. This should fail if | ||
| // the user hasn't opened the interactive window yet. | ||
| this.getInteractiveWindowNotebook() | ||
| .then((nb) => { | ||
| if (nb) { | ||
| const cellHashLogger = <ICellHashLogger>( | ||
| nb | ||
| .getLoggers() | ||
| .find((logger: INotebookExecutionLogger) => (<ICellHashLogger>logger).getCellHashProvider) | ||
| ); | ||
|
|
||
| if (cellHashLogger) { | ||
| this.hashProvider = cellHashLogger.getCellHashProvider(); | ||
| } | ||
| // From the notebook, find the logger that is the cell hash provider | ||
| // tslint:disable-next-line: no-any | ||
| this.hashProvider = (nb.getLoggers().find((l) => (l as any).getHashes) as any) as ICellHashProvider; | ||
| } | ||
| }) | ||
| .ignoreErrors(); | ||
| } | ||
|
|
||
| private async getNotebook(): Promise<INotebook | undefined> { | ||
| return this.notebookIdentity | ||
| ? this.notebookProvider.getOrCreateNotebook({ identity: this.notebookIdentity, getOnly: true }) | ||
| private async getInteractiveWindowNotebook(): Promise<INotebook | undefined> { | ||
| return this.interactiveIdentity | ||
| ? this.notebookProvider.getOrCreateNotebook({ identity: this.interactiveIdentity, getOnly: true }) | ||
| : undefined; | ||
| } | ||
|
|
||
| private onClosedDocument(doc: TextDocument) { | ||
| this.codeLensCache.delete(doc.fileName.toLocaleLowerCase()); | ||
|
|
||
| // Don't delete the document execution count, we need to keep track | ||
| // of it past the closing of a doc if the notebook or interactive window is still open. | ||
| } | ||
|
|
||
| private onChangedSettings() { | ||
| // When config settings change, refresh our code lenses. | ||
| this.codeLensCache.clear(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like it's on any settings change.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is that .affectConfiguration function we should be using here. Or else we are clearing these out too much.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No there isn't. Not on the config settings. That's on the workspaceConfiguration.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And the worst case is that the code lens are regenerated whenever you change your python settings. Meh.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can see where the configSettings uses that 'affectsConfiguration' in the configSettings.ts file (line 571 or so). It will only fire an onDidChangeSettings when the python settings change. In reply to: 407782405 [](ancestors = 407782405)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably recompute the code lenses though. Otherwise the next execution will recompute them all. In reply to: 407782955 [](ancestors = 407782955,407782405) |
||
| } | ||
|
|
||
| private enumerateCommands(resource: Resource): string[] { | ||
| let fullCommandList: string[]; | ||
| // Add our non-debug commands | ||
|
|
@@ -283,23 +367,21 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList | |
| return undefined; | ||
| } | ||
|
|
||
| private addExecutionCount(codeLens: CodeLens[], document: TextDocument, range: Range, hashes: IFileHashes[]) { | ||
| private createExecutionLens(document: TextDocument, range: Range, hashes: IFileHashes[]) { | ||
| const list = hashes.find((h) => this.fileSystem.arePathsSame(h.file, document.fileName)); | ||
| if (list) { | ||
| // Match just the start of the range. Should be - 2 (1 for 1 based numbers and 1 for skipping the comment at the top) | ||
| const rangeMatches = list.hashes.filter((h) => h.line - 2 === range.start.line); | ||
| if (rangeMatches && rangeMatches.length) { | ||
| const rangeMatch = rangeMatches[rangeMatches.length - 1]; | ||
| if (this.cellExecutionCounts.has(rangeMatch.id)) { | ||
| codeLens.push( | ||
| this.generateCodeLens( | ||
| range, | ||
| Commands.ScrollToCell, | ||
| localize.DataScience.scrollToCellTitleFormatMessage().format( | ||
| this.cellExecutionCounts.get(rangeMatch.id)! | ||
| ), | ||
| [document.fileName, rangeMatch.id] | ||
| ) | ||
| return this.generateCodeLens( | ||
| range, | ||
| Commands.ScrollToCell, | ||
| localize.DataScience.scrollToCellTitleFormatMessage().format( | ||
| this.cellExecutionCounts.get(rangeMatch.id)! | ||
| ), | ||
| [document.fileName, rangeMatch.id] | ||
| ); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we're reacting to an event that took place (i.e. this is meant to be an event handler), I think
onKernelRestartedis betterThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
In reply to: 407773224 [](ancestors = 407773224)