Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Rieken <johannes.rieken@gmail.com>2022-04-04 11:13:25 +0300
committerGitHub <noreply@github.com>2022-04-04 11:13:25 +0300
commit4ef3ed3ce8d7ab1857d41454449d32f946d3ac8c (patch)
treef211e51646c00aa397afdb0f866b9a2aae4f17df /src/vs/workbench/contrib
parentd8eee16887698292885ea5f7bc4dac397d0377fb (diff)
parent2ada17080cd3e351e093e5fce40d36f16db3124a (diff)
Merge branch 'main' into joh/cellUri
Diffstat (limited to 'src/vs/workbench/contrib')
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts10
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts78
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts16
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts13
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts1
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/editorSettingsMigration.ts72
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts63
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts7
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentNode.ts5
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts1
-rw-r--r--src/vs/workbench/contrib/comments/browser/media/review.css4
-rw-r--r--src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts6
-rw-r--r--src/vs/workbench/contrib/debug/node/terminals.ts5
-rw-r--r--src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts13
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts6
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts19
-rw-r--r--src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts54
-rw-r--r--src/vs/workbench/contrib/files/browser/files.contribution.ts5
-rw-r--r--src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts6
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts26
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts48
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css56
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts11
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts111
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts18
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts48
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/editActions.ts10
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts76
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts19
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts78
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts7
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditor.ts40
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts124
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts88
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts16
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts11
-rw-r--r--src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts12
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookCommon.ts33
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts15
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts19
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookService.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts52
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts20
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts48
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts12
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts2
-rw-r--r--src/vs/workbench/contrib/output/browser/media/output.css2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts90
-rw-r--r--src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css22
-rw-r--r--src/vs/workbench/contrib/remote/browser/remote.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/remote/browser/tunnelView.ts53
-rw-r--r--src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts6
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmViewService.ts13
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts2
-rw-r--r--src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts13
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts9
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalShellIntegrationLinkDetector.ts3
-rwxr-xr-xsrc/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh1
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/terminal.css20
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts27
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.ts13
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditor.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts28
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts37
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts1
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalUri.ts5
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalView.ts5
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts32
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminal.ts9
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts4
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts11
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/icons.ts3
-rw-r--r--src/vs/workbench/contrib/testing/browser/media/testing.css5
-rw-r--r--src/vs/workbench/contrib/testing/browser/testExplorerActions.ts44
-rw-r--r--src/vs/workbench/contrib/testing/browser/testing.contribution.ts4
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingDecorations.ts7
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts15
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerView.ts19
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts21
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/theme.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/constants.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/getComputedState.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/mainThreadTestCollection.ts11
-rw-r--r--src/vs/workbench/contrib/testing/common/testCoverage.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testExclusions.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts18
-rw-r--r--src/vs/workbench/contrib/testing/common/testItemCollection.ts (renamed from src/vs/workbench/contrib/testing/common/ownedTestCollection.ts)401
-rw-r--r--src/vs/workbench/contrib/testing/common/testProfileService.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testResult.ts36
-rw-r--r--src/vs/workbench/contrib/testing/common/testResultService.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testResultStorage.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testService.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testServiceImpl.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testTypes.ts (renamed from src/vs/workbench/contrib/testing/common/testCollection.ts)7
-rw-r--r--src/vs/workbench/contrib/testing/common/testingAutoRun.ts148
-rw-r--r--src/vs/workbench/contrib/testing/common/testingContentProvider.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testingContextKeys.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testingDecorations.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testingPeekOpener.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testingStates.ts6
-rw-r--r--src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts11
-rw-r--r--src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts12
-rw-r--r--src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts2
-rw-r--r--src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts17
-rw-r--r--src/vs/workbench/contrib/testing/test/common/testExplorerFilterState.test.ts3
-rw-r--r--src/vs/workbench/contrib/testing/test/common/testResultService.test.ts34
-rw-r--r--src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts8
-rw-r--r--src/vs/workbench/contrib/testing/test/common/testStubs.ts110
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewElement.ts2
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts5
-rw-r--r--src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts34
-rw-r--r--src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts2
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts2
141 files changed, 1840 insertions, 1081 deletions
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
index 6b0bb5fdf18..602b81639e2 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
@@ -37,8 +37,8 @@ export class BulkCellEdits {
@INotebookEditorModelResolverService private readonly _notebookModelService: INotebookEditorModelResolverService,
) { }
- async apply(): Promise<void> {
-
+ async apply(): Promise<readonly URI[]> {
+ const resources: URI[] = [];
const editsByNotebook = groupBy(this._edits, (a, b) => compare(a.resource.toString(), b.resource.toString()));
for (let group of editsByNotebook) {
@@ -56,10 +56,14 @@ export class BulkCellEdits {
// apply edits
const edits = group.map(entry => entry.cellEdit);
- ref.object.notebook.applyEdits(edits, true, undefined, () => undefined, this._undoRedoGroup);
+ ref.object.notebook.applyEdits(edits, true, undefined, () => undefined, this._undoRedoGroup, true);
ref.dispose();
this._progress.report(undefined);
+
+ resources.push(first.resource);
}
+
+ return resources;
}
}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts
index a259de1b6d5..aaab23b4da9 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts
@@ -21,7 +21,12 @@ import { LinkedList } from 'vs/base/common/linkedList';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { ResourceMap } from 'vs/base/common/map';
+import { ResourceMap, ResourceSet } from 'vs/base/common/map';
+import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
+import { URI } from 'vs/base/common/uri';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
class BulkEdit {
@@ -67,10 +72,10 @@ class BulkEdit {
}
}
- async perform(): Promise<void> {
+ async perform(): Promise<readonly URI[]> {
if (this._edits.length === 0) {
- return;
+ return [];
}
const ranges: number[] = [1];
@@ -88,6 +93,7 @@ class BulkEdit {
// Increment by percentage points since progress API expects that
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 100 / this._edits.length }) };
+ const resources: (readonly URI[])[] = [];
let index = 0;
for (let range of ranges) {
if (this._token.isCancellationRequested) {
@@ -95,34 +101,36 @@ class BulkEdit {
}
const group = this._edits.slice(index, index + range);
if (group[0] instanceof ResourceFileEdit) {
- await this._performFileEdits(<ResourceFileEdit[]>group, this._undoRedoGroup, this._undoRedoSource, this._confirmBeforeUndo, progress);
+ resources.push(await this._performFileEdits(<ResourceFileEdit[]>group, this._undoRedoGroup, this._undoRedoSource, this._confirmBeforeUndo, progress));
} else if (group[0] instanceof ResourceTextEdit) {
- await this._performTextEdits(<ResourceTextEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress);
+ resources.push(await this._performTextEdits(<ResourceTextEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress));
} else if (group[0] instanceof ResourceNotebookCellEdit) {
- await this._performCellEdits(<ResourceNotebookCellEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress);
+ resources.push(await this._performCellEdits(<ResourceNotebookCellEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress));
} else {
console.log('UNKNOWN EDIT');
}
index = index + range;
}
+
+ return resources.flat();
}
- private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, confirmBeforeUndo: boolean, progress: IProgress<void>) {
+ private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, confirmBeforeUndo: boolean, progress: IProgress<void>): Promise<readonly URI[]> {
this._logService.debug('_performFileEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._code || 'undoredo.workspaceEdit', undoRedoGroup, undoRedoSource, confirmBeforeUndo, progress, this._token, edits);
- await model.apply();
+ return await model.apply();
}
- private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<void> {
+ private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<readonly URI[]> {
this._logService.debug('_performTextEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._code || 'undoredo.workspaceEdit', this._editor, undoRedoGroup, undoRedoSource, progress, this._token, edits);
- await model.apply();
+ return await model.apply();
}
- private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<void> {
+ private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<readonly URI[]> {
this._logService.debug('_performCellEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits);
- await model.apply();
+ return await model.apply();
}
}
@@ -138,7 +146,9 @@ export class BulkEditService implements IBulkEditService {
@ILogService private readonly _logService: ILogService,
@IEditorService private readonly _editorService: IEditorService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
- @IDialogService private readonly _dialogService: IDialogService
+ @IDialogService private readonly _dialogService: IDialogService,
+ @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
+ @IConfigurationService private readonly _configService: IConfigurationService,
) { }
setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable {
@@ -212,8 +222,15 @@ export class BulkEditService implements IBulkEditService {
let listener: IDisposable | undefined;
try {
- listener = this._lifecycleService.onBeforeShutdown(e => e.veto(this.shouldVeto(label, e.reason), 'veto.blukEditService'));
- await bulkEdit.perform();
+ listener = this._lifecycleService.onBeforeShutdown(e => e.veto(this._shouldVeto(label, e.reason), 'veto.blukEditService'));
+ const resources = await bulkEdit.perform();
+
+ // when enabled (option AND setting) loop over all dirty working copies and trigger save
+ // for those that were involved in this bulk edit operation.
+ if (options?.respectAutoSaveConfig && this._configService.getValue(autoSaveSetting) === true && resources.length > 1) {
+ await this._saveAll(resources);
+ }
+
return { ariaSummary: bulkEdit.ariaMessage() };
} catch (err) {
// console.log('apply FAILED');
@@ -226,7 +243,23 @@ export class BulkEditService implements IBulkEditService {
}
}
- private async shouldVeto(label: string | undefined, reason: ShutdownReason): Promise<boolean> {
+ private async _saveAll(resources: readonly URI[]) {
+ const set = new ResourceSet(resources);
+ const saves = this._workingCopyService.dirtyWorkingCopies.map(async (copy) => {
+ if (set.has(copy.resource)) {
+ await copy.save();
+ }
+ });
+
+ const result = await Promise.allSettled(saves);
+ for (const item of result) {
+ if (item.status === 'rejected') {
+ this._logService.warn(item.reason);
+ }
+ }
+ }
+
+ private async _shouldVeto(label: string | undefined, reason: ShutdownReason): Promise<boolean> {
label = label || localize('fileOperation', "File operation");
const reasonLabel = reason === ShutdownReason.CLOSE ? localize('closeTheWindow', "Close Window") : reason === ShutdownReason.LOAD ? localize('changeWorkspace', "Change Workspace") :
reason === ShutdownReason.RELOAD ? localize('reloadTheWindow', "Reload Window") : localize('quit', "Quit");
@@ -240,3 +273,16 @@ export class BulkEditService implements IBulkEditService {
}
registerSingleton(IBulkEditService, BulkEditService, true);
+
+const autoSaveSetting = 'files.refactoring.autoSave';
+
+Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
+ id: 'files',
+ properties: {
+ [autoSaveSetting]: {
+ description: localize('refactoring.autoSave', "Controls if files that were part of a refactoring are saved automatically"),
+ default: true,
+ type: 'boolean'
+ }
+ }
+});
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts
index b3247bba89d..86de76556d2 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts
@@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer } from 'vs/base/common/buffer';
import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { flatten, tail } from 'vs/base/common/arrays';
+import { tail } from 'vs/base/common/arrays';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
interface IFileOperation {
@@ -51,7 +51,7 @@ class RenameOperation implements IFileOperation {
) { }
get uris() {
- return flatten(this._edits.map(edit => [edit.newUri, edit.oldUri]));
+ return this._edits.map(edit => [edit.newUri, edit.oldUri]).flat();
}
async perform(token: CancellationToken): Promise<IFileOperation> {
@@ -105,7 +105,7 @@ class CopyOperation implements IFileOperation {
) { }
get uris() {
- return flatten(this._edits.map(edit => [edit.newUri, edit.oldUri]));
+ return this._edits.map(edit => [edit.newUri, edit.oldUri]).flat();
}
async perform(token: CancellationToken): Promise<IFileOperation> {
@@ -293,7 +293,7 @@ class FileUndoRedoElement implements IWorkspaceUndoRedoElement {
readonly operations: IFileOperation[],
readonly confirmBeforeUndo: boolean
) {
- this.resources = (<URI[]>[]).concat(...operations.map(op => op.uris));
+ this.resources = operations.map(op => op.uris).flat();
}
async undo(): Promise<void> {
@@ -332,7 +332,7 @@ export class BulkFileEdits {
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
) { }
- async apply(): Promise<void> {
+ async apply(): Promise<readonly URI[]> {
const undoOperations: IFileOperation[] = [];
const undoRedoInfo = { undoRedoGroupId: this._undoRedoGroup.id };
@@ -350,7 +350,7 @@ export class BulkFileEdits {
}
if (edits.length === 0) {
- return;
+ return [];
}
const groups: Array<RenameEdit | CopyEdit | DeleteEdit | CreateEdit>[] = [];
@@ -395,6 +395,8 @@ export class BulkFileEdits {
this._progress.report(undefined);
}
- this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, this._code, undoOperations, this._confirmBeforeUndo), this._undoRedoGroup, this._undoRedoSource);
+ const undoRedoElement = new FileUndoRedoElement(this._label, this._code, undoOperations, this._confirmBeforeUndo);
+ this._undoRedoService.pushElement(undoRedoElement, this._undoRedoGroup, this._undoRedoSource);
+ return undoRedoElement.resources;
}
}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
index b0f3ba726bb..b1ff888c894 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
@@ -232,16 +232,17 @@ export class BulkTextEdits {
return { canApply: true };
}
- async apply(): Promise<void> {
+ async apply(): Promise<readonly URI[]> {
this._validateBeforePrepare();
const tasks = await this._createEditsTasks();
- if (this._token.isCancellationRequested) {
- return;
- }
try {
+ if (this._token.isCancellationRequested) {
+ return [];
+ }
+ const resources: URI[] = [];
const validation = this._validateTasks(tasks);
if (!validation.canApply) {
throw new Error(`${validation.reason.toString()} has changed in the meantime`);
@@ -254,6 +255,7 @@ export class BulkTextEdits {
this._undoRedoService.pushElement(singleModelEditStackElement, this._undoRedoGroup, this._undoRedoSource);
task.apply();
singleModelEditStackElement.close();
+ resources.push(task.model.uri);
}
this._progress.report(undefined);
} else {
@@ -267,10 +269,13 @@ export class BulkTextEdits {
for (const task of tasks) {
task.apply();
this._progress.report(undefined);
+ resources.push(task.model.uri);
}
multiModelEditStackElement.close();
}
+ return resources;
+
} finally {
dispose(tasks);
}
diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts
index db38d82e544..06f90fcfd98 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts
@@ -6,6 +6,7 @@
import './menuPreventer';
import './accessibility/accessibility';
import './diffEditorHelper';
+import './editorSettingsMigration';
import './inspectKeybindings';
import './largeFileOptimizations';
import './inspectEditorTokens/inspectEditorTokens';
diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorSettingsMigration.ts b/src/vs/workbench/contrib/codeEditor/browser/editorSettingsMigration.ts
new file mode 100644
index 00000000000..6fc9af98508
--- /dev/null
+++ b/src/vs/workbench/contrib/codeEditor/browser/editorSettingsMigration.ts
@@ -0,0 +1,72 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
+import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
+import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration';
+import { EditorSettingMigration, ISettingsReader, ISettingsWriter } from 'vs/editor/browser/config/migrateOptions';
+import { Disposable } from 'vs/base/common/lifecycle';
+
+class EditorSettingsMigration extends Disposable implements IWorkbenchContribution {
+
+ constructor(
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
+ @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
+ ) {
+ super();
+ this._register(this._workspaceService.onDidChangeWorkspaceFolders(async (e) => {
+ for (const folder of e.added) {
+ await this._migrateEditorSettingsForFolder(folder);
+ }
+ }));
+ this._migrateEditorSettings();
+ }
+
+ private async _migrateEditorSettings(): Promise<void> {
+ await this._migrateEditorSettingsForFolder(undefined);
+ for (const folder of this._workspaceService.getWorkspace().folders) {
+ await this._migrateEditorSettingsForFolder(folder);
+ }
+ }
+
+ private async _migrateEditorSettingsForFolder(folder: IWorkspaceFolder | undefined): Promise<void> {
+ await Promise.all(EditorSettingMigration.items.map(migration => this._migrateEditorSettingForFolderAndOverride(migration, { resource: folder?.uri })));
+ }
+
+ private async _migrateEditorSettingForFolderAndOverride(migration: EditorSettingMigration, overrides: IConfigurationOverrides): Promise<void> {
+ const data = this._configurationService.inspect(`editor.${migration.key}`, overrides);
+
+ await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userValue', ConfigurationTarget.USER);
+ await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userLocalValue', ConfigurationTarget.USER_LOCAL);
+ await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userRemoteValue', ConfigurationTarget.USER_REMOTE);
+ await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'workspaceFolderValue', ConfigurationTarget.WORKSPACE_FOLDER);
+ await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'workspaceValue', ConfigurationTarget.WORKSPACE);
+
+ if (typeof overrides.overrideIdentifier === 'undefined' && typeof data.overrideIdentifiers !== 'undefined') {
+ for (const overrideIdentifier of data.overrideIdentifiers) {
+ await this._migrateEditorSettingForFolderAndOverride(migration, { resource: overrides.resource, overrideIdentifier });
+ }
+ }
+ }
+
+ private async _migrateEditorSettingForFolderOverrideAndTarget(migration: EditorSettingMigration, overrides: IConfigurationOverrides, data: IConfigurationValue<any>, dataKey: keyof IConfigurationValue<any>, target: ConfigurationTarget): Promise<void> {
+ const value = data[dataKey];
+ if (typeof value === 'undefined') {
+ return;
+ }
+
+ const writeCalls: [string, any][] = [];
+ const read: ISettingsReader = (key: string) => this._configurationService.inspect(`editor.${key}`, overrides)[dataKey];
+ const write: ISettingsWriter = (key: string, value: any) => writeCalls.push([key, value]);
+ migration.migrate(value, read, write);
+ for (const [wKey, wValue] of writeCalls) {
+ await this._configurationService.updateValue(`editor.${wKey}`, wValue, overrides, target);
+ }
+ }
+}
+
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorSettingsMigration, LifecyclePhase.Eventually);
diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
index 7b54eef8f20..1e220e2de14 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
@@ -15,7 +15,7 @@ import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBo
import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon } from 'vs/editor/contrib/find/browser/findWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
+import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, errorForeground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
@@ -26,6 +26,12 @@ const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
+interface IFindOptions {
+ showOptionButtons?: boolean;
+ checkImeCompletionState?: boolean;
+ showResultCount?: boolean;
+}
+
export abstract class SimpleFindWidget extends Widget {
private readonly _findInput: FindInput;
private readonly _domNode: HTMLElement;
@@ -35,16 +41,16 @@ export abstract class SimpleFindWidget extends Widget {
private readonly _updateHistoryDelayer: Delayer<void>;
private readonly prevBtn: SimpleButton;
private readonly nextBtn: SimpleButton;
+ private _matchesCount: HTMLElement | undefined;
private _isVisible: boolean = false;
- private foundMatch: boolean = false;
+ private _foundMatch: boolean = false;
constructor(
@IContextViewService private readonly _contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService,
private readonly _state: FindReplaceState = new FindReplaceState(),
- showOptionButtons?: boolean,
- checkImeCompletionState?: boolean
+ private readonly _options: IFindOptions
) {
super();
@@ -59,20 +65,23 @@ export abstract class SimpleFindWidget extends Widget {
new RegExp(value);
return null;
} catch (e) {
- this.foundMatch = false;
- this.updateButtons(this.foundMatch);
+ this._foundMatch = false;
+ this.updateButtons(this._foundMatch);
return { content: e.message };
}
}
- }, contextKeyService, showOptionButtons));
+ }, contextKeyService, _options.showOptionButtons));
// Find History with update delayer
this._updateHistoryDelayer = new Delayer<void>(500);
- this._register(this._findInput.onInput((e) => {
- if (!checkImeCompletionState || !this._findInput.isImeSessionInProgress) {
- this.foundMatch = this._onInputChanged();
- this.updateButtons(this.foundMatch);
+ this._register(this._findInput.onInput(async (e) => {
+ if (!_options.checkImeCompletionState || !this._findInput.isImeSessionInProgress) {
+ this._foundMatch = this._onInputChanged();
+ if (this._options.showResultCount) {
+ await this.updateResultCount();
+ }
+ this.updateButtons(this._foundMatch);
this.focusFindBox();
this._delayedUpdateHistory();
}
@@ -152,6 +161,14 @@ export abstract class SimpleFindWidget extends Widget {
this._register(dom.addDisposableListener(this._innerDomNode, 'click', (event) => {
event.stopPropagation();
}));
+
+ if (_options?.showResultCount) {
+ this._domNode.classList.add('result-count');
+ this._register(this._findInput.onDidChange(() => {
+ this.updateResultCount();
+ this.updateButtons(this._foundMatch);
+ }));
+ }
}
protected abstract _onInputChanged(): boolean;
@@ -161,6 +178,7 @@ export abstract class SimpleFindWidget extends Widget {
protected abstract _onFocusTrackerBlur(): void;
protected abstract _onFindInputFocusTrackerFocus(): void;
protected abstract _onFindInputFocusTrackerBlur(): void;
+ protected abstract _getResultCount(): Promise<{ resultIndex: number; resultCount: number } | undefined>;
protected get inputValue() {
return this._findInput.getValue();
@@ -214,7 +232,7 @@ export abstract class SimpleFindWidget extends Widget {
}
this._isVisible = true;
- this.updateButtons(this.foundMatch);
+ this.updateButtons(this._foundMatch);
setTimeout(() => {
this._innerDomNode.classList.add('visible', 'visible-transition');
@@ -243,7 +261,7 @@ export abstract class SimpleFindWidget extends Widget {
// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
setTimeout(() => {
this._isVisible = false;
- this.updateButtons(this.foundMatch);
+ this.updateButtons(this._foundMatch);
this._innerDomNode.classList.remove('visible');
}, 200);
}
@@ -281,6 +299,20 @@ export abstract class SimpleFindWidget extends Widget {
this.nextBtn.focus();
this._findInput.inputBox.focus();
}
+
+ async updateResultCount(): Promise<void> {
+ const count = await this._getResultCount();
+ if (!this._matchesCount) {
+ this._matchesCount = document.createElement('div');
+ this._matchesCount.className = 'matchesCount';
+ }
+ this._matchesCount.innerText = '';
+ const label = count === undefined || count.resultCount === 0 ? `No Results` : `${count.resultIndex + 1} of ${count.resultCount}`;
+ this._matchesCount.appendChild(document.createTextNode(label));
+ this._matchesCount.classList.toggle('no-results', !count || count.resultCount === 0);
+ this._findInput?.domNode.insertAdjacentElement('afterend', this._matchesCount);
+ this._foundMatch = !!count && count.resultCount > 0;
+ }
}
// theming
@@ -299,4 +331,9 @@ registerThemingParticipant((theme, collector) => {
if (widgetShadowColor) {
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`);
}
+
+ const error = theme.getColor(errorForeground);
+ if (error) {
+ collector.addRule(`.no-results.matchesCount { color: ${error}; }`);
+ }
});
diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts
index a3162f0ffa2..53dc09e2826 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts
@@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { CharacterPair, CommentRule, EnterAction, ExplicitLanguageConfiguration, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration';
-import { LanguageConfigurationRegistry } from 'vs/editor/common/languages/languageConfigurationRegistry';
+import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -89,7 +89,8 @@ export class LanguageConfigurationFileHandler extends Disposable {
@ITextMateService textMateService: ITextMateService,
@ILanguageService private readonly _languageService: ILanguageService,
@IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService,
- @IExtensionService private readonly _extensionService: IExtensionService
+ @IExtensionService private readonly _extensionService: IExtensionService,
+ @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
) {
super();
@@ -414,7 +415,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
__electricCharacterSupport: undefined,
};
- LanguageConfigurationRegistry.register(languageId, richEditConfig, 50);
+ this._languageConfigurationService.register(languageId, richEditConfig, 50);
}
private _parseRegex(languageId: string, confPath: string, value: string | IRegExp): RegExp | undefined {
diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts
index fbd43cd3aed..d99f9c41a0b 100644
--- a/src/vs/workbench/contrib/comments/browser/commentNode.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts
@@ -45,6 +45,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private _domNode: HTMLElement;
private _body: HTMLElement;
private _md: HTMLElement | undefined;
+ private _plainText: HTMLElement | undefined;
private _clearTimeout: any;
private _editAction: Action | null = null;
@@ -125,8 +126,10 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private updateCommentBody(body: string | IMarkdownString) {
this._body.innerText = '';
this._md = undefined;
+ this._plainText = undefined;
if (typeof body === 'string') {
- this._body.innerText = body;
+ this._plainText = dom.append(this._body, dom.$('.comment-body-plainstring'));
+ this._plainText.innerText = body;
} else {
this._md = this.markdownRenderer.render(body).element;
this._body.appendChild(this._md);
diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
index ac743f0f7bb..b4989b77691 100644
--- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
@@ -311,7 +311,6 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
content.push(`.review-widget .body code {
font-family: var(${fontFamilyVar});
font-weight: var(${fontWeightVar});
- font-size: var(${fontSizeVar});
}`);
this._styleElement.textContent = content.join('\n');
diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css
index 791a2b70e7b..2539c3e3960 100644
--- a/src/vs/workbench/contrib/comments/browser/media/review.css
+++ b/src/vs/workbench/contrib/comments/browser/media/review.css
@@ -108,6 +108,10 @@
padding: 0 2px 0 2px;
}
+.review-widget .body .review-comment .review-comment-contents .comment-body .comment-body-plainstring {
+ white-space: pre;
+}
+
.review-widget .body .review-comment .review-comment-contents .comment-body {
padding-top: 4px;
}
diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts
index 12411801f21..5ec8c29bae1 100644
--- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts
+++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts
@@ -17,15 +17,15 @@ import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statu
export const STATUS_BAR_DEBUGGING_BACKGROUND = registerColor('statusBar.debuggingBackground', {
dark: '#CC6633',
light: '#CC6633',
- hcDark: '#CC6633',
- hcLight: '#CC6633'
+ hcDark: '#BA592C',
+ hcLight: '#B5200D'
}, localize('statusBarDebuggingBackground', "Status bar background color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_FOREGROUND = registerColor('statusBar.debuggingForeground', {
dark: STATUS_BAR_FOREGROUND,
light: STATUS_BAR_FOREGROUND,
hcDark: STATUS_BAR_FOREGROUND,
- hcLight: STATUS_BAR_FOREGROUND
+ hcLight: '#FFFFFF'
}, localize('statusBarDebuggingForeground', "Status bar foreground color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBorder', {
diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts
index d25efaffb6e..c27c88f27a6 100644
--- a/src/vs/workbench/contrib/debug/node/terminals.ts
+++ b/src/vs/workbench/contrib/debug/node/terminals.ts
@@ -6,6 +6,7 @@
import * as cp from 'child_process';
import { getDriveLetter } from 'vs/base/common/extpath';
import * as platform from 'vs/base/common/platform';
+// import { IProcessTreeNode } from 'windows-process-tree';
function spawnAsPromised(command: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
@@ -32,8 +33,8 @@ export async function hasChildProcesses(processId: number | undefined): Promise<
if (platform.isWindows) {
const windowsProcessTree = await import('windows-process-tree');
return new Promise<boolean>(resolve => {
- windowsProcessTree.getProcessTree(processId, (processTree) => {
- resolve(processTree.children.length > 0);
+ windowsProcessTree.getProcessTree(processId, processTree => {
+ resolve(!!processTree && processTree.children.length > 0);
});
});
} else {
diff --git a/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts
index f8735e39e90..fdcf9b5c181 100644
--- a/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts
+++ b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts
@@ -7,7 +7,7 @@ import { IGrammarContributions, EmmetEditorAction } from 'vs/workbench/contrib/e
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import * as assert from 'assert';
import { DisposableStore } from 'vs/base/common/lifecycle';
-import { ModesRegistry } from 'vs/editor/common/languages/modesRegistry';
+import { ILanguageService } from 'vs/editor/common/languages/language';
class MockGrammarContributions implements IGrammarContributions {
private scopeName: string;
@@ -24,13 +24,14 @@ class MockGrammarContributions implements IGrammarContributions {
suite('Emmet', () => {
test('Get language mode and parent mode for emmet', () => {
- withTestCodeEditor([], {}, (editor) => {
+ withTestCodeEditor([], {}, (editor, viewModel, instantiationService) => {
+ const languageService = instantiationService.get(ILanguageService);
const disposables = new DisposableStore();
- disposables.add(ModesRegistry.registerLanguage({ id: 'markdown' }));
- disposables.add(ModesRegistry.registerLanguage({ id: 'handlebars' }));
- disposables.add(ModesRegistry.registerLanguage({ id: 'nunjucks' }));
- disposables.add(ModesRegistry.registerLanguage({ id: 'laravel-blade' }));
+ disposables.add(languageService.registerLanguage({ id: 'markdown' }));
+ disposables.add(languageService.registerLanguage({ id: 'handlebars' }));
+ disposables.add(languageService.registerLanguage({ id: 'nunjucks' }));
+ disposables.add(languageService.registerLanguage({ id: 'laravel-blade' }));
function testIsEnabled(mode: string, scopeName: string, expectedLanguage?: string, expectedParentLanguage?: string) {
const model = editor.getModel();
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
index 0d0c23cdc12..41515d1c2e2 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -74,6 +74,8 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Event } from 'vs/base/common/event';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/extensions/browser/unsupportedExtensionsMigrationContribution';
+import { isWeb } from 'vs/base/common/platform';
+import { ExtensionsCleaner } from 'vs/workbench/contrib/extensions/browser/extensionsCleaner';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -1557,6 +1559,10 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, Life
workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementWorkspaceTrustTransitionParticipant, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsCompletionItemsProvider, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(UnsupportedExtensionsMigrationContrib, LifecyclePhase.Eventually);
+if (isWeb) {
+ workbenchRegistry.registerWorkbenchContribution(ExtensionsCleaner, LifecyclePhase.Eventually);
+}
+
// Running Extensions
registerAction2(ShowRuntimeExtensionsAction);
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts b/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts
new file mode 100644
index 00000000000..2d9b7872491
--- /dev/null
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts
@@ -0,0 +1,19 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
+import { IStorageService } from 'vs/platform/storage/common/storage';
+import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
+
+export class ExtensionsCleaner implements IWorkbenchContribution {
+
+ constructor(
+ @IExtensionManagementService extensionManagementService: IExtensionManagementService,
+ @IStorageService storageService: IStorageService,
+ ) {
+ ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
+ }
+}
diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
index 1ee398a691a..f7eca9c59df 100644
--- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
+++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
@@ -7,10 +7,10 @@ import { localize } from 'vs/nls';
import { assertIsDefined } from 'vs/base/common/types';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { toAction } from 'vs/base/common/actions';
-import { VIEWLET_ID, TEXT_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files';
+import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, BINARY_TEXT_FILE_MODE } from 'vs/workbench/contrib/files/common/files';
import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
-import { IEditorOpenContext, EditorInputCapabilities, isTextEditorViewState } from 'vs/workbench/common/editor';
+import { IEditorOpenContext, EditorInputCapabilities, isTextEditorViewState, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -24,7 +24,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
+import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IErrorWithActions } from 'vs/base/common/errorMessage';
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
@@ -212,8 +212,9 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
private openAsBinary(input: FileEditorInput, options: ITextEditorOptions | undefined): void {
const defaultBinaryEditor = this.configurationService.getValue<string | undefined>('workbench.editor.defaultBinaryEditor');
- const groupToOpen = this.group ?? this.editorGroupService.activeGroup;
- const editorOptions = {
+ const group = this.group ?? this.editorGroupService.activeGroup;
+
+ let editorOptions = {
...options,
// Make sure to not steal away the currently active group
// because we are triggering another openEditor() call
@@ -222,20 +223,43 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
activation: EditorActivation.PRESERVE
};
- // If we the user setting specifies a default binary editor we use that
- if (defaultBinaryEditor && defaultBinaryEditor !== '') {
- this.editorService.replaceEditors([{
- editor: input,
- replacement: { resource: input.resource, options: { ...editorOptions, override: defaultBinaryEditor } }
- }], groupToOpen);
+ // Check configuration and determine whether we open the binary
+ // file input in a different editor or going through the same
+ // editor.
+ // Going through the same editor is debt, and a better solution
+ // would be to introduce a real editor for the binary case
+ // and avoid enforcing binary or text on the file editor input.
+
+ if (defaultBinaryEditor && defaultBinaryEditor !== '' && defaultBinaryEditor !== DEFAULT_EDITOR_ASSOCIATION.id) {
+ this.doOpenAsBinaryInDifferentEditor(group, defaultBinaryEditor, input, editorOptions);
+ } else {
+ this.doOpenAsBinaryInSameEditor(group, defaultBinaryEditor, input, editorOptions);
}
+ }
- // Otherwise we mark file input for forced binary opening and reopen the file
- else {
- input.setForceOpenAsBinary();
+ private doOpenAsBinaryInDifferentEditor(group: IEditorGroup, editorId: string | undefined, editor: FileEditorInput, editorOptions: ITextEditorOptions): void {
+ this.editorService.replaceEditors([{
+ editor,
+ replacement: { resource: editor.resource, options: { ...editorOptions, override: editorId } }
+ }], group);
+ }
+
+ private doOpenAsBinaryInSameEditor(group: IEditorGroup, editorId: string | undefined, editor: FileEditorInput, editorOptions: ITextEditorOptions): void {
- groupToOpen.openEditor(input, editorOptions);
+ // Open binary as text
+ if (editorId === DEFAULT_EDITOR_ASSOCIATION.id) {
+ editor.setForceOpenAsText();
+ editor.setPreferredLanguageId(BINARY_TEXT_FILE_MODE); // https://github.com/microsoft/vscode/issues/131076
+
+ editorOptions = { ...editorOptions, forceReload: true }; // Same pane and same input, must force reload to clear cached state
}
+
+ // Open as binary
+ else {
+ editor.setForceOpenAsBinary();
+ }
+
+ group.openEditor(editor, editorOptions);
}
private async openAsFolder(input: FileEditorInput): Promise<void> {
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 317f7f2da0d..1a6f2c8f672 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -293,6 +293,11 @@ configurationRegistry.registerConfiguration({
'type': 'boolean',
'description': nls.localize('files.simpleDialog.enable', "Enables the simple file dialog. The simple file dialog replaces the system file dialog when enabled."),
'default': false
+ },
+ 'files.participants.timeout': {
+ type: 'number',
+ default: 60000,
+ markdownDescription: nls.localize('files.participants.timeout', "Timeout in milliseconds after which file participants for create, rename, and delete are cancelled. Use `0` to disable participants."),
}
}
});
diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts
index 55fb3414ea4..e34b213a567 100644
--- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts
+++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts
@@ -14,7 +14,7 @@ import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'v
import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { timeout } from 'vs/base/common/async';
-import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
+import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
@@ -171,7 +171,7 @@ suite('Files - FileEditorInput', () => {
test('preferred language', async function () {
const languageId = 'file-input-test';
- ModesRegistry.registerLanguage({
+ const registration = accessor.languageService.registerLanguage({
id: languageId,
});
@@ -190,6 +190,8 @@ suite('Files - FileEditorInput', () => {
const model2 = await input2.resolve() as TextFileEditorModel;
assert.strictEqual(model2.textEditorModel!.getLanguageId(), languageId);
+
+ registration.dispose();
});
test('preferred contents', async function () {
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
index 99d8c6897b6..52fe78d35bb 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
@@ -19,7 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { editorBackground, editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
-import { IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor';
+import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
import { CodeCellLayoutChangeEvent, IActiveNotebookEditorDelegate, ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@@ -54,9 +54,10 @@ import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover';
import { MarkerController } from 'vs/editor/contrib/gotoError/browser/gotoError';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
-import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
+import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/editor/common/editor';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@@ -110,6 +111,8 @@ export class InteractiveEditor extends EditorPane {
#onDidFocusWidget = this._register(new Emitter<void>());
override get onDidFocus(): Event<void> { return this.#onDidFocusWidget.event; }
+ #onDidChangeSelection = this._register(new Emitter<IEditorPaneSelectionChangeEvent>());
+ readonly onDidChangeSelection = this.#onDidChangeSelection.event;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -436,6 +439,10 @@ export class InteractiveEditor extends EditorPane {
}
}));
+ this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeCursorPosition(e => this.#onDidChangeSelection.fire({ reason: this.#toEditorPaneSelectionChangeReason(e) })));
+ this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeModelContent(() => this.#onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT })));
+
+
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeNotebookAffinity(this.#syncWithKernel, this));
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeSelectedNotebooks(this.#syncWithKernel, this));
@@ -495,6 +502,15 @@ export class InteractiveEditor extends EditorPane {
this.#syncWithKernel();
}
+ #toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason {
+ switch (e.source) {
+ case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC;
+ case TextEditorSelectionSource.NAVIGATION: return EditorPaneSelectionChangeReason.NAVIGATION;
+ case TextEditorSelectionSource.JUMP: return EditorPaneSelectionChangeReason.JUMP;
+ default: return EditorPaneSelectionChangeReason.USER;
+ }
+ }
+
#lastCell: ICellViewModel | undefined = undefined;
#lastCellDisposable = new DisposableStore();
#state: ScrollingState = ScrollingState.Initial;
@@ -565,7 +581,11 @@ export class InteractiveEditor extends EditorPane {
return;
}
- if (this.#lastCell instanceof CodeCellViewModel && (e as CodeCellLayoutChangeEvent).outputHeight === undefined && !this.#notebookWidget.value!.isScrolledToBottom()) {
+ if (!this.#notebookWidget.value) {
+ return;
+ }
+
+ if (this.#lastCell instanceof CodeCellViewModel && (e as CodeCellLayoutChangeEvent).outputHeight === undefined && !this.#notebookWidget.value.isScrolledToBottom()) {
return;
}
diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
index d79b71400e5..d6d4689d90e 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
+++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
@@ -41,6 +41,21 @@ class LanguageStatusViewModel {
}
}
+class StoredCounter {
+
+ constructor(@IStorageService private readonly _storageService: IStorageService, private readonly _key: string) { }
+
+ get value() {
+ return this._storageService.getNumber(this._key, StorageScope.GLOBAL, 0);
+ }
+
+ increment(): number {
+ const n = this.value + 1;
+ this._storageService.store(this._key, n, StorageScope.GLOBAL, StorageTarget.MACHINE);
+ return n;
+ }
+}
+
class EditorStatusContribution implements IWorkbenchContribution {
private static readonly _id = 'status.languageStatus';
@@ -48,6 +63,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
private static readonly _keyDedicatedItems = 'languageStatus.dedicated';
private readonly _disposables = new DisposableStore();
+ private readonly _interactionCounter: StoredCounter;
private _dedicated = new Set<string>();
@@ -65,6 +81,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
) {
_storageService.onDidChangeValue(this._handleStorageChange, this, this._disposables);
this._restoreState();
+ this._interactionCounter = new StoredCounter(_storageService, 'languageStatus.interactCount');
_languageStatusService.onDidChange(this._update, this, this._disposables);
_editorService.onDidActiveEditorChange(this._update, this, this._disposables);
@@ -181,6 +198,37 @@ class EditorStatusContribution implements IWorkbenchContribution {
} else {
this._combinedEntry.update(props);
}
+
+ // animate the status bar icon whenever language status changes, repeat animation
+ // when severity is warning or error, don't show animation when showing progress/busy
+ const userHasInteractedWithStatus = this._interactionCounter.value >= 3;
+ const node = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus span.codicon');
+ if (node instanceof HTMLElement) {
+ const _wiggle = 'wiggle';
+ const _repeat = 'repeat';
+ if (!isOneBusy) {
+ node.classList.toggle(_wiggle, showSeverity || !userHasInteractedWithStatus);
+ node.classList.toggle(_repeat, showSeverity);
+ this._renderDisposables.add(dom.addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle, _repeat)));
+ } else {
+ node.classList.remove(_wiggle, _repeat);
+ }
+ }
+
+ // track when the hover shows (this is automagic and DOM mutation spying is needed...)
+ // use that as signal that the user has interacted/learned language status items work
+ if (!userHasInteractedWithStatus) {
+ const hoverTarget = document.querySelector('.monaco-workbench .context-view');
+ if (hoverTarget instanceof HTMLElement) {
+ const observer = new MutationObserver(() => {
+ if (document.contains(element)) {
+ this._interactionCounter.increment();
+ observer.disconnect();
+ }
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+ }
+ }
}
// dedicated status bar items are shows as-is in the status bar
diff --git a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
index 8021133a7a1..131695f460a 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
+++ b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
@@ -3,9 +3,35 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-.monaco-workbench .statusbar DIV#status\.languageStatus .codicon-circle-large-filled::before {
- font-size: 12px;
- line-height: 18px;
+
+@keyframes wiggle {
+ 0% {
+ transform: rotate(0) scale(1)
+ }
+
+ 15%,
+ 45% {
+ transform: rotate(.04turn) scale(1.1)
+ }
+
+ 30%,
+ 60% {
+ transform: rotate(-.04turn) scale(1.2)
+ }
+
+ 100% {
+ transform: rotate(0) scale(1)
+ }
+}
+
+.monaco-workbench.enable-motion .statusbar DIV#status\.languageStatus span.codicon.wiggle {
+ animation-duration: .8s;
+ animation-iteration-count: 1;
+ animation-name: wiggle;
+}
+
+.monaco-workbench.enable-motion .statusbar DIV#status\.languageStatus span.codicon.wiggle.repeat {
+ animation-iteration-count: 3;
}
.monaco-workbench .hover-language-status {
@@ -17,61 +43,61 @@
border-bottom: 1px solid var(--vscode-notifications-border);
}
-.monaco-workbench .hover-language-status > .severity {
+.monaco-workbench .hover-language-status>.severity {
padding-right: 8px;
flex: 1;
margin: auto;
display: none;
}
-.monaco-workbench .hover-language-status > .severity.sev3 {
+.monaco-workbench .hover-language-status>.severity.sev3 {
color: var(--vscode-notificationsErrorIcon-foreground)
}
-.monaco-workbench .hover-language-status > .severity.sev2 {
+.monaco-workbench .hover-language-status>.severity.sev2 {
color: var(--vscode-notificationsInfoIcon-foreground)
}
-.monaco-workbench .hover-language-status > .severity.show {
+.monaco-workbench .hover-language-status>.severity.show {
display: inherit;
}
-.monaco-workbench .hover-language-status > .element {
+.monaco-workbench .hover-language-status>.element {
display: flex;
justify-content: space-between;
vertical-align: middle;
flex-grow: 100;
}
-.monaco-workbench .hover-language-status > .element > .left > .detail:not(:empty)::before {
+.monaco-workbench .hover-language-status>.element>.left>.detail:not(:empty)::before {
/* allow-any-unicode-next-line */
content: '–';
padding: 0 4px;
opacity: 0.6;
}
-.monaco-workbench .hover-language-status > .element > .left > .label:empty {
+.monaco-workbench .hover-language-status>.element>.left>.label:empty {
display: none;
}
-.monaco-workbench .hover-language-status > .element .left {
+.monaco-workbench .hover-language-status>.element .left {
margin: auto 0;
}
-.monaco-workbench .hover-language-status > .element .right {
+.monaco-workbench .hover-language-status>.element .right {
margin: auto 0;
display: flex;
}
-.monaco-workbench .hover-language-status > .element .right:not(:empty) {
+.monaco-workbench .hover-language-status>.element .right:not(:empty) {
padding-left: 16px;
}
-.monaco-workbench .hover-language-status > .element .right .monaco-link {
+.monaco-workbench .hover-language-status>.element .right .monaco-link {
margin: auto 0;
white-space: nowrap;
}
-.monaco-workbench .hover-language-status > .element .right .monaco-action-bar:not(:first-child) {
+.monaco-workbench .hover-language-status>.element .right .monaco-action-bar:not(:first-child) {
padding-left: 8px;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts
index 66037ed429c..7e3796f5342 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { flatten } from 'vs/base/common/arrays';
import { disposableTimeout, Throttler } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -119,7 +118,7 @@ class CellStatusBarHelper extends Disposable {
return;
}
- const items = flatten(itemLists.map(itemList => itemList.items));
+ const items = itemLists.map(itemList => itemList.items).flat();
const newIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]);
this._currentItemLists.forEach(itemList => itemList.dispose && itemList.dispose());
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
index 90a05458d9e..10037c96943 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
@@ -131,7 +131,7 @@ export function runPasteCells(editor: INotebookEditor, activeCell: ICellViewMode
kind: SelectionStateType.Index,
focus: { start: newFocusIndex, end: newFocusIndex + 1 },
selections: [{ start: newFocusIndex, end: newFocusIndex + pasteCells.items.length }]
- }), undefined);
+ }), undefined, true);
} else {
if (editor.getLength() !== 0) {
return false;
@@ -148,7 +148,7 @@ export function runPasteCells(editor: INotebookEditor, activeCell: ICellViewMode
kind: SelectionStateType.Index,
focus: { start: 0, end: 1 },
selections: [{ start: 1, end: pasteCells.items.length + 1 }]
- }), undefined);
+ }), undefined, true);
}
return true;
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
index 6b4c1aeb914..cb7ab0f8586 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
@@ -279,7 +279,7 @@ class ImplictKernelSelector implements IDisposable {
case NotebookCellsChangeType.ChangeCellContent:
case NotebookCellsChangeType.ModelChange:
case NotebookCellsChangeType.Move:
- case NotebookCellsChangeType.ChangeLanguage:
+ case NotebookCellsChangeType.ChangeCellLanguage:
logService.trace('IMPLICIT kernel selection because of change event', event.kind);
selectKernel();
break;
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts
index 99a308a9bd4..662b1929a2e 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts
@@ -41,6 +41,7 @@ import { isEqual } from 'vs/base/common/resources';
const FIND_HIDE_TRANSITION = 'find-hide-transition';
const FIND_SHOW_TRANSITION = 'find-show-transition';
let MAX_MATCHES_COUNT_WIDTH = 69;
+const PROGRESS_BAR_DELAY = 200; // show progress for at least 200ms
export class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution {
static id: string = 'workbench.notebook.find';
@@ -76,9 +77,9 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
if (e.isSearching) {
if (this._state.isSearching) {
- this._progressBar.infinite().show();
+ this._progressBar.infinite().show(PROGRESS_BAR_DELAY);
} else {
- this._progressBar.stop();
+ this._progressBar.stop().hide();
}
}
@@ -88,7 +89,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
}
const matches = this._findModel.findMatches;
- this._replaceAllBtn.setEnabled(matches.find(match => match.modelMatchCount < match.matches.length) === null);
+ this._replaceAllBtn.setEnabled(matches.length > 0 && matches.find(match => match.modelMatchCount < match.matches.length) === undefined);
if (e.filters) {
this._findInput.updateFilterState((this._state.filters?.markupPreview ?? false) || (this._state.filters?.codeOutput ?? false));
@@ -148,7 +149,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
if (currentMatch.isModelMatch) {
const match = currentMatch.match as FindMatch;
- this._progressBar.infinite().show();
+ this._progressBar.infinite().show(PROGRESS_BAR_DELAY);
const replacePattern = this.replacePattern;
const replaceString = replacePattern.buildReplaceString(match.matches, this._state.preserveCase);
@@ -168,7 +169,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
return;
}
- this._progressBar.infinite().show();
+ this._progressBar.infinite().show(PROGRESS_BAR_DELAY);
const replacePattern = this.replacePattern;
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
index 38c9042b175..9e7e660b6b0 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
@@ -34,6 +34,7 @@ import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
+import { isSafari } from 'vs/base/common/platform';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
@@ -73,57 +74,73 @@ class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem {
}
private getActions(): IAction[] {
- return [
- {
- checked: this.filters.markupInput,
- class: undefined,
- enabled: !this.filters.markupPreview,
- id: 'findInMarkdownInput',
- label: NOTEBOOK_FIND_IN_MARKUP_INPUT,
- run: async () => {
- this.filters.markupInput = !this.filters.markupInput;
- },
- tooltip: '',
- dispose: () => null
+ const markdownInput: IAction = {
+ checked: this.filters.markupInput,
+ class: undefined,
+ enabled: !this.filters.markupPreview,
+ id: 'findInMarkdownInput',
+ label: NOTEBOOK_FIND_IN_MARKUP_INPUT,
+ run: async () => {
+ this.filters.markupInput = !this.filters.markupInput;
},
- {
- checked: this.filters.markupPreview,
- class: undefined,
- enabled: true,
- id: 'findInMarkdownInput',
- label: NOTEBOOK_FIND_IN_MARKUP_PREVIEW,
- run: async () => {
- this.filters.markupPreview = !this.filters.markupPreview;
- },
- tooltip: '',
- dispose: () => null
+ tooltip: '',
+ dispose: () => null
+ };
+
+ const markdownPreview: IAction = {
+ checked: this.filters.markupPreview,
+ class: undefined,
+ enabled: true,
+ id: 'findInMarkdownInput',
+ label: NOTEBOOK_FIND_IN_MARKUP_PREVIEW,
+ run: async () => {
+ this.filters.markupPreview = !this.filters.markupPreview;
},
- new Separator(),
- {
- checked: this.filters.codeInput,
- class: undefined,
- enabled: true,
- id: 'findInCodeInput',
- label: NOTEBOOK_FIND_IN_CODE_INPUT,
- run: async () => {
- this.filters.codeInput = !this.filters.codeInput;
- },
- tooltip: '',
- dispose: () => null
+ tooltip: '',
+ dispose: () => null
+ };
+
+ const codeInput: IAction = {
+ checked: this.filters.codeInput,
+ class: undefined,
+ enabled: true,
+ id: 'findInCodeInput',
+ label: NOTEBOOK_FIND_IN_CODE_INPUT,
+ run: async () => {
+ this.filters.codeInput = !this.filters.codeInput;
},
- {
- checked: this.filters.codeOutput,
- class: undefined,
- enabled: true,
- id: 'findInCodeOutput',
- label: NOTEBOOK_FIND_IN_CODE_OUTPUT,
- run: async () => {
- this.filters.codeOutput = !this.filters.codeOutput;
- },
- tooltip: '',
- dispose: () => null
+ tooltip: '',
+ dispose: () => null
+ };
+
+ const codeOutput = {
+ checked: this.filters.codeOutput,
+ class: undefined,
+ enabled: true,
+ id: 'findInCodeOutput',
+ label: NOTEBOOK_FIND_IN_CODE_OUTPUT,
+ run: async () => {
+ this.filters.codeOutput = !this.filters.codeOutput;
},
- ];
+ tooltip: '',
+ dispose: () => null
+ };
+
+ if (isSafari) {
+ return [
+ markdownInput,
+ codeInput
+ ];
+ } else {
+ return [
+ markdownInput,
+ markdownPreview,
+ new Separator(),
+ codeInput,
+ codeOutput,
+ ];
+ }
+
}
override updateChecked(): void {
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts
index 239aa8064cf..6d2e6f4785e 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts
@@ -23,7 +23,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Progress } from 'vs/platform/progress/common/progress';
-import { flatten } from 'vs/base/common/arrays';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
// format notebook
@@ -91,7 +90,7 @@ registerAction2(class extends Action2 {
return [];
}));
- await bulkEditService.apply(/* edit */flatten(allCellEdits), { label: localize('label', "Format Notebook"), code: 'undoredo.formatNotebook', });
+ await bulkEditService.apply(/* edit */allCellEdits.flat(), { label: localize('label', "Format Notebook"), code: 'undoredo.formatNotebook', });
} finally {
disposable.dispose();
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
index 44ffbe69d1f..473ff42494f 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
@@ -430,11 +430,12 @@ export class NotebookCellOutline extends Disposable implements IOutline<OutlineE
// cap the amount of characters that we look at and use the following logic
// - for MD prefer headings (each header is an entry)
// - otherwise use the first none-empty line of the cell (MD or code)
- let content = cell.getText().substring(0, 10_000);
+ let content = this._getCellFirstNonEmptyLine(cell);
let hasHeader = false;
if (isMarkdown) {
- for (const token of marked.lexer(content, { gfm: true })) {
+ const fullContent = cell.getText().substring(0, 10_000);
+ for (const token of marked.lexer(fullContent, { gfm: true })) {
if (token.type === 'heading') {
hasHeader = true;
entries.push(new OutlineEntry(entries.length, token.depth, cell, renderMarkdownAsPlaintext({ value: token.text }).trim(), false, false));
@@ -558,6 +559,19 @@ export class NotebookCellOutline extends Disposable implements IOutline<OutlineE
}
}
+ private _getCellFirstNonEmptyLine(cell: ICellViewModel) {
+ const textBuffer = cell.textBuffer;
+ for (let i = 0; i < textBuffer.getLineCount(); i++) {
+ const firstNonWhitespace = textBuffer.getLineFirstNonWhitespaceColumn(i + 1);
+ const lineLength = textBuffer.getLineLength(i + 1);
+ if (firstNonWhitespace < lineLength) {
+ return textBuffer.getLineContent(i + 1);
+ }
+ }
+
+ return cell.getText().substring(0, 10_000);
+ }
+
get isEmpty(): boolean {
return this._entries.length === 0;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
index 8e6f97a2216..d0c6379a54b 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
@@ -10,7 +10,7 @@ import { isDocumentExcludePattern, TransientCellMetadata, TransientDocumentMetad
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
-CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, args): {
+CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor): {
viewType: string;
displayName: string;
options: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata };
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts
index 66f576aa3f4..f4209f52ad9 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts
@@ -143,7 +143,7 @@ export function runDeleteAction(editor: IActiveNotebookEditor, cell: ICellViewMo
return { kind: SelectionStateType.Index, focus: { start: 0, end: 0 }, selections: [{ start: 0, end: 0 }] };
}
}
- }, undefined);
+ }, undefined, true);
} else {
const focus = editor.getFocus();
const edits: ICellReplaceEdit[] = [{
@@ -169,14 +169,14 @@ export function runDeleteAction(editor: IActiveNotebookEditor, cell: ICellViewMo
textModel.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({
kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections
- }), undefined);
+ }), undefined, true);
} else {
// users decide to delete a cell out of current focus/selection
const newFocus = focus.start > targetCellIndex ? { start: focus.start - 1, end: focus.end - 1 } : focus;
textModel.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({
kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections
- }), undefined);
+ }), undefined, true);
}
}
}
@@ -222,7 +222,8 @@ export async function moveCellRange(context: INotebookCellActionContext, directi
selections: editor.getSelections()
},
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }),
- undefined
+ undefined,
+ true
);
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
editor.revealCellRangeInView(focusRange);
@@ -250,7 +251,8 @@ export async function moveCellRange(context: INotebookCellActionContext, directi
selections: editor.getSelections()
},
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }),
- undefined
+ undefined,
+ true
);
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
@@ -304,7 +306,8 @@ export async function copyCellRange(context: INotebookCellActionContext, directi
selections: selections
},
() => ({ kind: SelectionStateType.Index, focus: focus, selections: selections }),
- undefined
+ undefined,
+ true
);
} else {
// insert down, move selections
@@ -328,7 +331,8 @@ export async function copyCellRange(context: INotebookCellActionContext, directi
selections: selections
},
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: newSelections }),
- undefined
+ undefined,
+ true
);
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
@@ -624,10 +628,10 @@ export function insertCell(
const insertIndex = cell ?
(direction === 'above' ? index : nextIndex) :
index;
- return insertCellAtIndex(viewModel, insertIndex, initialText, language, type, undefined, [], true);
+ return insertCellAtIndex(viewModel, insertIndex, initialText, language, type, undefined, [], true, true);
}
-export function insertCellAtIndex(viewModel: NotebookViewModel, index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[], synchronous: boolean, pushUndoStop: boolean = true): CellViewModel {
+export function insertCellAtIndex(viewModel: NotebookViewModel, index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[], synchronous: boolean, pushUndoStop: boolean): CellViewModel {
const endSelections: ISelectionState = { kind: SelectionStateType.Index, focus: { start: index, end: index + 1 }, selections: [{ start: index, end: index + 1 }] };
viewModel.notebookDocument.applyEdits([
{
@@ -648,29 +652,3 @@ export function insertCellAtIndex(viewModel: NotebookViewModel, index: number, s
], synchronous, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => endSelections, undefined, pushUndoStop);
return viewModel.cellAt(index)!;
}
-
-
-/**
- *
- * @param index
- * @param length
- * @param newIdx in an index scheme for the state of the tree after the current cell has been "removed"
- * @param synchronous
- * @param pushedToUndoStack
- */
-export function moveCellToIdx(editor: IActiveNotebookEditor, index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean {
- const viewCell = editor.cellAt(index) as CellViewModel | undefined;
- if (!viewCell) {
- return false;
- }
-
- editor.textModel.applyEdits([
- {
- editType: CellEditType.Move,
- index,
- length,
- newIdx
- }
- ], synchronous, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: { start: newIdx, end: newIdx + 1 }, selections: [{ start: newIdx, end: newIdx + 1 }] }), undefined);
- return true;
-}
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
index 602e14a3940..588675f6403 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
@@ -208,7 +208,7 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction {
return;
}
- editor.textModel.applyEdits([{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined);
+ editor.textModel.applyEdits([{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined, true);
const runState = notebookExecutionStateService.getCellExecution(context.cell.uri)?.state;
if (runState !== NotebookCellExecutionState.Executing) {
@@ -220,7 +220,7 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction {
executionOrder: null,
lastRunSuccess: null
}
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
}
}
});
@@ -266,7 +266,7 @@ registerAction2(class ClearAllCellOutputsAction extends NotebookAction {
editor.textModel.applyEdits(
editor.textModel.cells.map((cell, index) => ({
editType: CellEditType.Output, index, outputs: []
- })), true, undefined, () => undefined, undefined);
+ })), true, undefined, () => undefined, undefined, true);
const clearExecutionMetadataEdits = editor.textModel.cells.map((cell, index) => {
const runState = notebookExecutionStateService.getCellExecution(cell.uri)?.state;
@@ -285,7 +285,7 @@ registerAction2(class ClearAllCellOutputsAction extends NotebookAction {
}
}).filter(edit => !!edit) as ICellEditOperation[];
if (clearExecutionMetadataEdits.length) {
- context.notebookEditor.textModel.applyEdits(clearExecutionMetadataEdits, true, undefined, () => undefined, undefined);
+ context.notebookEditor.textModel.applyEdits(clearExecutionMetadataEdits, true, undefined, () => undefined, undefined, true);
}
}
});
@@ -451,7 +451,7 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellR
const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model);
context.notebookEditor.textModel.applyEdits(
[{ editType: CellEditType.CellLanguage, index, language: languageId }],
- true, undefined, () => undefined, undefined
+ true, undefined, () => undefined, undefined, true
);
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
index 5e007b57a28..89c87eb81e7 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
@@ -517,7 +517,7 @@ abstract class AbstractElementRenderer extends Disposable {
this.notebookEditor.textModel!.applyEdits([
{ editType: CellEditType.Metadata, index, metadata: result }
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
} catch {
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts
index eea2a5a0c8c..91e50defad3 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts
@@ -11,7 +11,7 @@ import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { hash } from 'vs/base/common/hash';
import { toFormattedString } from 'vs/base/common/jsonFormatter';
-import { ICellOutput, IOutputDto, IOutputItemDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { ICellOutput, INotebookTextModel, IOutputDto, IOutputItemDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel';
import { URI } from 'vs/base/common/uri';
import { NotebookDiffEditorEventDispatcher, NotebookDiffViewEventType } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher';
@@ -117,7 +117,7 @@ export abstract class DiffElementViewModelBase extends Disposable {
private _metadataEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null;
constructor(
- readonly mainDocumentTextModel: NotebookTextModel,
+ readonly mainDocumentTextModel: INotebookTextModel,
readonly original: DiffNestedCellViewModel | undefined,
readonly modified: DiffNestedCellViewModel | undefined,
readonly type: 'unchanged' | 'insert' | 'delete' | 'modified',
@@ -645,7 +645,7 @@ function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) {
return OutputComparison.Unchanged;
}
-export function getFormattedMetadataJSON(documentTextModel: NotebookTextModel, metadata: NotebookCellMetadata, language?: string) {
+export function getFormattedMetadataJSON(documentTextModel: INotebookTextModel, metadata: NotebookCellMetadata, language?: string) {
let filteredMetadata: { [key: string]: any } = {};
if (documentTextModel) {
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
index 1672b6635b2..9de089e2f5a 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
@@ -8,8 +8,8 @@ import * as DOM from 'vs/base/browser/dom';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
-import { IEditorOpenContext } from 'vs/workbench/common/editor';
-import { cellEditorBackground, getDefaultNotebookCreationOptions, notebookCellBorder, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
+import { EditorPaneSelectionChangeReason, EditorPaneSelectionCompareResult, IEditorOpenContext, IEditorPaneSelection, IEditorPaneSelectionChangeEvent, IEditorPaneWithSelection } from 'vs/workbench/common/editor';
+import { cellEditorBackground, focusedEditorBorderColor, getDefaultNotebookCreationOptions, notebookCellBorder, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { NotebookDiffEditorInput } from '../notebookDiffEditorInput';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
@@ -42,10 +42,46 @@ import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
+import { IEditorOptions } from 'vs/platform/editor/common/editor';
+import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange';
const $ = DOM.$;
-export class NotebookTextDiffEditor extends EditorPane implements INotebookTextDiffEditor, INotebookDelegateForWebview {
+class NotebookDiffEditorSelection implements IEditorPaneSelection {
+
+ constructor(
+ private readonly selections: number[]
+ ) { }
+
+ compare(other: IEditorPaneSelection): EditorPaneSelectionCompareResult {
+ if (!(other instanceof NotebookDiffEditorSelection)) {
+ return EditorPaneSelectionCompareResult.DIFFERENT;
+ }
+
+ if (this.selections.length !== other.selections.length) {
+ return EditorPaneSelectionCompareResult.DIFFERENT;
+ }
+
+ for (let i = 0; i < this.selections.length; i++) {
+ if (this.selections[i] !== other.selections[i]) {
+ return EditorPaneSelectionCompareResult.DIFFERENT;
+ }
+ }
+
+ return EditorPaneSelectionCompareResult.IDENTICAL;
+ }
+
+ restore(options: IEditorOptions): INotebookEditorOptions {
+ const notebookOptions: INotebookEditorOptions = {
+ cellSelections: cellIndexesToRanges(this.selections)
+ };
+
+ Object.assign(notebookOptions, options);
+ return notebookOptions;
+ }
+}
+
+export class NotebookTextDiffEditor extends EditorPane implements INotebookTextDiffEditor, INotebookDelegateForWebview, IEditorPaneWithSelection {
creationOptions: INotebookEditorCreationOptions = getDefaultNotebookCreationOptions();
static readonly ID: string = NOTEBOOK_DIFF_EDITOR_ID;
@@ -86,6 +122,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
private _layoutCancellationTokenSource?: CancellationTokenSource;
+ private readonly _onDidChangeSelection = this._register(new Emitter<IEditorPaneSelectionChangeEvent>());
+ readonly onDidChangeSelection = this._onDidChangeSelection.event;
+
private _isDisposed: boolean = false;
get isDisposed() {
@@ -110,6 +149,11 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._revealFirst = true;
}
+ getSelection(): IEditorPaneSelection | undefined {
+ const selections = this._list.getFocus();
+ return new NotebookDiffEditorSelection(selections);
+ }
+
toggleNotebookCellSelection(cell: IGenericCellViewModel) {
// throw new Error('Method not implemented.');
}
@@ -227,6 +271,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
}
}));
+ this._register(this._list.onDidChangeFocus(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.USER })));
+
// transparent cover
this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover'));
this._webviewTransparentCover.style.display = 'none';
@@ -333,7 +379,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._modifiedResourceDisposableStore.add(this._modifiedWebview);
}
- await this.updateLayout(this._layoutCancellationTokenSource.token);
+ await this.updateLayout(this._layoutCancellationTokenSource.token, options?.cellSelections ? cellRangesToIndexes(options.cellSelections) : undefined);
}
private _detachModel() {
@@ -415,7 +461,14 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._originalWebview.element.style.left = `16px`;
}
- async updateLayout(token: CancellationToken) {
+ override setOptions(options: INotebookEditorOptions | undefined): void {
+ const selections = options?.cellSelections ? cellRangesToIndexes(options.cellSelections) : undefined;
+ if (selections) {
+ this._list.setFocus(selections);
+ }
+ }
+
+ async updateLayout(token: CancellationToken, selections?: number[]) {
if (!this._model) {
return;
}
@@ -445,6 +498,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._list.setFocus([firstChangeIndex]);
this._list.reveal(firstChangeIndex, 0.3);
}
+
+ if (selections) {
+ this._list.setFocus(selections);
+ }
}
private _isViewModelTheSame(viewModels: DiffElementViewModelBase[]) {
@@ -929,6 +986,15 @@ registerThemingParticipant((theme, collector) => {
}`);
}
+ const focusCellBackgroundColor = theme.getColor(focusedEditorBorderColor);
+
+ if (focusCellBackgroundColor) {
+ collector.addRule(`.notebook-text-diff-editor .monaco-list-row.focused .cell-body .border-container .top-border { border-top: 1px solid ${focusCellBackgroundColor};}`);
+ collector.addRule(`.notebook-text-diff-editor .monaco-list-row.focused .cell-body .border-container .bottom-border { border-top: 1px solid ${focusCellBackgroundColor};}`);
+ collector.addRule(`.notebook-text-diff-editor .monaco-list-row.focused .cell-body .border-container .left-border { border-left: 1px solid ${focusCellBackgroundColor};}`);
+ collector.addRule(`.notebook-text-diff-editor .monaco-list-row.focused .cell-body .border-container .right-border { border-right: 1px solid ${focusCellBackgroundColor};}`);
+ }
+
const diffDiagonalFillColor = theme.getColor(diffDiagonalFill);
collector.addRule(`
.notebook-text-diff-editor .diagonal-fill {
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
index b774588523b..fead08ce397 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./notebookDiff';
-import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
+import { IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import * as DOM from 'vs/base/browser/dom';
-import { IListStyles, IStyleController } from 'vs/base/browser/ui/list/listWidget';
+import { IListOptions, IListStyles, isMonacoEditor, IStyleController, MouseController } from 'vs/base/browser/ui/list/listWidget';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -281,6 +281,17 @@ export class CellDiffSideBySideRenderer implements IListRenderer<SideBySideDiffE
}
}
+export class NotebookMouseController<T> extends MouseController<T> {
+ protected override onViewPointer(e: IListMouseEvent<T>): void {
+ if (isMonacoEditor(e.browserEvent.target as HTMLElement)) {
+ const focus = typeof e.index === 'undefined' ? [] : [e.index];
+ this.list.setFocus(focus, e.browserEvent);
+ } else {
+ super.onViewPointer(e);
+ }
+ }
+}
+
export class NotebookTextDiffList extends WorkbenchList<DiffElementViewModelBase> implements IDisposable, IStyleController {
private styleElement?: HTMLStyleElement;
@@ -302,6 +313,10 @@ export class NotebookTextDiffList extends WorkbenchList<DiffElementViewModelBase
super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService);
}
+ protected override createMouseController(options: IListOptions<DiffElementViewModelBase>): MouseController<DiffElementViewModelBase> {
+ return new NotebookMouseController(this);
+ }
+
getAbsoluteTopOfElement(element: DiffElementViewModelBase): number {
const index = this.indexOf(element);
// if (index === undefined || index < 0 || index >= this.length) {
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
index 9fdabf420e3..2b793c40961 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -29,7 +29,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd
import { isCompositeNotebookEditorInput, NotebookEditorInput, NotebookEditorInputOptions } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
-import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookWorkingCopyTypeIdentifier, NotebookSetting, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookWorkingCopyTypeIdentifier, NotebookSetting, ICellOutput, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
@@ -99,7 +99,6 @@ import { NotebookExecutionService } from 'vs/workbench/contrib/notebook/browser/
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/services/notebookKeymapServiceImpl';
-import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
@@ -322,13 +321,16 @@ class CellContentProvider implements ITextModelContentProvider {
}
}
- if (result) {
- const once = Event.any(result.onWillDispose, ref.object.notebook.onWillDispose)(() => {
- once.dispose();
- ref.dispose();
- });
+ if (!result) {
+ ref.dispose();
+ return null;
}
+ const once = Event.any(result.onWillDispose, ref.object.notebook.onWillDispose)(() => {
+ once.dispose();
+ ref.dispose();
+ });
+
return result;
}
}
@@ -400,13 +402,16 @@ class CellInfoContentProvider {
}
}
- if (result) {
- const once = result.onWillDispose(() => {
- once.dispose();
- ref.dispose();
- });
+ if (!result) {
+ ref.dispose();
+ return null;
}
+ const once = result.onWillDispose(() => {
+ once.dispose();
+ ref.dispose();
+ });
+
return result;
}
@@ -429,7 +434,7 @@ class CellInfoContentProvider {
private _getResult(data: {
notebook: URI;
outputId?: string | undefined;
- }, cell: NotebookCellTextModel) {
+ }, cell: ICell) {
let result: { content: string; mode: ILanguageSelection } | undefined = undefined;
const mode = this._languageService.createById('json');
@@ -472,34 +477,36 @@ class CellInfoContentProvider {
const cell = ref.object.notebook.cells.find(cell => !!cell.outputs.find(op => op.outputId === data.outputId));
if (!cell) {
+ ref.dispose();
return null;
}
const result = this._getResult(data, cell);
- if (result) {
- const model = this._modelService.createModel(result.content, result.mode, resource);
- const cellModelListener = Event.any(cell.onDidChangeOutputs, cell.onDidChangeOutputItems)(() => {
- const newResult = this._getResult(data, cell);
+ if (!result) {
+ ref.dispose();
+ return null;
+ }
- if (!newResult) {
- return;
- }
+ const model = this._modelService.createModel(result.content, result.mode, resource);
+ const cellModelListener = Event.any(cell.onDidChangeOutputs ?? Event.None, cell.onDidChangeOutputItems ?? Event.None)(() => {
+ const newResult = this._getResult(data, cell);
- model.setValue(newResult.content);
- model.setMode(newResult.mode.languageId);
- });
+ if (!newResult) {
+ return;
+ }
- const once = model.onWillDispose(() => {
- once.dispose();
- cellModelListener.dispose();
- ref.dispose();
- });
+ model.setValue(newResult.content);
+ model.setMode(newResult.mode.languageId);
+ });
- return model;
- }
+ const once = model.onWillDispose(() => {
+ once.dispose();
+ cellModelListener.dispose();
+ ref.dispose();
+ });
- return null;
+ return model;
}
}
@@ -554,6 +561,15 @@ class NotebookEditorManager implements IWorkbenchContribution {
group.closeEditors(staleInputs);
}
}));
+
+ // CLOSE editors when we are about to open conflicting notebooks
+ this._disposables.add(_notebookEditorModelService.onWillFailWithConflict(e => {
+ for (const group of editorGroups.groups) {
+ const conflictInputs = group.editors.filter(input => input instanceof NotebookEditorInput && input.viewType !== e.viewType && isEqual(input.resource, e.resource));
+ const p = group.closeEditors(conflictInputs);
+ e.waitUntil(p);
+ }
+ }));
}
dispose(): void {
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
index aed8ee62af8..7153bcfe26c 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
@@ -26,6 +26,7 @@ import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKe
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { cellRangesToIndexes, ICellRange, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
+import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
//#region Shared commands
export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput';
@@ -628,6 +629,11 @@ export interface IActiveNotebookEditor extends INotebookEditor {
getNextVisibleCellIndex(index: number): number;
}
+export interface IBaseCellEditorOptions extends IDisposable {
+ readonly value: IEditorOptions;
+ readonly onDidChange: Event<void>;
+}
+
/**
* A mix of public interface and internal one (used by internal rendering code, e.g., cellRenderer)
*/
@@ -637,6 +643,7 @@ export interface INotebookEditorDelegate extends INotebookEditor {
readonly creationOptions: INotebookEditorCreationOptions;
readonly onDidChangeOptions: Event<void>;
readonly onDidChangeDecorations: Event<void>;
+ getBaseCellEditorOptions(language: string): IBaseCellEditorOptions;
createMarkupPreview(cell: ICellViewModel): Promise<void>;
unhideMarkupPreviews(cells: readonly ICellViewModel[]): Promise<void>;
hideMarkupPreviews(cells: readonly ICellViewModel[]): Promise<void>;
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 5420ef359f9..623686523fa 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -49,7 +49,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
private _rootElement!: HTMLElement;
private _dimension?: DOM.Dimension;
- private readonly inputListener = this._register(new MutableDisposable());
+ private readonly _inputListener = this._register(new MutableDisposable());
// override onDidFocus and onDidBlur to be based on the NotebookEditorWidget element
private readonly _onDidFocusWidget = this._register(new Emitter<void>());
@@ -66,36 +66,36 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
- @IInstantiationService private readonly instantiationService: IInstantiationService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorDropService private readonly _editorDropService: IEditorDropService,
@INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
- @IFileService private readonly fileService: IFileService,
+ @IFileService private readonly _fileService: IFileService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
- this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidChangeFileSystemProvider(e.scheme)));
- this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidChangeFileSystemProvider(e.scheme)));
+ this._register(this._fileService.onDidChangeFileSystemProviderCapabilities(e => this._onDidChangeFileSystemProvider(e.scheme)));
+ this._register(this._fileService.onDidChangeFileSystemProviderRegistrations(e => this._onDidChangeFileSystemProvider(e.scheme)));
}
- private onDidChangeFileSystemProvider(scheme: string): void {
+ private _onDidChangeFileSystemProvider(scheme: string): void {
if (this.input instanceof NotebookEditorInput && this.input.resource?.scheme === scheme) {
- this.updateReadonly(this.input);
+ this._updateReadonly(this.input);
}
}
- private onDidChangeInputCapabilities(input: NotebookEditorInput): void {
+ private _onDidChangeInputCapabilities(input: NotebookEditorInput): void {
if (this.input === input) {
- this.updateReadonly(input);
+ this._updateReadonly(input);
}
}
- private updateReadonly(input: NotebookEditorInput): void {
+ private _updateReadonly(input: NotebookEditorInput): void {
if (this._widget.value) {
this._widget.value.setOptions({ isReadOnly: input.hasCapability(EditorInputCapabilities.Readonly) });
}
@@ -128,7 +128,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
override getActionViewItem(action: IAction): IActionViewItem | undefined {
if (action.id === SELECT_KERNEL_ID) {
// this is being disposed by the consumer
- return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this);
+ return this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this);
}
return undefined;
}
@@ -170,13 +170,13 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
return !!value && (DOM.isAncestor(activeElement, value.getDomNode() || DOM.isAncestor(activeElement, value.getOverflowContainerDomNode())));
}
- override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
+ override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise<void> {
try {
clearMarks(input.resource);
mark(input.resource, 'startTime');
const group = this.group!;
- this.inputListener.value = input.onDidChangeCapabilities(() => this.onDidChangeInputCapabilities(input));
+ this._inputListener.value = input.onDidChangeCapabilities(() => this._onDidChangeInputCapabilities(input));
this._widgetDisposableStore.clear();
@@ -186,7 +186,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
this._widget.value.onWillHide();
}
- this._widget = <IBorrowValue<NotebookEditorWidget>>this.instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input);
+ this._widget = <IBorrowValue<NotebookEditorWidget>>this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input);
this._widgetDisposableStore.add(this._widget.value!.onDidChangeModel(() => this._onDidChangeModel.fire()));
this._widgetDisposableStore.add(this._widget.value!.onDidChangeActiveCell(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.USER })));
@@ -205,6 +205,16 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
return undefined;
}
+ // The widget has been taken away again. This can happen when the tab has been closed while
+ // loading was in progress, in particular when open the same resource as different view type.
+ // When this happen, retry once
+ if (!this._widget.value) {
+ if (noRetry) {
+ return undefined;
+ }
+ return this.setInput(input, options, context, token, true);
+ }
+
if (model === null) {
throw new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType));
}
@@ -314,7 +324,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
}
override clearInput(): void {
- this.inputListener.clear();
+ this._inputListener.clear();
if (this._widget.value) {
this._saveEditorViewState(this.input);
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index 4bb50abce37..aa1bcc7853f 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -21,6 +21,7 @@ import { Color, RGBA } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { deepClone } from 'vs/base/common/objects';
import { setTimeout0 } from 'vs/base/common/platform';
import { extname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
@@ -48,7 +49,7 @@ import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeg
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
-import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, IActiveNotebookEditorDelegate, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger';
@@ -87,6 +88,100 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
const $ = DOM.$;
+export class BaseCellEditorOptions extends Disposable implements IBaseCellEditorOptions {
+ private static fixedEditorOptions: IEditorOptions = {
+ scrollBeyondLastLine: false,
+ scrollbar: {
+ verticalScrollbarSize: 14,
+ horizontal: 'auto',
+ useShadows: true,
+ verticalHasArrows: false,
+ horizontalHasArrows: false,
+ alwaysConsumeMouseWheel: false
+ },
+ renderLineHighlightOnlyWhenFocus: true,
+ overviewRulerLanes: 0,
+ selectOnLineNumbers: false,
+ lineNumbers: 'off',
+ lineDecorationsWidth: 0,
+ folding: true,
+ fixedOverflowWidgets: true,
+ minimap: { enabled: false },
+ renderValidationDecorations: 'on',
+ lineNumbersMinChars: 3
+ };
+
+ private _localDisposableStore = this._register(new DisposableStore());
+ private readonly _onDidChange = this._register(new Emitter<void>());
+ readonly onDidChange: Event<void> = this._onDidChange.event;
+ private _value: IEditorOptions;
+
+ get value(): Readonly<IEditorOptions> {
+ return this._value;
+ }
+
+ constructor(readonly notebookEditor: INotebookEditorDelegate, readonly notebookOptions: NotebookOptions, readonly configurationService: IConfigurationService, readonly language: string) {
+ super();
+ this._register(configurationService.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) {
+ this._recomputeOptions();
+ }
+ }));
+
+ this._register(notebookOptions.onDidChangeOptions(e => {
+ if (e.cellStatusBarVisibility || e.editorTopPadding || e.editorOptionsCustomizations) {
+ this._recomputeOptions();
+ }
+ }));
+
+ this._register(this.notebookEditor.onDidChangeModel(() => {
+ this._localDisposableStore.clear();
+
+ if (this.notebookEditor.hasModel()) {
+ this._localDisposableStore.add(this.notebookEditor.onDidChangeOptions(() => {
+ this._recomputeOptions();
+ }));
+
+ this._recomputeOptions();
+ }
+ }));
+
+ if (this.notebookEditor.hasModel()) {
+ this._localDisposableStore.add(this.notebookEditor.onDidChangeOptions(() => {
+ this._recomputeOptions();
+ }));
+ }
+
+ this._value = this._computeEditorOptions();
+ }
+
+ private _recomputeOptions(): void {
+ this._value = this._computeEditorOptions();
+ this._onDidChange.fire();
+ }
+
+ private _computeEditorOptions() {
+ const editorOptions = deepClone(this.configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier: this.language }));
+ const layoutConfig = this.notebookOptions.getLayoutConfiguration();
+ const editorOptionsOverrideRaw = layoutConfig.editorOptionsCustomizations ?? {};
+ const editorOptionsOverride: { [key: string]: any } = {};
+ for (const key in editorOptionsOverrideRaw) {
+ if (key.indexOf('editor.') === 0) {
+ editorOptionsOverride[key.substring(7)] = editorOptionsOverrideRaw[key];
+ }
+ }
+ const computed = Object.freeze({
+ ...editorOptions,
+ ...BaseCellEditorOptions.fixedEditorOptions,
+ ...editorOptionsOverride,
+ ...{ padding: { top: 12, bottom: 12 } },
+ readOnly: this.notebookEditor.isReadOnly
+ });
+
+ return computed;
+ }
+}
+
export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOptions {
// We inlined the id to avoid loading comment contrib in tests
const skipContributions = ['editor.contrib.review'];
@@ -229,6 +324,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
return this._list.visibleRanges || [];
}
+ private _baseCellEditorOptions = new Map<string, IBaseCellEditorOptions>();
+
readonly isEmbedded: boolean;
private _readOnly: boolean;
@@ -452,6 +549,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
//#region Editor Core
+
+ getBaseCellEditorOptions(language: string): IBaseCellEditorOptions {
+ const existingOptions = this._baseCellEditorOptions.get(language);
+
+ if (existingOptions) {
+ return existingOptions;
+ } else {
+ const options = new BaseCellEditorOptions(this, this.notebookOptions, this.configurationService, language);
+ this._baseCellEditorOptions.set(language, options);
+ return options;
+ }
+ }
+
private _updateForNotebookConfiguration() {
if (!this._overlayContainer) {
return;
@@ -2809,6 +2919,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._overlayContainer.remove();
this.viewModel?.dispose();
+ this._renderedEditors.clear();
+ this._baseCellEditorOptions.forEach(v => v.dispose());
+ this._baseCellEditorOptions.clear();
+
+ super.dispose();
+
// unref
this._webview = null;
this._webviewResolvePromise = null;
@@ -2817,11 +2933,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._listTopCellToolbar = null;
this._notebookViewModel = undefined;
this._cellContextKeyManager = null;
- this._renderedEditors.clear();
+ this._notebookTopToolbar = null!;
+ this._list = null!;
+ this._listViewInfoAccessor = null!;
this._pendingLayouts = null;
this._listDelegate = null;
-
- super.dispose();
}
toJSON(): { notebookUri: URI | undefined } {
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
index 1999bf283bc..374f1d707a1 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
@@ -594,12 +594,10 @@ export class NotebookService extends Disposable implements INotebookService {
return this._registerProviderData(viewType, new SimpleNotebookProviderInfo(viewType, serializer, extensionData));
}
- async withNotebookDataProvider(resource: URI, viewType?: string): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo> {
- const providers = this.notebookProviderInfoStore.getContributedNotebook(resource);
- // If we have a viewtype specified we want that data provider, as the resource won't always map correctly
- const selected = viewType ? providers.find(p => p.id === viewType) : providers[0];
+ async withNotebookDataProvider(viewType: string): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo> {
+ const selected = this.notebookProviderInfoStore.get(viewType);
if (!selected) {
- throw new Error(`NO contribution for resource: '${resource.toString()}'`);
+ throw new Error(`UNKNOWN notebook type '${viewType}'`);
}
await this.canResolve(selected.id);
const result = this._notebookProviders.get(selected.id);
@@ -714,4 +712,3 @@ export class NotebookService extends Disposable implements INotebookService {
}
}
-
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
index 2f7413ad92f..8c0b8a99748 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
@@ -68,7 +68,7 @@ export class CellDragAndDropController extends Disposable {
private readonly listOnWillScrollListener = this._register(new MutableDisposable());
constructor(
- private readonly notebookEditor: INotebookEditorDelegate,
+ private notebookEditor: INotebookEditorDelegate,
private readonly notebookListContainer: HTMLElement
) {
super();
@@ -406,6 +406,11 @@ export class CellDragAndDropController extends Disposable {
return this.getDropInsertDirection(dragPosRatio);
}
+
+ override dispose() {
+ this.notebookEditor = null!;
+ super.dispose();
+ }
}
export function performCellDropEdits(editor: INotebookEditorDelegate, draggedCell: ICellViewModel, dropDirection: 'above' | 'below', draggedOverCell: ICellViewModel): void {
@@ -494,6 +499,6 @@ export function performCellDropEdits(editor: INotebookEditorDelegate, draggedCel
true,
{ kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() },
() => ({ kind: SelectionStateType.Index, focus: finalFocus, selections: [finalSelection] }),
- undefined);
+ undefined, true);
editor.revealCellRangeInView(finalSelection);
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts
index 00d68c38911..3059080c7d5 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts
@@ -4,8 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
-import { DisposableStore } from 'vs/base/common/lifecycle';
-import { deepClone } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IEditorOptions, LineNumbersType } from 'vs/editor/common/config/editorOptions';
import { localize } from 'vs/nls';
@@ -17,7 +15,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { Registry } from 'vs/platform/registry/common/platform';
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
import { INotebookCellToolbarActionContext, INotebookCommandContext, NotebookMultiCellAction, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
-import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { IBaseCellEditorOptions, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
import { NotebookCellInternalMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -25,66 +23,18 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOp
import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
export class CellEditorOptions extends CellPart {
- private static fixedEditorOptions: IEditorOptions = {
- scrollBeyondLastLine: false,
- scrollbar: {
- verticalScrollbarSize: 14,
- horizontal: 'auto',
- useShadows: true,
- verticalHasArrows: false,
- horizontalHasArrows: false,
- alwaysConsumeMouseWheel: false
- },
- renderLineHighlightOnlyWhenFocus: true,
- overviewRulerLanes: 0,
- selectOnLineNumbers: false,
- lineNumbers: 'off',
- lineDecorationsWidth: 0,
- folding: true,
- fixedOverflowWidgets: true,
- minimap: { enabled: false },
- renderValidationDecorations: 'on',
- lineNumbersMinChars: 3
- };
-
- private _value: IEditorOptions;
private _lineNumbers: 'on' | 'off' | 'inherit' = 'inherit';
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
- private _localDisposableStore = this._register(new DisposableStore());
+ private _value: IEditorOptions;
- constructor(readonly notebookEditor: INotebookEditorDelegate, readonly notebookOptions: NotebookOptions, readonly configurationService: IConfigurationService, readonly language: string) {
+ constructor(private readonly base: IBaseCellEditorOptions, readonly notebookOptions: NotebookOptions, readonly configurationService: IConfigurationService) {
super();
- this._register(configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) {
- this._recomputeOptions();
- }
- }));
- this._register(notebookOptions.onDidChangeOptions(e => {
- if (e.cellStatusBarVisibility || e.editorTopPadding || e.editorOptionsCustomizations) {
- this._recomputeOptions();
- }
+ this._register(base.onDidChange(() => {
+ this._recomputeOptions();
}));
- this._register(this.notebookEditor.onDidChangeModel(() => {
- this._localDisposableStore.clear();
-
- if (this.notebookEditor.hasModel()) {
- this._localDisposableStore.add(this.notebookEditor.onDidChangeOptions(() => {
- this._recomputeOptions();
- }));
-
- this._recomputeOptions();
- }
- }));
-
- if (this.notebookEditor.hasModel()) {
- this._localDisposableStore.add(this.notebookEditor.onDidChangeOptions(() => {
- this._recomputeOptions();
- }));
- }
-
this._value = this._computeEditorOptions();
}
@@ -102,25 +52,17 @@ export class CellEditorOptions extends CellPart {
private _computeEditorOptions() {
const renderLineNumbers = this.configurationService.getValue<'on' | 'off'>('notebook.lineNumbers') === 'on';
const lineNumbers: LineNumbersType = renderLineNumbers ? 'on' : 'off';
- const editorOptions = deepClone(this.configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier: this.language }));
- const layoutConfig = this.notebookOptions.getLayoutConfiguration();
- const editorOptionsOverrideRaw = layoutConfig.editorOptionsCustomizations ?? {};
- const editorOptionsOverride: { [key: string]: any } = {};
- for (const key in editorOptionsOverrideRaw) {
- if (key.indexOf('editor.') === 0) {
- editorOptionsOverride[key.substring(7)] = editorOptionsOverrideRaw[key];
- }
- }
- const computed = {
- ...editorOptions,
- ...CellEditorOptions.fixedEditorOptions,
- ... { lineNumbers },
- ...editorOptionsOverride,
- ...{ padding: { top: 12, bottom: 12 } },
- readOnly: this.notebookEditor.isReadOnly
- };
- return computed;
+ const value = this.base.value;
+
+ if (value.lineNumbers !== lineNumbers) {
+ return {
+ ...value,
+ ...{ lineNumbers }
+ };
+ } else {
+ return Object.assign({}, value);
+ }
}
getUpdatedValue(internalMetadata: NotebookCellInternalMetadata, cellUri: URI): IEditorOptions {
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
index fab7ccb3505..3f2df96e305 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
@@ -53,8 +53,8 @@ export class CodeCell extends Disposable {
) {
super();
- const cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, this.viewCell.language));
- this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: 2 });
+ const cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService));
+ this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: 500 });
this.cellParts = [...templateData.cellParts, cellEditorOptions, this._outputContainerRenderer];
const editorHeight = this.calculateInitEditorHeight();
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
index b1b5077afea..bf119feb15c 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
@@ -59,8 +59,7 @@ export class StatefulMarkdownCell extends Disposable {
this.constructDOM();
this.editorPart = templateData.editorPart;
-
- this.cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, this.viewCell.language));
+ this.cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService));
this.cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers);
this.editorOptions = this.cellEditorOptions.getValue(this.viewCell.internalMetadata, this.viewCell.uri);
diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
index 673fe4a4312..3ef1c58649e 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
@@ -1252,8 +1252,20 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
this.view.setScrollTop(this.view.elementTop(viewIndex));
break;
case CellRevealPosition.Center:
- this.view.setScrollTop(elementTop - this.view.renderHeight / 2);
- this.view.setScrollTop(this.view.elementTop(viewIndex) - this.view.renderHeight / 2);
+ {
+ // reveal the cell top in the viewport center initially
+ this.view.setScrollTop(elementTop - this.view.renderHeight / 2);
+ // cell rendered already, we now have a more accurate cell height
+ const newElementTop = this.view.elementTop(viewIndex);
+ const newElementHeight = this.view.elementHeight(viewIndex);
+ const renderHeight = this.getViewScrollBottom() - this.getViewScrollTop();
+ if (newElementHeight >= renderHeight) {
+ // cell is larger than viewport, reveal top
+ this.view.setScrollTop(newElementTop);
+ } else {
+ this.view.setScrollTop(newElementTop + (newElementHeight / 2) - (renderHeight / 2));
+ }
+ }
break;
case CellRevealPosition.Bottom:
this.view.setScrollTop(this.scrollTop + (elementBottom - wrapperBottom));
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
index 1beeb548519..02beef093cf 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
@@ -98,7 +98,7 @@ abstract class AbstractCellRenderer {
language: string,
protected dndController: CellDragAndDropController | undefined
) {
- this.editorOptions = new CellEditorOptions(notebookEditor, notebookEditor.notebookOptions, configurationService, language);
+ this.editorOptions = new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(language), this.notebookEditor.notebookOptions, configurationService);
}
dispose() {
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
index 11b51e17e2f..8b7c306af28 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
@@ -156,6 +156,8 @@ export abstract class BaseCellViewModel extends Disposable {
this._onDidChangeState.fire({ outputCollapsedChanged: true });
}
+ private _textEditorRestore: any;
+
constructor(
readonly viewType: string,
readonly model: NotebookCellTextModel,
@@ -234,7 +236,9 @@ export abstract class BaseCellViewModel extends Disposable {
this._textEditor = editor;
if (this._editorViewStates) {
- this._restoreViewState(this._editorViewStates);
+ this._textEditorRestore = setTimeout(() => {
+ this._restoreViewState(this._editorViewStates);
+ });
}
if (this._editorTransientState) {
@@ -260,6 +264,7 @@ export abstract class BaseCellViewModel extends Disposable {
}
detachTextEditor() {
+ clearTimeout(this._textEditorRestore);
this.saveViewState();
this.saveTransientState();
// decorations need to be cleared first as editors can be resued.
@@ -584,6 +589,7 @@ export abstract class BaseCellViewModel extends Disposable {
super.dispose();
dispose(this._editorListeners);
+ clearTimeout(this._textEditorRestore);
// Only remove the undo redo stack if we map this cell uri to itself
// If we are not in perCell mode, it will map to the full NotebookDocument and
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
index f897a762e74..243b4b96edc 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
@@ -602,6 +602,17 @@ export class NotebookEditorToolbar extends Disposable {
}
this._computeSizes();
}
+
+ override dispose() {
+ this._notebookLeftToolbar.context = undefined;
+ this._notebookRightToolbar.context = undefined;
+ this._notebookLeftToolbar.dispose();
+ this._notebookRightToolbar.dispose();
+ this._notebookLeftToolbar = null!;
+ this._notebookRightToolbar = null!;
+
+ super.dispose();
+ }
}
registerThemingParticipant((theme, collector) => {
diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
index a04f9eb9ecf..5f186e4b0da 100644
--- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { flatten } from 'vs/base/common/arrays';
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
@@ -324,7 +323,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
case 'language':
this._pauseableEmitter.fire({
- rawEvents: [{ kind: NotebookCellsChangeType.ChangeLanguage, index: this._getCellIndexByHandle(cell.handle), language: cell.language, transient: false }],
+ rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._getCellIndexByHandle(cell.handle), language: cell.language, transient: false }],
versionId: this.versionId,
synchronous: true,
endSelectionState: undefined
@@ -400,11 +399,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
],
true,
undefined, () => undefined,
- undefined
+ undefined,
+ true
);
}
- applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean = true): boolean {
+ applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean {
this._pauseableEmitter.pause();
this.pushStackElement('edit', beginSelectionState, undoRedoGroup);
@@ -502,7 +502,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return [...otherEdits.reverse(), ...replaceEdits];
});
- const flattenEdits = flatten(edits);
+ const flattenEdits = edits.flat();
for (const { edit, cellIndex } of flattenEdits) {
switch (edit.editType) {
@@ -937,7 +937,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
this._pauseableEmitter.fire({
- rawEvents: [{ kind: NotebookCellsChangeType.ChangeLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }],
+ rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }],
versionId: this.versionId,
synchronous: true,
endSelectionState: undefined
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index c5ffb1bbf2f..bc1f504dc36 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -5,7 +5,7 @@
import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { IDiffResult, ISequence } from 'vs/base/common/diff/diff';
+import { IDiffResult } from 'vs/base/common/diff/diff';
import { Event } from 'vs/base/common/event';
import * as glob from 'vs/base/common/glob';
import { Iterable } from 'vs/base/common/iterator';
@@ -18,11 +18,13 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Command } from 'vs/editor/common/languages';
+import { IReadonlyTextBuffer } from 'vs/editor/common/model';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
+import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
@@ -214,7 +216,10 @@ export interface ICell {
outputs: ICellOutput[];
metadata: NotebookCellMetadata;
internalMetadata: NotebookCellInternalMetadata;
+ getHashValue(): number;
+ textBuffer: IReadonlyTextBuffer;
onDidChangeOutputs?: Event<NotebookCellOutputsSplice>;
+ onDidChangeOutputItems?: Event<void>;
onDidChangeLanguage: Event<string>;
onDidChangeMetadata: Event<void>;
onDidChangeInternalMetadata: Event<CellInternalMetadataChangedEvent>;
@@ -223,10 +228,14 @@ export interface ICell {
export interface INotebookTextModel {
readonly viewType: string;
metadata: NotebookDocumentMetadata;
+ readonly transientOptions: TransientOptions;
readonly uri: URI;
readonly versionId: number;
-
+ readonly length: number;
readonly cells: readonly ICell[];
+ reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void;
+ applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo?: boolean): boolean;
+ onDidChangeContent: Event<NotebookTextModelChangedEvent>;
onWillDispose: Event<void>;
}
@@ -257,7 +266,7 @@ export interface IMainCellDto {
export enum NotebookCellsChangeType {
ModelChange = 1,
Move = 2,
- ChangeLanguage = 5,
+ ChangeCellLanguage = 5,
Initialize = 6,
ChangeCellMetadata = 7,
Output = 8,
@@ -308,7 +317,7 @@ export interface NotebookOutputItemChangedEvent {
}
export interface NotebookCellsChangeLanguageEvent {
- readonly kind: NotebookCellsChangeType.ChangeLanguage;
+ readonly kind: NotebookCellsChangeType.ChangeCellLanguage;
readonly index: number;
readonly language: string;
}
@@ -775,7 +784,7 @@ export interface INotebookEditorModel extends IEditorModel {
readonly onDidChangeReadonly: Event<void>;
readonly resource: URI;
readonly viewType: string;
- readonly notebook: NotebookTextModel | undefined;
+ readonly notebook: INotebookTextModel | undefined;
isResolved(): this is IResolvedNotebookEditorModel;
isDirty(): boolean;
isReadonly(): boolean;
@@ -869,20 +878,6 @@ export interface INotebookCellStatusBarItemProvider {
provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken): Promise<INotebookCellStatusBarItemList | undefined>;
}
-export class CellSequence implements ISequence {
-
- constructor(readonly textModel: NotebookTextModel) {
- }
-
- getElements(): string[] | number[] | Int32Array {
- const hashValue = new Int32Array(this.textModel.cells.length);
- for (let i = 0; i < this.textModel.cells.length; i++) {
- hashValue[i] = this.textModel.cells[i].getHashValue();
- }
-
- return hashValue;
- }
-}
export interface INotebookDiffResult {
cellsDiff: IDiffResult;
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
index 31ed15060f2..cffd2b0362d 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
@@ -262,7 +262,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
}
if (this.options._backupId) {
- const info = await this._notebookService.withNotebookDataProvider(this._editorModelReference.object.notebook.uri, this._editorModelReference.object.notebook.viewType);
+ const info = await this._notebookService.withNotebookDataProvider(this._editorModelReference.object.notebook.viewType);
if (!(info instanceof SimpleNotebookProviderInfo)) {
throw new Error('CANNOT open file notebook with this provider');
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
index 9e3e00d91ee..8938cff2e80 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
@@ -665,7 +665,7 @@ export class NotebookFileWorkingCopyModelFactory implements IStoredFileWorkingCo
async createModel(resource: URI, stream: VSBufferReadableStream, token: CancellationToken): Promise<NotebookFileWorkingCopyModel> {
- const info = await this._notebookService.withNotebookDataProvider(resource, this._viewType);
+ const info = await this._notebookService.withNotebookDataProvider(this._viewType);
if (!(info instanceof SimpleNotebookProviderInfo)) {
throw new Error('CANNOT open file notebook with this provider');
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts
index b2039e2694b..e2d2a6c4fe4 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts
@@ -7,10 +7,21 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { URI } from 'vs/base/common/uri';
import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IReference } from 'vs/base/common/lifecycle';
-import { Event } from 'vs/base/common/event';
+import { Event, IWaitUntil } from 'vs/base/common/event';
export const INotebookEditorModelResolverService = createDecorator<INotebookEditorModelResolverService>('INotebookModelResolverService');
+/**
+ * A notebook file can only be opened ONCE per notebook type.
+ * This event fires when a file is already open as type A
+ * and there is request to open it as type B. Listeners must
+ * do cleanup (close editor, release references) or the request fails
+ */
+export interface INotebookConflictEvent extends IWaitUntil {
+ resource: URI;
+ viewType: string;
+}
+
export interface IUntitledNotebookResource {
/**
* Depending on the value of `untitledResource` will
@@ -34,6 +45,8 @@ export interface INotebookEditorModelResolverService {
readonly onDidSaveNotebook: Event<URI>;
readonly onDidChangeDirty: Event<IResolvedNotebookEditorModel>;
+ readonly onWillFailWithConflict: Event<INotebookConflictEvent>;
+
isDirty(resource: URI): boolean;
resolve(resource: URI, viewType?: string): Promise<IReference<IResolvedNotebookEditorModel>>;
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts
index f5d98feee26..bc34431e3f5 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts
@@ -10,15 +10,16 @@ import { ComplexNotebookEditorModel, NotebookFileWorkingCopyModel, NotebookFileW
import { combinedDisposable, DisposableStore, dispose, IDisposable, IReference, ReferenceCollection, toDisposable } from 'vs/base/common/lifecycle';
import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ILogService } from 'vs/platform/log/common/log';
-import { Emitter, Event } from 'vs/base/common/event';
+import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
-import { INotebookEditorModelResolverService, IUntitledNotebookResource } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
+import { INotebookConflictEvent, INotebookEditorModelResolverService, IUntitledNotebookResource } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { ResourceMap } from 'vs/base/common/map';
import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
import { Schemas } from 'vs/base/common/network';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { assertIsDefined } from 'vs/base/common/types';
+import { CancellationToken } from 'vs/base/common/cancellation';
class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> {
@@ -61,7 +62,7 @@ class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IReso
protected async createReferencedObject(key: string, viewType: string, hasAssociatedFilePath: boolean): Promise<IResolvedNotebookEditorModel> {
const uri = URI.parse(key);
- const info = await this._notebookService.withNotebookDataProvider(uri, viewType);
+ const info = await this._notebookService.withNotebookDataProvider(viewType);
let result: IResolvedNotebookEditorModel;
@@ -136,6 +137,9 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes
readonly onDidSaveNotebook: Event<URI>;
readonly onDidChangeDirty: Event<IResolvedNotebookEditorModel>;
+ private readonly _onWillFailWithConflict = new AsyncEmitter<INotebookConflictEvent>();
+ readonly onWillFailWithConflict = this._onWillFailWithConflict.event;
+
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService private readonly _notebookService: INotebookService,
@@ -208,7 +212,14 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes
}
if (existingViewType && existingViewType !== viewType) {
- throw new Error(`A notebook with view type '${existingViewType}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`);
+
+ await this._onWillFailWithConflict.fireAsync({ resource, viewType }, CancellationToken.None);
+
+ // check again, listener should have done cleanup
+ const existingViewType2 = this._notebookService.getNotebookTextModel(resource)?.viewType;
+ if (existingViewType2 && existingViewType2 !== viewType) {
+ throw new Error(`A notebook with view type '${existingViewType2}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`);
+ }
}
const reference = this._data.acquire(resource.toString(), viewType, hasAssociatedFilePath);
diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts
index 305f67e5cbb..18faf5fdee5 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts
@@ -68,7 +68,7 @@ export interface INotebookService {
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: INotebookContentProvider): IDisposable;
registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable;
- withNotebookDataProvider(resource: URI, viewType?: string): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo>;
+ withNotebookDataProvider(viewType: string): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo>;
getOutputMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[];
diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts
index ace5f4f4c15..51b61d425b1 100644
--- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts
+++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts
@@ -122,7 +122,7 @@ class MirrorNotebookDocument {
} else if (e.kind === NotebookCellsChangeType.Output) {
const cell = this.cells[e.index];
cell.outputs = e.outputs;
- } else if (e.kind === NotebookCellsChangeType.ChangeLanguage) {
+ } else if (e.kind === NotebookCellsChangeType.ChangeCellLanguage) {
const cell = this.cells[e.index];
cell.language = e.language;
} else if (e.kind === NotebookCellsChangeType.ChangeCellMetadata) {
diff --git a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts
index 5a469da5487..08fd7b6e023 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts
@@ -5,7 +5,7 @@
import * as assert from 'assert';
import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel';
-import { changeCellToKind, computeCellLinesContents, copyCellRange, joinNotebookCells, moveCellRange, moveCellToIdx, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
+import { changeCellToKind, computeCellLinesContents, copyCellRange, joinNotebookCells, moveCellRange, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
import { Range } from 'vs/editor/common/core/range';
@@ -13,56 +13,6 @@ import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
suite('CellOperations', () => {
- test('move cells down', async function () {
- await withTestNotebook(
- [
- ['//a', 'javascript', CellKind.Code, [], {}],
- ['//b', 'javascript', CellKind.Code, [], {}],
- ['//c', 'javascript', CellKind.Code, [], {}],
- ],
- (editor, viewModel) => {
- moveCellToIdx(editor, 0, 1, 0, true);
- // no-op
- assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
- assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
-
- moveCellToIdx(editor, 0, 1, 1, true);
- // b, a, c
- assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
- assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
- assert.strictEqual(viewModel.cellAt(2)?.getText(), '//c');
-
- moveCellToIdx(editor, 0, 1, 2, true);
- // a, c, b
- assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
- assert.strictEqual(viewModel.cellAt(1)?.getText(), '//c');
- assert.strictEqual(viewModel.cellAt(2)?.getText(), '//b');
- }
- );
- });
-
- test('move cells up', async function () {
- await withTestNotebook(
- [
- ['//a', 'javascript', CellKind.Code, [], {}],
- ['//b', 'javascript', CellKind.Code, [], {}],
- ['//c', 'javascript', CellKind.Code, [], {}],
- ],
- (editor, viewModel) => {
- moveCellToIdx(editor, 1, 1, 0, true);
- // b, a, c
- assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
- assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
-
- moveCellToIdx(editor, 2, 1, 0, true);
- // c, b, a
- assert.strictEqual(viewModel.cellAt(0)?.getText(), '//c');
- assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
- assert.strictEqual(viewModel.cellAt(2)?.getText(), '//a');
- }
- );
- });
-
test('Move cells - single cell', async function () {
await withTestNotebook(
[
diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts
index 3a543fd6a4a..0e898675d75 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts
@@ -113,7 +113,7 @@ suite('Notebook Outline', function () {
], outline => {
assert.ok(outline instanceof NotebookCellOutline);
assert.deepStrictEqual(outline.config.quickPickDataSource.getQuickPickElements().length, 1);
- assert.deepStrictEqual(outline.config.quickPickDataSource.getQuickPickElements()[0].label, '!@#$\n Überschrïft');
+ assert.deepStrictEqual(outline.config.quickPickDataSource.getQuickPickElements()[0].label, '!@#$');
});
});
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts
index 2b59cccb488..8cc256883a7 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts
@@ -5,14 +5,30 @@
import * as assert from 'assert';
import { VSBuffer } from 'vs/base/common/buffer';
-import { LcsDiff } from 'vs/base/common/diff/diff';
+import { ISequence, LcsDiff } from 'vs/base/common/diff/diff';
import { Mimes } from 'vs/base/common/mime';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher';
import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor';
-import { CellKind, CellSequence } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellKind, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { withTestNotebookDiffModel } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
+class CellSequence implements ISequence {
+
+ constructor(readonly textModel: INotebookTextModel) {
+ }
+
+ getElements(): string[] | number[] | Int32Array {
+ const hashValue = new Int32Array(this.textModel.cells.length);
+ for (let i = 0; i < this.textModel.cells.length; i++) {
+ hashValue[i] = this.textModel.cells[i].getHashValue();
+ }
+
+ return hashValue;
+ }
+}
+
+
suite('NotebookCommon', () => {
const configurationService = new TestConfigurationService();
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
index aebc72563ab..6f65268830f 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
@@ -68,7 +68,7 @@ suite('NotebookExecutionService', () => {
async (viewModel) => {
const executionService = instantiationService.createInstance(NotebookExecutionService);
- const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
+ const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await assertThrowsAsync(async () => await executionService.executeNotebookCell(cell));
});
});
@@ -80,7 +80,7 @@ suite('NotebookExecutionService', () => {
kernelService.registerKernel(new TestNotebookKernel({ languages: ['testlang'] }));
const executionService = instantiationService.createInstance(NotebookExecutionService);
- const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
+ const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await assertThrowsAsync(async () => await executionService.executeNotebookCell(cell));
});
@@ -96,7 +96,7 @@ suite('NotebookExecutionService', () => {
const executeSpy = sinon.spy();
kernel.executeNotebookCellsRequest = executeSpy;
- const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
+ const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await executionService.executeNotebookCells(viewModel.notebookDocument, [cell]);
assert.strictEqual(executeSpy.calledOnce, true);
});
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts
index 42a2b1c6d2b..0bee94ca7a1 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts
@@ -40,7 +40,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
{ editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 6);
@@ -63,7 +63,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 6);
@@ -86,7 +86,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
{ editType: CellEditType.Replace, index: 3, count: 1, cells: [] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
assert.strictEqual(textModel.cells[1].getValue(), 'var c = 3;');
@@ -107,7 +107,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
{ editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 4);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
@@ -129,7 +129,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 4);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
@@ -151,7 +151,7 @@ suite('NotebookTextModel', () => {
const textModel = editor.textModel;
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 4);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
@@ -175,7 +175,7 @@ suite('NotebookTextModel', () => {
index: Number.MAX_VALUE,
editType: CellEditType.Output,
outputs: []
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
});
// invalid index 2
@@ -184,7 +184,7 @@ suite('NotebookTextModel', () => {
index: -1,
editType: CellEditType.Output,
outputs: []
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
});
textModel.applyEdits([{
@@ -194,7 +194,7 @@ suite('NotebookTextModel', () => {
outputId: 'someId',
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello_') }]
}]
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].outputs.length, 1);
@@ -208,7 +208,7 @@ suite('NotebookTextModel', () => {
outputId: 'someId2',
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello2_') }]
}]
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].outputs.length, 2);
@@ -224,7 +224,7 @@ suite('NotebookTextModel', () => {
outputId: 'someId3',
outputs: [{ mime: Mimes.text, data: valueBytesFromString('Last, replaced output') }]
}]
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].outputs.length, 1);
@@ -262,7 +262,7 @@ suite('NotebookTextModel', () => {
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 2') }]
}]
}
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].outputs.length, 2);
@@ -299,7 +299,7 @@ suite('NotebookTextModel', () => {
mime: Mimes.markdown, data: valueBytesFromString('append 2')
}]
}
- ], true, undefined, () => undefined, undefined);
+ ], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].outputs.length, 1, 'has 1 output');
@@ -324,7 +324,7 @@ suite('NotebookTextModel', () => {
index: Number.MAX_VALUE,
editType: CellEditType.Metadata,
metadata: {}
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
});
// invalid index 2
@@ -333,20 +333,20 @@ suite('NotebookTextModel', () => {
index: -1,
editType: CellEditType.Metadata,
metadata: {}
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
});
textModel.applyEdits([{
index: 0,
editType: CellEditType.Metadata,
metadata: { customProperty: 15 },
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
textModel.applyEdits([{
index: 0,
editType: CellEditType.Metadata,
metadata: {},
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].metadata.customProperty, undefined);
@@ -366,13 +366,13 @@ suite('NotebookTextModel', () => {
index: 0,
editType: CellEditType.PartialMetadata,
metadata: { customProperty: 15 },
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
textModel.applyEdits([{
index: 0,
editType: CellEditType.PartialMetadata,
metadata: {},
- }], true, undefined, () => undefined, undefined);
+ }], true, undefined, () => undefined, undefined, true);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].metadata.customProperty, 15);
@@ -403,7 +403,7 @@ suite('NotebookTextModel', () => {
textModel.applyEdits([
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] },
- ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined);
+ ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined, true);
assert.strictEqual(textModel.cells.length, 4);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
@@ -449,7 +449,7 @@ suite('NotebookTextModel', () => {
editType: CellEditType.Metadata,
metadata: {},
}
- ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined);
+ ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined, true);
assert.notStrictEqual(changeEvent, undefined);
assert.strictEqual(changeEvent!.rawEvents.length, 2);
@@ -641,7 +641,7 @@ suite('NotebookTextModel', () => {
}
];
- editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined);
+ editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
assert.strictEqual(notebook.cells[0].outputs.length, 1);
assert.strictEqual(notebook.cells[0].outputs[0].outputs.length, 2);
@@ -671,7 +671,7 @@ suite('NotebookTextModel', () => {
}
];
- editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined);
+ editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
assert.strictEqual(notebook.cells.length, 2);
assert.strictEqual(notebook.cells[0].outputs.length, 0);
@@ -707,7 +707,7 @@ suite('NotebookTextModel', () => {
}
];
- editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined);
+ editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
assert.strictEqual(notebook.cells.length, 2);
assert.strictEqual(notebook.cells[0].outputs.length, 1);
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
index ebc4e1f9cc6..becb17a1256 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts
@@ -91,13 +91,13 @@ suite('NotebookViewModel', () => {
const lastViewCell = viewModel.cellAt(viewModel.length - 1)!;
const insertIndex = viewModel.getCellIndex(firstViewCell) + 1;
- const cell = insertCellAtIndex(viewModel, insertIndex, 'var c = 3;', 'javascript', CellKind.Code, {}, [], true);
+ const cell = insertCellAtIndex(viewModel, insertIndex, 'var c = 3;', 'javascript', CellKind.Code, {}, [], true, true);
const addedCellIndex = viewModel.getCellIndex(cell);
runDeleteAction(editor, viewModel.cellAt(addedCellIndex)!);
const secondInsertIndex = viewModel.getCellIndex(lastViewCell) + 1;
- const cell2 = insertCellAtIndex(viewModel, secondInsertIndex, 'var d = 4;', 'javascript', CellKind.Code, {}, [], true);
+ const cell2 = insertCellAtIndex(viewModel, secondInsertIndex, 'var d = 4;', 'javascript', CellKind.Code, {}, [], true, true);
assert.strictEqual(viewModel.length, 3);
assert.strictEqual(viewModel.notebookDocument.cells.length, 3);
@@ -150,7 +150,7 @@ suite('NotebookViewModel Decorations', () => {
end: 2,
});
- insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true);
+ insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true, true);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 2,
@@ -164,7 +164,7 @@ suite('NotebookViewModel Decorations', () => {
end: 2
});
- insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true);
+ insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true, true);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 1,
@@ -207,14 +207,14 @@ suite('NotebookViewModel Decorations', () => {
end: 3
});
- insertCellAtIndex(viewModel, 5, 'var d = 9;', 'javascript', CellKind.Code, {}, [], true);
+ insertCellAtIndex(viewModel, 5, 'var d = 9;', 'javascript', CellKind.Code, {}, [], true, true);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 1,
end: 3
});
- insertCellAtIndex(viewModel, 4, 'var d = 10;', 'javascript', CellKind.Code, {}, [], true);
+ insertCellAtIndex(viewModel, 4, 'var d = 10;', 'javascript', CellKind.Code, {}, [], true, true);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 1,
diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
index fd0d94ee157..514c5231443 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
@@ -93,7 +93,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
return this._notebook.uri;
}
- get notebook() {
+ get notebook(): NotebookTextModel {
return this._notebook;
}
diff --git a/src/vs/workbench/contrib/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css
index f80049ed1dc..ea0ee3e8d6b 100644
--- a/src/vs/workbench/contrib/output/browser/media/output.css
+++ b/src/vs/workbench/contrib/output/browser/media/output.css
@@ -30,9 +30,7 @@
}
.monaco-workbench .part.sidebar > .title > .title-actions .switch-output > .monaco-select-box {
- border: none !important;
display: block !important;
- background-color: unset !important;
}
.monaco-pane-view .pane > .pane-header .monaco-action-bar .switch-output.action-item.select-container {
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
index b49b69c4acd..77942bbbf0a 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
@@ -152,7 +152,7 @@ export class SettingsEditor2 extends EditorPane {
private controlsElement!: HTMLElement;
private settingsTargetsWidget!: SettingsTargetsWidget;
- private splitView!: SplitView;
+ private splitView!: SplitView<number>;
private settingsTreeContainer!: HTMLElement;
private settingsTree!: SettingsTree;
@@ -712,9 +712,9 @@ export class SettingsEditor2 extends EditorPane {
element: this.tocTreeContainer,
minimumSize: SettingsEditor2.TOC_MIN_WIDTH,
maximumSize: Number.POSITIVE_INFINITY,
- layout: (width) => {
+ layout: (width, _, height) => {
this.tocTreeContainer.style.width = `${width}px`;
- this.tocTree.layout(undefined, width);
+ this.tocTree.layout(height, width);
}
}, startingWidth, undefined, true);
this.splitView.addView({
@@ -722,9 +722,9 @@ export class SettingsEditor2 extends EditorPane {
element: this.settingsTreeContainer,
minimumSize: SettingsEditor2.EDITOR_MIN_WIDTH,
maximumSize: Number.POSITIVE_INFINITY,
- layout: (width) => {
+ layout: (width, _, height) => {
this.settingsTreeContainer.style.width = `${width}px`;
- this.settingsTree.layout(undefined, width);
+ this.settingsTree.layout(height, width);
}
}, Sizing.Distribute, undefined, true);
this._register(this.splitView.onDidSashReset(() => {
@@ -1008,6 +1008,25 @@ export class SettingsEditor2 extends EditorPane {
}
private reportModifiedSetting(props: { key: string; query: string; searchResults: ISearchResult[] | null; rawResults: ISearchResult[] | null; showConfiguredOnly: boolean; isReset: boolean; settingsTarget: SettingsTarget }): void {
+ type SettingsEditorModifiedSettingEvent = {
+ key: string;
+ groupId: string | undefined;
+ nlpIndex: number | undefined;
+ displayIndex: number | undefined;
+ showConfiguredOnly: boolean;
+ isReset: boolean;
+ target: string;
+ };
+ type SettingsEditorModifiedSettingClassification = {
+ key: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'The setting that is being modified.' };
+ groupId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'Whether the setting is from the local search or remote search provider, if applicable.' };
+ nlpIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; comment: 'The index of the setting in the remote search provider results, if applicable.' };
+ displayIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; comment: 'The index of the setting in the combined search results, if applicable.' };
+ showConfiguredOnly: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; 'owner': 'rzhao271'; comment: 'Whether the user is in the modified view, which shows configured settings only.' };
+ isReset: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'Identifies whether a setting was reset to its default value.' };
+ target: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'The scope of the setting, such as user or workspace.' };
+ };
+
this.pendingSettingUpdate = null;
let groupId: string | undefined = undefined;
@@ -1050,18 +1069,7 @@ export class SettingsEditor2 extends EditorPane {
target: reportedTarget
};
- /* __GDPR__
- "settingsEditor.settingModified" : {
- "key" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "The setting that is being modified." },
- "groupId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "Whether the setting is from the local search or remote search provider, if applicable." },
- "nlpIndex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "The index of the setting in the remote search provider results, if applicable." },
- "displayIndex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "The index of the setting in the combined search results, if applicable." },
- "showConfiguredOnly" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "Whether the user is in the modified view, which shows configured settings only." },
- "isReset" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "Identifies whether a setting was reset to its default value." },
- "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "The scope of the setting, such as user or workspace." }
- }
- */
- this.telemetryService.publicLog('settingsEditor.settingModified', data);
+ this.telemetryService.publicLog2<SettingsEditorModifiedSettingEvent, SettingsEditorModifiedSettingClassification>('settingsEditor.settingModified', data);
}
private onSearchModeToggled(): void {
@@ -1271,7 +1279,7 @@ export class SettingsEditor2 extends EditorPane {
await this.triggerSearch(query.replace(/\u203A/g, ' '));
if (query && this.searchResultModel) {
- this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults()));
+ this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(this.searchResultModel!.getUniqueResults()));
}
}
@@ -1360,7 +1368,20 @@ export class SettingsEditor2 extends EditorPane {
return filterModel;
}
- private reportFilteringUsed(query: string, results: ISearchResult[]): void {
+ private reportFilteringUsed(results: ISearchResult[]): void {
+ type SettingsEditorFilterEvent = {
+ 'durations.nlpResult': number | undefined;
+ 'counts.nlpResult': number | undefined;
+ 'counts.filterResult': number | undefined;
+ 'requestCount': number | undefined;
+ };
+ type SettingsEditorFilterClassification = {
+ 'durations.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'How long the remote search provider took, if applicable.' };
+ 'counts.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of matches found by the remote search provider, if applicable.' };
+ 'counts.filterResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of matches found by the local search provider, if applicable.' };
+ 'requestCount': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of requests sent to Bing, if applicable.' };
+ };
+
const nlpResult = results[SearchResultIdx.Remote];
const nlpMetadata = nlpResult?.metadata;
@@ -1382,20 +1403,13 @@ export class SettingsEditor2 extends EditorPane {
const requestCount = nlpMetadata?.requestCount;
const data = {
- durations: duration,
- counts,
+ 'durations.nlpResult': duration.nlpResult,
+ 'counts.nlpResult': counts['nlpResult'],
+ 'counts.filterResult': counts['filterResult'],
requestCount
};
- /* __GDPR__
- "settingsEditor.filter" : {
- "durations.nlpResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "How long the remote search provider took, if applicable." },
- "counts.nlpResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "The number of matches found by the remote search provider, if applicable." },
- "counts.filterResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "The number of matches found by the local search provider, if applicable." },
- "requestCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "rzhao271", "comment": "The number of requests sent to Bing, if applicable." }
- }
- */
- this.telemetryService.publicLog('settingsEditor.filter', data);
+ this.telemetryService.publicLog2<SettingsEditorFilterEvent, SettingsEditorFilterClassification>('settingsEditor.filter', data);
}
private triggerFilterPreferences(query: string): Promise<void> {
@@ -1520,15 +1534,17 @@ export class SettingsEditor2 extends EditorPane {
if (isCancellationError(err)) {
return Promise.reject(err);
} else {
- /* __GDPR__
- "settingsEditor.searchError" : {
- "message": { "classification": "CallstackOrException", "purpose": "FeatureInsight", "owner": "rzhao271", "comment": "The error message of the search error." }
- }
- */
+ type SettingsSearchErrorEvent = {
+ 'message': string;
+ };
+ type SettingsSearchErrorClassification = {
+ 'message': { 'classification': 'CallstackOrException'; 'purpose': 'FeatureInsight'; 'owner': 'rzhao271'; 'comment': 'The error message of the search error.' };
+ };
+
const message = getErrorMessage(err).trim();
if (message && message !== 'Error') {
// "Error" = any generic network error
- this.telemetryService.publicLogError('settingsEditor.searchError', { message });
+ this.telemetryService.publicLogError2<SettingsSearchErrorEvent, SettingsSearchErrorClassification>('settingsEditor.searchError', { message });
this.logService.info('Setting search error: ' + message);
}
return null;
@@ -1545,7 +1561,7 @@ export class SettingsEditor2 extends EditorPane {
// space it has, otherwise setViewVisible results in the first panel
// showing up at the minimum size whenever the Settings editor
// opens for the first time.
- this.splitView.layout(this.bodyContainer.clientWidth);
+ this.splitView.layout(this.bodyContainer.clientWidth, listHeight);
const firstViewWasVisible = this.splitView.isViewVisible(0);
const firstViewVisible = this.bodyContainer.clientWidth >= SettingsEditor2.NARROW_TOTAL_WIDTH;
diff --git a/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css
index 243fc58a8a5..ef32cf43f39 100644
--- a/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css
+++ b/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css
@@ -36,25 +36,3 @@
.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie {
width: 0px !important;
}
-
-.monaco-workbench .part > .title > .title-actions .switch-remote {
- display: flex;
- align-items: center;
- font-size: 11px;
- margin-right: 0.3em;
- height: 20px;
- flex-shrink: 1;
-}
-
-.monaco-workbench.mac .part > .title > .title-actions .switch-remote {
- border-radius: 4px;
-}
-
-.switch-remote > .monaco-select-box {
- border: none;
- display: block;
-}
-
-.monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box {
- padding: 1px 22px 2px 6px;
-}
diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts
index 6982a033c25..63e7f2df0b4 100644
--- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts
+++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts
@@ -17,7 +17,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContri
workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteStatusIndicator, LifecyclePhase.Starting);
-workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Eventually);
+workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Restored);
workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteMarkers, LifecyclePhase.Eventually);
diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts
index e6b3c873d57..fae968c9cd6 100644
--- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts
+++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts
@@ -101,7 +101,8 @@ export class TunnelViewModel implements ITunnelViewModel {
id: TunnelPrivacyId.Private,
themeIcon: privatePortIcon.id,
label: nls.localize('tunnelPrivacy.private', "Private")
- }
+ },
+ strip: () => undefined
};
constructor(
@@ -415,6 +416,31 @@ class ActionBarRenderer extends Disposable implements ITableRenderer<ActionBarCe
}));
}
+ private tunnelContext(tunnel: ITunnelItem): ITunnelItem {
+ let context: ITunnelItem | undefined;
+ if (tunnel instanceof TunnelItem) {
+ context = tunnel.strip();
+ }
+ if (!context) {
+ context = {
+ tunnelType: tunnel.tunnelType,
+ remoteHost: tunnel.remoteHost,
+ remotePort: tunnel.remotePort,
+ localAddress: tunnel.localAddress,
+ protocol: tunnel.protocol,
+ localUri: tunnel.localUri,
+ localPort: tunnel.localPort,
+ name: tunnel.name,
+ closeable: tunnel.closeable,
+ source: tunnel.source,
+ privacy: tunnel.privacy,
+ processDescription: tunnel.processDescription,
+ label: tunnel.label
+ };
+ }
+ return context;
+ }
+
renderActionBarItem(element: ActionBarCell, templateData: IActionBarTemplateData): void {
templateData.label.element.style.display = 'flex';
templateData.label.setLabel(element.label, undefined,
@@ -424,7 +450,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer<ActionBarCe
: element.tooltip,
extraClasses: element.menuId === MenuId.TunnelLocalAddressInline ? ['ports-view-actionbar-cell-localaddress'] : undefined
});
- templateData.actionBar.context = element.tunnel;
+ templateData.actionBar.context = this.tunnelContext(element.tunnel);
templateData.container.style.paddingLeft = '10px';
const context: [string, any][] =
[
@@ -566,6 +592,29 @@ class TunnelItem implements ITunnelItem {
tunnelService);
}
+ /**
+ * Removes all non-serializable properties from the tunnel
+ * @returns A new TunnelItem without any services
+ */
+ public strip(): TunnelItem | undefined {
+ return new TunnelItem(
+ this.tunnelType,
+ this.remoteHost,
+ this.remotePort,
+ this.source,
+ this.hasRunningProcess,
+ this.protocol,
+ this.localUri,
+ this.localAddress,
+ this.localPort,
+ this.closeable,
+ this.name,
+ this.runningProcess,
+ this.pid,
+ this._privacy
+ );
+ }
+
constructor(
public tunnelType: TunnelType,
public remoteHost: string,
diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
index 055172e9e9b..600b2af4b21 100644
--- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
+++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
@@ -1371,7 +1371,8 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
private setViewState(state: IViewState): void {
this.viewState = state;
this.stylesheet.textContent = `
- .monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${state.width}px;}
+ .monaco-editor .dirty-diff-modified { background-size: ${state.width}px 4.5px; }
+ .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added{border-left-width:${state.width}px;}
.monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted {
opacity: ${state.visibility === 'always' ? 1 : 0};
}
@@ -1474,8 +1475,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
if (editorGutterModifiedBackgroundColor) {
collector.addRule(`
.monaco-editor .dirty-diff-modified {
- background-size: 3px 4.5px;
- background-repeat-x: no-repeat;
+ background-repeat: repeat-y;
background-image: linear-gradient(${linearGradient});
transition: opacity 0.5s;
}
diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts
index 330dab052b1..757c5526c7a 100644
--- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts
@@ -12,7 +12,7 @@ import { SCMMenus } from 'vs/workbench/contrib/scm/browser/menus';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { debounce } from 'vs/base/common/decorators';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
-import { compareFileNames } from 'vs/base/common/comparers';
+import { compareFileNames, comparePaths } from 'vs/base/common/comparers';
import { basename } from 'vs/base/common/resources';
import { binarySearch } from 'vs/base/common/arrays';
@@ -131,7 +131,12 @@ export class SCMViewService implements ISCMViewService {
const name1 = getRepositoryName(workspaceContextService, op1);
const name2 = getRepositoryName(workspaceContextService, op2);
- return compareFileNames(name1, name2);
+ const nameComparison = compareFileNames(name1, name2);
+ if (nameComparison === 0 && op1.provider.rootUri && op2.provider.rootUri) {
+ return comparePaths(op1.provider.rootUri.fsPath, op2.provider.rootUri.fsPath);
+ }
+
+ return nameComparison;
};
scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
@@ -275,9 +280,7 @@ export class SCMViewService implements ISCMViewService {
private insertRepository(repositories: ISCMRepository[], repository: ISCMRepository): void {
const index = binarySearch(repositories, repository, this._compareRepositories);
- if (index < 0) {
- repositories.splice(~index, 0, repository);
- }
+ repositories.splice(index < 0 ? ~index : index, 0, repository);
}
private onWillSaveState(): void {
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
index b073fc13e0d..aa6e96d9afe 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
@@ -83,7 +83,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider {
snippet: for (const snippet of snippets) {
- if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && !snippet.prefixLow.includes(triggerCharacterLow)) {
+ if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && !snippet.prefixLow.startsWith(triggerCharacterLow)) {
// strict -> when having trigger characters they must prefix-match
continue snippet;
}
diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
index 8f9616dbd3b..540e3fce155 100644
--- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
+++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
@@ -11,7 +11,6 @@ import { LanguageService } from 'vs/editor/common/services/languageService';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
-import { LanguageConfigurationRegistry } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { CompletionContext, CompletionItemLabel, CompletionItemRanges, CompletionTriggerKind } from 'vs/editor/common/languages';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
@@ -403,7 +402,8 @@ suite('SnippetsService', function () {
});
test('issue #61296: VS code freezes when editing CSS file with emoji', async function () {
- disposableStore.add(LanguageConfigurationRegistry.register('fooLang'!, {
+ const languageConfigurationService = new TestLanguageConfigurationService();
+ disposableStore.add(languageConfigurationService.register('fooLang', {
wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g
}));
@@ -417,7 +417,7 @@ suite('SnippetsService', function () {
SnippetSource.User
)]);
- const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
+ const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
let model = disposables.add(createTextModel('.🐷-a-b', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!;
@@ -548,7 +548,8 @@ suite('SnippetsService', function () {
});
test('Snippet will replace auto-closing pair if specified in prefix', async function () {
- disposableStore.add(LanguageConfigurationRegistry.register('fooLang'!, {
+ const languageConfigurationService = new TestLanguageConfigurationService();
+ disposableStore.add(languageConfigurationService.register('fooLang', {
brackets: [
['{', '}'],
['[', ']'],
@@ -566,7 +567,7 @@ suite('SnippetsService', function () {
SnippetSource.User
)]);
- const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
+ const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
let model = createTextModel('[psc]', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 5), context)!;
@@ -728,7 +729,7 @@ suite('SnippetsService', function () {
model.dispose();
});
- test('Snippets disappear with . key #145960', async function () {
+ test.skip('Snippets disappear with . key #145960', async function () {
snippetService = new SimpleSnippetService([
new Snippet(['fooLang'], 'div', 'div', '', 'div', '', SnippetSource.User),
new Snippet(['fooLang'], 'div.', 'div.', '', 'div.', '', SnippetSource.User),
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
index 22598d4aba7..043a8472b5e 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
@@ -148,7 +148,8 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
if (result) {
const { uri, isDirectory } = result;
const linkToOpen = {
- text: matchLink,
+ // Use the absolute URI's path here so the optional line/col get detected
+ text: result.uri.fsPath + (matchLink.match(/:\d+(:\d+)?$/)?.[0] || ''),
uri,
bufferRange: link.bufferRange,
type: link.type
@@ -194,7 +195,7 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
}
interface IResourceMatch {
- uri: URI | undefined;
+ uri: URI;
isDirectory?: boolean;
}
@@ -209,7 +210,9 @@ export class TerminalUrlLinkOpener implements ITerminalLinkOpener {
if (!link.uri) {
throw new Error('Tried to open a url without a resolved URI');
}
- this._openerService.open(link.uri || URI.parse(link.text), {
+ // It's important to use the raw string value here to avoid converting pre-encoded values
+ // from the URL like `%2B` -> `+`.
+ this._openerService.open(link.text, {
allowTunneling: this._isRemote,
allowContributedOpeners: true,
});
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalShellIntegrationLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalShellIntegrationLinkDetector.ts
index 15993914e93..c43e00e3f62 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalShellIntegrationLinkDetector.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalShellIntegrationLinkDetector.ts
@@ -51,6 +51,9 @@ export class TerminalShellIntegrationLinkDetector implements ITerminalLinkDetect
}
private _matches(lines: IBufferLine[]): boolean {
+ if (lines.length < linkCodes.length) {
+ return false;
+ }
let cell: IBufferCell | undefined;
for (let i = 0; i < linkCodes.length; i++) {
cell = lines[Math.floor(i / this.xterm.cols)].getCell(i % this.xterm.cols, cell);
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
index 2c6dcf55773..5b7471bb284 100755
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
@@ -27,7 +27,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then
fi
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
- echo -e "\033[1;33mShell integration was disabled by the shell\033[0m"
return
fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css
index af4b8524493..02867a99753 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css
+++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css
@@ -81,12 +81,26 @@
z-index: 31;
}
-.monaco-workbench .simple-find-part-wrapper {
+.xterm .xterm-screen {
+ cursor: text;
+}
+
+.monaco-workbench .simple-find-part-wrapper.result-count {
z-index: 33 !important;
+ padding-right: 80px;
}
-.xterm .xterm-screen {
- cursor: text;
+.result-count .simple-find-part {
+ width: 280px;
+ max-width: 280px;
+ min-width: 280px;
+}
+
+.result-count .matchesCount {
+ width: 68px;
+ max-width: 68px;
+ min-width: 68px;
+ padding-left: 5px;
}
.xterm .xterm-accessibility {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
index 27ec4f11389..f8aae95de73 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
@@ -9,12 +9,14 @@ import 'vs/css!./media/terminal';
import 'vs/css!./media/widgets';
import 'vs/css!./media/xterm';
import * as nls from 'vs/nls';
+import { URI } from 'vs/base/common/uri';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight, KeybindingsRegistry, IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views';
+import { Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry, IDraggedResourceEditorInput } from 'vs/workbench/browser/dnd';
import { registerTerminalActions, terminalSendSequenceCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
import { TERMINAL_VIEW_ID, TerminalCommandId, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -22,7 +24,7 @@ import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalCol
import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands';
import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService, ITerminalService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService, ITerminalService, TerminalDataTransfers, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
@@ -49,6 +51,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { RemoteTerminalBackendContribution } from 'vs/workbench/contrib/terminal/browser/remoteTerminalBackend';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { TerminalMainContribution } from 'vs/workbench/contrib/terminal/browser/terminalMainContribution';
+import { Schemas } from 'vs/base/common/network';
// Register services
registerSingleton(ITerminalService, TerminalService, true);
@@ -81,6 +84,7 @@ workbenchRegistry.registerWorkbenchContribution(RemoteTerminalBackendContributio
registerTerminalPlatformConfiguration();
registerTerminalConfiguration();
+// Register editor/dnd contributions
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(TerminalEditorInput.ID, TerminalInputSerializer);
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(
@@ -92,6 +96,27 @@ Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane
new SyncDescriptor(TerminalEditorInput)
]
);
+Registry.as<IDragAndDropContributionRegistry>(DragAndDropExtensions.DragAndDropContribution).register({
+ dataFormatKey: TerminalDataTransfers.Terminals,
+ getEditorInputs(data) {
+ const editors: IDraggedResourceEditorInput[] = [];
+ try {
+ const terminalEditors: string[] = JSON.parse(data);
+ for (const terminalEditor of terminalEditors) {
+ editors.push({ resource: URI.parse(terminalEditor) });
+ }
+ } catch (error) {
+ // Invalid transfer
+ }
+ return editors;
+ },
+ setData(resources, event) {
+ const terminalResources = resources.filter(({ resource }) => resource.scheme === Schemas.vscodeTerminal);
+ if (terminalResources.length) {
+ event.dataTransfer?.setData(TerminalDataTransfers.Terminals, JSON.stringify(terminalResources.map(({ resource }) => resource.toString())));
+ }
+ }
+});
// Register views
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index 37ac145ece8..c48979fa4d6 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -15,7 +15,7 @@ import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { IEditableData } from 'vs/workbench/common/views';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
-import { ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
+import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -146,6 +146,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
onDidInputInstanceData: Event<ITerminalInstance>;
onDidRegisterProcessSupport: Event<void>;
onDidChangeConnectionState: Event<void>;
+ onDidRequestHideFindWidget: Event<void>;
/**
* Creates a terminal.
@@ -554,6 +555,8 @@ export interface ITerminalInstance {
*/
onExit: Event<number | ITerminalLaunchError | undefined>;
+ onDidChangeFindResults: Event<{ resultIndex: number; resultCount: number } | undefined>;
+
readonly exitCode: number | undefined;
readonly areLinksReady: boolean;
@@ -649,7 +652,7 @@ export interface ITerminalInstance {
/**
* Copies the terminal selection to the clipboard.
*/
- copySelection(asHtml?: boolean): Promise<void>;
+ copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise<void>;
/**
* Current selection in the terminal.
@@ -864,6 +867,8 @@ export interface IXtermTerminal {
*/
target?: TerminalLocation;
+ findResult?: { resultIndex: number; resultCount: number };
+
/**
* Find the next instance of the term
*/
@@ -918,3 +923,7 @@ export const enum LinuxDistro {
Fedora = 2,
Ubuntu = 3,
}
+
+export const enum TerminalDataTransfers {
+ Terminals = 'Terminals'
+}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts
index 9fa3c648490..71eeff2a7ac 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts
@@ -75,6 +75,7 @@ export class TerminalEditor extends EditorPane {
this._findWidget = instantiationService.createInstance(TerminalFindWidget, this._findState);
this._dropdownMenu = this._register(menuService.createMenu(MenuId.TerminalNewDropdownContext, _contextKeyService));
this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalEditorInstanceContext, _contextKeyService));
+ this._register(this._terminalService.onDidRequestHideFindWidget(() => this.hideFindWidget()));
}
override async setInput(newInput: TerminalEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) {
@@ -91,6 +92,7 @@ export class TerminalEditor extends EditorPane {
// panel and the editors, this is needed so that the active instance gets set
// when focus changes between them.
this._register(this._editorInput.terminalInstance.onDidFocus(() => this._setActiveInstance()));
+ this._register(this._editorInput.terminalInstance.onDidChangeFindResults(() => this._findWidget.updateResultCount()));
this._editorInput.setCopyLaunchConfig(this._editorInput.terminalInstance.shellLaunchConfig);
}
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
index e4cda779a2b..6168afff93b 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
@@ -7,8 +7,9 @@ import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/s
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
-import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
+import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
export class TerminalFindWidget extends SimpleFindWidget {
protected _findInputFocused: IContextKey<boolean>;
@@ -19,9 +20,11 @@ export class TerminalFindWidget extends SimpleFindWidget {
findState: FindReplaceState,
@IContextViewService _contextViewService: IContextViewService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
- @ITerminalService private readonly _terminalService: ITerminalService
+ @ITerminalService private readonly _terminalService: ITerminalService,
+ @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService
) {
- super(_contextViewService, _contextKeyService, findState, true);
+ super(_contextViewService, _contextKeyService, findState, { showOptionButtons: true, showResultCount: true });
+
this._register(findState.onFindReplaceStateChange(() => {
this.show();
}));
@@ -68,7 +71,24 @@ export class TerminalFindWidget extends SimpleFindWidget {
if (instance) {
instance.focus();
}
- instance?.xterm?.clearSearchDecorations();
+ // Terminals in a group currently share a find widget, so hide
+ // all decorations for terminals in this group
+ const activeGroup = this._terminalGroupService.activeGroup;
+ if (instance?.target !== TerminalLocation.Editor && activeGroup) {
+ for (const terminal of activeGroup.terminalInstances) {
+ terminal.xterm?.clearSearchDecorations();
+ }
+ } else {
+ instance?.xterm?.clearSearchDecorations();
+ }
+ }
+
+ protected async _getResultCount(): Promise<{ resultIndex: number; resultCount: number } | undefined> {
+ const instance = this._terminalService.activeInstance;
+ if (instance) {
+ return instance.xterm?.findResult;
+ }
+ return undefined;
}
protected _onInputChanged() {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 4ffe4542f7c..18be1c0ae4a 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -52,7 +52,7 @@ import { CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd';
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick';
-import { IRequestAddInstanceToGroupEvent, ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { IRequestAddInstanceToGroupEvent, ITerminalExternalLinkProvider, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
@@ -329,6 +329,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event;
+ private readonly _onDidChangeFindResults = new Emitter<{ resultIndex: number; resultCount: number } | undefined>();
+ readonly onDidChangeFindResults = this._onDidChangeFindResults.event;
constructor(
private readonly _terminalFocusContextKey: IContextKey<boolean>,
@@ -444,10 +446,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xtermReadyPromise.then(async () => {
// Wait for a period to allow a container to be ready
await this._containerReadyBarrier.wait();
- if (this._configHelper.config.shellIntegration?.enabled && !this.shellLaunchConfig.executable) {
+
+ // Resolve the executable ahead of time if shell integration is enabled, this should not
+ // be done for custom PTYs as that would cause extension Pseudoterminal-based terminals
+ // to hang in resolver extensions
+ if (!this.shellLaunchConfig.customPtyImplementation && this._configHelper.config.shellIntegration?.enabled && !this.shellLaunchConfig.executable) {
const os = await this._processManager.getBackendOS();
this.shellLaunchConfig.executable = (await this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority: this.remoteAuthority, os })).path;
}
+
await this._createProcess();
// Re-establish the title after reconnect
@@ -647,7 +654,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const lineDataEventAddon = new LineDataEventAddon();
this.xterm.raw.loadAddon(lineDataEventAddon);
this.updateAccessibilitySupport();
- this.xterm.onDidRequestRunCommand(command => this.sendText(command, true));
+ this.xterm.onDidRequestRunCommand(e => {
+ if (e.copyAsHtml) {
+ this.copySelection(true, e.command);
+ } else {
+ this.sendText(e.command.command, true);
+ }
+ });
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
// onLineData events containing initialText
if (this._shellLaunchConfig.initialText) {
@@ -975,6 +988,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const screenElement = xterm.attachToElement(xtermElement);
+ xterm.onDidChangeFindResults((results) => this._onDidChangeFindResults.fire(results));
+
if (!xterm.raw.element || !xterm.raw.textarea) {
throw new Error('xterm elements not set after open');
}
@@ -1136,13 +1151,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return this.xterm ? this.xterm.raw.hasSelection() : false;
}
- async copySelection(asHtml?: boolean): Promise<void> {
+ async copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise<void> {
const xterm = await this._xtermReadyPromise;
- if (this.hasSelection()) {
+ if (this.hasSelection() || (asHtml && command)) {
if (asHtml) {
- const selectionAsHtml = await xterm.getSelectionAsHtml();
+ const textAsHtml = await xterm.getSelectionAsHtml(command);
function listener(e: any) {
- e.clipboardData.setData('text/html', selectionAsHtml);
+ e.clipboardData.setData('text/html', textAsHtml);
e.preventDefault();
}
document.addEventListener('copy', listener);
@@ -1737,7 +1752,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
@debounce(2000)
private async _updateProcessCwd(): Promise<void> {
- if (this._isDisposed) {
+ if (this._isDisposed || this.shellLaunchConfig.customPtyImplementation) {
return;
}
// reset cwd if it has changed, so file based url paths can be resolved
@@ -2282,7 +2297,7 @@ class TerminalInstanceDragAndDropController extends Disposable implements dom.ID
}
onDragEnter(e: DragEvent) {
- if (!containsDragType(e, DataTransfers.FILES, DataTransfers.RESOURCES, DataTransfers.TERMINALS, CodeDataTransfers.FILES)) {
+ if (!containsDragType(e, DataTransfers.FILES, DataTransfers.RESOURCES, TerminalDataTransfers.Terminals, CodeDataTransfers.FILES)) {
return;
}
@@ -2292,7 +2307,7 @@ class TerminalInstanceDragAndDropController extends Disposable implements dom.ID
}
// Dragging terminals
- if (containsDragType(e, DataTransfers.TERMINALS)) {
+ if (containsDragType(e, TerminalDataTransfers.Terminals)) {
const side = this._getDropSide(e);
this._dropOverlay.classList.toggle('drop-before', side === 'before');
this._dropOverlay.classList.toggle('drop-after', side === 'after');
@@ -2316,7 +2331,7 @@ class TerminalInstanceDragAndDropController extends Disposable implements dom.ID
}
// Dragging terminals
- if (containsDragType(e, DataTransfers.TERMINALS)) {
+ if (containsDragType(e, TerminalDataTransfers.Terminals)) {
const side = this._getDropSide(e);
this._dropOverlay.classList.toggle('drop-before', side === 'before');
this._dropOverlay.classList.toggle('drop-after', side === 'after');
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
index 873f4e0ea91..0fe61abdc4d 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
@@ -95,6 +95,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
if (highlights) {
return {
label,
+ description: terminal.description,
highlights: { label: highlights },
buttons: [
{
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 31995a09e77..8402e0e5362 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -141,6 +141,8 @@ export class TerminalService implements ITerminalService {
get onDidRegisterProcessSupport(): Event<void> { return this._onDidRegisterProcessSupport.event; }
private readonly _onDidChangeConnectionState = new Emitter<void>();
get onDidChangeConnectionState(): Event<void> { return this._onDidChangeConnectionState.event; }
+ private readonly _onDidRequestHideFindWidget = new Emitter<void>();
+ get onDidRequestHideFindWidget(): Event<void> { return this._onDidRequestHideFindWidget.event; }
constructor(
@IContextKeyService private _contextKeyService: IContextKeyService,
@@ -720,6 +722,7 @@ export class TerminalService implements ITerminalService {
}
sourceGroup.removeInstance(source);
this._terminalEditorService.openEditor(source);
+ this._onDidRequestHideFindWidget.fire();
}
async moveToTerminalView(source?: ITerminalInstance, target?: ITerminalInstance, side?: 'before' | 'after'): Promise<void> {
@@ -766,6 +769,7 @@ export class TerminalService implements ITerminalService {
this._onDidChangeInstances.fire();
this._onDidChangeActiveGroup.fire(this._terminalGroupService.activeGroup);
this._terminalGroupService.showPanel(true);
+ this._onDidRequestHideFindWidget.fire();
}
protected _initInstanceListeners(instance: ITerminalInstance): void {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts
index c20833c2e64..eec5a502976 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts
@@ -133,6 +133,7 @@ export class TerminalTabbedView extends Disposable {
this._register(this._terminalGroupService.onDidChangeInstances(() => this._refreshShowTabs()));
this._register(this._terminalGroupService.onDidChangeGroups(() => this._refreshShowTabs()));
this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(theme)));
+ this._register(this._terminalService.onDidRequestHideFindWidget(() => this.hideFindWidget()));
this._updateTheme();
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(CssClass.FindFocus));
@@ -150,7 +151,11 @@ export class TerminalTabbedView extends Disposable {
});
this._splitView = new SplitView(parentElement, { orientation: Orientation.HORIZONTAL, proportionalLayout: false });
-
+ this._terminalService.onDidCreateInstance(instance => {
+ instance.onDidChangeFindResults(() => {
+ this._findWidget.updateResultCount();
+ });
+ });
this._setupSplitView(terminalOuterContainer);
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
index b7a57b05460..5cf17115844 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
@@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -585,13 +585,13 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
// Attach terminals type to event
const terminals: ITerminalInstance[] = dndData.filter(e => 'instanceId' in (e as any));
if (terminals.length > 0) {
- originalEvent.dataTransfer.setData(DataTransfers.TERMINALS, JSON.stringify(terminals.map(e => e.resource.toString())));
+ originalEvent.dataTransfer.setData(TerminalDataTransfers.Terminals, JSON.stringify(terminals.map(e => e.resource.toString())));
}
}
onDragOver(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction {
if (data instanceof NativeDragAndDropData) {
- if (!containsDragType(originalEvent, DataTransfers.FILES, DataTransfers.RESOURCES, DataTransfers.TERMINALS, CodeDataTransfers.FILES)) {
+ if (!containsDragType(originalEvent, DataTransfers.FILES, DataTransfers.RESOURCES, TerminalDataTransfers.Terminals, CodeDataTransfers.FILES)) {
return false;
}
}
@@ -602,7 +602,7 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
this._autoFocusInstance = targetInstance;
}
- if (!targetInstance && !containsDragType(originalEvent, DataTransfers.TERMINALS)) {
+ if (!targetInstance && !containsDragType(originalEvent, TerminalDataTransfers.Terminals)) {
return data instanceof ElementsDragAndDropData;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts
index eb44e80d2f1..9a04a07b3c2 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts
@@ -3,10 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { DataTransfers } from 'vs/base/browser/dnd';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
-import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
export function parseTerminalUri(resource: URI): ITerminalIdentifier {
const [, workspaceId, instanceId] = resource.path.split('/');
@@ -34,7 +33,7 @@ export interface IPartialDragEvent {
}
export function getTerminalResourcesFromDragEvent(event: IPartialDragEvent): URI[] | undefined {
- const resources = event.dataTransfer?.getData(DataTransfers.TERMINALS);
+ const resources = event.dataTransfer?.getData(TerminalDataTransfers.Terminals);
if (resources) {
const json = JSON.parse(resources);
const result = [];
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
index 03f433bd72b..03cb3ead663 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
@@ -14,7 +14,7 @@ import { IThemeService, IColorTheme, registerThemingParticipant, ICssStyleCollec
import { switchTerminalActionViewItemSeparator, switchTerminalShowTabsTitle } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR, TERMINAL_DRAG_AND_DROP_BACKGROUND, TERMINAL_TAB_ACTIVE_BORDER } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
-import { ICreateTerminalOptions, ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ICreateTerminalOptions, ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalConnectionState, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -42,7 +42,6 @@ import { ColorScheme } from 'vs/platform/theme/common/theme';
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { withNullAsUndefined } from 'vs/base/common/types';
-import { DataTransfers } from 'vs/base/browser/dnd';
import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip';
@@ -449,7 +448,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
this._elementDisposables.push(dom.addDisposableListener(this.element, dom.EventType.DRAG_START, e => {
const instance = this._terminalGroupService.activeInstance;
if (e.dataTransfer && instance) {
- e.dataTransfer.setData(DataTransfers.TERMINALS, JSON.stringify([instance.resource.toString()]));
+ e.dataTransfer.setData(TerminalDataTransfers.Terminals, JSON.stringify([instance.resource.toString()]));
}
}));
}
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
index 678d1c5164a..40b75ff1352 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
@@ -49,7 +49,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
private _decorations: Map<number, IDisposableDecoration> = new Map();
private _placeholderDecoration: IDecoration | undefined;
- private readonly _onDidRequestRunCommand = this._register(new Emitter<string>());
+ private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>());
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
constructor(
@@ -313,10 +313,14 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
class: 'copy-output', tooltip: 'Copy Output', dispose: () => { }, id: 'terminal.copyOutput', label: localize("terminal.copyOutput", 'Copy Output'), enabled: true,
run: () => this._clipboardService.writeText(command.getOutput()!)
});
+ actions.push({
+ class: 'copy-output', tooltip: 'Copy Output as HTML', dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'), enabled: true,
+ run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true })
+ });
}
actions.push({
class: 'rerun-command', tooltip: 'Rerun Command', dispose: () => { }, id: 'terminal.rerunCommand', label: localize("terminal.rerunCommand", 'Rerun Command'), enabled: true,
- run: () => this._onDidRequestRunCommand.fire(command.command)
+ run: () => this._onDidRequestRunCommand.fire({ command })
});
return actions;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts
index 97171bea09d..30b72f90a8e 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts
@@ -32,7 +32,7 @@ import { Color } from 'vs/base/common/color';
import { ShellIntegrationAddon } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DecorationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/decorationAddon';
-import { ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
+import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
import { Emitter } from 'vs/base/common/event';
// How long in milliseconds should an average frame take to render for a notification to appear
@@ -68,9 +68,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
private _webglAddon?: WebglAddonType;
private _serializeAddon?: SerializeAddonType;
- private readonly _onDidRequestRunCommand = new Emitter<string>();
+ private _lastFindResult: { resultIndex: number; resultCount: number } | undefined;
+ get findResult(): { resultIndex: number; resultCount: number } | undefined { return this._lastFindResult; }
+
+ private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>();
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
+ private readonly _onDidChangeFindResults = new Emitter<{ resultIndex: number; resultCount: number } | undefined>();
+ readonly onDidChangeFindResults = this._onDidChangeFindResults.event;
+
get commandTracker(): ICommandTracker { return this._commandNavigationAddon; }
get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; }
@@ -169,17 +175,29 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
}
private _createDecorationAddon(): void {
this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, this._capabilities);
- this._decorationAddon.onDidRequestRunCommand(command => this._onDidRequestRunCommand.fire(command));
+ this._decorationAddon.onDidRequestRunCommand(e => this._onDidRequestRunCommand.fire(e));
this.raw.loadAddon(this._decorationAddon);
}
- async getSelectionAsHtml(): Promise<string> {
+ async getSelectionAsHtml(command?: ITerminalCommand): Promise<string> {
if (!this._serializeAddon) {
const Addon = await this._getSerializeAddonConstructor();
this._serializeAddon = new Addon();
this.raw.loadAddon(this._serializeAddon);
}
- return this._serializeAddon.serializeAsHTML({ onlySelection: true });
+ if (command) {
+ const length = command.getOutput()?.length;
+ const row = command.marker?.line;
+ if (!length || !row) {
+ throw new Error(`No row ${row} or output length ${length} for command ${command}`);
+ }
+ await this.raw.select(0, row + 1, length - Math.floor(length / this.raw.cols));
+ }
+ const result = this._serializeAddon.serializeAsHTML({ onlySelection: true });
+ if (command) {
+ this.raw.clearSelection();
+ }
+ return result;
}
attachToElement(container: HTMLElement): HTMLElement {
@@ -292,6 +310,10 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
const AddonCtor = await this._getSearchAddonConstructor();
this._searchAddon = new AddonCtor();
this.raw.loadAddon(this._searchAddon);
+ this._searchAddon.onDidChangeResults((results: { resultIndex: number; resultCount: number } | undefined) => {
+ this._lastFindResult = results;
+ this._onDidChangeFindResults.fire(results);
+ });
return this._searchAddon;
}
diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts
index 2422169e9b5..1d3a1aa85db 100644
--- a/src/vs/workbench/contrib/terminal/common/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminal.ts
@@ -163,7 +163,7 @@ class TerminalBackendRegistry implements ITerminalBackendRegistry {
private readonly _backends = new Map<string, ITerminalBackend>();
registerTerminalBackend(backend: ITerminalBackend): void {
- const key = backend.remoteAuthority ?? '';
+ const key = this._sanitizeRemoteAuthority(backend.remoteAuthority);
if (this._backends.has(key)) {
throw new Error(`A terminal backend with remote authority '${key}' was already registered.`);
}
@@ -171,7 +171,12 @@ class TerminalBackendRegistry implements ITerminalBackendRegistry {
}
getTerminalBackend(remoteAuthority: string | undefined): ITerminalBackend | undefined {
- return this._backends.get(remoteAuthority ?? '');
+ return this._backends.get(this._sanitizeRemoteAuthority(remoteAuthority));
+ }
+
+ private _sanitizeRemoteAuthority(remoteAuthority: string | undefined) {
+ // Normalize the key to lowercase as the authority is case-insensitive
+ return remoteAuthority?.toLowerCase() ?? '';
}
}
Registry.add(TerminalExtensions.Backend, new TerminalBackendRegistry());
diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts
index 55ffbaefd99..59ba5731ce5 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts
@@ -27,7 +27,7 @@ export const TERMINAL_SELECTION_BACKGROUND_COLOR = registerColor('terminal.selec
light: '#00000040',
dark: '#FFFFFF40',
hcDark: '#FFFFFF80',
- hcLight: '#F2F2F2'
+ hcLight: '#0F4A85'
}, nls.localize('terminal.selectionBackground', 'The selection background color of the terminal.'));
export const TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.defaultBackground', {
light: '#00000040',
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts
index 04324bfe4be..aabd2add651 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts
@@ -14,7 +14,7 @@ import { ByLocationTestItemElement } from 'vs/workbench/contrib/testing/browser/
import { IActionableTestTreeElement, ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
import { NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { IComputedStateAndDurationAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState';
-import { InternalTestItem, TestDiffOpType, TestItemExpandState, TestResultState, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem, TestDiffOpType, TestItemExpandState, TestResultState, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
@@ -105,7 +105,6 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
// children and should trust whatever the result service gives us.
const explicitComputed = item.children.size ? undefined : result.computedState;
- item.retired = result.retired;
item.ownState = result.ownComputedState;
item.ownDuration = result.ownDuration;
@@ -271,7 +270,6 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
const prevState = this.results.getStateById(treeElement.test.item.extId)?.[1];
if (prevState) {
- treeElement.retired = prevState.retired;
treeElement.ownState = prevState.computedState;
treeElement.ownDuration = prevState.ownDuration;
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts
index e7026fbfac6..cd34647d6d0 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts
@@ -9,7 +9,7 @@ import { flatTestItemDelimiter } from 'vs/workbench/contrib/testing/browser/expl
import { HierarchicalByLocationProjection as HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
import { ByLocationTestItemElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes';
import { NodeRenderDirective } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
-import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts
index 3634813c32b..48e0b19c426 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
-import { applyTestItemUpdate, InternalTestItem, ITestItemUpdate } from 'vs/workbench/contrib/testing/common/testCollection';
+import { applyTestItemUpdate, InternalTestItem, ITestItemUpdate } from 'vs/workbench/contrib/testing/common/testTypes';
/**
* Test tree element element that groups be hierarchy.
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts
index 93868e0df2c..b4fa7d0aad1 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts
@@ -10,7 +10,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Iterable } from 'vs/base/common/iterator';
import { IDisposable } from 'vs/base/common/lifecycle';
import { MarshalledId } from 'vs/base/common/marshallingIds';
-import { InternalTestItem, ITestItemContext, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem, ITestItemContext, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
/**
* Describes a rendering of tests in the explorer view. Different
@@ -120,11 +120,6 @@ export class TestItemTreeElement implements IActionableTestTreeElement {
}
/**
- * Whether the node's test result is 'retired' -- from an outdated test run.
- */
- public retired = false;
-
- /**
* @inheritdoc
*/
public state = TestResultState.Unset;
@@ -163,11 +158,11 @@ export class TestItemTreeElement implements IActionableTestTreeElement {
const context: ITestItemContext = {
$mid: MarshalledId.TestItemContext,
- tests: [this.test],
+ tests: [InternalTestItem.serialize(this.test)],
};
for (let p = this.parent; p && p.depth > 0; p = p.parent) {
- context.tests.unshift(p.test);
+ context.tests.unshift(InternalTestItem.serialize(p.test));
}
return context;
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay.ts
index 014af9cc1d2..0a1af60895c 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testTypes';
import { capabilityContextKeys } from 'vs/workbench/contrib/testing/common/testProfileService';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts
index c469bd8c448..d4a2e945add 100644
--- a/src/vs/workbench/contrib/testing/browser/icons.ts
+++ b/src/vs/workbench/contrib/testing/browser/icons.ts
@@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
import { registerIcon, spinningLoading } from 'vs/platform/theme/common/iconRegistry';
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { testingColorRunAction, testStatesToIconColors } from 'vs/workbench/contrib/testing/browser/theme';
-import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
export const testingRunIcon = registerIcon('testing-run-icon', Codicon.run, localize('testingRunIcon', 'Icon of the "run test" action.'));
@@ -18,7 +18,6 @@ export const testingDebugAllIcon = registerIcon('testing-debug-all-icon', Codico
export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAltSmall, localize('testingDebugIcon', 'Icon of the "debug test" action.'));
export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.debugStop, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.'));
export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.'));
-export const testingAutorunIcon = registerIcon('testing-autorun', Codicon.debugRerun, localize('autoRunIcon', 'Icon for the \'Autorun\' toggle in the testing view.'));
export const testingHiddenIcon = registerIcon('testing-hidden', Codicon.eyeClosed, localize('hiddenIcon', 'Icon shown beside hidden tests, when they\'ve been shown.'));
export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codicon.listTree, localize('testingShowAsList', 'Icon shown when the test explorer is disabled as a tree.'));
diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css
index 7758973aa12..7400d289061 100644
--- a/src/vs/workbench/contrib/testing/browser/media/testing.css
+++ b/src/vs/workbench/contrib/testing/browser/media/testing.css
@@ -65,11 +65,6 @@
margin-right: 0.25em;
}
-.test-explorer .computed-state.retired,
-.testing-run-glyph.retired {
- opacity: 0.7 !important;
-}
-
.test-explorer .test-is-hidden {
opacity: 0.8;
}
diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
index 8230419e933..9382f47bc67 100644
--- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
+++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
@@ -30,8 +30,7 @@ import * as icons from 'vs/workbench/contrib/testing/browser/icons';
import type { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService';
import { TestCommandId, TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants';
-import { InternalTestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
-import { ITestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun';
+import { InternalTestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
@@ -650,47 +649,6 @@ export class GoToTest extends Action2 {
}
}
-abstract class ToggleAutoRun extends Action2 {
-
- constructor(title: string, whenToggleIs: boolean) {
- super({
- id: TestCommandId.ToggleAutoRun,
- title,
- icon: icons.testingAutorunIcon,
- toggled: whenToggleIs === true ? ContextKeyExpr.true() : ContextKeyExpr.false(),
- menu: {
- id: MenuId.ViewTitle,
- order: ActionOrder.AutoRun,
- group: 'navigation',
- when: ContextKeyExpr.and(
- ContextKeyExpr.equals('view', Testing.ExplorerViewId),
- TestingContextKeys.autoRun.isEqualTo(whenToggleIs)
- )
- }
- });
- }
-
- /**
- * @override
- */
- public run(accessor: ServicesAccessor) {
- accessor.get(ITestingAutoRun).toggle();
- }
-}
-
-export class AutoRunOnAction extends ToggleAutoRun {
- constructor() {
- super(localize('testing.turnOnAutoRun', "Turn On Auto Run"), false);
- }
-}
-
-export class AutoRunOffAction extends ToggleAutoRun {
- constructor() {
- super(localize('testing.turnOffAutoRun', "Turn Off Auto Run"), true);
- }
-}
-
-
abstract class ExecuteTestAtCursor extends Action2 {
constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) {
super({
diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
index d50f2b5279a..3605c75ec80 100644
--- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
+++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
@@ -28,10 +28,9 @@ import { ITestingProgressUiService, TestingProgressTrigger, TestingProgressUiSer
import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer';
import { testingConfiguation } from 'vs/workbench/contrib/testing/common/configuration';
import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants';
-import { ITestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ITestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState, TestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
-import { ITestingAutoRun, TestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun';
import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingDecorationsService } from 'vs/workbench/contrib/testing/common/testingDecorations';
@@ -50,7 +49,6 @@ registerSingleton(ITestResultStorage, TestResultStorage, true);
registerSingleton(ITestProfileService, TestProfileService, true);
registerSingleton(ITestResultService, TestResultService, true);
registerSingleton(ITestExplorerFilterState, TestExplorerFilterState, true);
-registerSingleton(ITestingAutoRun, TestingAutoRun, true);
registerSingleton(ITestingOutputTerminalService, TestingOutputTerminalService, true);
registerSingleton(ITestingPeekOpener, TestingPeekOpener, true);
registerSingleton(ITestingProgressUiService, TestingProgressUiService, true);
diff --git a/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts b/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts
index 809868c283b..2427460910f 100644
--- a/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts
@@ -12,7 +12,7 @@ import { QuickPickInput, IQuickPickItem, IQuickInputService, IQuickPickItemButto
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { testingUpdateProfiles } from 'vs/workbench/contrib/testing/browser/icons';
import { testConfigurationGroupNames } from 'vs/workbench/contrib/testing/common/constants';
-import { InternalTestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { canUseProfileWithTest, ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
interface IConfigurationPickerOptions {
diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts
index 546db017969..5bdc3ee229a 100644
--- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts
@@ -40,7 +40,7 @@ import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/work
import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme';
import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { labelForTestInState, Testing } from 'vs/workbench/contrib/testing/common/constants';
-import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestDiffOpType, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestDiffOpType, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestDecoration as IPublicTestDecoration, ITestingDecorationsService, TestDecorations } from 'vs/workbench/contrib/testing/common/testingDecorations';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
@@ -439,7 +439,6 @@ const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[]
let computedState = TestResultState.Unset;
let hoverMessageParts: string[] = [];
let testIdWithMessages: string | undefined;
- let retired = false;
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
const resultItem = states[i];
@@ -448,7 +447,6 @@ const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[]
hoverMessageParts.push(labelForTestInState(test.item.label, state));
}
computedState = maxPriority(computedState, state);
- retired = retired || !!resultItem?.retired;
if (!testIdWithMessages && resultItem?.tasks.some(t => t.messages.length)) {
testIdWithMessages = test.item.extId;
}
@@ -462,9 +460,6 @@ const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[]
let hoverMessage: IMarkdownString | undefined;
let glyphMarginClassName = ThemeIcon.asClassName(icon) + ' testing-run-glyph';
- if (retired) {
- glyphMarginClassName += ' retired';
- }
return {
range: firstLineRange(range),
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
index 31d4e1eec73..c32661787e3 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
@@ -21,7 +21,7 @@ import { attachSuggestEnabledInputBoxStyler, ContextScopedSuggestEnabledInputWit
import { testingFilterIcon } from 'vs/workbench/contrib/testing/browser/icons';
import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { denamespaceTestTag } from 'vs/workbench/contrib/testing/common/testCollection';
+import { denamespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState, TestFilterTerm } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
@@ -78,7 +78,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
const insertText = `@${ctrlId}:${tagId}`;
return ({
label: `@${ctrlId}:${tagId}`,
- detail: tag.ctrlLabel,
+ detail: this.testService.collection.getNodeById(ctrlId)?.item.label,
insertText: tagId.includes(' ') ? `@${ctrlId}:"${tagId.replace(/(["\\])/g, '\\$1')}"` : insertText,
});
}),
@@ -205,6 +205,17 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
})),
new Separator(),
{
+ checked: this.filters.fuzzy.value,
+ class: undefined,
+ enabled: true,
+ id: 'fuzzy',
+ label: localize('testing.filters.fuzzyMatch', "Fuzzy Match"),
+ run: () => this.filters.fuzzy.value = !this.filters.fuzzy.value,
+ tooltip: '',
+ dispose: () => null
+ },
+ new Separator(),
+ {
checked: this.filters.isFilteringFor(TestFilterTerm.Hidden),
class: undefined,
enabled: this.testService.excluded.hasAny,
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
index 88ad930b4bd..b742efad054 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
@@ -56,9 +56,9 @@ import * as icons from 'vs/workbench/contrib/testing/browser/icons';
import { TestingExplorerFilter } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService';
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
-import { labelForTestInState, TestCommandId, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
+import { labelForTestInState, TestCommandId, TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { InternalTestItem, ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem, ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState, TestExplorerFilterState, TestFilterTerm } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
@@ -513,6 +513,7 @@ export class TestingExplorerViewModel extends Disposable {
this._register(Event.any(
filterState.text.onDidChange,
+ filterState.fuzzy.onDidChange,
testService.excluded.onTestExclusionsChanged,
)(this.tree.refilter, this.tree));
@@ -911,13 +912,14 @@ class TestsFilter implements ITreeFilter<TestExplorerTreeElement> {
return FilterResult.Include;
}
+ const fuzzy = this.state.fuzzy.value;
for (let e: TestItemTreeElement | null = element; e; e = e.parent) {
// start as included if the first glob is a negation
let included = this.state.globList[0].include === false ? FilterResult.Include : FilterResult.Inherit;
const data = e.label.toLowerCase();
for (const { include, text } of this.state.globList) {
- if (fuzzyContains(data, text)) {
+ if (fuzzy ? fuzzyContains(data, text) : data.includes(text)) {
included = include ? FilterResult.Include : FilterResult.Exclude;
}
}
@@ -1011,13 +1013,6 @@ const getLabelForTestTreeElement = (element: TestItemTreeElement) => {
comment: ['{0} is the original label in testing.treeElementLabel, {1} is a duration'],
}, '{0}, in {1}', label, formatDuration(element.duration));
}
-
- if (element.retired) {
- label = localize({
- key: 'testing.treeElementLabelOutdated',
- comment: ['{0} is the original label in testing.treeElementLabel'],
- }, '{0}, outdated result', label, testStateNames[element.state]);
- }
}
return label;
@@ -1210,10 +1205,6 @@ class TestItemRenderer extends ActionableItemTemplateData<TestItemTreeElement> {
: node.element.state);
data.icon.className = 'computed-state ' + (icon ? ThemeIcon.asClassName(icon) : '');
- if (node.element.retired) {
- data.icon.className += ' retired';
- }
-
label.resource = node.element.test.item.uri;
options.title = getLabelForTestTreeElement(node.element);
options.fileKind = FileKind.FILE;
diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
index 67897613b3c..71b7930768c 100644
--- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
@@ -65,7 +65,7 @@ import { AutoOpenPeekViewWhen, getTestingConfiguration, TestingConfigKeys } from
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
@@ -349,11 +349,20 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener
* Gets the first failed message that can be displayed from the result.
*/
private getFailedCandidateMessage(test: TestResultItem) {
- return mapFindTestMessage(test, (task, message, messageIndex, taskId) =>
- isFailedState(task.state) && message.location
- ? { taskId, index: messageIndex, message }
- : undefined
- );
+ let best: { taskId: number; index: number; message: ITestMessage } | undefined;
+ mapFindTestMessage(test, (task, message, messageIndex, taskId) => {
+ if (!isFailedState(task.state) || !message.location) {
+ return;
+ }
+
+ if (best && message.type !== TestMessageType.Error) {
+ return;
+ }
+
+ best = { taskId, index: messageIndex, message };
+ });
+
+ return best;
}
}
diff --git a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts
index 77ac8c24841..69b323858b3 100644
--- a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts
@@ -13,7 +13,7 @@ import { ProgressLocation, UnmanagedProgress } from 'vs/platform/progress/common
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { AutoOpenTesting, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
-import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { LiveTestResult, TestResultItemChangeReason, TestStateCount } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts
index 02c9577f04f..dcd6e2bb567 100644
--- a/src/vs/workbench/contrib/testing/browser/theme.ts
+++ b/src/vs/workbench/contrib/testing/browser/theme.ts
@@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
import { contrastBorder, editorErrorForeground, editorForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme';
-import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
export const testingColorIconFailed = registerColor('testing.iconFailed', {
dark: '#f14c4c',
diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts
index 44511be68c4..c8fc457be2d 100644
--- a/src/vs/workbench/contrib/testing/common/constants.ts
+++ b/src/vs/workbench/contrib/testing/common/constants.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
-import { TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
export const enum Testing {
// marked as "extension" so that any existing test extensions are assigned to it.
diff --git a/src/vs/workbench/contrib/testing/common/getComputedState.ts b/src/vs/workbench/contrib/testing/common/getComputedState.ts
index f3d87de8eff..74597ca30c3 100644
--- a/src/vs/workbench/contrib/testing/common/getComputedState.ts
+++ b/src/vs/workbench/contrib/testing/common/getComputedState.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Iterable } from 'vs/base/common/iterator';
-import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
import { maxPriority, statePriority } from 'vs/workbench/contrib/testing/common/testingStates';
/**
diff --git a/src/vs/workbench/contrib/testing/common/mainThreadTestCollection.ts b/src/vs/workbench/contrib/testing/common/mainThreadTestCollection.ts
index 52ddb69be63..42e68e67209 100644
--- a/src/vs/workbench/contrib/testing/common/mainThreadTestCollection.ts
+++ b/src/vs/workbench/contrib/testing/common/mainThreadTestCollection.ts
@@ -5,12 +5,11 @@
import { Emitter } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
-import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
+import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
import { IMainThreadTestCollection } from 'vs/workbench/contrib/testing/common/testService';
export class MainThreadTestCollection extends AbstractIncrementalTestCollection<IncrementalTestCollectionItem> implements IMainThreadTestCollection {
private busyProvidersChangeEmitter = new Emitter<number>();
- private retireTestEmitter = new Emitter<string>();
private expandPromises = new WeakMap<IncrementalTestCollectionItem, {
pendingLvl: number;
doneLvl: number;
@@ -43,7 +42,6 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
}
public readonly onBusyProvidersChange = this.busyProvidersChangeEmitter.event;
- public readonly onDidRetireTest = this.retireTestEmitter.event;
constructor(private readonly expandActual: (id: string, levels: number) => Promise<void>) {
super();
@@ -140,13 +138,6 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
return { ...internal, children: new Set() };
}
- /**
- * @override
- */
- protected override retireTest(testId: string) {
- this.retireTestEmitter.fire(testId);
- }
-
private *getIterator() {
const queue = [this.rootIds];
while (queue.length) {
diff --git a/src/vs/workbench/contrib/testing/common/testCoverage.ts b/src/vs/workbench/contrib/testing/common/testCoverage.ts
index 7bed594032d..53030124dfc 100644
--- a/src/vs/workbench/contrib/testing/common/testCoverage.ts
+++ b/src/vs/workbench/contrib/testing/common/testCoverage.ts
@@ -5,7 +5,7 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
-import { IFileCoverage, CoverageDetails, ICoveredCount } from 'vs/workbench/contrib/testing/common/testCollection';
+import { IFileCoverage, CoverageDetails, ICoveredCount } from 'vs/workbench/contrib/testing/common/testTypes';
export interface ICoverageAccessor {
provideFileCoverage: (token: CancellationToken) => Promise<IFileCoverage[]>;
diff --git a/src/vs/workbench/contrib/testing/common/testExclusions.ts b/src/vs/workbench/contrib/testing/common/testExclusions.ts
index 1cc222a0e5f..8479e3b95d5 100644
--- a/src/vs/workbench/contrib/testing/common/testExclusions.ts
+++ b/src/vs/workbench/contrib/testing/common/testExclusions.ts
@@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testTypes';
export class TestExclusions extends Disposable {
private readonly excluded = this._register(
diff --git a/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts b/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts
index 2f5f78039ca..0be3c3dcc8d 100644
--- a/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts
+++ b/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts
@@ -5,8 +5,10 @@
import { Emitter, Event } from 'vs/base/common/event';
import { splitGlobAware } from 'vs/base/common/glob';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
-import { namespaceTestTag } from 'vs/workbench/contrib/testing/common/testCollection';
+import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
+import { namespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes';
export interface ITestExplorerFilterState {
_serviceBrand: undefined;
@@ -36,6 +38,11 @@ export interface ITestExplorerFilterState {
readonly excludeTags: ReadonlySet<string>;
/**
+ * Whether fuzzy searching is enabled.
+ */
+ readonly fuzzy: MutableObservableValue<boolean>;
+
+ /**
* Focuses the filter input in the test explorer view.
*/
focusInput(): void;
@@ -81,10 +88,19 @@ export class TestExplorerFilterState implements ITestExplorerFilterState {
/** @inheritdoc */
public readonly text = new MutableObservableValue('');
+ /** @inheritdoc */
+ public readonly fuzzy = MutableObservableValue.stored(new StoredValue<boolean>({
+ key: 'testHistoryFuzzy',
+ scope: StorageScope.GLOBAL,
+ target: StorageTarget.USER,
+ }, this.storageService), false);
+
public readonly reveal = new MutableObservableValue</* test ID */string | undefined>(undefined);
public readonly onDidRequestInputFocus = this.focusEmitter.event;
+ constructor(@IStorageService private readonly storageService: IStorageService) { }
+
/** @inheritdoc */
public focusInput() {
this.focusEmitter.fire();
diff --git a/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts b/src/vs/workbench/contrib/testing/common/testItemCollection.ts
index c3409f6ba9e..06ad3b1b4b7 100644
--- a/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts
+++ b/src/vs/workbench/contrib/testing/common/testItemCollection.ts
@@ -4,31 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { Barrier, isThenable, RunOnceScheduler } from 'vs/base/common/async';
-import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { assertNever } from 'vs/base/common/types';
-// eslint-disable-next-line code-import-patterns
-import { diffTestItems, ExtHostTestItemEvent, ExtHostTestItemEventOp, getPrivateApiFor, TestItemImpl, TestItemRootImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
-// eslint-disable-next-line code-import-patterns
-import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
-import { applyTestItemUpdate, ITestTag, namespaceTestTag, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
+import { applyTestItemUpdate, ITestItem, ITestTag, namespaceTestTag, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
-import * as editorRange from 'vs/editor/common/core/range';
-
-type TestItemRaw = Convert.TestItem.Raw;
-
-export interface IHierarchyProvider {
- getChildren(node: TestItemRaw, token: CancellationToken): Iterable<TestItemRaw> | AsyncIterable<TestItemRaw> | undefined | null;
-}
/**
* @private
*/
-export interface OwnedCollectionTestItem {
+interface CollectionItem<T> {
readonly fullId: TestId;
readonly parent: TestId | null;
- actual: TestItemImpl;
+ actual: T;
expand: TestItemExpandState;
/**
* Number of levels of items below this one that are expanded. May be infinite.
@@ -37,23 +25,144 @@ export interface OwnedCollectionTestItem {
resolveBarrier?: Barrier;
}
+export const enum TestItemEventOp {
+ Upsert,
+ SetTags,
+ UpdateCanResolveChildren,
+ RemoveChild,
+ SetProp,
+ Bulk,
+}
+
+export interface ITestItemUpsertChild {
+ op: TestItemEventOp.Upsert;
+ item: ITestItemLike;
+}
+
+export interface ITestItemUpdateCanResolveChildren {
+ op: TestItemEventOp.UpdateCanResolveChildren;
+ state: boolean;
+}
+
+export interface ITestItemSetTags {
+ op: TestItemEventOp.SetTags;
+ new: ITestTag[];
+ old: ITestTag[];
+}
+
+export interface ITestItemRemoveChild {
+ op: TestItemEventOp.RemoveChild;
+ id: string;
+}
+
+export interface ITestItemSetProp {
+ op: TestItemEventOp.SetProp;
+ update: Partial<ITestItem>;
+}
+export interface ITestItemBulkReplace {
+ op: TestItemEventOp.Bulk;
+ ops: (ITestItemUpsertChild | ITestItemRemoveChild)[];
+}
+
+export type ExtHostTestItemEvent =
+ | ITestItemSetTags
+ | ITestItemUpsertChild
+ | ITestItemRemoveChild
+ | ITestItemUpdateCanResolveChildren
+ | ITestItemSetProp
+ | ITestItemBulkReplace;
+
+export interface ITestItemApi<T> {
+ controllerId: string;
+ parent?: T;
+ listener?: (evt: ExtHostTestItemEvent) => void;
+}
+
+export interface ITestItemCollectionOptions<T> {
+ /** Controller ID to use to prefix these test items. */
+ controllerId: string;
+
+ /** Gets API for the given test item, used to listen for events and set parents. */
+ getApiFor(item: T): ITestItemApi<T>;
+
+ /** Converts the full test item to the common interface. */
+ toITestItem(item: T): ITestItem;
+
+ /** Gets children for the item. */
+ getChildren(item: T): ITestChildrenLike<T>;
+
+ /** Root to use for the new test collection. */
+ root: T;
+}
+
+const strictEqualComparator = <T>(a: T, b: T) => a === b;
+const diffableProps: { [K in keyof ITestItem]?: (a: ITestItem[K], b: ITestItem[K]) => boolean } = {
+ range: (a, b) => {
+ if (a === b) { return true; }
+ if (!a || !b) { return false; }
+ return a.equalsRange(b);
+ },
+ busy: strictEqualComparator,
+ label: strictEqualComparator,
+ description: strictEqualComparator,
+ error: strictEqualComparator,
+ tags: (a, b) => {
+ if (a.length !== b.length) {
+ return false;
+ }
+
+ if (a.some(t1 => !b.includes(t1))) {
+ return false;
+ }
+
+ return true;
+ },
+};
+
+const diffTestItems = (a: ITestItem, b: ITestItem) => {
+ let output: Record<string, unknown> | undefined;
+ for (const [key, cmp] of Object.entries(diffableProps) as [keyof ITestItem, (a: any, b: any) => boolean][]) {
+ if (!cmp(a[key], b[key])) {
+ if (output) {
+ output[key] = b[key];
+ } else {
+ output = { [key]: b[key] };
+ }
+ }
+ }
+
+ return output as Partial<ITestItem> | undefined;
+};
+
+export interface ITestChildrenLike<T> extends Iterable<T> {
+ get(id: string): T | undefined;
+ delete(id: string): void;
+}
+
+export interface ITestItemLike {
+ id: string;
+ tags: readonly ITestTag[];
+ canResolveChildren: boolean;
+}
+
/**
- * Maintains tests created and registered for a single set of hierarchies
- * for a workspace or document.
- * @private
+ * Maintains a collection of test items for a single controller.
*/
-export class SingleUseTestCollection extends Disposable {
+export class TestItemCollection<T extends ITestItemLike> extends Disposable {
private readonly debounceSendDiff = this._register(new RunOnceScheduler(() => this.flushDiff(), 200));
private readonly diffOpEmitter = this._register(new Emitter<TestsDiff>());
- private _resolveHandler?: (item: TestItemRaw | undefined) => Promise<void> | void;
+ private _resolveHandler?: (item: T | undefined) => Promise<void> | void;
+
+ public get root() {
+ return this.options.root;
+ }
- public readonly root = new TestItemRootImpl(this.controllerId, this.controllerId);
- public readonly tree = new Map</* full test id */string, OwnedCollectionTestItem>();
+ public readonly tree = new Map</* full test id */string, CollectionItem<T>>();
private readonly tags = new Map<string, { label?: string; refCount: number }>();
protected diff: TestsDiff = [];
- constructor(private readonly controllerId: string) {
+ constructor(private readonly options: ITestItemCollectionOptions<T>) {
super();
this.root.canResolveChildren = true;
this.upsertItem(this.root, undefined);
@@ -62,7 +171,7 @@ export class SingleUseTestCollection extends Disposable {
/**
* Handler used for expanding test items.
*/
- public set resolveHandler(handler: undefined | ((item: TestItemRaw | undefined) => void)) {
+ public set resolveHandler(handler: undefined | ((item: T | undefined) => void)) {
this._resolveHandler = handler;
for (const test of this.tree.values()) {
this.updateExpandability(test);
@@ -139,7 +248,7 @@ export class SingleUseTestCollection extends Disposable {
public override dispose() {
for (const item of this.tree.values()) {
- getPrivateApiFor(item.actual).listener = undefined;
+ this.options.getApiFor(item.actual).listener = undefined;
}
this.tree.clear();
@@ -147,68 +256,49 @@ export class SingleUseTestCollection extends Disposable {
super.dispose();
}
- private onTestItemEvent(internal: OwnedCollectionTestItem, evt: ExtHostTestItemEvent) {
+ private onTestItemEvent(internal: CollectionItem<T>, evt: ExtHostTestItemEvent) {
switch (evt.op) {
- case ExtHostTestItemEventOp.Invalidated:
- this.pushDiff({ op: TestDiffOpType.Retire, itemId: internal.fullId.toString() });
- break;
-
- case ExtHostTestItemEventOp.RemoveChild:
+ case TestItemEventOp.RemoveChild:
this.removeItem(TestId.joinToString(internal.fullId, evt.id));
break;
- case ExtHostTestItemEventOp.Upsert:
- this.upsertItem(evt.item, internal);
+ case TestItemEventOp.Upsert:
+ this.upsertItem(evt.item as T, internal);
break;
- case ExtHostTestItemEventOp.Bulk:
+ case TestItemEventOp.Bulk:
for (const op of evt.ops) {
this.onTestItemEvent(internal, op);
}
break;
- case ExtHostTestItemEventOp.SetProp: {
- const { key, value, previous } = evt;
- const extId = internal.fullId.toString();
- switch (key) {
- case 'canResolveChildren':
- this.updateExpandability(internal);
- break;
- case 'tags':
- this.diffTagRefs(value, previous, extId);
- break;
- case 'range':
- this.pushDiff({
- op: TestDiffOpType.Update,
- item: { extId, item: { range: editorRange.Range.lift(Convert.Range.from(value)) } },
- });
- break;
- case 'error':
- this.pushDiff({ op: TestDiffOpType.Update, item: { extId, item: { error: Convert.MarkdownString.fromStrict(value) || null }, } });
- break;
- default:
- this.pushDiff({ op: TestDiffOpType.Update, item: { extId, item: { [key]: value ?? null } } });
- break;
- }
+ case TestItemEventOp.SetTags:
+ this.diffTagRefs(evt.new, evt.old, internal.fullId.toString());
+ break;
+
+ case TestItemEventOp.UpdateCanResolveChildren:
+ this.updateExpandability(internal);
+ break;
+
+ case TestItemEventOp.SetProp:
+ this.pushDiff({
+ op: TestDiffOpType.Update,
+ item: { extId: internal.fullId.toString(), item: evt.update }
+ });
break;
- }
default:
assertNever(evt);
}
}
- private upsertItem(actual: TestItemRaw, parent: OwnedCollectionTestItem | undefined) {
- if (!(actual instanceof TestItemImpl)) {
- throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`);
- }
-
+ private upsertItem(actual: T, parent: CollectionItem<T> | undefined) {
const fullId = TestId.fromExtHostTestItem(actual, this.root.id, parent?.actual);
// If this test item exists elsewhere in the tree already (exists at an
// old ID with an existing parent), remove that old item.
- const privateApi = getPrivateApiFor(actual);
+ const privateApi = this.options.getApiFor(actual);
if (privateApi.parent && privateApi.parent !== parent?.actual) {
- privateApi.parent.children.delete(actual.id);
+ this.options.getChildren(privateApi.parent).delete(actual.id);
}
let internal = this.tree.get(fullId.toString());
@@ -229,9 +319,9 @@ export class SingleUseTestCollection extends Disposable {
op: TestDiffOpType.Add,
item: {
parent: internal.parent && internal.parent.toString(),
- controllerId: this.controllerId,
+ controllerId: this.options.controllerId,
expand: internal.expand,
- item: Convert.TestItem.from(actual),
+ item: this.options.toITestItem(actual),
},
});
@@ -246,28 +336,34 @@ export class SingleUseTestCollection extends Disposable {
}
// Case 3: upsert of an existing item by ID, with a new instance
- const oldChildren = internal.actual.children;
+ const oldChildren = this.options.getChildren(internal.actual);
const oldActual = internal.actual;
- const changedProps = diffTestItems(oldActual, actual);
- getPrivateApiFor(oldActual).listener = undefined;
+ const update = diffTestItems(this.options.toITestItem(oldActual), this.options.toITestItem(actual));
+ this.options.getApiFor(oldActual).listener = undefined;
internal.actual = actual;
internal.expand = TestItemExpandState.NotExpandable; // updated by `connectItemAndChildren`
- for (const [key, value] of changedProps) {
- this.onTestItemEvent(internal, { op: ExtHostTestItemEventOp.SetProp, key, value, previous: oldActual[key] });
+
+ if (update) {
+ // tags are handled in a special way
+ if (update.hasOwnProperty('tags')) {
+ this.diffTagRefs(actual.tags, oldActual.tags, fullId.toString());
+ delete update.tags;
+ }
+ this.onTestItemEvent(internal, { op: TestItemEventOp.SetProp, update });
}
this.connectItemAndChildren(actual, internal, parent);
// Remove any orphaned children.
for (const child of oldChildren) {
- if (!actual.children.get(child.id)) {
+ if (!this.options.getChildren(actual).get(child.id)) {
this.removeItem(TestId.joinToString(fullId, child.id));
}
}
}
- private diffTagRefs(newTags: ITestTag[], oldTags: ITestTag[], extId: string) {
+ private diffTagRefs(newTags: readonly ITestTag[], oldTags: readonly ITestTag[], extId: string) {
const toDelete = new Set(oldTags.map(t => t.id));
for (const tag of newTags) {
if (!toDelete.delete(tag.id)) {
@@ -277,7 +373,7 @@ export class SingleUseTestCollection extends Disposable {
this.pushDiff({
op: TestDiffOpType.Update,
- item: { extId, item: { tags: newTags.map(v => Convert.TestTag.namespace(this.controllerId, v.id)) } }
+ item: { extId, item: { tags: newTags.map(v => namespaceTestTag(this.options.controllerId, v.id)) } }
});
toDelete.forEach(this.decrementTagRefs, this);
@@ -291,8 +387,7 @@ export class SingleUseTestCollection extends Disposable {
this.tags.set(tag.id, { refCount: 1 });
this.pushDiff({
op: TestDiffOpType.AddTag, tag: {
- id: Convert.TestTag.namespace(this.controllerId, tag.id),
- ctrlLabel: this.root.label,
+ id: namespaceTestTag(this.options.controllerId, tag.id),
}
});
}
@@ -302,27 +397,27 @@ export class SingleUseTestCollection extends Disposable {
const existing = this.tags.get(tagId);
if (existing && !--existing.refCount) {
this.tags.delete(tagId);
- this.pushDiff({ op: TestDiffOpType.RemoveTag, id: namespaceTestTag(this.controllerId, tagId) });
+ this.pushDiff({ op: TestDiffOpType.RemoveTag, id: namespaceTestTag(this.options.controllerId, tagId) });
}
}
- private setItemParent(actual: TestItemImpl, parent: OwnedCollectionTestItem | undefined) {
- getPrivateApiFor(actual).parent = parent && parent.actual !== this.root ? parent.actual : undefined;
+ private setItemParent(actual: T, parent: CollectionItem<T> | undefined) {
+ this.options.getApiFor(actual).parent = parent && parent.actual !== this.root ? parent.actual : undefined;
}
- private connectItem(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | undefined) {
+ private connectItem(actual: T, internal: CollectionItem<T>, parent: CollectionItem<T> | undefined) {
this.setItemParent(actual, parent);
- const api = getPrivateApiFor(actual);
+ const api = this.options.getApiFor(actual);
api.parent = parent?.actual;
api.listener = evt => this.onTestItemEvent(internal, evt);
this.updateExpandability(internal);
}
- private connectItemAndChildren(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | undefined) {
+ private connectItemAndChildren(actual: T, internal: CollectionItem<T>, parent: CollectionItem<T> | undefined) {
this.connectItem(actual, internal, parent);
// Discover any existing children that might have already been added
- for (const child of actual.children) {
+ for (const child of this.options.getChildren(actual)) {
this.upsertItem(child, internal);
}
}
@@ -332,7 +427,7 @@ export class SingleUseTestCollection extends Disposable {
* resolved state of the item changes. Can automatically expand the item
* if requested by a consumer.
*/
- private updateExpandability(internal: OwnedCollectionTestItem) {
+ private updateExpandability(internal: CollectionItem<T>) {
let newState: TestItemExpandState;
if (!this._resolveHandler) {
newState = TestItemExpandState.NotExpandable;
@@ -363,13 +458,13 @@ export class SingleUseTestCollection extends Disposable {
* the children will be expanded. If it's 1, the children and their children
* will be expanded. If it's <0, it's a no-op.
*/
- private expandChildren(internal: OwnedCollectionTestItem, levels: number): Promise<void> | void {
+ private expandChildren(internal: CollectionItem<T>, levels: number): Promise<void> | void {
if (levels < 0) {
return;
}
const expandRequests: Promise<void>[] = [];
- for (const child of internal.actual.children) {
+ for (const child of this.options.getChildren(internal.actual)) {
const promise = this.expand(TestId.joinToString(internal.fullId, child.id), levels);
if (isThenable(promise)) {
expandRequests.push(promise);
@@ -384,7 +479,7 @@ export class SingleUseTestCollection extends Disposable {
/**
* Calls `discoverChildren` on the item, refreshing all its tests.
*/
- private resolveChildren(internal: OwnedCollectionTestItem) {
+ private resolveChildren(internal: CollectionItem<T>) {
if (internal.resolveBarrier) {
return internal.resolveBarrier;
}
@@ -400,10 +495,7 @@ export class SingleUseTestCollection extends Disposable {
const barrier = internal.resolveBarrier = new Barrier();
const applyError = (err: Error) => {
- console.error(`Unhandled error in resolveHandler of test controller "${this.controllerId}"`);
- if (internal.actual !== this.root) {
- internal.actual.error = err.stack || err.message || String(err);
- }
+ console.error(`Unhandled error in resolveHandler of test controller "${this.options.controllerId}"`, err);
};
let r: Thenable<void> | void;
@@ -426,7 +518,7 @@ export class SingleUseTestCollection extends Disposable {
return internal.resolveBarrier;
}
- private pushExpandStateUpdate(internal: OwnedCollectionTestItem) {
+ private pushExpandStateUpdate(internal: CollectionItem<T>) {
this.pushDiff({ op: TestDiffOpType.Update, item: { extId: internal.fullId.toString(), expand: internal.expand } });
}
@@ -438,21 +530,21 @@ export class SingleUseTestCollection extends Disposable {
this.pushDiff({ op: TestDiffOpType.Remove, itemId: childId });
- const queue: (OwnedCollectionTestItem | undefined)[] = [childItem];
+ const queue: (CollectionItem<T> | undefined)[] = [childItem];
while (queue.length) {
const item = queue.pop();
if (!item) {
continue;
}
- getPrivateApiFor(item.actual).listener = undefined;
+ this.options.getApiFor(item.actual).listener = undefined;
for (const tag of item.actual.tags) {
this.decrementTagRefs(tag.id);
}
this.tree.delete(item.fullId.toString());
- for (const child of item.actual.children) {
+ for (const child of this.options.getChildren(item.actual)) {
queue.push(this.tree.get(TestId.joinToString(item.fullId, child.id)));
}
}
@@ -468,3 +560,120 @@ export class SingleUseTestCollection extends Disposable {
}
}
}
+
+/** Implementation os vscode.TestItemCollection */
+export interface ITestItemChildren<T extends ITestItemLike> extends Iterable<T> {
+ readonly size: number;
+ replace(items: readonly T[]): void;
+ forEach(callback: (item: T, collection: this) => unknown, thisArg?: unknown): void;
+ add(item: T): void;
+ delete(itemId: string): void;
+ get(itemId: string): T | undefined;
+
+ toJSON(): readonly T[];
+}
+
+export class DuplicateTestItemError extends Error {
+ constructor(id: string) {
+ super(`Attempted to insert a duplicate test item ID ${id}`);
+ }
+}
+
+export class InvalidTestItemError extends Error {
+ constructor(id: string) {
+ super(`TestItem with ID "${id}" is invalid. Make sure to create it from the createTestItem method.`);
+ }
+}
+
+export class MixedTestItemController extends Error {
+ constructor(id: string, ctrlA: string, ctrlB: string) {
+ super(`TestItem with ID "${id}" is from controller "${ctrlA}" and cannot be added as a child of an item from controller "${ctrlB}".`);
+ }
+}
+
+export const createTestItemChildren = <T extends ITestItemLike>(api: ITestItemApi<T>, getApi: (item: T) => ITestItemApi<T>, checkCtor: Function): ITestItemChildren<T> => {
+ let mapped = new Map<string, T>();
+
+ return {
+ /** @inheritdoc */
+ get size() {
+ return mapped.size;
+ },
+
+ /** @inheritdoc */
+ forEach(callback: (item: T, collection: ITestItemChildren<T>) => unknown, thisArg?: unknown) {
+ for (const item of mapped.values()) {
+ callback.call(thisArg, item, this);
+ }
+ },
+
+ /** @inheritdoc */
+ replace(items: Iterable<T>) {
+ const newMapped = new Map<string, T>();
+ const toDelete = new Set(mapped.keys());
+ const bulk: ITestItemBulkReplace = { op: TestItemEventOp.Bulk, ops: [] };
+
+ for (const item of items) {
+ if (!(item instanceof checkCtor)) {
+ throw new InvalidTestItemError(item.id);
+ }
+
+ const itemController = getApi(item).controllerId;
+ if (itemController !== api.controllerId) {
+ throw new MixedTestItemController(item.id, itemController, api.controllerId);
+ }
+
+ if (newMapped.has(item.id)) {
+ throw new DuplicateTestItemError(item.id);
+ }
+
+ newMapped.set(item.id, item);
+ toDelete.delete(item.id);
+ bulk.ops.push({ op: TestItemEventOp.Upsert, item });
+ }
+
+ for (const id of toDelete.keys()) {
+ bulk.ops.push({ op: TestItemEventOp.RemoveChild, id });
+ }
+
+ api.listener?.(bulk);
+
+ // important mutations come after firing, so if an error happens no
+ // changes will be "saved":
+ mapped = newMapped;
+ },
+
+
+ /** @inheritdoc */
+ add(item: T) {
+ if (!(item instanceof checkCtor)) {
+ throw new InvalidTestItemError(item.id);
+ }
+
+ mapped.set(item.id, item);
+ api.listener?.({ op: TestItemEventOp.Upsert, item });
+ },
+
+ /** @inheritdoc */
+ delete(id: string) {
+ if (mapped.delete(id)) {
+ api.listener?.({ op: TestItemEventOp.RemoveChild, id });
+ }
+ },
+
+ /** @inheritdoc */
+ get(itemId: string) {
+ return mapped.get(itemId);
+ },
+
+ /** JSON serialization function. */
+ toJSON() {
+ return Array.from(mapped.values());
+ },
+
+ /** @inheritdoc */
+ [Symbol.iterator]() {
+ return mapped.values();
+ },
+ };
+};
diff --git a/src/vs/workbench/contrib/testing/common/testProfileService.ts b/src/vs/workbench/contrib/testing/common/testProfileService.ts
index 7c83d34d371..ecdb9ffcd63 100644
--- a/src/vs/workbench/contrib/testing/common/testProfileService.ts
+++ b/src/vs/workbench/contrib/testing/common/testProfileService.ts
@@ -9,7 +9,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { InternalTestItem, ITestRunProfile, TestRunProfileBitset, testRunProfileBitsetList } from 'vs/workbench/contrib/testing/common/testCollection';
+import { InternalTestItem, ITestRunProfile, TestRunProfileBitset, testRunProfileBitsetList } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { IMainThreadTestController } from 'vs/workbench/contrib/testing/common/testService';
diff --git a/src/vs/workbench/contrib/testing/common/testResult.ts b/src/vs/workbench/contrib/testing/common/testResult.ts
index ad80e64dff4..dce023d7d60 100644
--- a/src/vs/workbench/contrib/testing/common/testResult.ts
+++ b/src/vs/workbench/contrib/testing/common/testResult.ts
@@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range';
import { localize } from 'vs/nls';
import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState';
import { IObservableValue, MutableObservableValue, staticObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
-import { IRichLocation, ISerializedTestResults, ITestItem, ITestMessage, ITestOutputMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestMessageType, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { IRichLocation, ISerializedTestResults, ITestItem, ITestMessage, ITestOutputMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestMessageType, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
import { maxPriority, statesInOrder, terminalStatePriorities } from 'vs/workbench/contrib/testing/common/testingStates';
@@ -230,18 +230,15 @@ const itemToNode = (controllerId: string, item: ITestItem, parent: string | null
tasks: [],
ownComputedState: TestResultState.Unset,
computedState: TestResultState.Unset,
- retired: false,
});
export const enum TestResultItemChangeReason {
- Retired,
- ParentRetired,
ComputedStateChange,
OwnStateChange,
}
export type TestResultItemChange = { item: TestResultItem; result: ITestResult } & (
- | { reason: TestResultItemChangeReason.Retired | TestResultItemChangeReason.ParentRetired | TestResultItemChangeReason.ComputedStateChange }
+ | { reason: TestResultItemChangeReason.ComputedStateChange }
| { reason: TestResultItemChangeReason.OwnStateChange; previousState: TestResultState; previousOwnDuration: number | undefined }
);
@@ -420,33 +417,6 @@ export class LiveTestResult implements ITestResult {
}
/**
- * Marks a test as retired. This can trigger it to be rerun in live mode.
- */
- public retire(testId: string) {
- const root = this.testById.get(testId);
- if (!root || root.retired) {
- return;
- }
-
- const queue = [[root]];
- while (queue.length) {
- for (const entry of queue.pop()!) {
- if (!entry.retired) {
- entry.retired = true;
- queue.push(entry.children);
- this.changeEmitter.fire({
- result: this,
- item: entry,
- reason: entry === root
- ? TestResultItemChangeReason.Retired
- : TestResultItemChangeReason.ParentRetired
- });
- }
- }
- }
- }
-
- /**
* Marks the task in the test run complete.
*/
public markTaskComplete(taskId: string) {
@@ -638,7 +608,7 @@ export class HydratedTestResult implements ITestResult {
this.request = serialized.request;
for (const item of serialized.items) {
- const cast: TestResultItem = { ...item, retired: true } as any;
+ const cast: TestResultItem = { ...item } as any;
cast.item.uri = URI.revive(cast.item.uri);
for (const task of cast.tasks) {
diff --git a/src/vs/workbench/contrib/testing/common/testResultService.ts b/src/vs/workbench/contrib/testing/common/testResultService.ts
index 061f3694be8..5c3211c0ad1 100644
--- a/src/vs/workbench/contrib/testing/common/testResultService.ts
+++ b/src/vs/workbench/contrib/testing/common/testResultService.ts
@@ -11,7 +11,7 @@ import { Iterable } from 'vs/base/common/iterator';
import { generateUuid } from 'vs/base/common/uuid';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { ExtensionRunTestsRequest, ITestRunProfile, ResolvedTestRunRequest, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ExtensionRunTestsRequest, ITestRunProfile, ResolvedTestRunRequest, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { ITestResult, LiveTestResult, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
diff --git a/src/vs/workbench/contrib/testing/common/testResultStorage.ts b/src/vs/workbench/contrib/testing/common/testResultStorage.ts
index 6c272186949..d2d294aa081 100644
--- a/src/vs/workbench/contrib/testing/common/testResultStorage.ts
+++ b/src/vs/workbench/contrib/testing/common/testResultStorage.ts
@@ -14,7 +14,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testTypes';
import { HydratedTestResult, ITestResult, LiveOutputController, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
export const RETAIN_MAX_RESULTS = 128;
diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts
index 68509583b60..7c2450a2e94 100644
--- a/src/vs/workbench/contrib/testing/common/testService.ts
+++ b/src/vs/workbench/contrib/testing/common/testService.ts
@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
-import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestItemContext, ResolvedTestRunRequest, RunTestForControllerRequest, TestItemExpandState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
+import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestItemContext, ResolvedTestRunRequest, RunTestForControllerRequest, TestItemExpandState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
index 083d40f7801..efc2afbd0ca 100644
--- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
+++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
@@ -17,7 +17,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
diff --git a/src/vs/workbench/contrib/testing/common/testCollection.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts
index 4a50a4c7d9e..6f3e639ef90 100644
--- a/src/vs/workbench/contrib/testing/common/testCollection.ts
+++ b/src/vs/workbench/contrib/testing/common/testTypes.ts
@@ -229,7 +229,7 @@ export interface ITestRunTask {
}
export interface ITestTag {
- id: string;
+ readonly id: string;
}
const testTagDelimiter = '\0';
@@ -244,7 +244,6 @@ export const denamespaceTestTag = (namespaced: string) => {
export interface ITestTagDisplayInfo {
id: string;
- ctrlLabel: string;
}
/**
@@ -419,8 +418,6 @@ export interface TestResultItem extends InternalTestItem {
ownComputedState: TestResultState;
/** Computed state based on children */
computedState: TestResultState;
- /** True if the test is outdated */
- retired: boolean;
/** Max duration of the item's tasks (if run directly) */
ownDuration?: number;
}
@@ -564,7 +561,7 @@ export interface ITestItemContext {
/** Marshalling marker */
$mid: MarshalledId.TestItemContext;
/** Tests and parents from the root to the current items */
- tests: InternalTestItem[];
+ tests: InternalTestItem.Serialized[];
}
/**
diff --git a/src/vs/workbench/contrib/testing/common/testingAutoRun.ts b/src/vs/workbench/contrib/testing/common/testingAutoRun.ts
deleted file mode 100644
index 905b7370f17..00000000000
--- a/src/vs/workbench/contrib/testing/common/testingAutoRun.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { RunOnceScheduler } from 'vs/base/common/async';
-import { CancellationTokenSource } from 'vs/base/common/cancellation';
-import { Iterable } from 'vs/base/common/iterator';
-import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { AutoRunMode, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
-import { InternalTestItem, TestDiffOpType, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
-import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
-import { TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
-import { isRunningTests, ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
-import { getCollectionItemParents, ITestService } from 'vs/workbench/contrib/testing/common/testService';
-
-export interface ITestingAutoRun {
- /**
- * Toggles autorun on or off.
- */
- toggle(): void;
-}
-
-export const ITestingAutoRun = createDecorator<ITestingAutoRun>('testingAutoRun');
-
-export class TestingAutoRun extends Disposable implements ITestingAutoRun {
- private enabled: IContextKey<boolean>;
- private runner = this._register(new MutableDisposable());
-
- constructor(
- @IContextKeyService contextKeyService: IContextKeyService,
- @ITestService private readonly testService: ITestService,
- @ITestResultService private readonly results: ITestResultService,
- @IConfigurationService private readonly configuration: IConfigurationService,
- ) {
- super();
- this.enabled = TestingContextKeys.autoRun.bindTo(contextKeyService);
-
- this._register(configuration.onDidChangeConfiguration(evt => {
- if (evt.affectsConfiguration(TestingConfigKeys.AutoRunMode) && this.enabled.get()) {
- this.runner.value = this.makeRunner();
- }
- }));
- }
-
- /**
- * @inheritdoc
- */
- public toggle(): void {
- const enabled = this.enabled.get();
- if (enabled) {
- this.runner.value = undefined;
- } else {
- this.runner.value = this.makeRunner();
- }
-
- this.enabled.set(!enabled);
- }
-
- /**
- * Creates the runner. Is triggered when tests are marked as retired.
- * Runs them on a debounce.
- */
- private makeRunner() {
- const rerunIds = new Map<string, InternalTestItem>();
- const store = new DisposableStore();
- const cts = new CancellationTokenSource();
- store.add(toDisposable(() => cts.dispose(true)));
-
- let delay = getTestingConfiguration(this.configuration, TestingConfigKeys.AutoRunDelay);
-
- store.add(this.configuration.onDidChangeConfiguration(() => {
- delay = getTestingConfiguration(this.configuration, TestingConfigKeys.AutoRunDelay);
- }));
-
- const scheduler = store.add(new RunOnceScheduler(async () => {
- if (rerunIds.size === 0) {
- return;
- }
-
- const tests = [...rerunIds.values()];
- rerunIds.clear();
- await this.testService.runTests({ group: TestRunProfileBitset.Run, tests, isAutoRun: true });
-
- if (rerunIds.size > 0) {
- scheduler.schedule(delay);
- }
- }, delay));
-
- const addToRerun = (test: InternalTestItem) => {
- rerunIds.set(test.item.extId, test);
- if (!isRunningTests(this.results)) {
- scheduler.schedule(delay);
- }
- };
-
- const removeFromRerun = (test: InternalTestItem) => {
- rerunIds.delete(test.item.extId);
- if (rerunIds.size === 0) {
- scheduler.cancel();
- }
- };
-
- store.add(this.results.onTestChanged(evt => {
- if (evt.reason === TestResultItemChangeReason.Retired) {
- addToRerun(evt.item);
- } else if ((evt.reason === TestResultItemChangeReason.OwnStateChange || evt.reason === TestResultItemChangeReason.ComputedStateChange)) {
- removeFromRerun(evt.item);
- }
- }));
-
- store.add(this.results.onResultsChanged(evt => {
- if ('completed' in evt && !isRunningTests(this.results) && rerunIds.size) {
- scheduler.schedule(0);
- }
- }));
-
- if (getTestingConfiguration(this.configuration, TestingConfigKeys.AutoRunMode) === AutoRunMode.AllInWorkspace) {
-
- store.add(this.testService.onDidProcessDiff(diff => {
- for (const entry of diff) {
- if (entry.op === TestDiffOpType.Add) {
- const test = entry.item;
- const isQueued = Iterable.some(
- getCollectionItemParents(this.testService.collection, test),
- t => rerunIds.has(test.item.extId),
- );
-
- const state = this.results.getStateById(test.item.extId);
- if (!isQueued && (!state || state[1].retired)) {
- addToRerun(test);
- }
- }
- }
- }));
-
-
- for (const root of this.testService.collection.rootItems) {
- addToRerun(root);
- }
- }
-
- return store;
- }
-}
diff --git a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
index 2f64387a111..c93c0968a07 100644
--- a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
+++ b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
@@ -9,7 +9,7 @@ import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
-import { TestMessageType } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestMessageType } from 'vs/workbench/contrib/testing/common/testTypes';
import { parseTestUri, TestUriType, TEST_DATA_SCHEME } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts
index d43cb0e7af1..2663b018541 100644
--- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts
+++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts
@@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { TestExplorerViewMode, TestExplorerViewSorting } from 'vs/workbench/contrib/testing/common/constants';
-import { TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
export namespace TestingContextKeys {
export const providerCount = new RawContextKey('testing.providerCount', 0);
diff --git a/src/vs/workbench/contrib/testing/common/testingDecorations.ts b/src/vs/workbench/contrib/testing/common/testingDecorations.ts
index 98abb6b86ec..3a148eb3e97 100644
--- a/src/vs/workbench/contrib/testing/common/testingDecorations.ts
+++ b/src/vs/workbench/contrib/testing/common/testingDecorations.ts
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { ITestMessage } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ITestMessage } from 'vs/workbench/contrib/testing/common/testTypes';
export interface ITestingDecorationsService {
_serviceBrand: undefined;
diff --git a/src/vs/workbench/contrib/testing/common/testingPeekOpener.ts b/src/vs/workbench/contrib/testing/common/testingPeekOpener.ts
index b3fcba06951..4d999302c9e 100644
--- a/src/vs/workbench/contrib/testing/common/testingPeekOpener.ts
+++ b/src/vs/workbench/contrib/testing/common/testingPeekOpener.ts
@@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultItem } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
export interface ITestingPeekOpener {
diff --git a/src/vs/workbench/contrib/testing/common/testingStates.ts b/src/vs/workbench/contrib/testing/common/testingStates.ts
index cb322d13399..38ea70ce4bf 100644
--- a/src/vs/workbench/contrib/testing/common/testingStates.ts
+++ b/src/vs/workbench/contrib/testing/common/testingStates.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
export type TreeStateNode = { statusNode: true; state: TestResultState; priority: number };
@@ -16,8 +16,8 @@ export const statePriority: { [K in TestResultState]: number } = {
[TestResultState.Running]: 6,
[TestResultState.Errored]: 5,
[TestResultState.Failed]: 4,
- [TestResultState.Passed]: 3,
- [TestResultState.Queued]: 2,
+ [TestResultState.Queued]: 3,
+ [TestResultState.Passed]: 2,
[TestResultState.Unset]: 1,
[TestResultState.Skipped]: 0,
};
diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts
index 66bf4f2db54..af584d316c4 100644
--- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts
+++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts
@@ -7,11 +7,11 @@ import * as assert from 'assert';
import { AbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree';
import { Emitter } from 'vs/base/common/event';
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
-import { TestDiffOpType, TestItemExpandState, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestDiffOpType, TestItemExpandState, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
-import { Convert, TestItemImpl } from 'vs/workbench/contrib/testing/test/common/testStubs';
import { TestTreeTestHarness } from 'vs/workbench/contrib/testing/test/browser/testObjectTree';
+import { TestTestItem } from 'vs/workbench/contrib/testing/test/common/testStubs';
class TestHierarchicalByLocationProjection extends HierarchicalByLocationProjection {
}
@@ -55,10 +55,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
harness.flush();
harness.pushDiff({
op: TestDiffOpType.Add,
- item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'c', undefined)) },
+ item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: new TestTestItem('ctrl2', 'c', 'c').toTestItem() },
}, {
op: TestDiffOpType.Add,
- item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'ca', undefined)) },
+ item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: new TestTestItem('ctrl2', 'c-a', 'ca').toTestItem() },
});
assert.deepStrictEqual(harness.flush(), [
@@ -76,7 +76,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
{ e: 'b' }
]);
- harness.c.root.children.get('id-a')!.children.add(new TestItemImpl('ctrlId', 'ac', 'ac', undefined));
+ harness.c.root.children.get('id-a')!.children.add(new TestTestItem('ctrlId', 'ac', 'ac'));
assert.deepStrictEqual(harness.flush(), [
{ e: 'a', children: [{ e: 'aa' }, { e: 'ab' }, { e: 'ac' }] },
@@ -119,7 +119,6 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
},
parent: 'id-root',
tasks: [],
- retired: false,
ownComputedState: state,
computedState: state,
expand: 0,
diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts
index 8e5b439a920..ec4424b8edd 100644
--- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts
+++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts
@@ -7,11 +7,11 @@ import * as assert from 'assert';
import { AbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree';
import { Emitter } from 'vs/base/common/event';
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
-import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestResultItemChange } from 'vs/workbench/contrib/testing/common/testResult';
-import { Convert, TestItemImpl } from 'vs/workbench/contrib/testing/test/common/testStubs';
import { TestTreeTestHarness } from 'vs/workbench/contrib/testing/test/browser/testObjectTree';
+import { TestTestItem } from 'vs/workbench/contrib/testing/test/common/testStubs';
suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
let harness: TestTreeTestHarness<HierarchicalByNameProjection>;
@@ -44,10 +44,10 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
harness.flush();
harness.pushDiff({
op: TestDiffOpType.Add,
- item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'root2', undefined)) },
+ item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: new TestTestItem('ctrl2', 'c', 'root2').toTestItem() },
}, {
op: TestDiffOpType.Add,
- item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'c', undefined)) },
+ item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: new TestTestItem('ctrl2', 'c-a', 'c', undefined).toTestItem() },
});
assert.deepStrictEqual(harness.flush(), [
@@ -59,7 +59,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
test('updates nodes if they add children', async () => {
harness.flush();
- harness.c.root.children.get('id-a')!.children.add(new TestItemImpl('ctrl2', 'ac', 'ac', undefined));
+ harness.c.root.children.get('id-a')!.children.add(new TestTestItem('ctrl2', 'ac', 'ac'));
assert.deepStrictEqual(harness.flush(), [
{ e: 'aa' },
@@ -81,7 +81,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
test('swaps when node is no longer leaf', async () => {
harness.flush();
- harness.c.root.children.get('id-b')!.children.add(new TestItemImpl('ctrl2', 'ba', 'ba', undefined));
+ harness.c.root.children.get('id-b')!.children.add(new TestTestItem('ctrl2', 'ba', 'ba'));
assert.deepStrictEqual(harness.flush(), [
{ e: 'aa' },
diff --git a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts
index d5dd271b36e..9fd9b315d93 100644
--- a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts
+++ b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts
@@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
-import { TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
+import { TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { testStubs } from 'vs/workbench/contrib/testing/test/common/testStubs';
diff --git a/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts b/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts
deleted file mode 100644
index ce29ffa8119..00000000000
--- a/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
-import { TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
-
-export class TestSingleUseCollection extends SingleUseTestCollection {
- public get currentDiff() {
- return this.diff;
- }
-
- public setDiff(diff: TestsDiff) {
- this.diff = diff;
- }
-}
diff --git a/src/vs/workbench/contrib/testing/test/common/testExplorerFilterState.test.ts b/src/vs/workbench/contrib/testing/test/common/testExplorerFilterState.test.ts
index 1b4657d5600..cc89323ebdd 100644
--- a/src/vs/workbench/contrib/testing/test/common/testExplorerFilterState.test.ts
+++ b/src/vs/workbench/contrib/testing/test/common/testExplorerFilterState.test.ts
@@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
+import { InMemoryStorageService } from 'vs/platform/storage/common/storage';
import { TestExplorerFilterState, TestFilterTerm } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
suite('TestExplorerFilterState', () => {
let t: TestExplorerFilterState;
setup(() => {
- t = new TestExplorerFilterState();
+ t = new TestExplorerFilterState(new InMemoryStorageService());
});
const assertFilteringFor = (expected: { [T in TestFilterTerm]?: boolean }) => {
diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts
index ea9d89f01ce..4452c491436 100644
--- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts
+++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts
@@ -9,14 +9,13 @@ import { bufferToStream, newWriteableBufferStream, VSBuffer } from 'vs/base/comm
import { Lazy } from 'vs/base/common/lazy';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { NullLogService } from 'vs/platform/log/common/log';
-import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
-import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
+import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage';
-import { Convert, getInitializedMainTestCollection, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/test/common/testStubs';
+import { getInitializedMainTestCollection, testStubs, TestTestCollection } from 'vs/workbench/contrib/testing/test/common/testStubs';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
export const emptyOutputController = () => new LiveOutputController(
@@ -32,7 +31,7 @@ suite('Workbench - Test Results Service', () => {
let r: TestLiveTestResult;
let changed = new Set<TestResultItemChange>();
- let tests: SingleUseTestCollection;
+ let tests: TestTestCollection;
const defaultOpts = (testIds: string[]): ResolvedTestRunRequest => ({
targets: [{
@@ -72,16 +71,15 @@ suite('Workbench - Test Results Service', () => {
throw new Error('timed out while expanding, diff: ' + JSON.stringify(tests.collectDiff()));
}
-
r.addTestChainToRun('ctrlId', [
- Convert.TestItem.from(tests.root),
- Convert.TestItem.from(tests.root.children.get('id-a') as TestItemImpl),
- Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa') as TestItemImpl),
+ tests.root.toTestItem(),
+ tests.root.children.get('id-a')!.toTestItem(),
+ tests.root.children.get('id-a')!.children.get('id-aa')!.toTestItem(),
]);
r.addTestChainToRun('ctrlId', [
- Convert.TestItem.from(tests.root.children.get('id-a') as TestItemImpl),
- Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-ab') as TestItemImpl),
+ tests.root.children.get('id-a')!.toTestItem(),
+ tests.root.children.get('id-a')!.children.get('id-ab')!.toTestItem(),
]);
});
@@ -171,20 +169,6 @@ suite('Workbench - Test Results Service', () => {
assert.deepStrictEqual(r.getStateById(testId)?.ownComputedState, TestResultState.Errored);
});
- test('retire', () => {
- changed.clear();
- r.retire(new TestId(['ctrlId', 'id-a']).toString());
- assert.deepStrictEqual(getChangeSummary(), [
- { label: 'a', reason: TestResultItemChangeReason.Retired },
- { label: 'aa', reason: TestResultItemChangeReason.ParentRetired },
- { label: 'ab', reason: TestResultItemChangeReason.ParentRetired },
- ]);
-
- changed.clear();
- r.retire(new TestId(['ctrlId', 'id-a']).toString());
- assert.strictEqual(changed.size, 0);
- });
-
test('ignores outside run', () => {
changed.clear();
r.updateState(new TestId(['ctrlId', 'id-b']).toString(), 't', TestResultState.Running);
@@ -252,7 +236,7 @@ suite('Workbench - Test Results Service', () => {
const expected: any = { ...r.getStateById(tests.root.id)! };
expected.item.uri = actual.item.uri;
expected.item.children = actual.item.children;
- assert.deepStrictEqual(actual, { ...expected, retired: true, children: [new TestId(['ctrlId', 'id-a']).toString()] });
+ assert.deepStrictEqual(actual, { ...expected, children: [new TestId(['ctrlId', 'id-a']).toString()] });
assert.deepStrictEqual(rehydrated.counts, r.counts);
assert.strictEqual(typeof rehydrated.completedAt, 'number');
});
diff --git a/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts
index 0fd486588e7..e10d994eb03 100644
--- a/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts
+++ b/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts
@@ -9,8 +9,8 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { ITestResult, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { InMemoryResultStorage, RETAIN_MAX_RESULTS } from 'vs/workbench/contrib/testing/common/testResultStorage';
-import { Convert, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/test/common/testStubs';
import { emptyOutputController } from 'vs/workbench/contrib/testing/test/common/testResultService.test';
+import { testStubs } from 'vs/workbench/contrib/testing/test/common/testStubs';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
suite('Workbench - Test Result Storage', () => {
@@ -28,9 +28,9 @@ suite('Workbench - Test Result Storage', () => {
const tests = testStubs.nested();
tests.expand(tests.root.id, Infinity);
t.addTestChainToRun('ctrlId', [
- Convert.TestItem.from(tests.root),
- Convert.TestItem.from(tests.root.children.get('id-a') as TestItemImpl),
- Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa') as TestItemImpl),
+ tests.root.toTestItem(),
+ tests.root.children.get('id-a')!.toTestItem(),
+ tests.root.children.get('id-a')!.children.get('id-aa')!.toTestItem(),
]);
if (addMessage) {
diff --git a/src/vs/workbench/contrib/testing/test/common/testStubs.ts b/src/vs/workbench/contrib/testing/test/common/testStubs.ts
index 9ef3d39af94..9c9bb140bc2 100644
--- a/src/vs/workbench/contrib/testing/test/common/testStubs.ts
+++ b/src/vs/workbench/contrib/testing/test/common/testStubs.ts
@@ -4,15 +4,95 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
-// eslint-disable-next-line code-import-patterns
-import { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
-import { TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection';
+import { ITestItem, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
+import { TestId } from 'vs/workbench/contrib/testing/common/testId';
+import { createTestItemChildren, ITestItemApi, ITestItemLike, TestItemCollection, TestItemEventOp } from 'vs/workbench/contrib/testing/common/testItemCollection';
-// eslint-disable-next-line code-import-patterns
-export * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
-// eslint-disable-next-line code-import-patterns
-export { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
+export class TestTestItem implements ITestItemLike {
+ private readonly props: ITestItem;
+ private _canResolveChildren = false;
+
+ public get tags() {
+ return this.props.tags.map(id => ({ id }));
+ }
+
+ public set tags(value) {
+ this.api.listener?.({ op: TestItemEventOp.SetTags, new: value, old: this.props.tags.map(t => ({ id: t })) });
+ this.props.tags = value.map(tag => tag.id);
+ }
+
+ public get canResolveChildren() {
+ return this._canResolveChildren;
+ }
+
+ public set canResolveChildren(value: boolean) {
+ this._canResolveChildren = value;
+ this.api.listener?.({ op: TestItemEventOp.UpdateCanResolveChildren, state: value });
+ }
+
+ public get parent() {
+ return this.api.parent;
+ }
+
+ public api: ITestItemApi<TestTestItem> = { controllerId: this.controllerId };
+
+ public children = createTestItemChildren(this.api, i => i.api, TestTestItem);
+
+ constructor(
+ public readonly controllerId: string,
+ public readonly id: string,
+ label: string,
+ uri?: URI,
+ ) {
+ this.props = {
+ extId: '',
+ busy: false,
+ description: null,
+ error: null,
+ label,
+ range: null,
+ sortText: null,
+ tags: [],
+ uri,
+ };
+ }
+
+ public get<K extends keyof ITestItem>(key: K): ITestItem[K] {
+ return this.props[key];
+ }
+
+ public set<K extends keyof ITestItem>(key: K, value: ITestItem[K]) {
+ this.props[key] = value;
+ this.api.listener?.({ op: TestItemEventOp.SetProp, update: { [key]: value } });
+ }
+
+ public toTestItem(): ITestItem {
+ const props = { ...this.props };
+ props.extId = TestId.fromExtHostTestItem(this, this.controllerId).toString();
+ return props;
+ }
+}
+
+export class TestTestCollection extends TestItemCollection<TestTestItem> {
+ constructor(controllerId = 'ctrlId') {
+ super({
+ controllerId,
+ getApiFor: t => t.api,
+ toITestItem: t => t.toTestItem(),
+ getChildren: t => t.children,
+ root: new TestTestItem(controllerId, controllerId, 'root'),
+ });
+ }
+
+ public get currentDiff() {
+ return this.diff;
+ }
+
+ public setDiff(diff: TestsDiff) {
+ this.diff = diff;
+ }
+}
/**
* Gets a main thread test collection initialized with the given set of
@@ -27,19 +107,17 @@ export const getInitializedMainTestCollection = async (singleUse = testStubs.nes
export const testStubs = {
nested: (idPrefix = 'id-') => {
- const collection = new TestSingleUseCollection('ctrlId');
- collection.root.label = 'root';
+ const collection = new TestTestCollection();
collection.resolveHandler = item => {
if (item === undefined) {
- const a = new TestItemImpl('ctrlId', idPrefix + 'a', 'a', URI.file('/'));
+ const a = new TestTestItem('ctrlId', idPrefix + 'a', 'a', URI.file('/'));
a.canResolveChildren = true;
- const b = new TestItemImpl('ctrlId', idPrefix + 'b', 'b', URI.file('/'));
- collection.root.children.replace([a, b]);
+ const b = new TestTestItem('ctrlId', idPrefix + 'b', 'b', URI.file('/'));
+ collection.root.children.add(a);
+ collection.root.children.add(b);
} else if (item.id === idPrefix + 'a') {
- item.children.replace([
- new TestItemImpl('ctrlId', idPrefix + 'aa', 'aa', URI.file('/')),
- new TestItemImpl('ctrlId', idPrefix + 'ab', 'ab', URI.file('/')),
- ]);
+ item.children.add(new TestTestItem('ctrlId', idPrefix + 'aa', 'aa', URI.file('/')));
+ item.children.add(new TestTestItem('ctrlId', idPrefix + 'ab', 'ab', URI.file('/')));
}
};
diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
index efb577a072b..1c312d73bdd 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
@@ -476,7 +476,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
}
if (this._webviewFindWidget) {
- parent.appendChild(this._webviewFindWidget.getDomNode()!);
+ parent.appendChild(this._webviewFindWidget.getDomNode());
}
parent.appendChild(this.element);
}
diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
index fde2df743dd..e7bad42e252 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
@@ -20,6 +20,9 @@ export interface WebviewFindDelegate {
}
export class WebviewFindWidget extends SimpleFindWidget {
+ protected async _getResultCount(dataChanged?: boolean): Promise<{ resultIndex: number; resultCount: number } | undefined> {
+ return undefined;
+ }
protected readonly _findWidgetFocused: IContextKey<boolean>;
@@ -28,7 +31,7 @@ export class WebviewFindWidget extends SimpleFindWidget {
@IContextViewService contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService
) {
- super(contextViewService, contextKeyService, undefined, false, _delegate.checkImeCompletionState);
+ super(contextViewService, contextKeyService, undefined, { showOptionButtons: false, checkImeCompletionState: _delegate.checkImeCompletionState });
this._findWidgetFocused = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED.bindTo(contextKeyService);
this._register(_delegate.hasFindResult(hasResult => {
diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts
index f7beac7c5bb..e6a1076d917 100644
--- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts
+++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts
@@ -6,7 +6,7 @@
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
-import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { generateUuid } from 'vs/base/common/uuid';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -23,10 +23,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
-import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
+import { IViewBadge, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
import { IOverlayWebview, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
+import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
declare const ResizeObserver: any;
@@ -48,6 +49,9 @@ export class WebviewViewPane extends ViewPane {
private readonly defaultTitle: string;
private setTitle: string | undefined;
+ private badge: IViewBadge | undefined;
+ private activity: IDisposable | undefined;
+
private readonly memento: Memento;
private readonly viewState: MementoObject;
private readonly extensionId?: ExtensionIdentifier;
@@ -69,6 +73,7 @@ export class WebviewViewPane extends ViewPane {
@IWebviewService private readonly webviewService: IWebviewService,
@IWebviewViewService private readonly webviewViewService: IWebviewViewService,
@IViewsService private readonly viewService: IViewsService,
+ @IActivityService private activityService: IActivityService
) {
super({ ...options, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this.extensionId = options.fromExtensionId;
@@ -211,6 +216,9 @@ export class WebviewViewPane extends ViewPane {
get description(): string | undefined { return self.titleDescription; },
set description(value: string | undefined) { self.updateTitleDescription(value); },
+ get badge(): IViewBadge | undefined { return self.badge; },
+ set badge(badge: IViewBadge | undefined) { self.updateBadge(badge); },
+
dispose: () => {
// Only reset and clear the webview itself. Don't dispose of the view container
this._activated = false;
@@ -232,6 +240,28 @@ export class WebviewViewPane extends ViewPane {
super.updateTitle(typeof value === 'string' ? value : this.defaultTitle);
}
+ protected updateBadge(badge: IViewBadge | undefined) {
+
+ if (this.badge?.value === badge?.value &&
+ this.badge?.tooltip === badge?.tooltip) {
+ return;
+ }
+
+ if (this.activity) {
+ this.activity.dispose();
+ this.activity = undefined;
+ }
+
+ this.badge = badge;
+ if (badge) {
+ const activity = {
+ badge: new NumberBadge(badge.value, () => badge.tooltip),
+ priority: 150
+ };
+ this.activityService.showViewActivity(this.id, activity);
+ }
+ }
+
private async withProgress(task: () => Promise<void>): Promise<void> {
return this.progressService.withProgress({ location: this.id, delay: 500 }, task);
}
diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts
index c3565f919d5..c4ebff8bfee 100644
--- a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts
+++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts
@@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { IViewBadge } from 'vs/workbench/common/views';
import { IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
export const IWebviewViewService = createDecorator<IWebviewViewService>('webviewViewService');
@@ -14,6 +15,7 @@ export const IWebviewViewService = createDecorator<IWebviewViewService>('webview
export interface WebviewView {
title?: string;
description?: string;
+ badge?: IViewBadge;
readonly webview: IOverlayWebview;
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts b/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts
index e8339183da5..d73cfe79d1c 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts
@@ -23,7 +23,7 @@ suite('Getting Started Markdown Renderer', () => {
const rendered = await renderer.renderMarkdown(mdPath, mdBase);
const imageSrcs = [...rendered.matchAll(/img src="[^"]*"/g)].map(match => match[0]);
for (const src of imageSrcs) {
- const targetSrcFormat = /^img src="https:\/\/file%2B.vscode-resource.vscode-webview.net\/.*\/vs\/workbench\/contrib\/welcomeGettingStarted\/common\/media\/.*.png"$/;
+ const targetSrcFormat = /^img src="https:\/\/file%2B.vscode-resource.vscode-cdn.net\/.*\/vs\/workbench\/contrib\/welcomeGettingStarted\/common\/media\/.*.png"$/;
assert(targetSrcFormat.test(src), `${src} didnt match regex`);
}
languageService.dispose();