Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)

* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79

* Fix breaks

* Extension management fixes

* Fix breaks in windows bundling

* Fix/skip failing tests

* Update distro

* Add clear to nuget.config

* Add hygiene task

* Bump distro

* Fix hygiene issue

* Add build to hygiene exclusion

* Update distro

* Update hygiene

* Hygiene exclusions

* Update tsconfig

* Bump distro for server breaks

* Update build config

* Update darwin path

* Add done calls to notebook tests

* Skip failing tests

* Disable smoke tests
This commit is contained in:
Karl Burtram
2021-02-09 16:15:05 -08:00
committed by GitHub
parent 6f192f9af5
commit ce612a3d96
1929 changed files with 68012 additions and 34564 deletions

View File

@@ -5,7 +5,7 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker';
// Register Backup Tracker

View File

@@ -5,22 +5,53 @@
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopy, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
export class BrowserBackupTracker extends BackupTracker implements IWorkbenchContribution {
// Delay creation of backups when content changes to avoid too much
// load on the backup service when the user is typing into the editor
// Since we always schedule a backup, even when auto save is on (web
// only), we have different scheduling delays based on auto save. This
// helps to avoid a race between saving (after 1s per default) and making
// a backup of the working copy.
private static readonly BACKUP_SCHEDULE_DELAYS = {
[AutoSaveMode.OFF]: 1000,
[AutoSaveMode.ON_FOCUS_CHANGE]: 1000,
[AutoSaveMode.ON_WINDOW_CHANGE]: 1000,
[AutoSaveMode.AFTER_SHORT_DELAY]: 2000, // explicitly higher to prevent races
[AutoSaveMode.AFTER_LONG_DELAY]: 1000
};
constructor(
@IBackupFileService backupFileService: IBackupFileService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILifecycleService lifecycleService: ILifecycleService,
@ILogService logService: ILogService
) {
super(backupFileService, filesConfigurationService, workingCopyService, logService, lifecycleService);
super(backupFileService, workingCopyService, logService, lifecycleService);
}
protected shouldScheduleBackup(workingCopy: IWorkingCopy): boolean {
// Web: we always want to schedule a backup, even if auto save
// is enabled because in web there is no handler on shutdown
// to trigger saving so there is a higher chance of dataloss.
// See https://github.com/microsoft/vscode/issues/108789
return true;
}
protected getBackupScheduleDelay(workingCopy: IWorkingCopy): number {
let autoSaveMode = this.filesConfigurationService.getAutoSaveMode();
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
autoSaveMode = AutoSaveMode.OFF; // auto-save is never on for untitled working copies
}
return BrowserBackupTracker.BACKUP_SCHEDULE_DELAYS[autoSaveMode];
}
protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
@@ -41,7 +72,8 @@ export class BrowserBackupTracker extends BackupTracker implements IWorkbenchCon
for (const dirtyWorkingCopy of dirtyWorkingCopies) {
if (!this.backupFileService.hasBackupSync(dirtyWorkingCopy.resource, this.getContentVersion(dirtyWorkingCopy))) {
console.warn('Unload prevented: pending backups');
this.logService.warn('Unload veto: pending backups');
return true; // dirty without backup: veto
}
}

View File

@@ -6,7 +6,7 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
// Register Backup Restorer
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupRestorer, LifecyclePhase.Starting);

View File

@@ -9,7 +9,7 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { Schemas } from 'vs/base/common/network';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IUntitledTextResourceEditorInput, IEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputWithOptions } from 'vs/workbench/common/editor';
import { toLocalResource, isEqual } from 'vs/base/common/resources';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -101,7 +101,8 @@ export class BackupRestorer implements IWorkbenchContribution {
// an associated file path or not by just looking at the path. and
// if so, we must ensure to restore the local resource it had.
if (resource.scheme === Schemas.untitled && !BackupRestorer.UNTITLED_REGEX.test(resource.path) && BackupRestorer.SQLQUERY_REGEX.test(resource.path)) { // {{SQL CARBON EDIT}} @anthonydresser add sql regex test
return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme), options, forceUntitled: true };
return { resource: toLocalResource(resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme), options, forceUntitled: true };
}
// handle custom editors by asking the custom editor input factory

View File

@@ -5,47 +5,29 @@
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILogService } from 'vs/platform/log/common/log';
import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export abstract class BackupTracker extends Disposable {
// Disable backup for when a short auto-save delay is configured with
// the rationale that the auto save will trigger a save periodically
// anway and thus creating frequent backups is not useful
//
// This will only apply to working copies that are not untitled where
// auto save is actually saving.
private static DISABLE_BACKUP_AUTO_SAVE_THRESHOLD = 1500;
// Delay creation of backups when content changes to avoid too much
// load on the backup service when the user is typing into the editor
protected static BACKUP_FROM_CONTENT_CHANGE_DELAY = 1000;
// A map from working copy to a version ID we compute on each content
// change. This version ID allows to e.g. ask if a backup for a specific
// content has been made before closing.
private readonly mapWorkingCopyToContentVersion = new Map<IWorkingCopy, number>();
private backupsDisabledForAutoSaveables = false;
// A map of scheduled pending backups for working copies
private readonly pendingBackups = new Map<IWorkingCopy, IDisposable>();
constructor(
protected readonly backupFileService: IBackupFileService,
protected readonly filesConfigurationService: IFilesConfigurationService,
protected readonly workingCopyService: IWorkingCopyService,
protected readonly logService: ILogService,
protected readonly lifecycleService: ILifecycleService
) {
super();
// Figure out initial auto save config
this.onAutoSaveConfigurationChange(filesConfigurationService.getAutoSaveConfiguration());
// Fill in initial dirty working copies
this.workingCopyService.dirtyWorkingCopies.forEach(workingCopy => this.onDidRegister(workingCopy));
@@ -60,9 +42,6 @@ export abstract class BackupTracker extends Disposable {
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidChangeDirty(workingCopy)));
this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy)));
// Listen to auto save config changes
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c)));
// Lifecycle (handled in subclasses)
this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason)));
}
@@ -105,40 +84,71 @@ export abstract class BackupTracker extends Disposable {
}
}
private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void {
this.backupsDisabledForAutoSaveables = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < BackupTracker.DISABLE_BACKUP_AUTO_SAVE_THRESHOLD;
}
/**
* Allows subclasses to conditionally opt-out of doing a backup, e.g. if
* auto save is enabled.
*/
protected abstract shouldScheduleBackup(workingCopy: IWorkingCopy): boolean;
/**
* Allows subclasses to control the delay before performing a backup from
* working copy content changes.
*/
protected abstract getBackupScheduleDelay(workingCopy: IWorkingCopy): number;
private scheduleBackup(workingCopy: IWorkingCopy): void {
if (this.backupsDisabledForAutoSaveables && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) {
return; // skip if auto save is enabled with a short delay
}
// Clear any running backup operation
dispose(this.pendingBackups.get(workingCopy));
this.pendingBackups.delete(workingCopy);
this.cancelBackup(workingCopy);
// subclass prevented backup for working copy
if (!this.shouldScheduleBackup(workingCopy)) {
return;
}
this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString());
// Schedule new backup
const cts = new CancellationTokenSource();
const handle = setTimeout(async () => {
if (cts.token.isCancellationRequested) {
return;
}
// Backup if dirty
if (workingCopy.isDirty()) {
this.logService.trace(`[backup tracker] creating backup`, workingCopy.resource.toString());
try {
const backup = await workingCopy.backup(cts.token);
if (cts.token.isCancellationRequested) {
return;
}
if (workingCopy.isDirty()) {
this.logService.trace(`[backup tracker] storing backup`, workingCopy.resource.toString());
await this.backupFileService.backup(workingCopy.resource, backup.content, this.getContentVersion(workingCopy), backup.meta, cts.token);
}
} catch (error) {
this.logService.error(error);
}
}
if (cts.token.isCancellationRequested) {
return;
}
// Clear disposable
this.pendingBackups.delete(workingCopy);
// Backup if dirty
if (workingCopy.isDirty()) {
this.logService.trace(`[backup tracker] running backup`, workingCopy.resource.toString());
const backup = await workingCopy.backup();
this.backupFileService.backup(workingCopy.resource, backup.content, this.getContentVersion(workingCopy), backup.meta);
}
}, BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY);
}, this.getBackupScheduleDelay(workingCopy));
// Keep in map for disposal as needed
this.pendingBackups.set(workingCopy, toDisposable(() => {
this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString());
cts.dispose(true);
clearTimeout(handle);
}));
}
@@ -151,12 +161,16 @@ export abstract class BackupTracker extends Disposable {
this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString());
// Clear any running backup operation
dispose(this.pendingBackups.get(workingCopy));
this.pendingBackups.delete(workingCopy);
this.cancelBackup(workingCopy);
// Forward to backup file service
this.backupFileService.discardBackup(workingCopy.resource);
}
private cancelBackup(workingCopy: IWorkingCopy): void {
dispose(this.pendingBackups.get(workingCopy));
this.pendingBackups.delete(workingCopy);
}
protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean>;
}

View File

@@ -5,7 +5,7 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
// Register Backup Tracker

View File

@@ -8,35 +8,65 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ConfirmResult, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { isMacintosh } from 'vs/base/common/platform';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
import { ILogService } from 'vs/platform/log/common/log';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { SaveReason } from 'vs/workbench/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CancellationToken } from 'vs/base/common/cancellation';
export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution {
// Delay creation of backups when working copy changes to avoid too much
// load on the backup service when the user is typing into the editor
private static readonly BACKUP_SCHEDULE_DELAY = 1000;
// Disable backup for when a short auto-save delay is configured with
// the rationale that the auto save will trigger a save periodically
// anway and thus creating frequent backups is not useful
//
// This will only apply to working copies that are not untitled where
// auto save is actually saving.
private static readonly DISABLE_BACKUP_AUTO_SAVE_THRESHOLD = 1500;
constructor(
@IBackupFileService backupFileService: IBackupFileService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILifecycleService lifecycleService: ILifecycleService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IDialogService private readonly dialogService: IDialogService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IElectronService private readonly electronService: IElectronService,
@INativeHostService private readonly nativeHostService: INativeHostService,
@ILogService logService: ILogService,
@IEditorService private readonly editorService: IEditorService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(backupFileService, filesConfigurationService, workingCopyService, logService, lifecycleService);
super(backupFileService, workingCopyService, logService, lifecycleService);
}
protected shouldScheduleBackup(workingCopy: IWorkingCopy): boolean {
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
return true; // always backup untitled
}
const autoSaveConfiguration = this.filesConfigurationService.getAutoSaveConfiguration();
if (typeof autoSaveConfiguration.autoSaveDelay === 'number' && autoSaveConfiguration.autoSaveDelay < NativeBackupTracker.DISABLE_BACKUP_AUTO_SAVE_THRESHOLD) {
return false; // skip backup when auto save is already enabled with a low delay
}
return true;
}
protected getBackupScheduleDelay(): number {
return NativeBackupTracker.BACKUP_SCHEDULE_DELAY;
}
protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
@@ -128,7 +158,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
case ShutdownReason.CLOSE:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else if (await this.electronService.getWindowCount() > 1 || isMacintosh) {
} else if (await this.nativeHostService.getWindowCount() > 1 || isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
} else {
doBackup = true; // backup if last window is closed on win/linux where the application quits right after
@@ -166,7 +196,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
// Backup does not exist
else {
const backup = await workingCopy.backup();
const backup = await workingCopy.backup(CancellationToken.None);
await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta);
backups.push(workingCopy);

View File

@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { hashPath } from 'vs/workbench/services/backup/node/backupFileService';
import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService';
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';

View File

@@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { hashPath } from 'vs/workbench/services/backup/node/backupFileService';
import { hashPath } from 'vs/workbench/services/backup/electron-browser/backupFileService';
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -28,13 +28,13 @@ import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/ele
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { toResource } from 'vs/base/test/common/utils';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopyBackup, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILogService } from 'vs/platform/log/common/log';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -45,6 +45,10 @@ import { TestFilesConfigurationService } from 'vs/workbench/test/browser/workben
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices';
import { CancellationToken } from 'vs/base/common/cancellation';
import { timeout } from 'vs/base/common/async';
import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace';
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer');
const backupHome = path.join(userdataDir, 'Backups');
@@ -63,15 +67,16 @@ class TestBackupTracker extends NativeBackupTracker {
@IFileDialogService fileDialogService: IFileDialogService,
@IDialogService dialogService: IDialogService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IElectronService electronService: IElectronService,
@INativeHostService nativeHostService: INativeHostService,
@ILogService logService: ILogService,
@IEditorService editorService: IEditorService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, electronService, logService, editorService, environmentService);
super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, nativeHostService, logService, editorService, environmentService);
}
// Reduce timeout for tests
BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY = 10;
protected getBackupScheduleDelay(): number {
return 10; // Reduce timeout for tests
}
}
@@ -105,6 +110,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests
// Delete any existing backups completely and then re-create it.
await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
await pfs.mkdirp(backupHome);
await pfs.mkdirp(workspaceBackupPath);
return pfs.writeFile(workspacesJsonPath, '');
});
@@ -214,6 +220,55 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests
tracker.dispose();
});
test('Track backups (custom)', async function () {
const [accessor, part, tracker] = await createTracker();
class TestBackupWorkingCopy extends TestWorkingCopy {
backupDelay = 0;
constructor(resource: URI) {
super(resource);
accessor.workingCopyService.registerWorkingCopy(this);
}
async backup(token: CancellationToken): Promise<IWorkingCopyBackup> {
await timeout(this.backupDelay);
return {};
}
}
const resource = toResource.call(this, '/path/custom.txt');
const customWorkingCopy = new TestBackupWorkingCopy(resource);
// Normal
customWorkingCopy.setDirty(true);
await accessor.backupFileService.joinBackupResource();
assert.equal(accessor.backupFileService.hasBackupSync(resource), true);
customWorkingCopy.setDirty(false);
customWorkingCopy.setDirty(true);
await accessor.backupFileService.joinBackupResource();
assert.equal(accessor.backupFileService.hasBackupSync(resource), true);
customWorkingCopy.setDirty(false);
await accessor.backupFileService.joinDiscardBackup();
assert.equal(accessor.backupFileService.hasBackupSync(resource), false);
// Cancellation
customWorkingCopy.setDirty(true);
await timeout(0);
customWorkingCopy.setDirty(false);
await accessor.backupFileService.joinDiscardBackup();
assert.equal(accessor.backupFileService.hasBackupSync(resource), false);
customWorkingCopy.dispose();
part.dispose();
tracker.dispose();
});
test('onWillShutdown - no veto if no dirty files', async function () {
const [accessor, part, tracker] = await createTracker();
@@ -450,7 +505,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests
// Set multiple windows if required
if (multipleWindows) {
accessor.electronService.windowCount = Promise.resolve(2);
accessor.nativeHostService.windowCount = Promise.resolve(2);
}
// Set cancel to force a veto if hot exit does not trigger

View File

@@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import { groupBy } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { compare } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { WorkspaceEditMetadata } from 'vs/editor/common/modes';
import { IProgress } from 'vs/platform/progress/common/progress';
import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
export class ResourceNotebookCellEdit extends ResourceEdit {
@@ -28,9 +29,11 @@ export class ResourceNotebookCellEdit extends ResourceEdit {
export class BulkCellEdits {
constructor(
private readonly _undoRedoGroup: UndoRedoGroup,
undoRedoSource: UndoRedoSource | undefined,
private readonly _progress: IProgress<void>,
private readonly _token: CancellationToken,
private readonly _edits: ResourceNotebookCellEdit[],
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorModelResolverService private readonly _notebookModelService: INotebookEditorModelResolverService,
) { }
@@ -39,6 +42,9 @@ export class BulkCellEdits {
const editsByNotebook = groupBy(this._edits, (a, b) => compare(a.resource.toString(), b.resource.toString()));
for (let group of editsByNotebook) {
if (this._token.isCancellationRequested) {
break;
}
const [first] = group;
const ref = await this._notebookModelService.resolve(first.resource);
@@ -50,8 +56,7 @@ export class BulkCellEdits {
// apply edits
const edits = group.map(entry => entry.cellEdit);
this._notebookService.transformEditsOutputs(ref.object.notebook, edits);
ref.object.notebook.applyEdits(ref.object.notebook.versionId, edits, true, undefined, () => undefined);
ref.object.notebook.applyEdits(ref.object.notebook.versionId, edits, true, undefined, () => undefined, this._undoRedoGroup);
ref.dispose();
this._progress.report(undefined);

View File

@@ -16,6 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { BulkTextEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkTextEdits';
import { BulkFileEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkFileEdits';
import { BulkCellEdits, ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
import { LinkedList } from 'vs/base/common/linkedList';
import { CancellationToken } from 'vs/base/common/cancellation';
class BulkEdit {
@@ -23,7 +26,10 @@ class BulkEdit {
private readonly _label: string | undefined,
private readonly _editor: ICodeEditor | undefined,
private readonly _progress: IProgress<IProgressStep>,
private readonly _token: CancellationToken,
private readonly _edits: ResourceEdit[],
private readonly _undoRedoGroup: UndoRedoGroup,
private readonly _undoRedoSource: UndoRedoSource | undefined,
@IInstantiationService private readonly _instaService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
) {
@@ -57,19 +63,24 @@ class BulkEdit {
}
}
this._progress.report({ total: this._edits.length });
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 1 }) };
// Show infinte progress when there is only 1 item since we do not know how long it takes
const increment = this._edits.length > 1 ? 0 : undefined;
this._progress.report({ increment, total: 100 });
// Increment by percentage points since progress API expects that
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 100 / this._edits.length }) };
let index = 0;
for (let range of ranges) {
if (this._token.isCancellationRequested) {
break;
}
const group = this._edits.slice(index, index + range);
if (group[0] instanceof ResourceFileEdit) {
await this._performFileEdits(<ResourceFileEdit[]>group, progress);
await this._performFileEdits(<ResourceFileEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress);
} else if (group[0] instanceof ResourceTextEdit) {
await this._performTextEdits(<ResourceTextEdit[]>group, progress);
await this._performTextEdits(<ResourceTextEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress);
} else if (group[0] instanceof ResourceNotebookCellEdit) {
await this._performCellEdits(<ResourceNotebookCellEdit[]>group, progress);
await this._performCellEdits(<ResourceNotebookCellEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress);
} else {
console.log('UNKNOWN EDIT');
}
@@ -77,21 +88,21 @@ class BulkEdit {
}
}
private async _performFileEdits(edits: ResourceFileEdit[], progress: IProgress<void>) {
private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>) {
this._logService.debug('_performFileEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), progress, edits);
const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), undoRedoGroup, undoRedoSource, progress, this._token, edits);
await model.apply();
}
private async _performTextEdits(edits: ResourceTextEdit[], progress: IProgress<void>): Promise<void> {
private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<void> {
this._logService.debug('_performTextEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._editor, progress, edits);
const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._editor, undoRedoGroup, undoRedoSource, progress, this._token, edits);
await model.apply();
}
private async _performCellEdits(edits: ResourceNotebookCellEdit[], progress: IProgress<void>): Promise<void> {
private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<void> {
this._logService.debug('_performCellEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkCellEdits, progress, edits);
const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits);
await model.apply();
}
}
@@ -100,6 +111,7 @@ export class BulkEditService implements IBulkEditService {
declare readonly _serviceBrand: undefined;
private readonly _activeUndoRedoGroups = new LinkedList<UndoRedoGroup>();
private _previewHandler?: IBulkEditPreviewHandler;
constructor(
@@ -145,11 +157,33 @@ export class BulkEditService implements IBulkEditService {
codeEditor = undefined;
}
// undo-redo-group: if a group id is passed then try to find it
// in the list of active edits. otherwise (or when not found)
// create a separate undo-redo-group
let undoRedoGroup: UndoRedoGroup | undefined;
let undoRedoGroupRemove = () => { };
if (typeof options?.undoRedoGroupId === 'number') {
for (let candidate of this._activeUndoRedoGroups) {
if (candidate.id === options.undoRedoGroupId) {
undoRedoGroup = candidate;
break;
}
}
}
if (!undoRedoGroup) {
undoRedoGroup = new UndoRedoGroup();
undoRedoGroupRemove = this._activeUndoRedoGroups.push(undoRedoGroup);
}
const bulkEdit = this._instaService.createInstance(
BulkEdit,
options?.quotableLabel || options?.label,
codeEditor, options?.progress ?? Progress.None,
edits
codeEditor,
options?.progress ?? Progress.None,
options?.token ?? CancellationToken.None,
edits,
undoRedoGroup,
options?.undoRedoSource
);
try {
@@ -160,6 +194,8 @@ export class BulkEditService implements IBulkEditService {
// console.log(err);
this._logService.error(err);
throw err;
} finally {
undoRedoGroupRemove();
}
}
}

View File

@@ -5,25 +5,35 @@
import { WorkspaceFileEditOptions } from 'vs/editor/common/modes';
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { IFileService, FileSystemProviderCapabilities, IFileContent } from 'vs/platform/files/common/files';
import { IProgress } from 'vs/platform/progress/common/progress';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer } from 'vs/base/common/buffer';
import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import * as resources from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
interface IFileOperationUndoRedoInfo {
undoRedoGroupId?: number;
isUndoing?: boolean;
}
interface IFileOperation {
uris: URI[];
perform(): Promise<IFileOperation>;
perform(token: CancellationToken): Promise<IFileOperation>;
}
class Noop implements IFileOperation {
readonly uris = [];
async perform() { return this; }
toString(): string {
return '(noop)';
}
}
class RenameOperation implements IFileOperation {
@@ -32,6 +42,7 @@ class RenameOperation implements IFileOperation {
readonly newUri: URI,
readonly oldUri: URI,
readonly options: WorkspaceFileEditOptions,
readonly undoRedoInfo: IFileOperationUndoRedoInfo,
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
@IFileService private readonly _fileService: IFileService,
) { }
@@ -40,13 +51,54 @@ class RenameOperation implements IFileOperation {
return [this.newUri, this.oldUri];
}
async perform(): Promise<IFileOperation> {
async perform(token: CancellationToken): Promise<IFileOperation> {
// rename
if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite });
return new RenameOperation(this.oldUri, this.newUri, this.options, this._workingCopyFileService, this._fileService);
await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
return new RenameOperation(this.oldUri, this.newUri, this.options, { isUndoing: true }, this._workingCopyFileService, this._fileService);
}
toString(): string {
const oldBasename = resources.basename(this.oldUri);
const newBasename = resources.basename(this.newUri);
if (oldBasename !== newBasename) {
return `(rename ${oldBasename} to ${newBasename})`;
}
return `(rename ${this.oldUri} to ${this.newUri})`;
}
}
class CopyOperation implements IFileOperation {
constructor(
readonly newUri: URI,
readonly oldUri: URI,
readonly options: WorkspaceFileEditOptions,
readonly undoRedoInfo: IFileOperationUndoRedoInfo,
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
@IFileService private readonly _fileService: IFileService,
@IInstantiationService private readonly _instaService: IInstantiationService
) { }
get uris() {
return [this.newUri, this.oldUri];
}
async perform(token: CancellationToken): Promise<IFileOperation> {
// copy
if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
await this._workingCopyFileService.copy([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, true);
}
toString(): string {
return `(copy ${this.oldUri} to ${this.newUri})`;
}
}
@@ -55,6 +107,7 @@ class CreateOperation implements IFileOperation {
constructor(
readonly newUri: URI,
readonly options: WorkspaceFileEditOptions,
readonly undoRedoInfo: IFileOperationUndoRedoInfo,
readonly contents: VSBuffer | undefined,
@IFileService private readonly _fileService: IFileService,
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
@@ -65,13 +118,22 @@ class CreateOperation implements IFileOperation {
return [this.newUri];
}
async perform(): Promise<IFileOperation> {
async perform(token: CancellationToken): Promise<IFileOperation> {
// create file
if (this.options.overwrite === undefined && this.options.ignoreIfExists && await this._fileService.exists(this.newUri)) {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite });
return this._instaService.createInstance(DeleteOperation, this.newUri, this.options);
if (this.options.folder) {
await this._workingCopyFileService.createFolder(this.newUri, { ...this.undoRedoInfo }, token);
} else {
await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
}
return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, !this.options.folder && !this.contents);
}
toString(): string {
return this.options.folder ? `create ${resources.basename(this.newUri)} folder`
: `(create ${resources.basename(this.newUri)} with ${this.contents?.byteLength || 0} bytes)`;
}
}
@@ -80,6 +142,8 @@ class DeleteOperation implements IFileOperation {
constructor(
readonly oldUri: URI,
readonly options: WorkspaceFileEditOptions,
readonly undoRedoInfo: IFileOperationUndoRedoInfo,
private readonly _undoesCreateOperation: boolean,
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
@IFileService private readonly _fileService: IFileService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -91,7 +155,7 @@ class DeleteOperation implements IFileOperation {
return [this.oldUri];
}
async perform(): Promise<IFileOperation> {
async perform(token: CancellationToken): Promise<IFileOperation> {
// delete file
if (!await this._fileService.exists(this.oldUri)) {
if (!this.options.ignoreIfNotExists) {
@@ -100,16 +164,26 @@ class DeleteOperation implements IFileOperation {
return new Noop();
}
let contents: VSBuffer | undefined;
try {
contents = (await this._fileService.readFile(this.oldUri)).value;
} catch (err) {
this._logService.critical(err);
let fileContent: IFileContent | undefined;
if (!this._undoesCreateOperation && !this.options.folder) {
try {
fileContent = await this._fileService.readFile(this.oldUri);
} catch (err) {
this._logService.critical(err);
}
}
const useTrash = this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue<boolean>('files.enableTrash');
await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive });
return this._instaService.createInstance(CreateOperation, this.oldUri, this.options, contents);
const useTrash = !this.options.skipTrashBin && this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue<boolean>('files.enableTrash');
await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive, ...this.undoRedoInfo }, token);
if (typeof this.options.maxSize === 'number' && fileContent && (fileContent?.size > this.options.maxSize)) {
return new Noop();
}
return this._instaService.createInstance(CreateOperation, this.oldUri, this.options, { isUndoing: true }, fileContent?.value);
}
toString(): string {
return `(delete ${resources.basename(this.oldUri)})`;
}
}
@@ -137,17 +211,24 @@ class FileUndoRedoElement implements IWorkspaceUndoRedoElement {
private async _reverse() {
for (let i = 0; i < this.operations.length; i++) {
const op = this.operations[i];
const undo = await op.perform();
const undo = await op.perform(CancellationToken.None);
this.operations[i] = undo;
}
}
public toString(): string {
return this.operations.map(op => String(op)).join(', ');
}
}
export class BulkFileEdits {
constructor(
private readonly _label: string,
private readonly _undoRedoGroup: UndoRedoGroup,
private readonly _undoRedoSource: UndoRedoSource | undefined,
private readonly _progress: IProgress<void>,
private readonly _token: CancellationToken,
private readonly _edits: ResourceFileEdit[],
@IInstantiationService private readonly _instaService: IInstantiationService,
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
@@ -155,27 +236,35 @@ export class BulkFileEdits {
async apply(): Promise<void> {
const undoOperations: IFileOperation[] = [];
const undoRedoInfo = { undoRedoGroupId: this._undoRedoGroup.id };
for (const edit of this._edits) {
this._progress.report(undefined);
if (this._token.isCancellationRequested) {
break;
}
const options = edit.options || {};
let op: IFileOperation | undefined;
if (edit.newResource && edit.oldResource) {
if (edit.newResource && edit.oldResource && !options.copy) {
// rename
op = this._instaService.createInstance(RenameOperation, edit.newResource, edit.oldResource, options);
op = this._instaService.createInstance(RenameOperation, edit.newResource, edit.oldResource, options, undoRedoInfo);
} else if (edit.newResource && edit.oldResource && options.copy) {
op = this._instaService.createInstance(CopyOperation, edit.newResource, edit.oldResource, options, undoRedoInfo);
} else if (!edit.newResource && edit.oldResource) {
// delete file
op = this._instaService.createInstance(DeleteOperation, edit.oldResource, options);
op = this._instaService.createInstance(DeleteOperation, edit.oldResource, options, undoRedoInfo, false);
} else if (edit.newResource && !edit.oldResource) {
// create file
op = this._instaService.createInstance(CreateOperation, edit.newResource, options, undefined);
op = this._instaService.createInstance(CreateOperation, edit.newResource, options, undoRedoInfo, undefined);
}
if (op) {
const undoOp = await op.perform();
const undoOp = await op.perform(this._token);
undoOperations.push(undoOp);
}
this._progress.report(undefined);
}
this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations));
this._undoRedoService.pushElement(new FileUndoRedoElement(this._label, undoOperations), this._undoRedoGroup, this._undoRedoSource);
}
}

View File

@@ -14,11 +14,12 @@ import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'v
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IProgress } from 'vs/platform/progress/common/progress';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
import { SingleModelEditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack';
import { ResourceMap } from 'vs/base/common/map';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { CancellationToken } from 'vs/base/common/cancellation';
type ValidationResult = { canApply: true } | { canApply: false, reason: URI };
@@ -39,6 +40,18 @@ class ModelEditTask implements IDisposable {
this._modelReference.dispose();
}
isNoOp() {
if (this._edits.length > 0) {
// contains textual edits
return false;
}
if (this._newEol !== undefined && this._newEol !== this.model.getEndOfLineSequence()) {
// contains an eol change that is a real change
return false;
}
return true;
}
addEdit(resourceEdit: ResourceTextEdit): void {
this._expectedModelVersionId = resourceEdit.versionId;
const { textEdit } = resourceEdit;
@@ -121,7 +134,10 @@ export class BulkTextEdits {
constructor(
private readonly _label: string,
private readonly _editor: ICodeEditor | undefined,
private readonly _undoRedoGroup: UndoRedoGroup,
private readonly _undoRedoSource: UndoRedoSource | undefined,
private readonly _progress: IProgress<void>,
private readonly _token: CancellationToken,
edits: ResourceTextEdit[],
@IEditorWorkerService private readonly _editorWorker: IEditorWorkerService,
@IModelService private readonly _modelService: IModelService,
@@ -209,6 +225,9 @@ export class BulkTextEdits {
this._validateBeforePrepare();
const tasks = await this._createEditsTasks();
if (this._token.isCancellationRequested) {
return;
}
try {
const validation = this._validateTasks(tasks);
@@ -217,19 +236,21 @@ export class BulkTextEdits {
}
if (tasks.length === 1) {
// This edit touches a single model => keep things simple
for (const task of tasks) {
task.model.pushStackElement();
const task = tasks[0];
if (!task.isNoOp()) {
const singleModelEditStackElement = new SingleModelEditStackElement(task.model, task.getBeforeCursorState());
this._undoRedoService.pushElement(singleModelEditStackElement, this._undoRedoGroup, this._undoRedoSource);
task.apply();
task.model.pushStackElement();
this._progress.report(undefined);
singleModelEditStackElement.close();
}
this._progress.report(undefined);
} else {
// prepare multi model undo element
const multiModelEditStackElement = new MultiModelEditStackElement(
this._label,
tasks.map(t => new SingleModelEditStackElement(t.model, t.getBeforeCursorState()))
);
this._undoRedoService.pushElement(multiModelEditStackElement);
this._undoRedoService.pushElement(multiModelEditStackElement, this._undoRedoGroup, this._undoRedoSource);
for (const task of tasks) {
task.apply();
this._progress.report(undefined);

View File

@@ -11,6 +11,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { ILogService } from 'vs/platform/log/common/log';
export class ConflictDetector {
@@ -24,6 +26,7 @@ export class ConflictDetector {
edits: ResourceEdit[],
@IFileService fileService: IFileService,
@IModelService modelService: IModelService,
@ILogService logService: ILogService,
) {
const _workspaceEditResources = new ResourceMap<boolean>();
@@ -46,27 +49,25 @@ export class ConflictDetector {
} else if (edit.oldResource) {
_workspaceEditResources.set(edit.oldResource, true);
}
} else if (edit instanceof ResourceNotebookCellEdit) {
_workspaceEditResources.set(edit.resource, true);
} else {
//todo@jrieken
console.log('UNKNOWN EDIT TYPE');
logService.warn('UNKNOWN edit type', edit);
}
}
// listen to file changes
this._disposables.add(fileService.onDidFilesChange(e => {
for (let change of e.changes) {
if (modelService.getModel(change.resource)) {
// ignore changes for which a model exists
// because we have a better check for models
continue;
}
// conflict
if (_workspaceEditResources.has(change.resource)) {
this._conflicts.set(change.resource, true);
for (const uri of _workspaceEditResources.keys()) {
// conflict happens when a file that we are working
// on changes on disk. ignore changes for which a model
// exists because we have a better check for models
if (!modelService.getModel(uri) && e.contains(uri)) {
this._conflicts.set(uri, true);
this._onDidConflict.fire(this);
break;
}
}
}));

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
@@ -28,6 +28,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
async function getBulkEditPane(viewsService: IViewsService): Promise<BulkEditPane | undefined> {
const view = await viewsService.openView(BulkEditPane.ID, true);
@@ -169,7 +170,7 @@ registerAction2(class ApplyAction extends Action2 {
id: 'refactorPreview.apply',
title: { value: localize('apply', "Apply Refactoring"), original: 'Apply Refactoring' },
category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' },
icon: { id: 'codicon/check' },
icon: Codicon.check,
precondition: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, BulkEditPane.ctxHasCheckedChanges),
menu: [{
id: MenuId.BulkEditTitle,
@@ -203,7 +204,7 @@ registerAction2(class DiscardAction extends Action2 {
id: 'refactorPreview.discard',
title: { value: localize('Discard', "Discard Refactoring"), original: 'Discard Refactoring' },
category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' },
icon: { id: 'codicon/clear-all' },
icon: Codicon.clearAll,
precondition: BulkEditPreviewContribution.ctxEnabled,
menu: [{
id: MenuId.BulkEditTitle,
@@ -264,7 +265,7 @@ registerAction2(class GroupByFile extends Action2 {
id: 'refactorPreview.groupByFile',
title: { value: localize('groupByFile', "Group Changes By File"), original: 'Group Changes By File' },
category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' },
icon: { id: 'codicon/ungroup-by-ref-type' },
icon: Codicon.ungroupByRefType,
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate(), BulkEditPreviewContribution.ctxEnabled),
menu: [{
id: MenuId.BulkEditTitle,
@@ -291,7 +292,7 @@ registerAction2(class GroupByType extends Action2 {
id: 'refactorPreview.groupByType',
title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' },
category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' },
icon: { id: 'codicon/group-by-ref-type' },
icon: Codicon.groupByRefType,
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile, BulkEditPreviewContribution.ctxEnabled),
menu: [{
id: MenuId.BulkEditTitle,
@@ -318,7 +319,7 @@ registerAction2(class ToggleGrouping extends Action2 {
id: 'refactorPreview.toggleGrouping',
title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' },
category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' },
icon: { id: 'codicon/list-tree' },
icon: Codicon.listTree,
toggled: BulkEditPane.ctxGroupByFile.negate(),
precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPreviewContribution.ctxEnabled),
menu: [{
@@ -341,6 +342,8 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).regi
BulkEditPreviewContribution, LifecyclePhase.Ready
);
const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.'));
const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: BulkEditPane.ID,
name: localize('panel', "Refactor Preview"),
@@ -349,7 +352,7 @@ const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.V
ViewPaneContainer,
[BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]
),
icon: Codicon.lightbulb.classNames,
icon: refactorPreviewViewIcon,
storageId: BulkEditPane.ID
}, ViewContainerLocation.Panel);
@@ -358,5 +361,5 @@ Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews
name: localize('panel', "Refactor Preview"),
when: BulkEditPreviewContribution.ctxEnabled,
ctorDescriptor: new SyncDescriptor(BulkEditPane),
containerIcon: Codicon.lightbulb.classNames,
containerIcon: refactorPreviewViewIcon,
}], container);

View File

@@ -12,7 +12,7 @@ import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeServ
import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry';
import { localize } from 'vs/nls';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview';
import { ILabelService } from 'vs/platform/label/common/label';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -26,7 +26,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl
import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { basename } from 'vs/base/common/resources';
import { basename, dirname } from 'vs/base/common/resources';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IAction } from 'vs/base/common/actions';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
@@ -34,7 +34,7 @@ import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -128,7 +128,7 @@ export class BulkEditPane extends ViewPane {
this._tree = <WorkbenchAsyncDataTree<BulkFileOperations, BulkEditElement, FuzzyScore>>this._instaService.createInstance(
WorkbenchAsyncDataTree, this.id, treeContainer,
new BulkEditDelegate(),
[new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer, resourceLabels), new CategoryElementRenderer()],
[this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer, resourceLabels), this._instaService.createInstance(CategoryElementRenderer)],
this._treeDataSource,
{
accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider),
@@ -293,7 +293,7 @@ export class BulkEditPane extends ViewPane {
this._setTreeInput(input);
// (3) remember preference
this._storageService.store(BulkEditPane._memGroupByFile, this._treeDataSource.groupByFile, StorageScope.GLOBAL);
this._storageService.store(BulkEditPane._memGroupByFile, this._treeDataSource.groupByFile, StorageScope.GLOBAL, StorageTarget.USER);
this._ctxGroupByFile.set(this._treeDataSource.groupByFile);
}
}
@@ -356,15 +356,16 @@ export class BulkEditPane extends ViewPane {
leftResource,
rightResource: previewUri,
label,
description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }),
options
});
}, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
}
private _onContextMenu(e: ITreeContextMenuEvent<any>): void {
const menu = this._menuService.createMenu(MenuId.BulkEditContext, this._contextKeyService);
const actions: IAction[] = [];
const disposable = createAndFillInContextMenuActions(menu, undefined, actions, this._contextMenuService);
const disposable = createAndFillInContextMenuActions(menu, undefined, actions);
this._contextMenuService.showContextMenu({
getActions: () => actions,

View File

@@ -22,6 +22,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { localize } from 'vs/nls';
import { extUri } from 'vs/base/common/resources';
import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { Codicon } from 'vs/base/common/codicons';
export class CheckedStates<T extends object> {
@@ -116,7 +117,7 @@ export class BulkCategory {
private static readonly _defaultMetadata = Object.freeze({
label: localize('default', "Other"),
icon: { id: 'codicon/symbol-file' },
icon: Codicon.symbolFile,
needsConfirmation: false
});

View File

@@ -21,7 +21,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import type { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { basename } from 'vs/base/common/resources';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { compare } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
@@ -386,6 +386,8 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
readonly templateId: string = CategoryElementRenderer.id;
constructor(@IThemeService private readonly _themeService: IThemeService) { }
renderTemplate(container: HTMLElement): CategoryElementTemplate {
return new CategoryElementTemplate(container);
}
@@ -394,12 +396,15 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
template.icon.style.setProperty('--background-dark', null);
template.icon.style.setProperty('--background-light', null);
template.icon.style.color = '';
const { metadata } = node.element.category;
if (ThemeIcon.isThemeIcon(metadata.iconPath)) {
// css
const className = ThemeIcon.asClassName(metadata.iconPath);
template.icon.className = className ? `theme-icon ${className}` : '';
template.icon.style.color = metadata.iconPath.color ? this._themeService.getColorTheme().getColor(metadata.iconPath.color.id)?.toString() ?? '' : '';
} else if (URI.isUri(metadata.iconPath)) {
// background-image
@@ -532,7 +537,7 @@ class TextEditElementTemplate {
private readonly _icon: HTMLDivElement;
private readonly _label: HighlightedLabel;
constructor(container: HTMLElement) {
constructor(container: HTMLElement, @IThemeService private readonly _themeService: IThemeService) {
container.classList.add('textedit');
this._checkbox = document.createElement('input');
@@ -597,6 +602,8 @@ class TextEditElementTemplate {
// css
const className = ThemeIcon.asClassName(iconPath);
this._icon.className = className ? `theme-icon ${className}` : '';
this._icon.style.color = iconPath.color ? this._themeService.getColorTheme().getColor(iconPath.color.id)?.toString() ?? '' : '';
} else if (URI.isUri(iconPath)) {
// background-image
@@ -623,8 +630,10 @@ export class TextEditElementRenderer implements ITreeRenderer<TextEditElement, F
readonly templateId: string = TextEditElementRenderer.id;
constructor(@IThemeService private readonly _themeService: IThemeService) { }
renderTemplate(container: HTMLElement): TextEditElementTemplate {
return new TextEditElementTemplate(container);
return new TextEditElementTemplate(container, this._themeService);
}
renderElement({ element }: ITreeNode<TextEditElement, FuzzyScore>, _index: number, template: TextEditElementTemplate): void {

View File

@@ -18,12 +18,13 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { PeekContext } from 'vs/editor/contrib/peekView/peekView';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Range } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { registerIcon, Codicon } from 'vs/base/common/codicons';
import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
const _ctxHasCallHierarchyProvider = new RawContextKey<boolean>('editorHasCallHierarchyProvider', false);
const _ctxCallHierarchyVisible = new RawContextKey<boolean>('callHierarchyVisible', false);
@@ -128,7 +129,7 @@ class CallHierarchyController implements IEditorContribution {
this._widget.showLoading();
this._sessionDisposables.add(this._widget.onDidClose(() => {
this.endCallHierarchy();
this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL);
this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL, StorageTarget.USER);
}));
this._sessionDisposables.add({ dispose() { cts.dispose(true); } });
this._sessionDisposables.add(this._widget);
@@ -207,7 +208,7 @@ registerAction2(class extends EditorAction2 {
super({
id: 'editor.showIncomingCalls',
title: { value: localize('title.incoming', "Show Incoming Calls"), original: 'Show Incoming Calls' },
icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming),
icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming, localize('showIncomingCallsIcons', 'Icon for incoming calls in the call hierarchy view.')),
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -232,7 +233,7 @@ registerAction2(class extends EditorAction2 {
super({
id: 'editor.showOutgoingCalls',
title: { value: localize('title.outgoing', "Show Outgoing Calls"), original: 'Show Outgoing Calls' },
icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing),
icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing, localize('showOutgoingCallsIcon', 'Icon for outgoing calls in the call hierarchy view.')),
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,

View File

@@ -28,7 +28,7 @@ import { registerThemingParticipant, themeColorFromId, IThemeService, IColorThem
import { IPosition } from 'vs/editor/common/core/position';
import { IAction } from 'vs/base/common/actions';
import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Color } from 'vs/base/common/color';
import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { URI } from 'vs/base/common/uri';
@@ -45,7 +45,7 @@ const enum State {
class LayoutInfo {
static store(info: LayoutInfo, storageService: IStorageService): void {
storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL);
storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
static retrieve(storageService: IStorageService): LayoutInfo {
@@ -150,7 +150,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
protected _fillBody(parent: HTMLElement): void {
this._layoutInfo = LayoutInfo.retrieve(this._storageService);
this._dim = { height: 0, width: 0 };
this._dim = new Dimension(0, 0);
this._parent = parent;
parent.classList.add('call-hierarchy');
@@ -277,7 +277,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
this.dispose();
this._editorService.openEditor({
resource: e.element.item.uri,
options: { selection: e.element.item.selectionRange }
options: { selection: e.element.item.selectionRange, pinned: true }
});
}
}));
@@ -289,7 +289,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
this.dispose();
this._editorService.openEditor({
resource: element.item.uri,
options: { selection: element.item.selectionRange }
options: { selection: element.item.selectionRange, pinned: true }
});
}
}));
@@ -427,7 +427,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
protected _doLayoutBody(height: number, width: number): void {
if (this._dim.height !== height || this._dim.width !== width) {
super._doLayoutBody(height, width);
this._dim = { height, width };
this._dim = new Dimension(width, height);
this._layoutInfo.height = this._viewZone ? this._viewZone.heightInLines : this._layoutInfo.height;
this._splitView.layout(width);
this._splitView.resizeView(0, width * this._layoutInfo.ratio);

View File

@@ -114,6 +114,7 @@ export class CallRenderer implements ITreeRenderer<Call, FuzzyScore, CallRenderi
const label = new IconLabel(container, { supportHighlights: true });
return new CallRenderingTemplate(icon, label);
}
renderElement(node: ITreeNode<Call, FuzzyScore>, _index: number, template: CallRenderingTemplate): void {
const { element, filterData } = node;
const deprecated = element.item.tags?.includes(SymbolTag.Deprecated);

View File

@@ -8,18 +8,18 @@ import * as path from 'vs/base/common/path';
import * as cp from 'child_process';
import * as pfs from 'vs/base/node/pfs';
import * as extpath from 'vs/base/node/extpath';
import * as platform from 'vs/base/common/platform';
import { promisify } from 'util';
import { Action } from 'vs/base/common/actions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import product from 'vs/platform/product/common/product';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { ILogService } from 'vs/platform/log/common/log';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { FileAccess } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
function ignore<T>(code: string, value: T): (err: any) => Promise<T> {
return err => err.code === code ? Promise.resolve<T>(value) : Promise.reject<T>(err);
@@ -28,7 +28,7 @@ function ignore<T>(code: string, value: T): (err: any) => Promise<T> {
let _source: string | null = null;
function getSource(): string {
if (!_source) {
const root = getPathFromAmdModule(require, '');
const root = FileAccess.asFileUri('', require).fsPath;
_source = path.resolve(root, '..', 'bin', 'code');
}
return _source;
@@ -38,44 +38,65 @@ function isAvailable(): Promise<boolean> {
return Promise.resolve(pfs.exists(getSource()));
}
class InstallAction extends Action {
const category = nls.localize('shellCommand', "Shell Command");
static readonly ID = 'workbench.action.installCommandLine';
static readonly LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName);
class InstallAction extends Action2 {
constructor(
id: string,
label: string,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
@ILogService private readonly logService: ILogService
) {
super(id, label);
constructor() {
super({
id: 'workbench.action.installCommandLine',
title: {
value: nls.localize('install', "Install '{0}' command in PATH", product.applicationName),
original: `Shell Command: Install \'${product.applicationName}\' command in PATH`
},
category,
f1: true,
precondition: ContextKeyExpr.and(IsMacNativeContext, ContextKeyExpr.equals('remoteName', ''))
});
}
private get target(): string {
return `/usr/local/bin/${product.applicationName}`;
}
run(accessor: ServicesAccessor): Promise<void> {
const productService = accessor.get(IProductService);
const notificationService = accessor.get(INotificationService);
const logService = accessor.get(ILogService);
const dialogService = accessor.get(IDialogService);
const target = `/usr/local/bin/${productService.applicationName}`;
run(): Promise<void> {
return isAvailable().then(isAvailable => {
if (!isAvailable) {
const message = nls.localize('not available', "This command is not available");
this.notificationService.info(message);
notificationService.info(message);
return undefined;
}
return this.isInstalled()
return this.isInstalled(target)
.then(isInstalled => {
if (!isAvailable || isInstalled) {
return Promise.resolve(null);
} else {
return pfs.unlink(this.target)
return pfs.unlink(target)
.then(undefined, ignore('ENOENT', null))
.then(() => pfs.symlink(getSource(), this.target))
.then(() => pfs.symlink(getSource(), target))
.then(undefined, err => {
if (err.code === 'EACCES' || err.code === 'ENOENT') {
return this.createBinFolderAndSymlinkAsAdmin();
return new Promise<void>((resolve, reject) => {
const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(result => {
switch (result.choice) {
case 0 /* OK */:
const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + target + '\'\\" with administrator privileges"';
promisify(cp.exec)(command, {})
.then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'."))))
.then(() => resolve(), reject);
break;
case 1 /* Cancel */:
reject(new Error(nls.localize('aborted', "Aborted")));
break;
}
});
});
}
return Promise.reject(err);
@@ -83,112 +104,84 @@ class InstallAction extends Action {
}
})
.then(() => {
this.logService.trace('cli#install', this.target);
this.notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", product.applicationName));
logService.trace('cli#install', target);
notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", productService.applicationName));
});
});
}
private isInstalled(): Promise<boolean> {
return pfs.lstat(this.target)
private isInstalled(target: string): Promise<boolean> {
return pfs.lstat(target)
.then(stat => stat.isSymbolicLink())
.then(() => extpath.realpath(this.target))
.then(() => extpath.realpath(target))
.then(link => link === getSource())
.then(undefined, ignore('ENOENT', false));
}
private createBinFolderAndSymlinkAsAdmin(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
this.dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(result => {
switch (result.choice) {
case 0 /* OK */:
const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + this.target + '\'\\" with administrator privileges"';
promisify(cp.exec)(command, {})
.then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'."))))
.then(() => resolve(), reject);
break;
case 1 /* Cancel */:
reject(new Error(nls.localize('aborted', "Aborted")));
break;
}
});
});
}
}
class UninstallAction extends Action {
class UninstallAction extends Action2 {
static readonly ID = 'workbench.action.uninstallCommandLine';
static readonly LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName);
constructor(
id: string,
label: string,
@INotificationService private readonly notificationService: INotificationService,
@ILogService private readonly logService: ILogService,
@IDialogService private readonly dialogService: IDialogService
) {
super(id, label);
constructor() {
super({
id: 'workbench.action.uninstallCommandLine',
title: {
value: nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName),
original: `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`
},
category,
f1: true,
precondition: ContextKeyExpr.and(IsMacNativeContext, ContextKeyExpr.equals('remoteName', ''))
});
}
private get target(): string {
return `/usr/local/bin/${product.applicationName}`;
}
run(accessor: ServicesAccessor): Promise<void> {
const productService = accessor.get(IProductService);
const notificationService = accessor.get(INotificationService);
const logService = accessor.get(ILogService);
const dialogService = accessor.get(IDialogService);
const target = `/usr/local/bin/${productService.applicationName}`;
run(): Promise<void> {
return isAvailable().then(isAvailable => {
if (!isAvailable) {
const message = nls.localize('not available', "This command is not available");
this.notificationService.info(message);
notificationService.info(message);
return undefined;
}
const uninstall = () => {
return pfs.unlink(this.target)
return pfs.unlink(target)
.then(undefined, ignore('ENOENT', null));
};
return uninstall().then(undefined, err => {
if (err.code === 'EACCES') {
return this.deleteSymlinkAsAdmin();
return new Promise<void>(async (resolve, reject) => {
const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
const { choice } = await dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 });
switch (choice) {
case 0 /* OK */:
const command = 'osascript -e "do shell script \\"rm \'' + target + '\'\\" with administrator privileges"';
promisify(cp.exec)(command, {})
.then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", target))))
.then(() => resolve(), reject);
break;
case 1 /* Cancel */:
reject(new Error(nls.localize('aborted', "Aborted")));
break;
}
});
}
return Promise.reject(err);
}).then(() => {
this.logService.trace('cli#uninstall', this.target);
this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName));
logService.trace('cli#uninstall', target);
notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", productService.applicationName));
});
});
}
private deleteSymlinkAsAdmin(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
const { choice } = await this.dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 });
switch (choice) {
case 0 /* OK */:
const command = 'osascript -e "do shell script \\"rm \'' + this.target + '\'\\" with administrator privileges"';
promisify(cp.exec)(command, {})
.then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target))))
.then(() => resolve(), reject);
break;
case 1 /* Cancel */:
reject(new Error(nls.localize('aborted', "Aborted")));
break;
}
});
}
}
if (platform.isMacintosh) {
const category = nls.localize('shellCommand', "Shell Command");
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(InstallAction), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category);
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(UninstallAction), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category);
}
registerAction2(InstallAction);
registerAction2(UninstallAction);

View File

@@ -5,7 +5,7 @@
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { CodeActionsContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/codeActionsContribution';

View File

@@ -16,12 +16,12 @@ import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { EditorCommand, registerEditorContribution, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -30,6 +30,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { contrastBorder, editorWidgetBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey<boolean>('accessibilityHelpWidgetVisible', false);
@@ -116,7 +120,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget {
if (e.equals(KeyMod.CtrlCmd | KeyCode.KEY_E)) {
alert(nls.localize('emergencyConfOn', "Now changing the setting `editor.accessibilitySupport` to 'on'."));
this._configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
this._configurationService.updateValue('editor.accessibilitySupport', 'on');
e.preventDefault();
e.stopPropagation();
@@ -273,16 +277,15 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget {
}
}
class ShowAccessibilityHelpAction extends EditorAction {
// Show Accessibility Help is a workench command so it can also be shown when there is no editor open #108850
class ShowAccessibilityHelpAction extends Action2 {
constructor() {
super({
id: 'editor.action.showAccessibilityHelp',
label: nls.localize('ShowAccessibilityHelpAction', "Show Accessibility Help"),
alias: 'Show Accessibility Help',
precondition: undefined,
kbOpts: {
kbExpr: EditorContextKeys.focus,
title: { value: nls.localize('ShowAccessibilityHelpAction', "Show Accessibility Help"), original: 'Show Accessibility Help' },
f1: true,
keybinding: {
primary: KeyMod.Alt | KeyCode.F1,
weight: KeybindingWeight.EditorContrib,
linux: {
@@ -293,16 +296,26 @@ class ShowAccessibilityHelpAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
let controller = AccessibilityHelpController.get(editor);
if (controller) {
controller.show();
async run(accessor: ServicesAccessor): Promise<void> {
const commandService = accessor.get(ICommandService);
const editorService = accessor.get(ICodeEditorService);
let activeEditor = editorService.getActiveCodeEditor();
if (!activeEditor) {
await commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID);
}
activeEditor = editorService.getActiveCodeEditor();
if (activeEditor) {
const controller = AccessibilityHelpController.get(activeEditor);
if (controller) {
controller.show();
}
}
}
}
registerEditorContribution(AccessibilityHelpController.ID, AccessibilityHelpController);
registerEditorAction(ShowAccessibilityHelpAction);
registerAction2(ShowAccessibilityHelpAction);
const AccessibilityHelpCommand = EditorCommand.bindToContribution<AccessibilityHelpController>(AccessibilityHelpController.get);

View File

@@ -50,7 +50,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont
[{
label: nls.localize('removeTimeout', "Remove limit"),
run: () => {
this._configurationService.updateValue('diffEditor.maxComputationTime', 0, ConfigurationTarget.USER);
this._configurationService.updateValue('diffEditor.maxComputationTime', 0);
}
}],
{}

View File

@@ -12,7 +12,7 @@ import { Delayer } from 'vs/base/common/async';
import { KeyCode } from 'vs/base/common/keyCodes';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { SimpleButton, findCloseIcon, findNextMatchIcon, findPreviousMatchIcon, findReplaceIcon, findReplaceAllIcon } from 'vs/editor/contrib/find/findWidget';
import { SimpleButton, findNextMatchIcon, findPreviousMatchIcon, findReplaceIcon, findReplaceAllIcon } from 'vs/editor/contrib/find/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';
@@ -21,6 +21,7 @@ import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/b
import { ReplaceInput, IReplaceInputStyles } from 'vs/base/browser/ui/findinput/replaceInput';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
@@ -140,12 +141,13 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._findInput.setRegex(this._state.isRegex);
this._findInput.setWholeWords(this._state.wholeWord);
this._findInput.setCaseSensitive(this._state.matchCase);
this._replaceInput.setPreserveCase(this._state.preserveCase);
this.findFirst();
}));
this.prevBtn = this._register(new SimpleButton({
label: NLS_PREVIOUS_MATCH_BTN_LABEL,
className: findPreviousMatchIcon.classNames,
icon: findPreviousMatchIcon,
onTrigger: () => {
this.find(true);
}
@@ -153,7 +155,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this.nextBtn = this._register(new SimpleButton({
label: NLS_NEXT_MATCH_BTN_LABEL,
className: findNextMatchIcon.classNames,
icon: findNextMatchIcon,
onTrigger: () => {
this.find(false);
}
@@ -161,7 +163,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
const closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL,
className: findCloseIcon.classNames,
icon: widgetClose,
onTrigger: () => {
this.hide();
}
@@ -219,7 +221,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._replaceBtn = this._register(new SimpleButton({
label: NLS_REPLACE_BTN_LABEL,
className: findReplaceIcon.classNames,
icon: findReplaceIcon,
onTrigger: () => {
this.replaceOne();
}
@@ -228,7 +230,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
// Replace all button
this._replaceAllBtn = this._register(new SimpleButton({
label: NLS_REPLACE_ALL_BTN_LABEL,
className: findReplaceAllIcon.classNames,
icon: findReplaceAllIcon,
onTrigger: () => {
this.replaceAll();
}
@@ -314,7 +316,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible);
this._domNode.classList.toggle('replaceToggled', this._isReplaceVisible);
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
}
@@ -345,8 +347,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this.updateButtons(this.foundMatch);
setTimeout(() => {
dom.addClass(this._domNode, 'visible');
dom.addClass(this._domNode, 'visible-transition');
this._domNode.classList.add('visible', 'visible-transition');
this._domNode.setAttribute('aria-hidden', 'false');
this._findInput.select();
}, 0);
@@ -364,8 +365,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._isVisible = true;
setTimeout(() => {
dom.addClass(this._domNode, 'visible');
dom.addClass(this._domNode, 'visible-transition');
this._domNode.classList.add('visible', 'visible-transition');
this._domNode.setAttribute('aria-hidden', 'false');
this.focus();
@@ -391,8 +391,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
}
setTimeout(() => {
dom.addClass(this._domNode, 'visible');
dom.addClass(this._domNode, 'visible-transition');
this._domNode.classList.add('visible', 'visible-transition');
this._domNode.setAttribute('aria-hidden', 'false');
this._updateButtons();
@@ -402,13 +401,13 @@ export abstract class SimpleFindReplaceWidget extends Widget {
public hide(): void {
if (this._isVisible) {
dom.removeClass(this._domNode, 'visible-transition');
this._domNode.classList.remove('visible-transition');
this._domNode.setAttribute('aria-hidden', 'true');
// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
setTimeout(() => {
this._isVisible = false;
this.updateButtons(this.foundMatch);
dom.removeClass(this._domNode, 'visible');
this._domNode.classList.remove('visible');
}, 200);
}
}
@@ -454,6 +453,6 @@ registerThemingParticipant((theme, collector) => {
const widgetShadowColor = theme.getColor(widgetShadow);
if (widgetShadowColor) {
collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`);
}
});

View File

@@ -12,12 +12,13 @@ import { Delayer } from 'vs/base/common/async';
import { KeyCode } from 'vs/base/common/keyCodes';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon, findCloseIcon } from 'vs/editor/contrib/find/findWidget';
import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon } from 'vs/editor/contrib/find/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 { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
@@ -94,7 +95,7 @@ export abstract class SimpleFindWidget extends Widget {
this.prevBtn = this._register(new SimpleButton({
label: NLS_PREVIOUS_MATCH_BTN_LABEL,
className: findPreviousMatchIcon.classNames,
icon: findPreviousMatchIcon,
onTrigger: () => {
this.find(true);
}
@@ -102,7 +103,7 @@ export abstract class SimpleFindWidget extends Widget {
this.nextBtn = this._register(new SimpleButton({
label: NLS_NEXT_MATCH_BTN_LABEL,
className: findNextMatchIcon.classNames,
icon: findNextMatchIcon,
onTrigger: () => {
this.find(false);
}
@@ -110,7 +111,7 @@ export abstract class SimpleFindWidget extends Widget {
const closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL,
className: findCloseIcon.classNames,
icon: widgetClose,
onTrigger: () => {
this.hide();
}
@@ -212,8 +213,7 @@ export abstract class SimpleFindWidget extends Widget {
this.updateButtons(this.foundMatch);
setTimeout(() => {
dom.addClass(this._innerDomNode, 'visible');
dom.addClass(this._innerDomNode, 'visible-transition');
this._innerDomNode.classList.add('visible', 'visible-transition');
this._innerDomNode.setAttribute('aria-hidden', 'false');
this._findInput.select();
}, 0);
@@ -227,21 +227,20 @@ export abstract class SimpleFindWidget extends Widget {
this._isVisible = true;
setTimeout(() => {
dom.addClass(this._innerDomNode, 'visible');
dom.addClass(this._innerDomNode, 'visible-transition');
this._innerDomNode.classList.add('visible', 'visible-transition');
this._innerDomNode.setAttribute('aria-hidden', 'false');
}, 0);
}
public hide(): void {
if (this._isVisible) {
dom.removeClass(this._innerDomNode, 'visible-transition');
this._innerDomNode.classList.remove('visible-transition');
this._innerDomNode.setAttribute('aria-hidden', 'true');
// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
setTimeout(() => {
this._isVisible = false;
this.updateButtons(this.foundMatch);
dom.removeClass(this._innerDomNode, 'visible');
this._innerDomNode.classList.remove('visible');
}, 200);
}
}
@@ -287,6 +286,6 @@ registerThemingParticipant((theme, collector) => {
const widgetShadowColor = theme.getColor(widgetShadow);
if (widgetShadowColor) {
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`);
}
});

View File

@@ -31,10 +31,26 @@
.tiw-metadata-value {
font-family: var(--monaco-monospace-font);
text-align: right;
word-break: break-word;
}
.tiw-metadata-values {
list-style: none;
max-height: 300px;
overflow-y: auto;
margin-right: -10px;
padding-left: 0;
}
.tiw-metadata-values > .tiw-metadata-value {
margin-right: 10px;
}
.tiw-metadata-key {
width: 1px;
min-width: 150px;
padding-right: 10px;
white-space: nowrap;
vertical-align: top;
}

View File

@@ -594,11 +594,17 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
theme.resolveScopes(definition, scopesDefinition);
const matchingRule = scopesDefinition[property];
if (matchingRule && scopesDefinition.scope) {
const strScopes = Array.isArray(matchingRule.scope) ? matchingRule.scope.join(', ') : String(matchingRule.scope);
const scopes = $('ul.tiw-metadata-values');
const strScopes = Array.isArray(matchingRule.scope) ? matchingRule.scope : [String(matchingRule.scope)];
for (let strScope of strScopes) {
scopes.appendChild($('li.tiw-metadata-value.tiw-metadata-scopes', undefined, strScope));
}
elements.push(
scopesDefinition.scope.join(' '),
$('br'),
$('code.tiw-theme-selector', undefined, strScopes, $('br'), JSON.stringify(matchingRule.settings, null, '\t')));
scopes,
$('code.tiw-theme-selector', undefined, JSON.stringify(matchingRule.settings, null, '\t')));
return elements;
}
return elements;

View File

@@ -9,7 +9,7 @@ import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Action } from 'vs/base/common/actions';
@@ -53,4 +53,4 @@ class InspectKeyMapJSON extends Action {
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(InspectKeyMapJSON), 'Developer: Inspect Key Mappings (JSON)', nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(InspectKeyMapJSON), 'Developer: Inspect Key Mappings (JSON)', CATEGORIES.Developer.value);

View File

@@ -11,7 +11,6 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
/**
* Shows a message when opening a large file which has been memory optimized (and features disabled).
@@ -24,14 +23,9 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC
private readonly _editor: ICodeEditor,
@INotificationService private readonly _notificationService: INotificationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
super();
// opt-in to syncing
const neverShowAgainId = 'editor.contrib.largeFileOptimizationsWarner';
storageKeysSyncRegistryService.registerStorageKey({ key: neverShowAgainId, version: 1 });
this._register(this._editor.onDidChangeModel((e) => {
const model = this._editor.getModel();
if (!model) {
@@ -61,7 +55,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC
});
}
}
], { neverShowAgain: { id: neverShowAgainId } });
], { neverShowAgain: { id: 'editor.contrib.largeFileOptimizationsWarner' } });
}
}));
}

View File

@@ -5,7 +5,6 @@
import { localize } from 'vs/nls';
import { IKeyMods, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess';
@@ -17,6 +16,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider {
@@ -41,10 +41,12 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv
return this.editorService.activeTextEditorControl;
}
protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void {
protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void {
// Check for sideBySide use
if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) {
context.restoreViewState?.(); // since we open to the side, restore view state in this editor
this.editorService.openEditor(this.editorService.activeEditor, {
selection: options.range,
pinned: options.keyMods.alt || this.configuration.openEditorPinned,
@@ -54,7 +56,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv
// Otherwise let parent handle it
else {
super.gotoLocation(editor, options);
super.gotoLocation(context, options);
}
}
}

View File

@@ -26,6 +26,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
@@ -42,11 +43,12 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
//#region DocumentSymbols (text editor required)
protected provideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
if (this.canPickFromTableOfContents()) {
return this.doGetTableOfContentsPicks(picker);
}
return super.provideWithTextEditor(editor, picker, token);
return super.provideWithTextEditor(context, picker, token);
}
private get configuration() {
@@ -62,10 +64,12 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
return this.editorService.activeTextEditorControl;
}
protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void {
protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void {
// Check for sideBySide use
if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) {
context.restoreViewState?.(); // since we open to the side, restore view state in this editor
this.editorService.openEditor(this.editorService.activeEditor, {
selection: options.range,
pinned: options.keyMods.alt || this.configuration.openEditorPinned,
@@ -75,7 +79,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
// Otherwise let parent handle it
else {
super.gotoLocation(editor, options);
super.gotoLocation(context, options);
}
}

View File

@@ -28,7 +28,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@@ -287,8 +287,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
? setting
: Object.keys(setting).filter(x => setting[x]);
const codeActionsOnSave = settingItems
.map(x => new CodeActionKind(x));
const codeActionsOnSave = this.createCodeActionsOnSave(settingItems);
if (!Array.isArray(setting)) {
codeActionsOnSave.sort((a, b) => {
@@ -319,6 +318,15 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token);
}
private createCodeActionsOnSave(settingItems: readonly string[]): CodeActionKind[] {
const kinds = settingItems.map(x => new CodeActionKind(x));
// Remove subsets
return kinds.filter(kind => {
return kinds.every(otherKind => otherKind.equals(kind) || !otherKind.contains(kind));
});
}
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
const getActionProgress = new class implements IProgress<CodeActionProvider> {
@@ -344,7 +352,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, getActionProgress, token);
try {
for (const action of actionsToRun.validActions) {
progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.title) });
progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.action.title) });
await this.instantiationService.invokeFunction(applyCodeAction, action);
}
} catch {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.suggest-input-container {
padding: 4px;
padding: 2px 4px;
}
.suggest-input-container .monaco-editor-background,
@@ -20,8 +20,7 @@
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
margin-top: 2px;
margin-left: 1px;
margin-top: 1px;
}
.suggest-input-container .monaco-editor,

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./suggestEnabledInput';
import { $, Dimension, addClass, append, removeClass } from 'vs/base/browser/dom';
import { $, Dimension, append } from 'vs/base/browser/dom';
import { Widget } from 'vs/base/browser/ui/widget';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
@@ -152,11 +152,11 @@ export class SuggestEnabledInput extends Widget implements IThemable {
this._register((this.inputWidget.onDidFocusEditorText(() => {
if (options.focusContextKey) { options.focusContextKey.set(true); }
addClass(this.stylingContainer, 'synthetic-focus');
this.stylingContainer.classList.add('synthetic-focus');
})));
this._register((this.inputWidget.onDidBlurEditorText(() => {
if (options.focusContextKey) { options.focusContextKey.set(false); }
removeClass(this.stylingContainer, 'synthetic-focus');
this.stylingContainer.classList.remove('synthetic-focus');
})));
const onKeyDownMonaco = Event.chain(this.inputWidget.onKeyDown);

View File

@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
@@ -42,7 +42,7 @@ export class ToggleColumnSelectionAction extends Action {
public async run(): Promise<any> {
const oldValue = this._configurationService.getValue<boolean>('editor.columnSelection');
const codeEditor = this._getCodeEditor();
await this._configurationService.updateValue('editor.columnSelection', !oldValue, ConfigurationTarget.USER);
await this._configurationService.updateValue('editor.columnSelection', !oldValue);
const newValue = this._configurationService.getValue<boolean>('editor.columnSelection');
if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) {
return;

View File

@@ -6,10 +6,10 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { /*MenuId, MenuRegistry,*/ SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
// import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
export class ToggleMinimapAction extends Action {
public static readonly ID = 'editor.action.toggleMinimap';
@@ -25,12 +25,12 @@ export class ToggleMinimapAction extends Action {
public run(): Promise<any> {
const newValue = !this._configurationService.getValue<boolean>('editor.minimap.enabled');
return this._configurationService.updateValue('editor.minimap.enabled', newValue, ConfigurationTarget.USER);
return this._configurationService.updateValue('editor.minimap.enabled', newValue);
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMinimapAction), 'View: Toggle Minimap', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMinimapAction), 'View: Toggle Minimap', CATEGORIES.View.value);
/* {{SQL CARBON EDIT}} - Disable unused menu item
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {

View File

@@ -7,9 +7,9 @@ import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import * as platform from 'vs/base/common/platform';
import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
@@ -33,7 +33,7 @@ export class ToggleMultiCursorModifierAction extends Action {
const editorConf = this.configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
const newValue: 'ctrlCmd' | 'alt' = (editorConf.multiCursorModifier === 'ctrlCmd' ? 'alt' : 'ctrlCmd');
return this.configurationService.updateValue(ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, newValue, ConfigurationTarget.USER);
return this.configurationService.updateValue(ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, newValue);
}
}

View File

@@ -6,10 +6,10 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { /*MenuId, MenuRegistry,*/ SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
// import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
export class ToggleRenderControlCharacterAction extends Action {
@@ -26,12 +26,12 @@ export class ToggleRenderControlCharacterAction extends Action {
public run(): Promise<any> {
let newRenderControlCharacters = !this._configurationService.getValue<boolean>('editor.renderControlCharacters');
return this._configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters, ConfigurationTarget.USER);
return this._configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters);
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleRenderControlCharacterAction), 'View: Toggle Control Characters', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleRenderControlCharacterAction), 'View: Toggle Control Characters', CATEGORIES.View.value);
/* {{SQL CARBON EDIT}} - Disable unused menu item
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {

View File

@@ -6,10 +6,10 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { /*MenuId, MenuRegistry,*/ SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
// import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
export class ToggleRenderWhitespaceAction extends Action {
@@ -34,12 +34,12 @@ export class ToggleRenderWhitespaceAction extends Action {
newRenderWhitespace = 'none';
}
return this._configurationService.updateValue('editor.renderWhitespace', newRenderWhitespace, ConfigurationTarget.USER);
return this._configurationService.updateValue('editor.renderWhitespace', newRenderWhitespace);
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleRenderWhitespaceAction), 'View: Toggle Render Whitespace', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleRenderWhitespaceAction), 'View: Toggle Render Whitespace', CATEGORIES.View.value);
/* {{SQL CARBON EDIT}} - Disable unused menu item
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {

View File

@@ -17,13 +17,13 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Codicon } from 'vs/base/common/codicons';
const transientWordWrapState = 'transientWordWrapState';
const isWordWrapMinifiedKey = 'isWordWrapMinified';
const isDominatedByLongLinesKey = 'isDominatedByLongLines';
const inDiffEditorKey = 'inDiffEditor';
/**
* State written/read by the toggle word wrap action and associated with a particular model.
@@ -138,12 +138,6 @@ class ToggleWordWrapAction extends EditorAction {
if (!editor.hasModel()) {
return;
}
if (editor.getOption(EditorOption.inDiffEditor)) {
// Cannot change wrapping settings inside the diff editor
const notificationService = accessor.get(INotificationService);
notificationService.info(nls.localize('wordWrap.notInDiffEditor', "Cannot toggle word wrap in a diff editor."));
return;
}
const textResourceConfigurationService = accessor.get(ITextResourceConfigurationService);
const codeEditorService = accessor.get(ICodeEditorService);
@@ -179,18 +173,16 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const isWordWrapMinified = this.contextKeyService.createKey(isWordWrapMinifiedKey, wrappingInfo.isWordWrapMinified);
const isDominatedByLongLines = this.contextKeyService.createKey(isDominatedByLongLinesKey, wrappingInfo.isDominatedByLongLines);
const inDiffEditor = this.contextKeyService.createKey(inDiffEditorKey, options.get(EditorOption.inDiffEditor));
let currentlyApplyingEditorConfig = false;
this._register(editor.onDidChangeConfiguration((e) => {
if (!e.hasChanged(EditorOption.wrappingInfo) && !e.hasChanged(EditorOption.inDiffEditor)) {
if (!e.hasChanged(EditorOption.wrappingInfo)) {
return;
}
const options = this.editor.getOptions();
const wrappingInfo = options.get(EditorOption.wrappingInfo);
isWordWrapMinified.set(wrappingInfo.isWordWrapMinified);
isDominatedByLongLines.set(wrappingInfo.isDominatedByLongLines);
inDiffEditor.set(options.get(EditorOption.inDiffEditor));
if (!currentlyApplyingEditorConfig) {
// I am not the cause of the word wrap getting changed
ensureWordWrapSettings();
@@ -220,10 +212,6 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution
return;
}
if (this.editor.getOption(EditorOption.inDiffEditor)) {
return;
}
if (!canToggleWordWrap(newModel.uri)) {
return;
}
@@ -275,14 +263,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: TOGGLE_WORD_WRAP_ID,
title: nls.localize('unwrapMinified', "Disable wrapping for this file"),
icon: {
id: 'codicon/word-wrap'
}
icon: Codicon.wordWrap
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(
ContextKeyExpr.not(inDiffEditorKey),
ContextKeyExpr.has(isDominatedByLongLinesKey),
ContextKeyExpr.has(isWordWrapMinifiedKey)
)
@@ -291,14 +276,12 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: TOGGLE_WORD_WRAP_ID,
title: nls.localize('wrapMinified', "Enable wrapping for this file"),
icon: {
id: 'codicon/word-wrap'
}
icon: Codicon.wordWrap
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(
ContextKeyExpr.not(inDiffEditorKey),
EditorContextKeys.inDiffEditor.negate(),
ContextKeyExpr.has(isDominatedByLongLinesKey),
ContextKeyExpr.not(isWordWrapMinifiedKey)
)

View File

@@ -3,14 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as nls from 'vs/nls';
import { Range } from 'vs/editor/common/core/range';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -22,6 +20,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { Constants } from 'vs/base/common/uint';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { join } from 'vs/base/common/path';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
class StartDebugTextMate extends Action {
@@ -37,7 +36,8 @@ class StartDebugTextMate extends Action {
@IModelService private readonly _modelService: IModelService,
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IHostService private readonly _hostService: IHostService
@IHostService private readonly _hostService: IHostService,
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService
) {
super(id, label);
}
@@ -59,7 +59,7 @@ class StartDebugTextMate extends Action {
}
public async run(): Promise<any> {
const pathInTemp = join(os.tmpdir(), `vcode-tm-log-${generateUuid()}.txt`);
const pathInTemp = join(this._environmentService.tmpDir.fsPath, `vcode-tm-log-${generateUuid()}.txt`);
const logger = createRotatingLogger(`tm-log`, pathInTemp, 1024 * 1024 * 30, 1);
const model = this._getOrCreateModel();
const append = (str: string) => {
@@ -70,7 +70,8 @@ class StartDebugTextMate extends Action {
};
await this._hostService.openWindow([{ fileUri: URI.file(pathInTemp) }], { forceNewWindow: true });
const textEditorPane = await this._editorService.openEditor({
resource: model.uri
resource: model.uri,
options: { pinned: true }
});
if (!textEditorPane) {
return;
@@ -104,4 +105,4 @@ class StartDebugTextMate extends Action {
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(StartDebugTextMate), 'Start Text Mate Syntax Grammar Logging', nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(StartDebugTextMate), 'Start Text Mate Syntax Grammar Logging', CATEGORIES.Developer.value);

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './displayChangeRemeasureFonts';
import './inputClipboardActions';
import './selectionClipboard';
import './sleepResumeRepaintMinimap';

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 { Disposable } from 'vs/base/common/lifecycle';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IDisplayMainService } from 'vs/platform/display/common/displayMainService';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
class DisplayChangeRemeasureFonts extends Disposable implements IWorkbenchContribution {
constructor(
@IMainProcessService mainProcessService: IMainProcessService
) {
super();
const displayMainService = createChannelSender<IDisplayMainService>(mainProcessService.getChannel('display'));
displayMainService.onDidDisplayChanged(() => {
clearAllFontInfos();
});
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DisplayChangeRemeasureFonts, LifecyclePhase.Eventually);

View File

@@ -16,7 +16,7 @@ import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
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 { IConfigurationService } from 'vs/platform/configuration/common/configuration';

View File

@@ -3,22 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
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 { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { Disposable } from 'vs/base/common/lifecycle';
class SleepResumeRepaintMinimap extends Disposable implements IWorkbenchContribution {
constructor(
@ICodeEditorService codeEditorService: ICodeEditorService,
@IElectronService electronService: IElectronService
@INativeHostService nativeHostService: INativeHostService
) {
super();
this._register(electronService.onOSResume(() => {
this._register(nativeHostService.onDidResumeOS(() => {
codeEditorService.listCodeEditors().forEach(editor => editor.render(true));
}));
}

View File

@@ -116,12 +116,12 @@ suite('Save Participants', function () {
model.textEditorModel.pushEditOperations([new Selection(1, 14, 1, 14)], textEdits, () => { return [new Selection(1, 15, 1, 15)]; });
// undo
model.textEditorModel.undo();
await model.textEditorModel.undo();
assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`);
// trim final new lines should not mess the undo stack
await participant.participate(model, { reason: SaveReason.EXPLICIT });
model.textEditorModel.redo();
await model.textEditorModel.redo();
assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}.`);
});
@@ -146,9 +146,9 @@ suite('Save Participants', function () {
assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`);
// undo should go back to previous content immediately
model.textEditorModel.undo();
await model.textEditorModel.undo();
assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${eol}`);
model.textEditorModel.redo();
await model.textEditorModel.redo();
assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`);
});

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { IAction } from 'vs/base/common/actions';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
@@ -25,7 +24,7 @@ export class CommentFormActions implements IDisposable {
setActions(menu: IMenu) {
this._toDispose.clear();
this._buttonElements.forEach(b => DOM.removeNode(b));
this._buttonElements.forEach(b => b.remove());
const groups = menu.getActions({ shouldForwardArgs: true });
for (const group of groups) {

View File

@@ -7,14 +7,12 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { IAction } from 'vs/base/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Comment, CommentThread } from 'vs/editor/common/modes';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export class CommentMenus implements IDisposable {
constructor(
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
@IMenuService private readonly menuService: IMenuService
) { }
getCommentThreadTitleActions(commentThread: CommentThread, contextKeyService: IContextKeyService): IMenu {
@@ -40,7 +38,7 @@ export class CommentMenus implements IDisposable {
const secondary: IAction[] = [];
const result = { primary, secondary };
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
return menu;
}

View File

@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
@@ -34,6 +34,7 @@ import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commen
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { Codicon } from 'vs/base/common/codicons';
export class CommentNode extends Disposable {
private _domNode: HTMLElement;
@@ -156,7 +157,7 @@ export class CommentNode extends Disposable {
{
actionViewItemProvider: action => this.actionViewItemProvider(action as Action),
actionRunner: this.actionRunner,
classNames: ['toolbar-toggle-pickReactions', 'codicon', 'codicon-reactions'],
classNames: ['toolbar-toggle-pickReactions', ...Codicon.reactions.classNamesArray],
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
}
);
@@ -519,9 +520,9 @@ export class CommentNode extends Disposable {
focus() {
this.domNode.focus();
if (!this._clearTimeout) {
dom.addClass(this.domNode, 'focus');
this.domNode.classList.add('focus');
this._clearTimeout = setTimeout(() => {
dom.removeClass(this.domNode, 'focus');
this.domNode.classList.remove('focus');
}, 3000);
}
}

View File

@@ -10,7 +10,6 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Range, IRange } from 'vs/editor/common/core/range';
import { CancellationToken } from 'vs/base/common/cancellation';
import { assign } from 'vs/base/common/objects';
import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel';
import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments';
import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus';
@@ -166,7 +165,7 @@ export class CommentService extends Disposable implements ICommentService {
}
updateComments(ownerId: string, event: CommentThreadChangedEvent): void {
const evt: ICommentThreadChangedEvent = assign({}, event, { owner: ownerId });
const evt: ICommentThreadChangedEvent = Object.assign({}, event, { owner: ownerId });
this._onDidUpdateCommentThreads.fire(evt);
}

View File

@@ -6,7 +6,6 @@
import * as dom from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action, IAction } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@@ -22,7 +21,7 @@ import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import * as nls from 'vs/nls';
@@ -32,7 +31,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { IColorTheme, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions';
import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget';
import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus';
@@ -47,9 +46,15 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { Codicon } from 'vs/base/common/codicons';
const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.'));
export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration';
const COLLAPSE_ACTION_CLASS = 'expand-review-action codicon-chevron-up';
const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon);
const COMMENT_SCHEME = 'comment';
@@ -61,10 +66,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
protected _actionbarWidget!: ActionBar;
private _bodyElement!: HTMLElement;
private _parentEditor: ICodeEditor;
private _commentEditor!: ICodeEditor;
private _commentsElement!: HTMLElement;
private _commentElements: CommentNode[] = [];
private _commentForm!: HTMLElement;
private _commentReplyComponent?: {
editor: ICodeEditor;
form: HTMLElement;
commentEditorIsEmpty: IContextKey<boolean>;
};
private _reviewThreadReplyButton!: HTMLElement;
private _resizeObserver: any;
private readonly _onDidClose = new Emitter<ReviewZoneWidget | undefined>();
@@ -82,7 +90,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
private _contextKeyService: IContextKeyService;
private _threadIsEmpty: IContextKey<boolean>;
private _commentThreadContextValue: IContextKey<string>;
private _commentEditorIsEmpty!: IContextKey<boolean>;
private _commentFormActions!: CommentFormActions;
private _scopedInstatiationService: IInstantiationService;
private _focusedComment: number | undefined = undefined;
@@ -151,7 +158,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}));
this._applyTheme(this.themeService.getColorTheme());
this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer(editor, this.modeService, this.openerService));
this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer({ editor }, this.modeService, this.openerService));
this._parentEditor = editor;
}
@@ -199,8 +206,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
public getPendingComment(): string | null {
if (this._commentEditor) {
let model = this._commentEditor.getModel();
if (this._commentReplyComponent) {
let model = this._commentReplyComponent.editor.getModel();
if (model && model.getValueLength() > 0) { // checking length is cheap
return model.getValue();
@@ -367,8 +374,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
if (!this._reviewThreadReplyButton) {
this.createReplyButton();
if (!this._reviewThreadReplyButton && this._commentReplyComponent) {
this.createReplyButton(this._commentReplyComponent.editor, this._commentReplyComponent.form);
}
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
@@ -400,11 +407,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
protected _onWidth(widthInPixel: number): void {
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
display(lineNumber: number) {
@@ -448,52 +455,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
this._commentEditor = this._scopedInstatiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
this._commentEditorIsEmpty.set(!this._pendingComment);
const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
const params = JSON.stringify({
extensionId: this.extensionId,
commentThreadId: this.commentThread.threadId
});
let resource = URI.parse(`${COMMENT_SCHEME}://${this.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority.
let commentController = this.commentService.getCommentController(this.owner);
if (commentController) {
resource = resource.with({ authority: commentController.id });
// create comment thread only when it supports reply
if (this._commentThread.canReply) {
this.createCommentForm();
}
const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource), resource, false);
this._disposables.add(model);
this._commentEditor.setModel(model);
this._disposables.add(this._commentEditor);
this._disposables.add(this._commentEditor.getModel()!.onDidChangeContent(() => {
this.setCommentEditorDecorations();
this._commentEditorIsEmpty.set(!this._commentEditor.getValue());
}));
this.createTextModelListener();
this.setCommentEditorDecorations();
// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
if (hasExistingComments) {
this.createReplyButton();
} else {
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
this.expandReplyArea();
}
}
this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden'));
this._formActions = dom.append(this._commentForm, dom.$('.form-actions'));
this.createCommentWidgetActions(this._formActions, model);
this.createCommentWidgetActionsListener();
this._resizeObserver = new MutationObserver(this._refresh.bind(this));
this._resizeObserver.observe(this._bodyElement, {
@@ -509,25 +475,94 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
// If there are no existing comments, place focus on the text area. This must be done after show, which also moves focus.
// if this._commentThread.comments is undefined, it doesn't finish initialization yet, so we don't focus the editor immediately.
if (!this._commentThread.comments || !this._commentThread.comments.length) {
this._commentEditor.focus();
} else if (this._commentEditor.getModel()!.getValueLength() > 0) {
this.expandReplyArea();
if (this._commentThread.canReply && this._commentReplyComponent) {
if (!this._commentThread.comments || !this._commentThread.comments.length) {
this._commentReplyComponent.editor.focus();
} else if (this._commentReplyComponent.editor.getModel()!.getValueLength() > 0) {
this.expandReplyArea();
}
}
this._commentThreadDisposables.push(this._commentThread.onDidChangeCanReply(() => {
if (this._commentReplyComponent) {
if (!this._commentThread.canReply) {
this._commentReplyComponent.form.style.display = 'none';
} else {
this._commentReplyComponent.form.style.display = 'block';
}
} else {
if (this._commentThread.canReply) {
this.createCommentForm();
}
}
}));
}
private createTextModelListener() {
this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
private createCommentForm() {
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
const commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
const commentEditor = this._scopedInstatiationService.createInstance(SimpleCommentEditor, commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
const commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
commentEditorIsEmpty.set(!this._pendingComment);
this._commentReplyComponent = {
form: commentForm,
editor: commentEditor,
commentEditorIsEmpty
};
const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
const params = JSON.stringify({
extensionId: this.extensionId,
commentThreadId: this.commentThread.threadId
});
let resource = URI.parse(`${COMMENT_SCHEME}://${this.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority.
let commentController = this.commentService.getCommentController(this.owner);
if (commentController) {
resource = resource.with({ authority: commentController.id });
}
const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource), resource, false);
this._disposables.add(model);
commentEditor.setModel(model);
this._disposables.add(commentEditor);
this._disposables.add(commentEditor.getModel()!.onDidChangeContent(() => {
this.setCommentEditorDecorations();
commentEditorIsEmpty?.set(!commentEditor.getValue());
}));
this.createTextModelListener(commentEditor, commentForm);
this.setCommentEditorDecorations();
// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
if (hasExistingComments) {
this.createReplyButton(commentEditor, commentForm);
} else {
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
this.expandReplyArea();
}
}
this._error = dom.append(commentForm, dom.$('.validation-error.hidden'));
this._formActions = dom.append(commentForm, dom.$('.form-actions'));
this.createCommentWidgetActions(this._formActions, model);
this.createCommentWidgetActionsListener();
}
private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) {
this._commentThreadDisposables.push(commentEditor.onDidFocusEditorWidget(() => {
this._commentThread.input = {
uri: this._commentEditor.getModel()!.uri,
value: this._commentEditor.getValue()
uri: commentEditor.getModel()!.uri,
value: commentEditor.getValue()
};
this.commentService.setActiveCommentThread(this._commentThread);
}));
this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = this._commentEditor.getValue();
if (this._commentThread.input && this._commentThread.input.uri === this._commentEditor.getModel()!.uri && this._commentThread.input.value !== modelContent) {
this._commentThreadDisposables.push(commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = commentEditor.getValue();
if (this._commentThread.input && this._commentThread.input.uri === commentEditor.getModel()!.uri && this._commentThread.input.value !== modelContent) {
let newInput: modes.CommentInput = this._commentThread.input;
newInput.value = modelContent;
this._commentThread.input = newInput;
@@ -538,24 +573,22 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => {
let thread = this._commentThread;
if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) {
if (thread.input && thread.input.uri !== commentEditor.getModel()!.uri) {
return;
}
if (!input) {
return;
}
if (this._commentEditor.getValue() !== input.value) {
this._commentEditor.setValue(input.value);
if (commentEditor.getValue() !== input.value) {
commentEditor.setValue(input.value);
if (input.value === '') {
this._pendingComment = '';
if (dom.hasClass(this._commentForm, 'expand')) {
dom.removeClass(this._commentForm, 'expand');
}
this._commentEditor.getDomNode()!.style.outline = '';
commentForm.classList.remove('expand');
commentEditor.getDomNode()!.style.outline = '';
this._error.textContent = '';
dom.addClass(this._error, 'hidden');
this._error.classList.add('hidden');
}
}
}));
@@ -641,7 +674,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
action.run({
thread: this._commentThread,
text: this._commentEditor.getValue(),
text: this._commentReplyComponent?.editor.getValue(),
$mid: 8
});
@@ -683,10 +716,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
label = this._commentThread.label;
if (label === undefined) {
if (this._commentThread.comments && this._commentThread.comments.length) {
const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', ');
label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList);
} else {
if (!(this._commentThread.comments && this._commentThread.comments.length)) {
label = nls.localize('startThread', "Start discussion");
}
}
@@ -698,25 +728,25 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private expandReplyArea() {
if (!dom.hasClass(this._commentForm, 'expand')) {
dom.addClass(this._commentForm, 'expand');
this._commentEditor.focus();
if (!this._commentReplyComponent?.form.classList.contains('expand')) {
this._commentReplyComponent?.form.classList.add('expand');
this._commentReplyComponent?.editor.focus();
}
}
private hideReplyArea() {
this._commentEditor.setValue('');
this._pendingComment = '';
if (dom.hasClass(this._commentForm, 'expand')) {
dom.removeClass(this._commentForm, 'expand');
if (this._commentReplyComponent) {
this._commentReplyComponent.editor.setValue('');
this._commentReplyComponent.editor.getDomNode()!.style.outline = '';
}
this._commentEditor.getDomNode()!.style.outline = '';
this._pendingComment = '';
this._commentReplyComponent?.form.classList.remove('expand');
this._error.textContent = '';
dom.addClass(this._error, 'hidden');
this._error.classList.add('hidden');
}
private createReplyButton() {
this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(this._commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
private createReplyButton(commentEditor: ICodeEditor, commentForm: HTMLElement) {
this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
this._reviewThreadReplyButton.title = this._commentOptions?.prompt || nls.localize('reply', "Reply...");
this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply...");
@@ -724,9 +754,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea()));
this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea()));
this._commentEditor.onDidBlurEditorWidget(() => {
if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) {
dom.removeClass(this._commentForm, 'expand');
commentEditor.onDidBlurEditorWidget(() => {
if (commentEditor.getModel()!.getValueLength() === 0 && commentForm.classList.contains('expand')) {
commentForm.classList.remove('expand');
}
});
}
@@ -757,7 +787,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
if (!this._commentThread.comments || !this._commentThread.comments.length) {
this._commentEditor.focus();
this._commentReplyComponent?.editor.focus();
}
this._relayout(computedLinesNumber);
@@ -765,7 +795,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private setCommentEditorDecorations() {
const model = this._commentEditor && this._commentEditor.getModel();
const model = this._commentReplyComponent && this._commentReplyComponent.editor.getModel();
if (model) {
const valueLength = model.getValueLength();
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
@@ -789,7 +819,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}];
this._commentEditor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
this._commentReplyComponent?.editor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
}
}
@@ -855,13 +885,18 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private _applyTheme(theme: IColorTheme) {
const borderColor = theme.getColor(peekViewBorder) || Color.transparent;
const borderColor = theme.getColor(peekViewBorder);
this.style({
arrowColor: borderColor,
frameColor: borderColor
arrowColor: borderColor || Color.transparent,
frameColor: borderColor || Color.transparent
});
const content: string[] = [];
if (borderColor) {
content.push(`.monaco-editor .review-widget > .body { border-top: 1px solid ${borderColor} }`);
}
const linkColor = theme.getColor(textLinkForeground);
if (linkColor) {
content.push(`.monaco-editor .review-widget .body .comment-body a { color: ${linkColor} }`);
@@ -888,6 +923,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
content.push(`.monaco-editor .review-widget .body .review-comment blockquote { border-color: ${blockQuoteBOrder}; }`);
}
const border = theme.getColor(PANEL_BORDER);
if (border) {
content.push(`.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label { border-color: ${border}; }`);
}
const hcBorder = theme.getColor(contrastBorder);
if (hcBorder) {
content.push(`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color: ${hcBorder}; }`);
@@ -929,9 +969,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
hide() {
this._isExpanded = false;
// Focus the container so that the comment editor will be blurred before it is hidden
this.editor.focus();
if (this._isExpanded) {
this._isExpanded = false;
// Focus the container so that the comment editor will be blurred before it is hidden
this.editor.focus();
}
super.hide();
}

View File

@@ -182,7 +182,6 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
dataSource,
{
accessibilityProvider: options.accessibilityProvider,
keyboardSupport: true,
identityProvider: {
getId: (element: any) => {
if (element instanceof CommentsModel) {

View File

@@ -6,7 +6,7 @@
import 'vs/css!./media/panel';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { basename } from 'vs/base/common/resources';
import { basename, isEqual } from 'vs/base/common/resources';
import { IAction, Action } from 'vs/base/common/actions';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -34,7 +34,6 @@ export class CommentsPanel extends ViewPane {
private tree!: CommentsList;
private treeContainer!: HTMLElement;
private messageBoxContainer!: HTMLElement;
private messageBox!: HTMLElement;
private commentsModel!: CommentsModel;
private collapseAllAction?: IAction;
@@ -60,7 +59,7 @@ export class CommentsPanel extends ViewPane {
public renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(container, 'comments-panel');
container.classList.add('comments-panel');
let domContainer = dom.append(container, dom.$('.comments-panel-container'));
this.treeContainer = dom.append(domContainer, dom.$('.tree-container'));
@@ -85,6 +84,18 @@ export class CommentsPanel extends ViewPane {
this.renderComments();
}
public focus(): void {
if (this.tree && this.tree.getHTMLElement() === document.activeElement) {
return;
}
if (!this.commentsModel.hasCommentThreads() && this.messageBoxContainer) {
this.messageBoxContainer.focus();
} else if (this.tree) {
this.tree.domFocus();
}
}
private applyStyles(styleElement: HTMLStyleElement) {
const content: string[] = [];
@@ -113,9 +124,9 @@ export class CommentsPanel extends ViewPane {
}
private async renderComments(): Promise<void> {
dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads());
await this.tree.setInput(this.commentsModel);
this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads());
this.renderMessage();
await this.tree.setInput(this.commentsModel);
}
public getActions(): IAction[] {
@@ -138,13 +149,12 @@ export class CommentsPanel extends ViewPane {
private createMessageBox(parent: HTMLElement): void {
this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container'));
this.messageBox = dom.append(this.messageBoxContainer, dom.$('span'));
this.messageBox.setAttribute('tabindex', '0');
this.messageBoxContainer.setAttribute('tabIndex', '0');
}
private renderMessage(): void {
this.messageBox.textContent = this.commentsModel.getMessage();
dom.toggleClass(this.messageBoxContainer, 'hidden', this.commentsModel.hasCommentThreads());
this.messageBoxContainer.textContent = this.commentsModel.getMessage();
this.messageBoxContainer.classList.toggle('hidden', this.commentsModel.hasCommentThreads());
}
private createTree(): void {
@@ -196,7 +206,7 @@ export class CommentsPanel extends ViewPane {
const activeEditor = this.editorService.activeEditor;
let currentActiveResource = activeEditor ? activeEditor.resource : undefined;
if (currentActiveResource && currentActiveResource.toString() === element.resource.toString()) {
if (currentActiveResource && isEqual(currentActiveResource, element.resource)) {
const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId;
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread;
const control = this.editorService.activeTextEditorControl;
@@ -231,18 +241,23 @@ export class CommentsPanel extends ViewPane {
return true;
}
private refresh(): void {
private async refresh(): Promise<void> {
if (this.isVisible()) {
if (this.collapseAllAction) {
this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads();
}
dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads());
this.tree.updateChildren().then(() => {
this.renderMessage();
}, (e) => {
console.log(e);
});
this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads());
this.renderMessage();
await this.tree.updateChildren();
if (this.tree.getSelection().length === 0 && this.commentsModel.hasCommentThreads()) {
const firstComment = this.commentsModel.resourceCommentThreads[0].commentThreads[0];
if (firstComment) {
this.tree.setFocus([firstComment]);
this.tree.setSelection([firstComment]);
}
}
}
}

View File

@@ -48,10 +48,7 @@
.comments-panel .comments-panel-container .message-box-container {
line-height: 22px;
padding-left: 20px;
}
.comments-panel .comments-panel-container .message-box-container span:focus {
outline: none;
height: inherit;
}
.comments-panel .comments-panel-container .tree-container .count-badge-wrapper {
@@ -61,4 +58,4 @@
.comments-panel .comments-panel-container .tree-container .comment-container {
line-height: 22px;
margin-right: 5px;
}
}

View File

@@ -119,18 +119,17 @@
white-space: pre;
text-align: center;
font-size: 12px;
display: flex;
}
.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item .action-label .reaction-icon {
background-size: 12px;
background-size: 14px;
background-position: left center;
background-repeat: no-repeat;
width: 16px;
height: 12px;
width: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-block;
margin-top: 3px;
margin-right: 4px;
}
@@ -165,11 +164,7 @@
}
.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label {
border: 1px solid transparent;
}
.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.active {
border: 1px solid grey;
border: 1px solid;
}
.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.disabled {
@@ -402,7 +397,7 @@ div.preview.inline .monaco-editor .comment-range-glyph {
}
.monaco-editor .margin-view-overlays > div:hover > .comment-range-glyph.comment-diff-added:before {
content: "+";
content: "💬";
}
.monaco-editor .comment-range-glyph.comment-thread {

View File

@@ -106,7 +106,8 @@ export class SimpleCommentEditor extends CodeEditorWidget {
acceptSuggestionOnEnter: 'smart',
minimap: {
enabled: false
}
},
quickSuggestions: false
};
}
}

View File

@@ -134,7 +134,7 @@ export class CommentsModel {
public getMessage(): string {
if (!this.resourceCommentThreads.length) {
return localize('noComments', "There are no comments on this review.");
return localize('noComments', "There are no comments in this workspace yet.");
} else {
return '';
}

View File

@@ -6,19 +6,18 @@
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper';
import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper';
export class ExtensionPoints implements IWorkbenchContribution {
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
) {
// Config Exporter
if (environmentService.configuration['export-default-configuration']) {
if (environmentService.args['export-default-configuration']) {
instantiationService.createInstance(DefaultConfigurationExportHelper);
}
}

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { writeFile } from 'vs/base/node/pfs';
import product from 'vs/platform/product/common/product';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IFileService } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import { IProductService } from 'vs/platform/product/common/productService';
interface IExportedConfigurationNode {
name: string;
@@ -31,26 +32,32 @@ interface IConfigurationExport {
export class DefaultConfigurationExportHelper {
constructor(
@IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
@IExtensionService private readonly extensionService: IExtensionService,
@ICommandService private readonly commandService: ICommandService) {
if (environmentService.args['export-default-configuration']) {
this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
@ICommandService private readonly commandService: ICommandService,
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService
) {
const exportDefaultConfigurationPath = environmentService.args['export-default-configuration'];
if (exportDefaultConfigurationPath) {
this.writeConfigModelAndQuit(URI.file(exportDefaultConfigurationPath));
}
}
private writeConfigModelAndQuit(targetPath: string): Promise<void> {
return Promise.resolve(this.extensionService.whenInstalledExtensionsRegistered())
.then(() => this.writeConfigModel(targetPath))
.finally(() => this.commandService.executeCommand('workbench.action.quit'))
.then(() => { });
private async writeConfigModelAndQuit(target: URI): Promise<void> {
try {
await this.extensionService.whenInstalledExtensionsRegistered();
await this.writeConfigModel(target);
} finally {
this.commandService.executeCommand('workbench.action.quit');
}
}
private writeConfigModel(targetPath: string): Promise<void> {
private async writeConfigModel(target: URI): Promise<void> {
const config = this.getConfigModel();
const resultString = JSON.stringify(config, undefined, ' ');
return writeFile(targetPath, resultString);
await this.fileService.writeFile(target, VSBuffer.fromString(resultString));
}
private getConfigModel(): IConfigurationExport {
@@ -106,8 +113,8 @@ export class DefaultConfigurationExportHelper {
const result: IConfigurationExport = {
settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
buildTime: Date.now(),
commit: product.commit,
buildNumber: product.settingsSearchBuildId
commit: this.productService.commit,
buildNumber: this.productService.settingsSearchBuildId
};
return result;

View File

@@ -6,7 +6,7 @@
import { Schemas } from 'vs/base/common/network';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';

View File

@@ -223,15 +223,14 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return newEditor;
}
public undo(): void {
public undo(): void | Promise<void> {
assertIsDefined(this._modelRef);
this.undoRedoService.undo(this.resource);
return this.undoRedoService.undo(this.resource);
}
public redo(): void {
public redo(): void | Promise<void> {
assertIsDefined(this._modelRef);
this.undoRedoService.redo(this.resource);
return this.undoRedoService.redo(this.resource);
}
private _moveHandler?: (newResource: URI) => void;

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { coalesce, distinct } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { Lazy } from 'vs/base/common/lazy';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -29,7 +30,7 @@ import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, Cust
import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager';
import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId, defaultEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorOpenWith';
import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorOpenWith';
import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService';
import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors';
import { CustomEditorInput } from './customEditorInput';
@@ -96,10 +97,13 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
return [...this._contributedEditors];
}
private withActiveCustomEditor(f: (editor: CustomEditorInput) => void): boolean {
private withActiveCustomEditor(f: (editor: CustomEditorInput) => void | Promise<void>): boolean | Promise<void> {
const activeEditor = this.editorService.activeEditor;
if (activeEditor instanceof CustomEditorInput) {
f(activeEditor);
const result = f(activeEditor);
if (result) {
return result;
}
return true;
}
return false;
@@ -170,7 +174,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
: undefined,
detail: editorDescriptor.providerDisplayName,
buttons: resourceExt ? [{
iconClass: 'codicon-settings-gear',
iconClass: Codicon.settingsGear.classNames,
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
}] : undefined
}));
@@ -444,6 +448,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
constructor(
@IEditorService private readonly editorService: IEditorService,
@ICustomEditorService private readonly customEditorService: ICustomEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
@@ -454,11 +459,6 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => {
const currentEditor = group?.editors.find(editor => isEqual(editor.resource, resource));
const defaultEditorOverride: IOpenEditorOverrideEntry = {
...defaultEditorOverrideEntry,
active: this._fileEditorInputFactory.isFileEditorInput(currentEditor),
};
const toOverride = (entry: CustomEditorInfo): IOpenEditorOverrideEntry => {
return {
id: entry.id,
@@ -470,11 +470,6 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
if (typeof options?.override === 'string') {
// A specific override was requested. Only return it.
if (options.override === defaultEditorOverride.id) {
return [defaultEditorOverride];
}
const matchingEditor = this.customEditorService.getCustomEditor(options.override);
return matchingEditor ? [toOverride(matchingEditor)] : [];
}
@@ -485,12 +480,9 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
return [];
}
return [
defaultEditorOverride,
...customEditors.allEditors
.filter(entry => entry.id !== defaultCustomEditor.id)
.map(toOverride)
];
return customEditors.allEditors
.filter(entry => entry.id !== defaultCustomEditor.id)
.map(toOverride);
}
}));
}
@@ -661,7 +653,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
if (modifiedOverride || originalOverride) {
return {
override: (async () => {
const input = new DiffEditorInput(editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true);
const input = this.instantiationService.createInstance(DiffEditorInput, editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true);
return this.editorService.openEditor(input, { ...options, override: false }, group);
})(),
};

View File

@@ -8,12 +8,12 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento';
import { CustomEditorDescriptor, CustomEditorInfo, CustomEditorPriority } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/common/extensionPoint';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { DEFAULT_EDITOR_ID } from 'vs/workbench/services/editor/common/editorOpenWith';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in");
@@ -40,7 +40,7 @@ export class ContributedCustomEditors extends Disposable {
this._memento = new Memento(ContributedCustomEditors.CUSTOM_EDITORS_STORAGE_ID, storageService);
const mementoObject = this._memento.getMemento(StorageScope.GLOBAL);
const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
for (const info of (mementoObject[ContributedCustomEditors.CUSTOM_EDITORS_ENTRY_ID] || []) as CustomEditorDescriptor[]) {
this.add(new CustomEditorInfo(info));
}
@@ -68,7 +68,7 @@ export class ContributedCustomEditors extends Disposable {
}
}
const mementoObject = this._memento.getMemento(StorageScope.GLOBAL);
const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
mementoObject[ContributedCustomEditors.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._editors.values());
this._memento.saveMemento();

View File

@@ -159,6 +159,7 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
data.toDispose.dispose();
data.toDispose = Disposable.None;
const { element } = node;
this.renderExpression(element, data, createMatches(node.filterData));
if (element === this.debugService.getViewModel().getSelectedExpression() || (element instanceof Variable && element.errorMessage)) {
const options = this.getInputBoxOptions(element);
if (options) {
@@ -166,7 +167,6 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
return;
}
}
this.renderExpression(element, data, createMatches(node.filterData));
}
renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable {

View File

@@ -22,7 +22,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { getBreakpointMessageAndIcon } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { generateUuid } from 'vs/base/common/uuid';
import { memoize } from 'vs/base/common/decorators';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -32,9 +32,10 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { isSafari } from 'vs/base/browser/browser';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { ILabelService } from 'vs/platform/label/common/label';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
@@ -46,7 +47,7 @@ interface IBreakpointDecoration {
}
const breakpointHelperDecoration: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-hint',
glyphMarginClassName: ThemeIcon.asClassName(icons.debugBreakpointHint),
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
@@ -72,7 +73,7 @@ export function createBreakpointDecorations(model: ITextModel, breakpoints: Read
}
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
const { className, message } = getBreakpointMessageAndClassName(state, breakpointsActivated, breakpoint, undefined);
const { icon, message } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined);
let glyphMarginHoverMessage: MarkdownString | undefined;
if (message) {
@@ -94,7 +95,7 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber));
return {
glyphMarginClassName: `${className}`,
glyphMarginClassName: ThemeIcon.asClassName(icon),
glyphMarginHoverMessage,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
beforeContentClassName: renderInline ? `debug-breakpoint-placeholder` : undefined,
@@ -174,7 +175,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
private registerListeners(): void {
this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => {
if (!this.debugService.getConfigurationManager().hasDebuggers()) {
if (!this.debugService.getAdapterManager().hasDebuggers()) {
return;
}
@@ -183,7 +184,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
return;
}
const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model);
const canSetBreakpoints = this.debugService.canSetBreakpointsIn(model);
const lineNumber = e.target.position.lineNumber;
const uri = model.uri;
@@ -240,7 +241,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId()));
}
} else if (canSetBreakpoints) {
this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorGutter`);
this.debugService.addBreakpoints(uri, [{ lineNumber }]);
}
}
}));
@@ -252,13 +253,13 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
* 2. When users click on line numbers, the breakpoint hint displays immediately, however it doesn't create the breakpoint unless users click on the left gutter. On a touch screen, it's hard to click on that small area.
*/
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => {
if (!this.debugService.getConfigurationManager().hasDebuggers()) {
if (!this.debugService.getAdapterManager().hasDebuggers()) {
return;
}
let showBreakpointHintAtLineNumber = -1;
const model = this.editor.getModel();
if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) &&
if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.canSetBreakpointsIn(model) &&
this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
const data = e.target.detail as IMarginData;
if (!data.isAfterLines) {
@@ -349,7 +350,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
nls.localize('addBreakpoint', "Add Breakpoint"),
undefined,
true,
() => this.debugService.addBreakpoints(uri, [{ lineNumber, column }], `debugEditorContextMenu`)
() => this.debugService.addBreakpoints(uri, [{ lineNumber, column }])
));
actions.push(new Action(
'addConditionalBreakpoint',
@@ -453,9 +454,9 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
// Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there
// In practice this happens for the first breakpoint that was set on a line
// We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information
const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).className : 'codicon-debug-breakpoint-disabled';
const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.debugBreakpointDisabled;
const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn);
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);
return {
decorationId,
@@ -575,15 +576,14 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable {
private create(cssClass: string | null | undefined): void {
this.domNode = $('.inline-breakpoint-widget');
this.domNode.classList.add('codicon');
if (cssClass) {
this.domNode.classList.add(cssClass);
this.domNode.classList.add(...cssClass.split(' '));
}
this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, async e => {
if (this.breakpoint) {
await this.debugService.removeBreakpoints(this.breakpoint.getId());
} else {
await this.debugService.addBreakpoints(this.editor.getModel().uri, [{ lineNumber: this.range!.startLineNumber, column: this.range!.startColumn }], 'debugEditorInlineWidget');
await this.debugService.addBreakpoints(this.editor.getModel().uri, [{ lineNumber: this.range!.startLineNumber, column: this.range!.startColumn }]);
}
}));
this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, e => {
@@ -645,15 +645,15 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground);
if (debugIconBreakpointColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-breakpoint,
.monaco-workbench .codicon-debug-breakpoint-conditional,
.monaco-workbench .codicon-debug-breakpoint-log,
.monaco-workbench .codicon-debug-breakpoint-function,
.monaco-workbench .codicon-debug-breakpoint-data,
.monaco-workbench .codicon-debug-breakpoint-unsupported,
.monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']),
.monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after,
.monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe::after {
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointConditional)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointLog)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointFunction)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointData)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointUnsupported)},
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointHint)}:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']),
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)}::after,
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpoint)}${ThemeIcon.asCSSSelector(icons.debugStackframe)}::after {
color: ${debugIconBreakpointColor} !important;
}
`);
@@ -680,7 +680,7 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground);
if (debugIconBreakpointCurrentStackframeForegroundColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-stackframe,
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframe)},
.monaco-editor .debug-top-stack-frame-column::before {
color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important;
}
@@ -690,7 +690,7 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground);
if (debugIconBreakpointStackframeFocusedColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-stackframe-focused {
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)} {
color: ${debugIconBreakpointStackframeFocusedColor} !important;
}
`);

View File

@@ -98,7 +98,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super(editor, { showFrame: true, showArrow: false, frameWidth: 1 });
super(editor, { showFrame: true, showArrow: false, frameWidth: 1, isAccessible: true });
this.toDispose = [];
const model = this.editor.getModel();
@@ -325,7 +325,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
condition,
hitCondition,
logMessage
}], `breakpointWidget`);
}]);
}
}
}

View File

@@ -13,7 +13,7 @@ import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAl
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Constants } from 'vs/base/common/uint';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list';
@@ -37,6 +37,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
@@ -76,7 +77,7 @@ export class BreakpointsView extends ViewPane {
@IContextKeyService contextKeyService: IContextKeyService,
@IOpenerService openerService: IOpenerService,
@ITelemetryService telemetryService: ITelemetryService,
@ILabelService private readonly labelService: ILabelService
@ILabelService private readonly labelService: ILabelService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
@@ -93,6 +94,7 @@ export class BreakpointsView extends ViewPane {
this.list = <WorkbenchList<BreakpointItem>>this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [
this.instantiationService.createInstance(BreakpointsRenderer),
new ExceptionBreakpointsRenderer(this.debugService),
new ExceptionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService),
this.instantiationService.createInstance(FunctionBreakpointsRenderer),
this.instantiationService.createInstance(DataBreakpointsRenderer),
new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService, this.labelService)
@@ -133,11 +135,11 @@ export class BreakpointsView extends ViewPane {
const element = this.list.element(e.element);
if (element instanceof Breakpoint) {
openBreakpointSource(element, e.sideBySide, e.editorOptions.preserveFocus || false, this.debugService, this.editorService);
openBreakpointSource(element, e.sideBySide, e.editorOptions.preserveFocus || false, e.editorOptions.pinned || !e.editorOptions.preserveFocus, this.debugService, this.editorService);
}
if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedBreakpoint()) {
// double click
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.debugService.getViewModel().setSelectedBreakpoint(element);
this.onBreakpointsChange();
}
}));
@@ -192,7 +194,7 @@ export class BreakpointsView extends ViewPane {
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => {
if (element instanceof Breakpoint) {
const editor = await openBreakpointSource(element, false, false, this.debugService, this.editorService);
const editor = await openBreakpointSource(element, false, false, true, this.debugService, this.editorService);
if (editor) {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
@@ -200,12 +202,19 @@ export class BreakpointsView extends ViewPane {
}
}
} else {
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.debugService.getViewModel().setSelectedBreakpoint(element);
this.onBreakpointsChange();
}
}));
actions.push(new Separator());
}
if (element instanceof ExceptionBreakpoint && element.supportsCondition) {
actions.push(new Action('workbench.action.debug.editExceptionBreakpointCondition', nls.localize('editCondition', "Edit Condition..."), '', true, async () => {
this.debugService.getViewModel().setSelectedBreakpoint(element);
this.onBreakpointsChange();
}));
actions.push(new Separator());
}
actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService));
@@ -285,7 +294,7 @@ class BreakpointsDelegate implements IListVirtualDelegate<BreakpointItem> {
return BreakpointsRenderer.ID;
}
if (element instanceof FunctionBreakpoint) {
const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
const selected = this.debugService.getViewModel().getSelectedBreakpoint();
if (!element.name || (selected && selected.getId() === element.getId())) {
return FunctionBreakpointInputRenderer.ID;
}
@@ -293,6 +302,10 @@ class BreakpointsDelegate implements IListVirtualDelegate<BreakpointItem> {
return FunctionBreakpointsRenderer.ID;
}
if (element instanceof ExceptionBreakpoint) {
const selected = this.debugService.getViewModel().getSelectedBreakpoint();
if (selected && selected.getId() === element.getId()) {
return ExceptionBreakpointInputRenderer.ID;
}
return ExceptionBreakpointsRenderer.ID;
}
if (element instanceof DataBreakpoint) {
@@ -320,7 +333,11 @@ interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
filePath: HTMLElement;
}
interface IInputTemplateData {
interface IExceptionBreakpointTemplateData extends IBaseBreakpointTemplateData {
condition: HTMLElement;
}
interface IFunctionBreakpointInputTemplateData {
inputBox: InputBox;
checkbox: HTMLInputElement;
icon: HTMLElement;
@@ -329,6 +346,14 @@ interface IInputTemplateData {
toDispose: IDisposable[];
}
interface IExceptionBreakpointInputTemplateData {
inputBox: InputBox;
checkbox: HTMLInputElement;
breakpoint: IExceptionBreakpoint;
reactedOnEvent: boolean;
toDispose: IDisposable[];
}
class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTemplateData> {
constructor(
@@ -379,8 +404,8 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
data.filePath.textContent = this.labelService.getUriLabel(resources.dirname(breakpoint.uri), { relative: true });
data.checkbox.checked = breakpoint.enabled;
const { message, className } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { message, icon } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService);
data.icon.className = ThemeIcon.asClassName(icon);
data.breakpoint.title = breakpoint.message || message || '';
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
@@ -394,7 +419,7 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
}
}
class ExceptionBreakpointsRenderer implements IListRenderer<IExceptionBreakpoint, IBaseBreakpointTemplateData> {
class ExceptionBreakpointsRenderer implements IListRenderer<IExceptionBreakpoint, IExceptionBreakpointTemplateData> {
constructor(
private debugService: IDebugService
@@ -408,8 +433,8 @@ class ExceptionBreakpointsRenderer implements IListRenderer<IExceptionBreakpoint
return ExceptionBreakpointsRenderer.ID;
}
renderTemplate(container: HTMLElement): IBaseBreakpointTemplateData {
const data: IBreakpointTemplateData = Object.create(null);
renderTemplate(container: HTMLElement): IExceptionBreakpointTemplateData {
const data: IExceptionBreakpointTemplateData = Object.create(null);
data.breakpoint = dom.append(container, $('.breakpoint'));
data.checkbox = createCheckbox();
@@ -421,19 +446,22 @@ class ExceptionBreakpointsRenderer implements IListRenderer<IExceptionBreakpoint
dom.append(data.breakpoint, data.checkbox);
data.name = dom.append(data.breakpoint, $('span.name'));
data.condition = dom.append(data.breakpoint, $('span.condition'));
data.breakpoint.classList.add('exception');
return data;
}
renderElement(exceptionBreakpoint: IExceptionBreakpoint, index: number, data: IBaseBreakpointTemplateData): void {
renderElement(exceptionBreakpoint: IExceptionBreakpoint, index: number, data: IExceptionBreakpointTemplateData): void {
data.context = exceptionBreakpoint;
data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
data.breakpoint.title = data.name.textContent;
data.checkbox.checked = exceptionBreakpoint.enabled;
data.condition.textContent = exceptionBreakpoint.condition || '';
data.condition.title = nls.localize('expressionCondition', "Expression condition: {0}", exceptionBreakpoint.condition);
}
disposeTemplate(templateData: IBaseBreakpointTemplateData): void {
disposeTemplate(templateData: IExceptionBreakpointTemplateData): void {
dispose(templateData.toDispose);
}
}
@@ -475,8 +503,8 @@ class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, I
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = functionBreakpoint;
data.name.textContent = functionBreakpoint.name;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
data.icon.className = ThemeIcon.asClassName(icon);
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
data.breakpoint.title = message ? message : '';
@@ -531,8 +559,8 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBrea
renderElement(dataBreakpoint: DataBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = dataBreakpoint;
data.name.textContent = dataBreakpoint.description;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService);
data.icon.className = ThemeIcon.asClassName(icon);
data.icon.title = message ? message : '';
data.checkbox.checked = dataBreakpoint.enabled;
data.breakpoint.title = message ? message : '';
@@ -550,7 +578,7 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBrea
}
}
class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoint, IInputTemplateData> {
class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoint, IFunctionBreakpointInputTemplateData> {
constructor(
private debugService: IDebugService,
@@ -567,8 +595,8 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
return FunctionBreakpointInputRenderer.ID;
}
renderTemplate(container: HTMLElement): IInputTemplateData {
const template: IInputTemplateData = Object.create(null);
renderTemplate(container: HTMLElement): IFunctionBreakpointInputTemplateData {
const template: IFunctionBreakpointInputTemplateData = Object.create(null);
const breakpoint = dom.append(container, $('.breakpoint'));
template.icon = $('.icon');
@@ -587,7 +615,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
const wrapUp = (renamed: boolean) => {
if (!template.reactedOnEvent) {
template.reactedOnEvent = true;
this.debugService.getViewModel().setSelectedFunctionBreakpoint(undefined);
this.debugService.getViewModel().setSelectedBreakpoint(undefined);
if (inputBox.value && (renamed || template.breakpoint.name)) {
this.debugService.renameFunctionBreakpoint(template.breakpoint.getId(), renamed ? inputBox.value : template.breakpoint.name);
} else {
@@ -619,12 +647,12 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
return template;
}
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IInputTemplateData): void {
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IFunctionBreakpointInputTemplateData): void {
data.breakpoint = functionBreakpoint;
data.reactedOnEvent = false;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
data.icon.className = ThemeIcon.asClassName(icon);
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
data.checkbox.disabled = true;
@@ -635,7 +663,89 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
}, 0);
}
disposeTemplate(templateData: IInputTemplateData): void {
disposeTemplate(templateData: IFunctionBreakpointInputTemplateData): void {
dispose(templateData.toDispose);
}
}
class ExceptionBreakpointInputRenderer implements IListRenderer<IExceptionBreakpoint, IExceptionBreakpointInputTemplateData> {
constructor(
private debugService: IDebugService,
private contextViewService: IContextViewService,
private themeService: IThemeService
) {
// noop
}
static readonly ID = 'exceptionbreakpointinput';
get templateId() {
return ExceptionBreakpointInputRenderer.ID;
}
renderTemplate(container: HTMLElement): IExceptionBreakpointInputTemplateData {
const template: IExceptionBreakpointInputTemplateData = Object.create(null);
const breakpoint = dom.append(container, $('.breakpoint'));
breakpoint.classList.add('exception');
template.checkbox = createCheckbox();
dom.append(breakpoint, template.checkbox);
const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer'));
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
placeholder: nls.localize('exceptionBreakpointPlaceholder', "Break when expression evaluates to true"),
ariaLabel: nls.localize('exceptionBreakpointAriaLabel', "Type exception breakpoint condition")
});
const styler = attachInputBoxStyler(inputBox, this.themeService);
const toDispose: IDisposable[] = [inputBox, styler];
const wrapUp = (success: boolean) => {
if (!template.reactedOnEvent) {
template.reactedOnEvent = true;
this.debugService.getViewModel().setSelectedBreakpoint(undefined);
let newCondition = template.breakpoint.condition;
if (success) {
newCondition = inputBox.value !== '' ? inputBox.value : undefined;
}
this.debugService.setExceptionBreakpointCondition(template.breakpoint, newCondition);
}
};
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.preventDefault();
e.stopPropagation();
wrapUp(isEnter);
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
// Need to react with a timeout on the blur event due to possible concurent splices #56443
setTimeout(() => {
wrapUp(true);
});
}));
template.inputBox = inputBox;
template.toDispose = toDispose;
return template;
}
renderElement(exceptionBreakpoint: ExceptionBreakpoint, _index: number, data: IExceptionBreakpointInputTemplateData): void {
data.breakpoint = exceptionBreakpoint;
data.reactedOnEvent = false;
data.checkbox.checked = exceptionBreakpoint.enabled;
data.checkbox.disabled = true;
data.inputBox.value = exceptionBreakpoint.condition || '';
setTimeout(() => {
data.inputBox.focus();
data.inputBox.select();
}, 0);
}
disposeTemplate(templateData: IExceptionBreakpointInputTemplateData): void {
dispose(templateData.toDispose);
}
}
@@ -664,14 +774,14 @@ class BreakpointsAccessibilityProvider implements IListAccessibilityProvider<Bre
return element.toString();
}
const { message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint, this.labelService);
const { message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint, this.labelService);
const toString = element.toString();
return message ? `${toString}, ${message}` : toString;
}
}
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditorPane | undefined> {
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, pinned: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditorPane | undefined> {
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
return Promise.resolve(undefined);
}
@@ -695,17 +805,17 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
selection,
revealIfOpened: true,
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,
pinned: !preserveFocus
pinned
}
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
export function getBreakpointMessageAndClassName(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, className: string } {
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, icon: ThemeIcon } {
const debugActive = state === State.Running || state === State.Stopped;
if (!breakpoint.enabled || !breakpointsActivated) {
return {
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-disabled' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-disabled' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-disabled' : 'codicon-debug-breakpoint-disabled',
icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataDisabled : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionDisabled : breakpoint.logMessage ? icons.debugBreakpointLogDisabled : icons.debugBreakpointDisabled,
message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled Logpoint") : nls.localize('disabledBreakpoint', "Disabled Breakpoint"),
};
}
@@ -715,7 +825,7 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
};
if (debugActive && !breakpoint.verified) {
return {
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-unverified' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-unverified' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-unverified' : 'codicon-debug-breakpoint-unverified',
icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataUnverified : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionUnverified : breakpoint.logMessage ? icons.debugBreakpointLogUnverified : icons.debugBreakpointUnverified,
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
};
}
@@ -723,13 +833,13 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
if (breakpoint instanceof FunctionBreakpoint) {
if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-function-unverified',
icon: icons.debugBreakpointFunctionUnverified,
message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"),
};
}
return {
className: 'codicon-debug-breakpoint-function',
icon: icons.debugBreakpointFunction,
message: breakpoint.message || nls.localize('functionBreakpoint', "Function Breakpoint")
};
}
@@ -737,13 +847,13 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
if (breakpoint instanceof DataBreakpoint) {
if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-data-unverified',
icon: icons.debugBreakpointDataUnverified,
message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"),
};
}
return {
className: 'codicon-debug-breakpoint-data',
icon: icons.debugBreakpointData,
message: breakpoint.message || nls.localize('dataBreakpoint', "Data Breakpoint")
};
}
@@ -753,7 +863,7 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-unsupported',
icon: icons.debugBreakpointUnsupported,
message: nls.localize('breakpointUnsupported', "Breakpoints of this type are not supported by the debugger"),
};
}
@@ -762,21 +872,21 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
messages.push(nls.localize('logMessage', "Log Message: {0}", breakpoint.logMessage));
}
if (breakpoint.condition) {
messages.push(nls.localize('expression', "Expression: {0}", breakpoint.condition));
messages.push(nls.localize('expression', "Expression condition: {0}", breakpoint.condition));
}
if (breakpoint.hitCondition) {
messages.push(nls.localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
}
return {
className: breakpoint.logMessage ? 'codicon-debug-breakpoint-log' : 'codicon-debug-breakpoint-conditional',
icon: breakpoint.logMessage ? icons.debugBreakpointLog : icons.debugBreakpointConditional,
message: appendMessage(messages.join('\n'))
};
}
const message = ('message' in breakpoint && breakpoint.message) ? breakpoint.message : breakpoint instanceof Breakpoint && labelService ? labelService.getUriLabel(breakpoint.uri) : nls.localize('breakpoint', "Breakpoint");
return {
className: 'codicon-debug-breakpoint',
icon: icons.debugBreakpoint,
message
};
}

View File

@@ -5,26 +5,39 @@
import { Constants } from 'vs/base/common/uint';
import { Range, IRange } from 'vs/editor/common/core/range';
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model';
import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { distinct } from 'vs/base/common/arrays';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';
const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
const TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe',
stickiness
glyphMarginClassName: ThemeIcon.asClassName(debugStackframe),
stickiness,
overviewRuler: {
position: OverviewRulerLane.Full,
color: themeColorFromId(topStackFrameColor)
}
};
const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe-focused',
stickiness
glyphMarginClassName: ThemeIcon.asClassName(debugStackframeFocused),
stickiness,
overviewRuler: {
position: OverviewRulerLane.Full,
color: themeColorFromId(focusedStackFrameColor)
}
};
const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
@@ -94,6 +107,7 @@ export class CallStackEditorContribution implements IEditorContribution {
constructor(
private readonly editor: ICodeEditor,
@IDebugService private readonly debugService: IDebugService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
const setDecorations = () => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, this.createCallStackDecorations());
this.toDispose.push(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => {
@@ -121,14 +135,15 @@ export class CallStackEditorContribution implements IEditorContribution {
}
}
if (candidateStackFrame && candidateStackFrame.source.uri.toString() === this.editor.getModel()?.uri.toString()) {
if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, this.editor.getModel()?.uri)) {
decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange, isSessionFocused));
}
}
});
});
return decorations;
// Deduplicate same decorations so colors do not stack #109045
return distinct(decorations, d => `${d.options.className} ${d.options.glyphMarginClassName} ${d.range.startLineNumber} ${d.range.startColumn}`);
}
dispose(): void {
@@ -141,7 +156,6 @@ registerThemingParticipant((theme, collector) => {
const topStackFrame = theme.getColor(topStackFrameColor);
if (topStackFrame) {
collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`);
collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`);
}
const focusedStackFrame = theme.getColor(focusedStackFrameColor);
@@ -149,6 +163,3 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`);
}
});
const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));

View File

@@ -36,7 +36,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
@@ -46,6 +46,7 @@ import { posix } from 'vs/base/common/path';
import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
@@ -111,8 +112,8 @@ async function expandTo(session: IDebugSession, tree: WorkbenchCompressibleAsync
}
export class CallStackView extends ViewPane {
private pauseMessage!: HTMLSpanElement;
private pauseMessageLabel!: HTMLSpanElement;
private stateMessage!: HTMLSpanElement;
private stateMessageLabel!: HTMLSpanElement;
private onCallStackChangeScheduler: RunOnceScheduler;
private needsRefresh = false;
private ignoreSelectionChangedEvent = false;
@@ -137,7 +138,7 @@ export class CallStackView extends ViewPane {
@IContextKeyService readonly contextKeyService: IContextKeyService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@ITelemetryService telemetryService: ITelemetryService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
@@ -156,15 +157,19 @@ export class CallStackView extends ViewPane {
const thread = sessions.length === 1 && sessions[0].getAllThreads().length === 1 ? sessions[0].getAllThreads()[0] : undefined;
if (thread && thread.stoppedDetails) {
this.pauseMessageLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || '');
this.pauseMessageLabel.title = thread.stoppedDetails.text || '';
this.pauseMessageLabel.classList.toggle('exception', thread.stoppedDetails.reason === 'exception');
this.pauseMessage.hidden = false;
this.updateActions();
this.stateMessageLabel.textContent = thread.stateLabel;
this.stateMessageLabel.title = thread.stateLabel;
this.stateMessageLabel.classList.toggle('exception', thread.stoppedDetails.reason === 'exception');
this.stateMessage.hidden = false;
} else if (sessions.length === 1 && sessions[0].state === State.Running) {
this.stateMessageLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
this.stateMessageLabel.title = sessions[0].getLabel();
this.stateMessageLabel.classList.remove('exception');
this.stateMessage.hidden = false;
} else {
this.pauseMessage.hidden = true;
this.updateActions();
this.stateMessage.hidden = true;
}
this.updateActions();
this.needsRefresh = false;
this.dataSource.deemphasizedStackFramesToShow = [];
@@ -195,14 +200,14 @@ export class CallStackView extends ViewPane {
const titleContainer = dom.append(container, $('.debug-call-stack-title'));
super.renderHeaderTitle(titleContainer, this.options.title);
this.pauseMessage = dom.append(titleContainer, $('span.pause-message'));
this.pauseMessage.hidden = true;
this.pauseMessageLabel = dom.append(this.pauseMessage, $('span.label'));
this.stateMessage = dom.append(titleContainer, $('span.state-message'));
this.stateMessage.hidden = true;
this.stateMessageLabel = dom.append(this.stateMessage, $('span.label'));
}
getActions(): IAction[] {
if (this.pauseMessage.hidden) {
return [new CollapseAction(() => this.tree, true, 'explorer-action codicon-collapse-all')];
if (this.stateMessage.hidden) {
return [new CollapseAction(() => this.tree, true, 'explorer-action ' + ThemeIcon.asClassName(icons.debugCollapseAll))];
}
return [];
@@ -437,7 +442,7 @@ export class CallStackView extends ViewPane {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, g => /^inline/.test(g));
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
@@ -497,7 +502,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
renderTemplate(container: HTMLElement): ISessionTemplateData {
const session = dom.append(container, $('.session'));
dom.append(session, $('.codicon.codicon-bug'));
dom.append(session, $(ThemeIcon.asCSSSelector(icons.callstackViewSession)));
const name = dom.append(session, $('.name'));
const stateLabel = dom.append(session, $('span.state.label.monaco-count-badge.long'));
const label = new HighlightedLabel(name, false);
@@ -547,7 +552,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
data.stateLabel.style.display = '';
if (thread && thread.stoppedDetails) {
data.stateLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || '');
data.stateLabel.textContent = thread.stateLabel;
if (thread.stoppedDetails.text) {
data.session.title = thread.stoppedDetails.text;
}
@@ -577,7 +582,7 @@ class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore,
renderTemplate(container: HTMLElement): IThreadTemplateData {
const thread = dom.append(container, $('.thread'));
const name = dom.append(thread, $('.name'));
const stateLabel = dom.append(thread, $('span.state.label'));
const stateLabel = dom.append(thread, $('span.state.label.monaco-count-badge.long'));
const label = new HighlightedLabel(name, false);
const actionBar = new ActionBar(thread);
@@ -655,7 +660,7 @@ class StackFramesRenderer implements ICompressibleTreeRenderer<IStackFrame, Fuzz
data.actionBar.clear();
if (hasActions) {
const action = new Action('debug.callStack.restartFrame', nls.localize('restartFrame', "Restart Frame"), 'codicon-debug-restart-frame', true, async () => {
const action = new Action('debug.callStack.restartFrame', nls.localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => {
try {
await stackFrame.restart();
} catch (e) {
@@ -914,7 +919,7 @@ class CallStackDataSource implements IAsyncDataSource<IDebugModel, CallStackItem
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]);
}
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
if (!thread.reachedEndOfCallStack && thread.stoppedDetails) {
callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]);
}
@@ -930,13 +935,15 @@ class CallStackAccessibilityProvider implements IListAccessibilityProvider<CallS
getAriaLabel(element: CallStackItem): string {
if (element instanceof Thread) {
return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
return nls.localize({ key: 'threadAriaLabel', comment: ['Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped'] }, "Thread {0} {1}", element.name, element.stateLabel);
}
if (element instanceof StackFrame) {
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, getSpecificSourceName(element));
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}", element.name, element.range.startLineNumber, getSpecificSourceName(element));
}
if (isDebugSession(element)) {
return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
const thread = element.getAllThreads().find(t => t.stopped);
const state = thread ? thread.stateLabel : nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
return nls.localize({ key: 'sessionLabel', comment: ['Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"'] }, "Session {0} {1}", element.getLabel(), state);
}
if (typeof element === 'string') {
return element;
@@ -988,7 +995,7 @@ class StopAction extends Action {
private readonly session: IDebugSession,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action codicon-debug-stop');
super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStop));
}
public run(): Promise<any> {
@@ -1002,7 +1009,7 @@ class DisconnectAction extends Action {
private readonly session: IDebugSession,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action codicon-debug-disconnect');
super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugDisconnect));
}
public run(): Promise<any> {
@@ -1016,7 +1023,7 @@ class RestartAction extends Action {
private readonly session: IDebugSession,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action codicon-debug-restart');
super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugRestart));
}
public run(): Promise<any> {
@@ -1030,7 +1037,7 @@ class StepOverAction extends Action {
private readonly thread: IThread,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action codicon-debug-step-over', thread.stopped);
super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepOver), thread.stopped);
}
public run(): Promise<any> {
@@ -1044,7 +1051,7 @@ class StepIntoAction extends Action {
private readonly thread: IThread,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action codicon-debug-step-into', thread.stopped);
super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepInto), thread.stopped);
}
public run(): Promise<any> {
@@ -1058,7 +1065,7 @@ class StepOutAction extends Action {
private readonly thread: IThread,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action codicon-debug-step-out', thread.stopped);
super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugStepOut), thread.stopped);
}
public run(): Promise<any> {
@@ -1072,7 +1079,7 @@ class PauseAction extends Action {
private readonly thread: IThread,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action codicon-debug-pause', !thread.stopped);
super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugPause), !thread.stopped);
}
public run(): Promise<any> {
@@ -1086,7 +1093,7 @@ class ContinueAction extends Action {
private readonly thread: IThread,
@ICommandService private readonly commandService: ICommandService
) {
super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action codicon-debug-continue', thread.stopped);
super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action ' + ThemeIcon.asClassName(icons.debugContinue), thread.stopped);
}
public run(): Promise<any> {

View File

@@ -11,7 +11,7 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/
import { Registry } from 'vs/platform/registry/common/platform';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
@@ -29,7 +29,7 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { URI } from 'vs/base/common/uri';
import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debugStatus';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView';
import { ADD_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID, RunToCursorAction, registerEditorActions } from 'vs/workbench/contrib/debug/browser/debugEditorActions';
@@ -49,9 +49,10 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl
import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess';
import { DebugProgressContribution } from 'vs/workbench/contrib/debug/browser/debugProgress';
import { DebugTitleContribution } from 'vs/workbench/contrib/debug/browser/debugTitle';
import { Codicon } from 'vs/base/common/codicons';
import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors';
import { DebugEditorContribution } from 'vs/workbench/contrib/debug/browser/debugEditorContribution';
import { FileAccess } from 'vs/base/common/network';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryExtensions.WorkbenchActions);
const debugCategory = nls.localize('debugCategory', "Debug");
@@ -152,16 +153,16 @@ function registerCommandsAndActions(): void {
});
};
registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, { id: 'codicon/debug-continue' }, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, { id: 'codicon/debug-pause' }, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running'));
registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, { id: 'codicon/debug-stop' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated());
registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, { id: 'codicon/debug-disconnect' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH);
registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, { id: 'codicon/debug-step-over' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, { id: 'codicon/debug-step-into' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, { id: 'codicon/debug-step-out' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, { id: 'codicon/debug-restart' });
registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, { id: 'codicon/debug-step-back' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running'));
registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated());
registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH);
registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart);
registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
// Debug callstack context menu
const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => {
@@ -209,15 +210,15 @@ function registerCommandsAndActions(): void {
});
};
registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-tb.png')));
registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png')));
registerTouchBarEntry(CONTINUE_ID, CONTINUE_LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-tb.png')));
registerTouchBarEntry(PAUSE_ID, PAUSE_LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/pause-tb.png')));
registerTouchBarEntry(STEP_OVER_ID, STEP_OVER_LABEL, 2, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepover-tb.png')));
registerTouchBarEntry(STEP_INTO_ID, STEP_INTO_LABEL, 3, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepinto-tb.png')));
registerTouchBarEntry(STEP_OUT_ID, STEP_OUT_LABEL, 4, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepout-tb.png')));
registerTouchBarEntry(RESTART_SESSION_ID, RESTART_LABEL, 5, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/restart-tb.png')));
registerTouchBarEntry(STOP_ID, STOP_LABEL, 6, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stop-tb.png')));
registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-tb.png', require));
registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png', require));
registerTouchBarEntry(CONTINUE_ID, CONTINUE_LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/continue-tb.png', require));
registerTouchBarEntry(PAUSE_ID, PAUSE_LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/pause-tb.png', require));
registerTouchBarEntry(STEP_OVER_ID, STEP_OVER_LABEL, 2, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/stepover-tb.png', require));
registerTouchBarEntry(STEP_INTO_ID, STEP_INTO_LABEL, 3, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/stepinto-tb.png', require));
registerTouchBarEntry(STEP_OUT_ID, STEP_OUT_LABEL, 4, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/stepout-tb.png', require));
registerTouchBarEntry(RESTART_SESSION_ID, RESTART_LABEL, 5, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/restart-tb.png', require));
registerTouchBarEntry(STOP_ID, STOP_LABEL, 6, CONTEXT_IN_DEBUG_MODE, FileAccess.asFileUri('vs/workbench/contrib/debug/browser/media/stop-tb.png', require));
}
}
@@ -459,7 +460,7 @@ function registerDebugPanel(): void {
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: DEBUG_PANEL_ID,
name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
icon: Codicon.debugConsole.classNames,
icon: icons.debugConsoleViewIcon,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: DEBUG_PANEL_ID,
focusCommand: { id: OpenDebugConsoleAction.ID },
@@ -470,35 +471,36 @@ function registerDebugPanel(): void {
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([{
id: REPL_VIEW_ID,
name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
containerIcon: Codicon.debugConsole.classNames,
containerIcon: icons.debugConsoleViewIcon,
canToggleVisibility: false,
canMoveView: true,
when: CONTEXT_DEBUGGERS_AVAILABLE,
ctorDescriptor: new SyncDescriptor(Repl),
}], VIEW_CONTAINER);
registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugConsoleAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y }), 'View: Debug Console', nls.localize('view', "View"), CONTEXT_DEBUGGERS_AVAILABLE);
registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugConsoleAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y }), 'View: Debug Console', CATEGORIES.View.value, CONTEXT_DEBUGGERS_AVAILABLE);
}
function registerDebugView(): void {
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: VIEWLET_ID,
name: nls.localize('run', "Run"),
ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer),
icon: Codicon.debugAlt.classNames,
icon: icons.runViewIcon,
alwaysUseContainerInfo: true,
order: 13 // {{SQL CARBON EDIT}}
}, ViewContainerLocation.Sidebar);
registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugViewletAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D }), 'View: Show Run and Debug', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugViewletAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D }), 'View: Show Run and Debug', CATEGORIES.View.value);
// Register default debug views
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer);
viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: Codicon.debugAlt.classNames, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: icons.variablesViewIcon, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: icons.watchViewIcon, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: icons.callStackViewIcon, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: icons.runViewIcon, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer);
viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
}
function registerConfiguration(): void {

View File

@@ -7,13 +7,13 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { RGBA, Color } from 'vs/base/common/color';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
/**
* @param text The content to stylize.
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
*/
export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, debugSession: IDebugSession): HTMLSpanElement {
export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, workspaceFolder: IWorkspaceFolder | undefined): HTMLSpanElement {
const root: HTMLSpanElement = document.createElement('span');
const textLength: number = text.length;
@@ -54,7 +54,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme
if (sequenceFound) {
// Flush buffer with previous styles.
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor);
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor);
buffer = '';
@@ -100,7 +100,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme
// Flush remaining text buffer if not empty.
if (buffer) {
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor);
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor);
}
return root;
@@ -268,7 +268,7 @@ export function appendStylizedStringToContainer(
stringContent: string,
cssClasses: string[],
linkDetector: LinkDetector,
debugSession: IDebugSession,
workspaceFolder: IWorkspaceFolder | undefined,
customTextColor?: RGBA,
customBackgroundColor?: RGBA
): void {
@@ -276,7 +276,7 @@ export function appendStylizedStringToContainer(
return;
}
const container = linkDetector.linkify(stringContent, true, debugSession.root);
const container = linkDetector.linkify(stringContent, true, workspaceFolder);
container.className = cssClasses.join(' ');
if (customTextColor) {

View File

@@ -11,8 +11,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch, IDebugConfigurationProvider } from 'vs/workbench/contrib/debug/common/debug';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch } from 'vs/workbench/contrib/debug/common/debug';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { selectBorder, selectBackground } from 'vs/platform/theme/common/colorRegistry';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
@@ -20,6 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
@@ -34,7 +35,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
private options: { label: string, handler: (() => Promise<boolean>) }[] = [];
private toDispose: IDisposable[];
private selected = 0;
private providers: { label: string, provider: IDebugConfigurationProvider, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = [];
private providers: { label: string, type: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = [];
constructor(
private context: unknown,
@@ -68,7 +69,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
render(container: HTMLElement): void {
this.container = container;
container.classList.add('start-debug-action-item');
this.start = dom.append(container, $('.codicon.codicon-debug-start'));
this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart)));
this.start.title = this.action.label;
this.start.setAttribute('role', 'button');
this.start.tabIndex = 0;
@@ -106,7 +107,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
if (shouldBeSelected) {
this.selected = e.index;
} else {
// Some select options should not remain selected https://github.com/Microsoft/vscode/issues/31526
// Some select options should not remain selected https://github.com/microsoft/vscode/issues/31526
this.selectBox.select(this.selected);
}
}));
@@ -188,23 +189,35 @@ export class StartDebugActionViewItem implements IActionViewItem {
});
});
if (this.options.length === 0) {
this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false });
} else {
this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });
disabledIdxs.push(this.options.length - 1);
}
this.providers.forEach(p => {
if (p.provider.type === manager.selectedConfiguration.config?.type) {
// Only take 3 elements from the recent dynamic configurations to not clutter the dropdown
manager.getRecentDynamicConfigurations().slice(0, 3).forEach(({ name, type }) => {
if (type === manager.selectedConfiguration.type && manager.selectedConfiguration.name === name) {
this.selected = this.options.length;
}
this.options.push({
label: name,
handler: async () => {
await manager.selectConfiguration(undefined, name, undefined, { type });
return true;
}
});
});
if (this.options.length === 0) {
this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false });
}
this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });
disabledIdxs.push(this.options.length - 1);
this.providers.forEach(p => {
this.options.push({
label: `${p.label}...`, handler: async () => {
label: `${p.label}...`,
handler: async () => {
const picked = await p.pick();
if (picked) {
await manager.selectConfiguration(picked.launch, picked.config.name, picked.config);
await manager.selectConfiguration(picked.launch, picked.config.name, picked.config, { type: p.type });
return true;
}
return false;
@@ -212,11 +225,6 @@ export class StartDebugActionViewItem implements IActionViewItem {
});
});
if (this.providers.length > 0) {
this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });
disabledIdxs.push(this.options.length - 1);
}
manager.getLaunches().filter(l => !l.hidden).forEach(l => {
const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
this.options.push({
@@ -234,6 +242,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
export class FocusSessionActionViewItem extends SelectActionViewItem {
constructor(
action: IAction,
session: IDebugSession | undefined,
@IDebugService protected readonly debugService: IDebugService,
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService,
@@ -262,15 +271,17 @@ export class FocusSessionActionViewItem extends SelectActionViewItem {
});
this._register(this.debugService.onDidEndSession(() => this.update()));
this.update();
this.update(session);
}
protected getActionContext(_: string, index: number): any {
return this.getSessions()[index];
}
private update() {
const session = this.getSelectedSession();
private update(session?: IDebugSession) {
if (!session) {
session = this.getSelectedSession();
}
const sessions = this.getSessions();
const names = sessions.map(s => {
const label = s.getLabel();

View File

@@ -14,6 +14,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { deepClone } from 'vs/base/common/objects';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export abstract class AbstractDebugAction extends Action {
@@ -64,7 +66,7 @@ export class ConfigureAction extends AbstractDebugAction {
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure), debugService, keybindingService);
this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
this.updateClass();
}
@@ -79,7 +81,7 @@ export class ConfigureAction extends AbstractDebugAction {
private updateClass(): void {
const configurationManager = this.debugService.getConfigurationManager();
this.class = configurationManager.selectedConfiguration.name ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification';
this.class = configurationManager.selectedConfiguration.name ? 'debug-action' + ThemeIcon.asClassName(icons.debugConfigure) : 'debug-action ' + ThemeIcon.asClassName(icons.debugConfigure) + ' notification';
}
async run(): Promise<any> {
@@ -132,7 +134,8 @@ export class StartAction extends AbstractDebugAction {
}
async run(): Promise<boolean> {
let { launch, name, config } = this.debugService.getConfigurationManager().selectedConfiguration;
let { launch, name, getConfig } = this.debugService.getConfigurationManager().selectedConfiguration;
const config = await getConfig();
const clonedConfig = deepClone(config);
return this.debugService.startDebugging(launch, clonedConfig || name, { noDebug: this.isNoDebug() });
}
@@ -147,10 +150,10 @@ export class StartAction extends AbstractDebugAction {
if (debugService.state === State.Initializing) {
return false;
}
let { name, config } = debugService.getConfigurationManager().selectedConfiguration;
let nameToStart = name || config?.name;
let { name, launch } = debugService.getConfigurationManager().selectedConfiguration;
let nameToStart = name;
if (sessions.some(s => s.configuration.name === nameToStart)) {
if (sessions.some(s => s.configuration.name === nameToStart && s.root === launch?.workspace)) {
// There is already a debug session running and we do not have any launch configuration selected
return false;
}
@@ -209,7 +212,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action codicon-close-all', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsRemoveAll), debugService, keybindingService);
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
@@ -267,7 +270,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action codicon-activate-breakpoints', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.breakpointsActivate), debugService, keybindingService);
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => {
@@ -310,7 +313,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction {
static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action codicon-add', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAddFuncBreakpoint), debugService, keybindingService);
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
@@ -319,7 +322,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction {
}
protected isEnabled(_: State): boolean {
return !this.debugService.getViewModel().getSelectedFunctionBreakpoint()
return !this.debugService.getViewModel().getSelectedBreakpoint()
&& this.debugService.getModel().getFunctionBreakpoints().every(fbp => !!fbp.name);
}
}
@@ -329,7 +332,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction {
static readonly LABEL = nls.localize('addWatchExpression', "Add Expression");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action codicon-add', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsAdd), debugService, keybindingService);
this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement()));
}
@@ -349,7 +352,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action codicon-close-all', debugService, keybindingService);
super(id, label, 'debug-action ' + ThemeIcon.asClassName(icons.watchExpressionsRemoveAll), debugService, keybindingService);
this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
}

View File

@@ -0,0 +1,263 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import * as strings from 'vs/base/common/strings';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ITextModel } from 'vs/editor/common/model';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfiguration, IConfig, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, IDebugAdapterFactory, CONTEXT_DEBUGGERS_AVAILABLE, IAdapterManager } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
export class AdapterManager implements IAdapterManager {
private debuggers: Debugger[];
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private debugAdapterFactories = new Map<string, IDebugAdapterFactory>();
private debuggersAvailable: IContextKey<boolean>;
private readonly _onDidRegisterDebugger = new Emitter<void>();
private readonly _onDidDebuggersExtPointRead = new Emitter<void>();
private breakpointModeIdsSet = new Set<string>();
constructor(
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandService: ICommandService,
@IExtensionService private readonly extensionService: IExtensionService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.adapterDescriptorFactories = [];
this.debuggers = [];
this.registerListeners();
this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService);
}
private registerListeners(): void {
debuggersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => {
added.value.forEach(rawAdapter => {
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
}
if (rawAdapter.type !== '*') {
const existing = this.getDebugger(rawAdapter.type);
if (existing) {
existing.merge(rawAdapter, added.description);
} else {
this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
}
}
});
});
// take care of all wildcard contributions
extensions.forEach(extension => {
extension.value.forEach(rawAdapter => {
if (rawAdapter.type === '*') {
this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
}
});
});
delta.removed.forEach(removed => {
const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
});
// update the schema to include all attributes, snippets and types from extensions.
this.debuggers.forEach(adapter => {
const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
const schemaAttributes = adapter.getSchemaAttributes();
if (schemaAttributes && items.oneOf) {
items.oneOf.push(...schemaAttributes);
}
const configurationSnippets = adapter.configurationSnippets;
if (configurationSnippets && items.defaultSnippets) {
items.defaultSnippets.push(...configurationSnippets);
}
});
this._onDidDebuggersExtPointRead.fire();
});
breakpointsExtPoint.setHandler((extensions, delta) => {
delta.removed.forEach(removed => {
removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
});
delta.added.forEach(added => {
added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
});
});
}
registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable {
debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher));
this.debuggersAvailable.set(this.debugAdapterFactories.size > 0);
this._onDidRegisterDebugger.fire();
return {
dispose: () => {
debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
}
};
}
hasDebuggers(): boolean {
return this.debugAdapterFactories.size > 0;
}
createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined {
let factory = this.debugAdapterFactories.get(session.configuration.type);
if (factory) {
return factory.createDebugAdapter(session);
}
return undefined;
}
substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
let factory = this.debugAdapterFactories.get(debugType);
if (factory) {
return factory.substituteVariables(folder, config);
}
return Promise.resolve(config);
}
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
let factory = this.debugAdapterFactories.get(debugType);
if (factory) {
return factory.runInTerminal(args);
}
return Promise.resolve(void 0);
}
registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable {
this.adapterDescriptorFactories.push(debugAdapterProvider);
return {
dispose: () => {
this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
}
};
}
unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void {
const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider);
if (ix >= 0) {
this.adapterDescriptorFactories.splice(ix, 1);
}
}
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined> {
const config = session.configuration;
const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor);
if (providers.length === 1) {
return providers[0].createDebugAdapterDescriptor(session);
} else {
// TODO@AW handle n > 1 case
}
return Promise.resolve(undefined);
}
getDebuggerLabel(type: string): string | undefined {
const dbgr = this.getDebugger(type);
if (dbgr) {
return dbgr.label;
}
return undefined;
}
get onDidRegisterDebugger(): Event<void> {
return this._onDidRegisterDebugger.event;
}
get onDidDebuggersExtPointRead(): Event<void> {
return this._onDidDebuggersExtPointRead.event;
}
canSetBreakpointsIn(model: ITextModel): boolean {
const modeId = model.getLanguageIdentifier().language;
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
// do not allow breakpoints in our settings files and output
return false;
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
return true;
}
return this.breakpointModeIdsSet.has(modeId);
}
getDebugger(type: string): Debugger | undefined {
return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type));
}
isDebuggerInterestedInLanguage(language: string): boolean {
return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0);
}
async guessDebugger(type?: string): Promise<Debugger | undefined> {
if (type) {
const adapter = this.getDebugger(type);
return Promise.resolve(adapter);
}
const activeTextEditorControl = this.editorService.activeTextEditorControl;
let candidates: Debugger[] | undefined;
if (isCodeEditor(activeTextEditorControl)) {
const model = activeTextEditorControl.getModel();
const language = model ? model.getLanguageIdentifier().language : undefined;
const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
if (adapters.length === 1) {
return adapters[0];
}
if (adapters.length > 1) {
candidates = adapters;
}
}
if (!candidates) {
await this.activateDebuggers('onDebugInitialConfigurations');
candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider());
}
candidates.sort((first, second) => first.label.localeCompare(second.label));
const picks = candidates.map(c => ({ label: c.label, debugger: c }));
return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked && picked.debugger) {
return picked.debugger;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
});
}
async activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
const promises: Promise<any>[] = [
this.extensionService.activateByEvent(activationEvent),
this.extensionService.activateByEvent('onDebug')
];
if (debugType) {
promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`));
}
await Promise.all(promises);
}
}

View File

@@ -113,9 +113,9 @@ function isSessionContext(obj: any): obj is CallStackContext {
export function registerCommands(): void {
// These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar
// These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar
// When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id
// Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command pallete) we do not pass any id and just take whatever is the focussed thread
// Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread
// Same for stackFrame commands and session commands.
CommandsRegistry.registerCommand({
id: COPY_STACK_TRACE_ID,
@@ -521,13 +521,13 @@ export function registerCommands(): void {
const control = editorService.activeTextEditorControl;
if (isCodeEditor(control)) {
const position = control.getPosition();
if (position && control.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(control.getModel())) {
if (position && control.hasModel() && debugService.canSetBreakpointsIn(control.getModel())) {
const modelUri = control.getModel().uri;
const breakpointAlreadySet = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri })
.some(bp => (bp.sessionAgnosticData.column === position.column || (!bp.column && position.column <= 1)));
if (!breakpointAlreadySet) {
debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column > 1 ? position.column : undefined }], 'debugCommands.inlineBreakpointCommand');
debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column > 1 ? position.column : undefined }]);
}
}
}
@@ -564,7 +564,7 @@ export function registerCommands(): void {
if (list instanceof List) {
const focus = list.getFocusedElements();
if (focus.length && focus[0] instanceof Breakpoint) {
return openBreakpointSource(focus[0], true, false, accessor.get(IDebugService), accessor.get(IEditorService));
return openBreakpointSource(focus[0], true, false, true, accessor.get(IDebugService), accessor.get(IEditorService));
}
}

View File

@@ -6,30 +6,25 @@
import * as nls from 'vs/nls';
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
import * as json from 'vs/base/common/json';
import { URI as uri } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorPane } from 'vs/workbench/common/editor';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IDebugConfigurationProvider, ICompound, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, CONTEXT_DEBUG_CONFIGURATION_TYPE, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { launchSchema } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@@ -37,9 +32,13 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import { withUndefinedAsNull } from 'vs/base/common/types';
import { sequence } from 'vs/base/common/async';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { flatten } from 'vs/base/common/arrays';
import { flatten, distinct } from 'vs/base/common/arrays';
import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager';
import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
@@ -48,40 +47,34 @@ const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
// Debug type is only stored if a dynamic configuration is used for better restore
const DEBUG_SELECTED_TYPE = 'debug.selectedtype';
const DEBUG_RECENT_DYNAMIC_CONFIGURATIONS = 'debug.recentdynamicconfigurations';
interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig }
export class ConfigurationManager implements IConfigurationManager {
private debuggers: Debugger[];
private breakpointModeIdsSet = new Set<string>();
private launches!: ILaunch[];
private selectedName: string | undefined;
private selectedLaunch: ILaunch | undefined;
private selectedConfig: IConfig | undefined;
private getSelectedConfig: () => Promise<IConfig | undefined> = () => Promise.resolve(undefined);
private selectedType: string | undefined;
private toDispose: IDisposable[];
private readonly _onDidSelectConfigurationName = new Emitter<void>();
private configProviders: IDebugConfigurationProvider[];
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private debugAdapterFactories = new Map<string, IDebugAdapterFactory>();
private debugConfigurationTypeContext: IContextKey<string>;
private debuggersAvailable: IContextKey<boolean>;
private readonly _onDidRegisterDebugger = new Emitter<void>();
constructor(
private readonly adapterManager: AdapterManager,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandService: ICommandService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IHistoryService private readonly historyService: IHistoryService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
this.debuggers = [];
this.toDispose = [];
this.initLaunches();
this.registerListeners();
@@ -90,111 +83,13 @@ export class ConfigurationManager implements IConfigurationManager {
const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot);
const previousSelectedName = this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE);
this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService);
this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService);
if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) {
this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, previousSelectedType);
this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, { type: previousSelectedType });
} else if (this.launches.length > 0) {
this.selectConfiguration(undefined, previousSelectedName, undefined, previousSelectedType);
this.selectConfiguration(undefined, previousSelectedName, undefined, { type: previousSelectedType });
}
}
// debuggers
registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable {
debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher));
this.debuggersAvailable.set(this.debugAdapterFactories.size > 0);
this._onDidRegisterDebugger.fire();
return {
dispose: () => {
debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
}
};
}
hasDebuggers(): boolean {
return this.debugAdapterFactories.size > 0;
}
createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined {
let factory = this.debugAdapterFactories.get(session.configuration.type);
if (factory) {
return factory.createDebugAdapter(session);
}
return undefined;
}
substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
let factory = this.debugAdapterFactories.get(debugType);
if (factory) {
return factory.substituteVariables(folder, config);
}
return Promise.resolve(config);
}
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
let factory = this.debugAdapterFactories.get(debugType);
if (factory) {
return factory.runInTerminal(args);
}
return Promise.resolve(void 0);
}
// debug adapter
registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable {
this.adapterDescriptorFactories.push(debugAdapterProvider);
return {
dispose: () => {
this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
}
};
}
unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void {
const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider);
if (ix >= 0) {
this.adapterDescriptorFactories.splice(ix, 1);
}
}
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined> {
const config = session.configuration;
// first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable
const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable);
if (providers0.length === 1 && providers0[0].debugAdapterExecutable) {
return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined);
} else {
// TODO@AW handle n > 1 case
}
// new API
const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor);
if (providers.length === 1) {
return providers[0].createDebugAdapterDescriptor(session);
} else {
// TODO@AW handle n > 1 case
}
return Promise.resolve(undefined);
}
getDebuggerLabel(type: string): string | undefined {
const dbgr = this.getDebugger(type);
if (dbgr) {
return dbgr.label;
}
return undefined;
}
get onDidRegisterDebugger(): Event<void> {
return this._onDidRegisterDebugger.event;
}
// debug configurations
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
this.configProviders.push(debugConfigurationProvider);
return {
@@ -263,7 +158,7 @@ export class ConfigurationManager implements IConfigurationManager {
return results.reduce((first, second) => first.concat(second), []);
}
async getDynamicProviders(): Promise<{ label: string, provider: IDebugConfigurationProvider, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> {
async getDynamicProviders(): Promise<{ label: string, type: string, getProvider: () => Promise<IDebugConfigurationProvider | undefined>, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> {
const extensions = await this.extensionService.getExtensions();
const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations';
const debugDynamicExtensionsTypes = extensions.reduce((acc, e) => {
@@ -293,20 +188,23 @@ export class ConfigurationManager implements IConfigurationManager {
return acc;
}, [] as string[]);
await Promise.all(debugDynamicExtensionsTypes.map(type => this.activateDebuggers(onDebugDynamicConfigurationsName, type)));
return debugDynamicExtensionsTypes.map(type => {
const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)!;
return {
label: this.getDebuggerLabel(type)!,
provider,
label: this.adapterManager.getDebuggerLabel(type)!,
getProvider: async () => {
await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
return this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations);
},
type,
pick: async () => {
// Do a late 'onDebugDynamicConfigurationsName' activation so extensions are not activated too early #108578
await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
const disposables = new DisposableStore();
const input = disposables.add(this.quickInputService.createQuickPick<IDynamicPickItem>());
input.busy = true;
input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration");
input.show();
let chosenDidCancel = false;
const chosenPromise = new Promise<IDynamicPickItem | undefined>(resolve => {
disposables.add(input.onDidAccept(() => resolve(input.activeItems[0])));
disposables.add(input.onDidTriggerItemButton(async (context) => {
@@ -317,11 +215,11 @@ export class ConfigurationManager implements IConfigurationManager {
await (launch as Launch).writeConfiguration(config);
await this.selectConfiguration(launch, config.name);
}));
disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(undefined); }));
});
const token = new CancellationTokenSource();
const picks: Promise<IDynamicPickItem[]>[] = [];
const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations);
this.getLaunches().forEach(launch => {
if (launch.workspace && provider) {
picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({
@@ -329,7 +227,7 @@ export class ConfigurationManager implements IConfigurationManager {
description: launch.name,
config,
buttons: [{
iconClass: 'codicon-gear',
iconClass: ThemeIcon.asClassName(debugConfigure),
tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
}],
launch
@@ -340,16 +238,9 @@ export class ConfigurationManager implements IConfigurationManager {
const nestedPicks = await Promise.all(picks);
const items = flatten(nestedPicks);
let chosen: IDynamicPickItem | undefined;
// If there's exactly one item to choose from, pick it automatically
if (items.length === 1 && !chosenDidCancel) {
chosen = items[0];
} else {
input.items = items;
input.busy = false;
chosen = await chosenPromise;
}
input.items = items;
input.busy = false;
const chosen = await chosenPromise;
disposables.dispose();
@@ -379,69 +270,11 @@ export class ConfigurationManager implements IConfigurationManager {
return getVisibleAndSorted(all);
}
getRecentDynamicConfigurations(): { name: string, type: string }[] {
return JSON.parse(this.storageService.get(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, StorageScope.WORKSPACE, '[]'));
}
private registerListeners(): void {
debuggersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => {
added.value.forEach(rawAdapter => {
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
}
if (rawAdapter.enableBreakpointsFor) {
rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
this.breakpointModeIdsSet.add(modeId);
});
}
if (rawAdapter.type !== '*') {
const existing = this.getDebugger(rawAdapter.type);
if (existing) {
existing.merge(rawAdapter, added.description);
} else {
this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
}
}
});
});
// take care of all wildcard contributions
extensions.forEach(extension => {
extension.value.forEach(rawAdapter => {
if (rawAdapter.type === '*') {
this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
}
});
});
delta.removed.forEach(removed => {
const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
});
// update the schema to include all attributes, snippets and types from extensions.
this.debuggers.forEach(adapter => {
const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
const schemaAttributes = adapter.getSchemaAttributes();
if (schemaAttributes && items.oneOf) {
items.oneOf.push(...schemaAttributes);
}
const configurationSnippets = adapter.configurationSnippets;
if (configurationSnippets && items.defaultSnippets) {
items.defaultSnippets.push(...configurationSnippets);
}
});
this.setCompoundSchemaValues();
});
breakpointsExtPoint.setHandler((extensions, delta) => {
delta.removed.forEach(removed => {
removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
});
delta.added.forEach(added => {
added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
});
});
this.toDispose.push(Event.any<IWorkspaceFoldersChangeEvent | WorkbenchState>(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => {
this.initLaunches();
this.selectConfiguration(undefined);
@@ -454,14 +287,17 @@ export class ConfigurationManager implements IConfigurationManager {
this.setCompoundSchemaValues();
}
}));
this.toDispose.push(this.adapterManager.onDidDebuggersExtPointRead(() => {
this.setCompoundSchemaValues();
}));
}
private initLaunches(): void {
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, this.adapterManager, folder));
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this));
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this, this.adapterManager));
}
this.launches.push(this.instantiationService.createInstance(UserLaunch, this));
this.launches.push(this.instantiationService.createInstance(UserLaunch, this, this.adapterManager));
if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) {
this.selectConfiguration(undefined);
@@ -490,14 +326,15 @@ export class ConfigurationManager implements IConfigurationManager {
return undefined;
}
return this.launches.find(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString());
return this.launches.find(l => l.workspace && this.uriIdentityService.extUri.isEqual(l.workspace.uri, workspaceUri));
}
get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined, config: IConfig | undefined } {
get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined, getConfig: () => Promise<IConfig | undefined>, type: string | undefined } {
return {
launch: this.selectedLaunch,
name: this.selectedName,
config: this.selectedConfig
getConfig: this.getSelectedConfig,
type: this.selectedType
};
}
@@ -513,7 +350,7 @@ export class ConfigurationManager implements IConfigurationManager {
return undefined;
}
async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, type?: string): Promise<void> {
async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, dynamicConfig?: { type?: string }): Promise<void> {
if (typeof launch === 'undefined') {
const rootUri = this.historyService.getLastActiveWorkspaceRoot();
launch = this.getLaunch(rootUri);
@@ -527,38 +364,56 @@ export class ConfigurationManager implements IConfigurationManager {
this.selectedLaunch = launch;
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE, StorageTarget.MACHINE);
} else {
this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
}
const names = launch ? launch.getConfigurationNames() : [];
if ((name && names.indexOf(name) >= 0) || config) {
this.getSelectedConfig = () => Promise.resolve(config);
let type = config?.type;
if (name && names.indexOf(name) >= 0) {
this.setSelectedLaunchName(name);
} else if (!this.selectedName || names.indexOf(this.selectedName) === -1) {
// We could not find the previously used name. We should get all dynamic configurations from providers
} else if (dynamicConfig && dynamicConfig.type) {
// We could not find the previously used name and config is not passed. We should get all dynamic configurations from providers
// And potentially auto select the previously used dynamic configuration #96293
const providers = await this.getDynamicProviders();
const provider = providers.find(p => p.provider.type === type);
let nameToSet = names.length ? names[0] : undefined;
if (provider && launch && launch.workspace) {
const token = new CancellationTokenSource();
const dynamicConfigs = await provider.provider.provideDebugConfigurations!(launch.workspace.uri, token.token);
const dynamicConfig = dynamicConfigs.find(c => c.name === name);
if (dynamicConfig) {
config = dynamicConfig;
nameToSet = name;
}
}
type = dynamicConfig.type;
if (!config) {
const providers = (await this.getDynamicProviders()).filter(p => p.type === type);
this.getSelectedConfig = async () => {
const activatedProviders = await Promise.all(providers.map(p => p.getProvider()));
const provider = activatedProviders.find(p => p && p.type === type);
if (provider && launch && launch.workspace) {
const token = new CancellationTokenSource();
const dynamicConfigs = await provider.provideDebugConfigurations!(launch.workspace.uri, token.token);
const dynamicConfig = dynamicConfigs.find(c => c.name === name);
if (dynamicConfig) {
return dynamicConfig;
}
}
return undefined;
};
}
this.setSelectedLaunchName(name);
let recentDynamicProviders = this.getRecentDynamicConfigurations();
if (name && dynamicConfig.type) {
// We need to store the recently used dynamic configurations to be able to show them in UI #110009
recentDynamicProviders.unshift({ name, type: dynamicConfig.type });
recentDynamicProviders = distinct(recentDynamicProviders, t => `${t.name} : ${t.type}`);
this.storageService.store(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, JSON.stringify(recentDynamicProviders), StorageScope.WORKSPACE, StorageTarget.USER);
}
} else if (!this.selectedName || names.indexOf(this.selectedName) === -1) {
// We could not find the configuration to select, pick the first one, or reset the selection if there is no launch configuration
const nameToSet = names.length ? names[0] : undefined;
this.setSelectedLaunchName(nameToSet);
}
this.selectedConfig = config;
this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedConfig?.type, StorageScope.WORKSPACE);
const configForType = this.selectedConfig || (this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined);
if (configForType) {
this.debugConfigurationTypeContext.set(configForType.type);
this.selectedType = dynamicConfig?.type || config?.type;
this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedType, StorageScope.WORKSPACE, StorageTarget.MACHINE);
if (type) {
this.debugConfigurationTypeContext.set(type);
} else {
this.debugConfigurationTypeContext.reset();
}
@@ -568,66 +423,6 @@ export class ConfigurationManager implements IConfigurationManager {
}
}
canSetBreakpointsIn(model: ITextModel): boolean {
const modeId = model.getLanguageIdentifier().language;
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
// do not allow breakpoints in our settings files and output
return false;
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
return true;
}
return this.breakpointModeIdsSet.has(modeId);
}
getDebugger(type: string): Debugger | undefined {
return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type));
}
isDebuggerInterestedInLanguage(language: string): boolean {
return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0);
}
async guessDebugger(type?: string): Promise<Debugger | undefined> {
if (type) {
const adapter = this.getDebugger(type);
return Promise.resolve(adapter);
}
const activeTextEditorControl = this.editorService.activeTextEditorControl;
let candidates: Debugger[] | undefined;
if (isCodeEditor(activeTextEditorControl)) {
const model = activeTextEditorControl.getModel();
const language = model ? model.getLanguageIdentifier().language : undefined;
const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
if (adapters.length === 1) {
return adapters[0];
}
if (adapters.length > 1) {
candidates = adapters;
}
}
if (!candidates) {
await this.activateDebuggers('onDebugInitialConfigurations');
candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider());
}
candidates.sort((first, second) => first.label.localeCompare(second.label));
const picks = candidates.map(c => ({ label: c.label, debugger: c }));
return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked && picked.debugger) {
return picked.debugger;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
});
}
async activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
const promises: Promise<any>[] = [
this.extensionService.activateByEvent(activationEvent),
@@ -643,7 +438,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.selectedName = selectedName;
if (this.selectedName) {
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE, StorageTarget.MACHINE);
} else {
this.storageService.remove(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE);
}
@@ -657,8 +452,10 @@ export class ConfigurationManager implements IConfigurationManager {
abstract class AbstractLaunch {
protected abstract getConfig(): IGlobalConfig | undefined;
constructor(protected configurationManager: ConfigurationManager) {
}
constructor(
protected configurationManager: ConfigurationManager,
private readonly adapterManager: AdapterManager
) { }
getCompound(name: string): ICompound | undefined {
const config = this.getConfig();
@@ -696,13 +493,22 @@ abstract class AbstractLaunch {
if (!config || !config.configurations) {
return undefined;
}
return config.configurations.find(config => config && config.name === name);
const configuration = config.configurations.find(config => config && config.name === name);
if (configuration) {
if (this instanceof UserLaunch) {
configuration.__configurationTarget = ConfigurationTarget.USER;
} else if (this instanceof WorkspaceLaunch) {
configuration.__configurationTarget = ConfigurationTarget.WORKSPACE;
} else {
configuration.__configurationTarget = ConfigurationTarget.WORKSPACE_FOLDER;
}
}
return configuration;
}
async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise<string> {
let content = '';
const adapter = await this.configurationManager.guessDebugger(type);
const adapter = await this.adapterManager.guessDebugger(type);
if (adapter) {
const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None);
content = await adapter.getInitialConfigurationContent(initialConfigs);
@@ -719,13 +525,14 @@ class Launch extends AbstractLaunch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
adapterManager: AdapterManager,
public workspace: IWorkspaceFolder,
@IFileService private readonly fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(configurationManager);
super(configurationManager, adapterManager);
}
get uri(): uri {
@@ -802,11 +609,12 @@ class Launch extends AbstractLaunch implements ILaunch {
class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
adapterManager: AdapterManager,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
) {
super(configurationManager);
super(configurationManager, adapterManager);
}
get workspace(): undefined {
@@ -853,10 +661,11 @@ class UserLaunch extends AbstractLaunch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
adapterManager: AdapterManager,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IPreferencesService private readonly preferencesService: IPreferencesService
) {
super(configurationManager);
super(configurationManager, adapterManager);
}
get workspace(): undefined {
@@ -880,7 +689,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch {
}
async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> {
const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus });
const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus, revealSetting: { key: 'launch' } });
return ({
editor: withUndefinedAsNull(editor),
created: false

View File

@@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
@@ -19,6 +19,11 @@ import { IViewsService } from 'vs/workbench/common/views';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
import { getDomNodePagePosition } from 'vs/base/browser/dom';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { Position } from 'vs/editor/common/core/position';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { raceTimeout } from 'vs/base/common/async';
export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint';
class ToggleBreakpointAction extends EditorAction {
@@ -40,7 +45,7 @@ class ToggleBreakpointAction extends EditorAction {
if (editor.hasModel()) {
const debugService = accessor.get(IDebugService);
const modelUri = editor.getModel().uri;
const canSet = debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel());
const canSet = debugService.canSetBreakpointsIn(editor.getModel());
// Does not account for multi line selections, Set to remove multiple cursor on the same line
const lineNumbers = [...new Set(editor.getSelections().map(s => s.getPosition().lineNumber))];
@@ -49,7 +54,7 @@ class ToggleBreakpointAction extends EditorAction {
if (bps.length) {
return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId())));
} else if (canSet) {
return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }], 'debugEditorActions.toggleBreakpointAction'));
return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }]));
} else {
return [];
}
@@ -74,7 +79,7 @@ class ConditionalBreakpointAction extends EditorAction {
const debugService = accessor.get(IDebugService);
const position = editor.getPosition();
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) {
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION);
}
}
@@ -96,7 +101,7 @@ class LogPointAction extends EditorAction {
const debugService = accessor.get(IDebugService);
const position = editor.getPosition();
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) {
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE);
}
}
@@ -127,8 +132,32 @@ export class RunToCursorAction extends EditorAction {
return;
}
let breakpointToRemove: IBreakpoint;
const oneTimeListener = focusedSession.onDidChangeState(() => {
const position = editor.getPosition();
if (!(editor.hasModel() && position)) {
return;
}
const uri = editor.getModel().uri;
const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length);
let breakpointToRemove: IBreakpoint | undefined;
let threadToContinue = debugService.getViewModel().focusedThread;
if (!bpExists) {
const addResult = await this.addBreakpoints(accessor, uri, position);
if (addResult.thread) {
threadToContinue = addResult.thread;
}
if (addResult.breakpoint) {
breakpointToRemove = addResult.breakpoint;
}
}
if (!threadToContinue) {
return;
}
const oneTimeListener = threadToContinue.session.onDidChangeState(() => {
const state = focusedSession.state;
if (state === State.Stopped || state === State.Inactive) {
if (breakpointToRemove) {
@@ -138,27 +167,90 @@ export class RunToCursorAction extends EditorAction {
}
});
const position = editor.getPosition();
if (editor.hasModel() && position) {
const uri = editor.getModel().uri;
const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length);
if (!bpExists) {
let column = 0;
const focusedStackFrame = debugService.getViewModel().focusedStackFrame;
if (focusedStackFrame && focusedStackFrame.source.uri.toString() === uri.toString() && focusedStackFrame.range.startLineNumber === position.lineNumber) {
// If the cursor is on a line different than the one the debugger is currently paused on, then send the breakpoint at column 0 on the line
// otherwise set it at the precise column #102199
column = position.column;
}
await threadToContinue.continue();
}
const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column }], 'debugEditorActions.runToCursorAction');
if (breakpoints && breakpoints.length) {
breakpointToRemove = breakpoints[0];
private async addBreakpoints(accessor: ServicesAccessor, uri: URI, position: Position) {
const debugService = accessor.get(IDebugService);
const debugModel = debugService.getModel();
const viewModel = debugService.getViewModel();
const uriIdentityService = accessor.get(IUriIdentityService);
let column = 0;
const focusedStackFrame = viewModel.focusedStackFrame;
if (focusedStackFrame && uriIdentityService.extUri.isEqual(focusedStackFrame.source.uri, uri) && focusedStackFrame.range.startLineNumber === position.lineNumber) {
// If the cursor is on a line different than the one the debugger is currently paused on, then send the breakpoint at column 0 on the line
// otherwise set it at the precise column #102199
column = position.column;
}
const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column }], false);
const breakpoint = breakpoints?.[0];
if (!breakpoint) {
return { breakpoint: undefined, thread: viewModel.focusedThread };
}
// If the breakpoint was not initially verified, wait up to 2s for it to become so.
// Inherently racey if multiple sessions can verify async, but not solvable...
if (!breakpoint.verified) {
let listener: IDisposable;
await raceTimeout(new Promise<void>(resolve => {
listener = debugModel.onDidChangeBreakpoints(() => {
if (breakpoint.verified) {
resolve();
}
});
}), 2000);
listener!.dispose();
}
// Look at paused threads for sessions that verified this bp. Prefer, in order:
const enum Score {
/** The focused thread */
Focused,
/** Any other stopped thread of a session that verified the bp */
Verified,
/** Any thread that verified and paused in the same file */
VerifiedAndPausedInFile,
/** The focused thread if it verified the breakpoint */
VerifiedAndFocused,
}
let bestThread = viewModel.focusedThread;
let bestScore = Score.Focused;
for (const sessionId of breakpoint.sessionsThatVerified) {
const session = debugModel.getSession(sessionId);
if (!session) {
continue;
}
const threads = session.getAllThreads().filter(t => t.stopped);
if (bestScore < Score.VerifiedAndFocused) {
if (viewModel.focusedThread && threads.includes(viewModel.focusedThread)) {
bestThread = viewModel.focusedThread;
bestScore = Score.VerifiedAndFocused;
}
}
await debugService.getViewModel().focusedThread!.continue();
if (bestScore < Score.VerifiedAndPausedInFile) {
const pausedInThisFile = threads.find(t => {
const top = t.getTopStackFrame();
return top && uriIdentityService.extUri.isEqual(top.source.uri, uri);
});
if (pausedInThisFile) {
bestThread = pausedInThisFile;
bestScore = Score.VerifiedAndPausedInFile;
}
}
if (bestScore < Score.Verified) {
bestThread = threads[0];
bestScore = Score.VerifiedAndPausedInFile;
}
}
return { thread: bestThread, breakpoint };
}
}
@@ -272,10 +364,11 @@ class StepIntoTargetsAction extends EditorAction {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const contextMenuService = accessor.get(IContextMenuService);
const uriIdentityService = accessor.get(IUriIdentityService);
const session = debugService.getViewModel().focusedSession;
const frame = debugService.getViewModel().focusedStackFrame;
if (session && frame && editor.hasModel() && editor.getModel().uri.toString() === frame.source.uri.toString()) {
if (session && frame && editor.hasModel() && uriIdentityService.extUri.isEqual(editor.getModel().uri, frame.source.uri)) {
const targets = await session.stepInTargets(frame.frameId);
if (!targets) {
return;
@@ -305,6 +398,8 @@ class GoToBreakpointAction extends EditorAction {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
const debugService = accessor.get(IDebugService);
const editorService = accessor.get(IEditorService);
const uriIdentityService = accessor.get(IUriIdentityService);
if (editor.hasModel()) {
const currentUri = editor.getModel().uri;
const currentLine = editor.getPosition().lineNumber;
@@ -314,8 +409,8 @@ class GoToBreakpointAction extends EditorAction {
//Try to find breakpoint in current file
let moveBreakpoint =
this.isNext
? allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber > currentLine).shift()
: allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber < currentLine).pop();
? allEnabledBreakpoints.filter(bp => uriIdentityService.extUri.isEqual(bp.uri, currentUri) && bp.lineNumber > currentLine).shift()
: allEnabledBreakpoints.filter(bp => uriIdentityService.extUri.isEqual(bp.uri, currentUri) && bp.lineNumber < currentLine).pop();
//Try to find breakpoints in following files
if (!moveBreakpoint) {
@@ -331,7 +426,7 @@ class GoToBreakpointAction extends EditorAction {
}
if (moveBreakpoint) {
return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService);
return openBreakpointSource(moveBreakpoint, false, true, false, debugService, editorService);
}
}
}
@@ -359,6 +454,27 @@ class GoToPreviousBreakpointAction extends GoToBreakpointAction {
}
}
class CloseExceptionWidgetAction extends EditorAction {
constructor() {
super({
id: 'editor.debug.action.closeExceptionWidget',
label: nls.localize('closeExceptionWidget', "Close Exception Widget"),
alias: 'Close Exception Widget',
precondition: CONTEXT_EXCEPTION_WIDGET_VISIBLE,
kbOpts: {
primary: KeyCode.Escape,
weight: KeybindingWeight.EditorContrib
}
});
}
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const contribution = editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);
contribution.closeExceptionWidget();
}
}
export function registerEditorActions(): void {
registerEditorAction(ToggleBreakpointAction);
registerEditorAction(ConditionalBreakpointAction);
@@ -370,4 +486,5 @@ export function registerEditorActions(): void {
registerEditorAction(ShowDebugHoverAction);
registerEditorAction(GoToNextBreakpointAction);
registerEditorAction(GoToPreviousBreakpointAction);
registerEditorAction(CloseExceptionWidgetAction);
}

View File

@@ -10,7 +10,7 @@ import { visit } from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Constants } from 'vs/base/common/uint';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { StandardTokenType } from 'vs/editor/common/modes';
import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
@@ -21,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugEditorContribution, IDebugService, State, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugEditorContribution, IDebugService, State, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug';
import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget';
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
import { Position } from 'vs/editor/common/core/position';
@@ -33,8 +33,14 @@ import { ITextModel } from 'vs/editor/common/model';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { basename } from 'vs/base/common/path';
import { domEvent } from 'vs/base/browser/event';
import { ModesHoverController } from 'vs/editor/contrib/hover/hover';
import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { Event } from 'vs/base/common/event';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const HOVER_DELAY = 300;
const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/;
const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration';
const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons
@@ -168,11 +174,13 @@ export class DebugEditorContribution implements IDebugEditorContribution {
private hoverWidget: DebugHoverWidget;
private hoverRange: Range | null = null;
private mouseDown = false;
private exceptionWidgetVisible: IContextKey<boolean>;
private static readonly MEMOIZER = createMemoizer();
private exceptionWidget: ExceptionWidget | undefined;
private configurationWidget: FloatingClickWidget | undefined;
private altListener: IDisposable | undefined;
private altPressed = false;
constructor(
private editor: ICodeEditor,
@@ -182,12 +190,16 @@ export class DebugEditorContribution implements IDebugEditorContribution {
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IHostService private readonly hostService: IHostService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor);
this.toDispose = [];
this.registerListeners();
this.updateConfigurationWidgetVisibility();
this.codeEditorService.registerDecorationType(INLINE_VALUE_DECORATION_KEY, {});
this.exceptionWidgetVisible = CONTEXT_EXCEPTION_WIDGET_VISIBLE.bindTo(contextKeyService);
this.toggleExceptionWidget();
}
@@ -219,7 +231,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
if (model) {
this._applyHoverConfiguration(model, stackFrame);
this.applyHoverConfiguration(model, stackFrame);
}
this.toggleExceptionWidget();
this.hideHoverWidget();
@@ -240,14 +252,49 @@ export class DebugEditorContribution implements IDebugEditorContribution {
return getWordToLineNumbersMap(this.editor.getModel());
}
private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void {
if (stackFrame && model.uri.toString() === stackFrame.source.uri.toString()) {
this.editor.updateOptions({
hover: {
enabled: false
private applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void {
if (stackFrame && this.uriIdentityService.extUri.isEqual(model.uri, stackFrame.source.uri)) {
if (this.altListener) {
this.altListener.dispose();
}
// When the alt key is pressed show regular editor hover and hide the debug hover #84561
this.altListener = domEvent(document, 'keydown')(keydownEvent => {
const standardKeyboardEvent = new StandardKeyboardEvent(keydownEvent);
if (standardKeyboardEvent.keyCode === KeyCode.Alt) {
this.altPressed = true;
const debugHoverWasVisible = this.hoverWidget.isVisible();
this.hoverWidget.hide();
this.enableEditorHover();
if (debugHoverWasVisible && this.hoverRange) {
// If the debug hover was visible immediately show the editor hover for the alt transition to be smooth
const hoverController = this.editor.getContribution<ModesHoverController>(ModesHoverController.ID);
hoverController.showContentHover(this.hoverRange, HoverStartMode.Immediate, false);
}
const listener = Event.any<KeyboardEvent | boolean>(this.hostService.onDidChangeFocus, domEvent(document, 'keyup'))(keyupEvent => {
let standardKeyboardEvent = undefined;
if (keyupEvent instanceof KeyboardEvent) {
standardKeyboardEvent = new StandardKeyboardEvent(keyupEvent);
}
if (!standardKeyboardEvent || standardKeyboardEvent.keyCode === KeyCode.Alt) {
this.altPressed = false;
this.editor.updateOptions({ hover: { enabled: false } });
listener.dispose();
}
});
}
});
this.editor.updateOptions({ hover: { enabled: false } });
} else {
this.altListener?.dispose();
this.enableEditorHover();
}
}
private enableEditorHover(): void {
if (this.editor.hasModel()) {
const model = this.editor.getModel();
let overrides = {
resource: model.uri,
overrideIdentifier: model.getLanguageIdentifier().language
@@ -266,7 +313,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
async showHover(range: Range, focus: boolean): Promise<void> {
const sf = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
if (sf && model && sf.source.uri.toString() === model.uri.toString()) {
if (sf && model && this.uriIdentityService.extUri.isEqual(sf.source.uri, model.uri) && !this.altPressed) {
return this.hoverWidget.showAt(range, focus);
}
}
@@ -274,8 +321,8 @@ export class DebugEditorContribution implements IDebugEditorContribution {
private async onFocusStackFrame(sf: IStackFrame | undefined): Promise<void> {
const model = this.editor.getModel();
if (model) {
this._applyHoverConfiguration(model, sf);
if (sf && sf.source.uri.toString() === model.uri.toString()) {
this.applyHoverConfiguration(model, sf);
if (sf && this.uriIdentityService.extUri.isEqual(sf.source.uri, model.uri)) {
await this.toggleExceptionWidget();
} else {
this.hideHoverWidget();
@@ -287,11 +334,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
@memoize
private get showHoverScheduler(): RunOnceScheduler {
const hoverOption = this.editor.getOption(EditorOption.hover);
const scheduler = new RunOnceScheduler(() => {
if (this.hoverRange) {
this.showHover(this.hoverRange, false);
}
}, HOVER_DELAY);
}, hoverOption.delay);
this.toDispose.push(scheduler);
return scheduler;
@@ -299,18 +347,19 @@ export class DebugEditorContribution implements IDebugEditorContribution {
@memoize
private get hideHoverScheduler(): RunOnceScheduler {
const hoverOption = this.editor.getOption(EditorOption.hover);
const scheduler = new RunOnceScheduler(() => {
if (!this.hoverWidget.isHovered()) {
this.hoverWidget.hide();
}
}, 2 * HOVER_DELAY);
}, hoverOption.delay);
this.toDispose.push(scheduler);
return scheduler;
}
private hideHoverWidget(): void {
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.willBeVisible()) {
this.hideHoverScheduler.schedule();
}
this.showHoverScheduler.cancel();
@@ -378,7 +427,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
return;
}
const sameUri = exceptionSf.source.uri.toString() === model.uri.toString();
const sameUri = this.uriIdentityService.extUri.isEqual(exceptionSf.source.uri, model.uri);
if (this.exceptionWidget && !sameUri) {
this.closeExceptionWidget();
} else if (sameUri) {
@@ -396,13 +445,16 @@ export class DebugEditorContribution implements IDebugEditorContribution {
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession);
this.exceptionWidget.show({ lineNumber, column }, 0);
this.exceptionWidget.focus();
this.editor.revealLine(lineNumber);
this.exceptionWidgetVisible.set(true);
}
private closeExceptionWidget(): void {
closeExceptionWidget(): void {
if (this.exceptionWidget) {
this.exceptionWidget.dispose();
this.exceptionWidget = undefined;
this.exceptionWidgetVisible.set(false);
}
}

View File

@@ -31,10 +31,10 @@ import { coalesce } from 'vs/base/common/arrays';
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { isMacintosh } from 'vs/base/common/platform';
const $ = dom.$;
const MAX_TREE_HEIGHT = 324;
async function doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise<IExpression | null> {
if (!container) {
@@ -71,9 +71,11 @@ export class DebugHoverWidget implements IContentWidget {
allowEditorOverflow = true;
private _isVisible: boolean;
private showCancellationSource?: CancellationTokenSource;
private domNode!: HTMLElement;
private tree!: AsyncDataTree<IExpression, IExpression, any>;
private showAtPosition: Position | null;
private positionPreference: ContentWidgetPositionPreference[];
private highlightDecorations: string[];
private complexValueContainer!: HTMLElement;
private complexValueTitle!: HTMLElement;
@@ -86,13 +88,14 @@ export class DebugHoverWidget implements IContentWidget {
private editor: ICodeEditor,
@IDebugService private readonly debugService: IDebugService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IThemeService private readonly themeService: IThemeService
) {
this.toDispose = [];
this._isVisible = false;
this.showAtPosition = null;
this.highlightDecorations = [];
this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW];
}
private create(): void {
@@ -101,6 +104,8 @@ export class DebugHoverWidget implements IContentWidget {
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
this.treeContainer.setAttribute('role', 'tree');
const tip = dom.append(this.complexValueContainer, $('.tip'));
tip.textContent = nls.localize('quickTip', 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt');
const dataSource = new DebugHoverDataSource();
this.tree = <WorkbenchAsyncDataTree<IExpression, IExpression, any>>this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)],
@@ -140,7 +145,7 @@ export class DebugHoverWidget implements IContentWidget {
this.domNode.style.color = '';
}
}));
this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer()));
this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer(false)));
this.registerListeners();
this.editor.addContentWidget(this);
@@ -160,13 +165,17 @@ export class DebugHoverWidget implements IContentWidget {
}
isHovered(): boolean {
return this.domNode.matches(':hover');
return !!this.domNode?.matches(':hover');
}
isVisible(): boolean {
return this._isVisible;
}
willBeVisible(): boolean {
return !!this.showCancellationSource;
}
getId(): string {
return DebugHoverWidget.ID;
}
@@ -176,6 +185,8 @@ export class DebugHoverWidget implements IContentWidget {
}
async showAt(range: Range, focus: boolean): Promise<void> {
this.showCancellationSource?.cancel();
const cancellationSource = this.showCancellationSource = new CancellationTokenSource();
const session = this.debugService.getViewModel().focusedSession;
if (!session || !this.editor.hasModel()) {
@@ -192,7 +203,7 @@ export class DebugHoverWidget implements IContentWidget {
const supports = EvaluatableExpressionProviderRegistry.ordered(model);
const promises = supports.map(support => {
return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => {
return Promise.resolve(support.provideEvaluatableExpression(model, pos, cancellationSource.token)).then(expression => {
return expression;
}, err => {
//onUnexpectedExternalError(err);
@@ -235,7 +246,7 @@ export class DebugHoverWidget implements IContentWidget {
}
}
if (!expression || (expression instanceof Expression && !expression.available)) {
if (cancellationSource.token.isCancellationRequested || !expression || (expression instanceof Expression && !expression.available)) {
this.hide();
return;
}
@@ -285,9 +296,7 @@ export class DebugHoverWidget implements IContentWidget {
await this.tree.setInput(expression);
this.complexValueTitle.textContent = expression.value;
this.complexValueTitle.title = expression.value;
this.layoutTreeAndContainer();
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
this.layoutTreeAndContainer(true);
this.tree.scrollTop = 0;
this.tree.scrollLeft = 0;
this.complexValueContainer.hidden = false;
@@ -298,14 +307,29 @@ export class DebugHoverWidget implements IContentWidget {
}
}
private layoutTreeAndContainer(): void {
const scrollBarHeight = 8;
const treeHeight = Math.min(MAX_TREE_HEIGHT, this.tree.contentHeight + scrollBarHeight);
private layoutTreeAndContainer(initialLayout: boolean): void {
const scrollBarHeight = 10;
const treeHeight = Math.min(this.editor.getLayoutInfo().height * 0.7, this.tree.contentHeight + scrollBarHeight);
this.treeContainer.style.height = `${treeHeight}px`;
this.tree.layout(treeHeight, 324);
this.tree.layout(treeHeight, initialLayout ? 400 : undefined);
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
}
afterRender(positionPreference: ContentWidgetPositionPreference | null) {
if (positionPreference) {
// Remember where the editor placed you to keep position stable #109226
this.positionPreference = [positionPreference];
}
}
hide(): void {
if (this.showCancellationSource) {
this.showCancellationSource.cancel();
this.showCancellationSource = undefined;
}
if (!this._isVisible) {
return;
}
@@ -317,15 +341,13 @@ export class DebugHoverWidget implements IContentWidget {
this.editor.deltaDecorations(this.highlightDecorations, []);
this.highlightDecorations = [];
this.editor.layoutContentWidget(this);
this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW];
}
getPosition(): IContentWidgetPosition | null {
return this._isVisible ? {
position: this.showAtPosition,
preference: [
ContentWidgetPositionPreference.ABOVE,
ContentWidgetPositionPreference.BELOW
]
preference: this.positionPreference
} : null;
}
@@ -341,7 +363,7 @@ class DebugHoverAccessibilityProvider implements IListAccessibilityProvider<IExp
}
getAriaLabel(element: IExpression): string {
return nls.localize({ key: 'variableAriaLabel', comment: ['Do not translate placholders. Placeholders are name and value of a variable.'] }, "{0}, value {1}, variables, debug", element.name, element.value);
return nls.localize({ key: 'variableAriaLabel', comment: ['Do not translate placeholders. Placeholders are name and value of a variable.'] }, "{0}, value {1}, variables, debug", element.name, element.value);
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { localize } from 'vs/nls';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
export const debugConsoleViewIcon = registerIcon('debug-console-view-icon', Codicon.debugConsole, localize('debugConsoleViewIcon', 'View icon of the debug console view.'));
export const runViewIcon = registerIcon('run-view-icon', Codicon.debugAlt, localize('runViewIcon', 'View icon of the run view.'));
export const variablesViewIcon = registerIcon('variables-view-icon', Codicon.debugAlt, localize('variablesViewIcon', 'View icon of the variables view.'));
export const watchViewIcon = registerIcon('watch-view-icon', Codicon.debugAlt, localize('watchViewIcon', 'View icon of the watch view.'));
export const callStackViewIcon = registerIcon('callstack-view-icon', Codicon.debugAlt, localize('callStackViewIcon', 'View icon of the call stack view.'));
export const breakpointsViewIcon = registerIcon('breakpoints-view-icon', Codicon.debugAlt, localize('breakpointsViewIcon', 'View icon of the breakpoints view.'));
export const loadedScriptsViewIcon = registerIcon('loaded-scripts-view-icon', Codicon.debugAlt, localize('loadedScriptsViewIcon', 'View icon of the loaded scripts view.'));
export const debugBreakpoint = registerIcon('debug-breakpoint', Codicon.debugBreakpoint, localize('debugBreakpoint', 'Icon for breakpoints.'));
export const debugBreakpointDisabled = registerIcon('debug-breakpoint-disabled', Codicon.debugBreakpointDisabled, localize('debugBreakpointDisabled', 'Icon for disabled breakpoints.'));
export const debugBreakpointUnverified = registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.'));
export const debugBreakpointHint = registerIcon('debug-hint', Codicon.debugHint, localize('debugBreakpointHint', 'Icon for breakpoint hints shown on hover in editor glyph margin.'));
export const debugBreakpointFunction = registerIcon('debug-breakpoint-function', Codicon.debugBreakpointFunction, localize('debugBreakpointFunction', 'Icon for function breakpoints.'));
export const debugBreakpointFunctionUnverified = registerIcon('debug-breakpoint-function-unverified', Codicon.debugBreakpointFunctionUnverified, localize('debugBreakpointFunctionUnverified', 'Icon for unverified function breakpoints.'));
export const debugBreakpointFunctionDisabled = registerIcon('debug-breakpoint-function-disabled', Codicon.debugBreakpointFunctionDisabled, localize('debugBreakpointFunctionDisabled', 'Icon for disabled function breakpoints.'));
export const debugBreakpointUnsupported = registerIcon('debug-breakpoint-unsupported', Codicon.debugBreakpointUnsupported, localize('debugBreakpointUnsupported', 'Icon for unsupported breakpoints.'));
export const debugBreakpointConditionalUnverified = registerIcon('debug-breakpoint-conditional-unverified', Codicon.debugBreakpointConditionalUnverified, localize('debugBreakpointConditionalUnverified', 'Icon for unverified conditional breakpoints.'));
export const debugBreakpointConditional = registerIcon('debug-breakpoint-conditional', Codicon.debugBreakpointConditional, localize('debugBreakpointConditional', 'Icon for conditional breakpoints.'));
export const debugBreakpointConditionalDisabled = registerIcon('debug-breakpoint-conditional-disabled', Codicon.debugBreakpointConditionalDisabled, localize('debugBreakpointConditionalDisabled', 'Icon for disabled conditional breakpoints.'));
export const debugBreakpointDataUnverified = registerIcon('debug-breakpoint-data-unverified', Codicon.debugBreakpointDataUnverified, localize('debugBreakpointDataUnverified', 'Icon for unverified data breakpoints.'));
export const debugBreakpointData = registerIcon('debug-breakpoint-data', Codicon.debugBreakpointData, localize('debugBreakpointData', 'Icon for data breakpoints.'));
export const debugBreakpointDataDisabled = registerIcon('debug-breakpoint-data-disabled', Codicon.debugBreakpointDataDisabled, localize('debugBreakpointDataDisabled', 'Icon for disabled data breakpoints.'));
export const debugBreakpointLogUnverified = registerIcon('debug-breakpoint-log-unverified', Codicon.debugBreakpointLogUnverified, localize('debugBreakpointLogUnverified', 'Icon for unverified log breakpoints.'));
export const debugBreakpointLog = registerIcon('debug-breakpoint-log', Codicon.debugBreakpointLog, localize('debugBreakpointLog', 'Icon for log breakpoints.'));
export const debugBreakpointLogDisabled = registerIcon('debug-breakpoint-log-disabled', Codicon.debugBreakpointLogDisabled, localize('debugBreakpointLogDisabled', 'Icon for disabled log breakpoint.'));
export const debugStackframe = registerIcon('debug-stackframe', Codicon.debugStackframe, localize('debugStackframe', 'Icon for a stackframe shown in the editor glyph margin.'));
export const debugStackframeFocused = registerIcon('debug-stackframe-focused', Codicon.debugStackframeFocused, localize('debugStackframeFocused', 'Icon for a focused stackframe shown in the editor glyph margin.'));
export const debugGripper = registerIcon('debug-gripper', Codicon.gripper, localize('debugGripper', 'Icon for the debug bar gripper.'));
export const debugRestartFrame = registerIcon('debug-restart-frame', Codicon.debugRestartFrame, localize('debugRestartFrame', 'Icon for the debug restart frame action.'));
export const debugStop = registerIcon('debug-stop', Codicon.debugStop, localize('debugStop', 'Icon for the debug stop action.'));
export const debugDisconnect = registerIcon('debug-disconnect', Codicon.debugDisconnect, localize('debugDisconnect', 'Icon for the debug disconnect action.'));
export const debugRestart = registerIcon('debug-restart', Codicon.debugRestart, localize('debugRestart', 'Icon for the debug restart action.'));
export const debugStepOver = registerIcon('debug-step-over', Codicon.debugStepOver, localize('debugStepOver', 'Icon for the debug step over action.'));
export const debugStepInto = registerIcon('debug-step-into', Codicon.debugStepInto, localize('debugStepInto', 'Icon for the debug step into action.'));
export const debugStepOut = registerIcon('debug-step-out', Codicon.debugStepOut, localize('debugStepOut', 'Icon for the debug step out action.'));
export const debugStepBack = registerIcon('debug-step-back', Codicon.debugStepBack, localize('debugStepBack', 'Icon for the debug step back action.'));
export const debugPause = registerIcon('debug-pause', Codicon.debugPause, localize('debugPause', 'Icon for the debug pause action.'));
export const debugContinue = registerIcon('debug-continue', Codicon.debugContinue, localize('debugContinue', 'Icon for the debug continue action.'));
export const debugReverseContinue = registerIcon('debug-reverse-continue', Codicon.debugReverseContinue, localize('debugReverseContinue', 'Icon for the debug reverse continue action.'));
export const debugStart = registerIcon('debug-start', Codicon.debugStart, localize('debugStart', 'Icon for the debug start action.'));
export const debugConfigure = registerIcon('debug-configure', Codicon.gear, localize('debugConfigure', 'Icon for the debug configure action.'));
export const debugConsole = registerIcon('debug-console', Codicon.gear, localize('debugConsole', 'Icon for the debug console open action.'));
export const debugCollapseAll = registerIcon('debug-collapse-all', Codicon.collapseAll, localize('debugCollapseAll', 'Icon for the collapse all action in the debug views.'));
export const callstackViewSession = registerIcon('callstack-view-session', Codicon.bug, localize('callstackViewSession', 'Icon for the session icon in the call stack view.'));
export const debugConsoleClearAll = registerIcon('debug-console-clear-all', Codicon.clearAll, localize('debugConsoleClearAll', 'Icon for the clear all action in the debug console.'));
export const watchExpressionsRemoveAll = registerIcon('watch-expressions-remove-all', Codicon.closeAll, localize('watchExpressionsRemoveAll', 'Icon for the remove all action in the watch view.'));
export const watchExpressionsAdd = registerIcon('watch-expressions-add', Codicon.add, localize('watchExpressionsAdd', 'Icon for the add action in the watch view.'));
export const watchExpressionsAddFuncBreakpoint = registerIcon('watch-expressions-add-function-breakpoint', Codicon.add, localize('watchExpressionsAddFuncBreakpoint', 'Icon for the add function breakpoint action in the watch view.'));
export const breakpointsRemoveAll = registerIcon('breakpoints-remove-all', Codicon.closeAll, localize('breakpointsRemoveAll', 'Icon for the remove all action in the breakpoints view.'));
export const breakpointsActivate = registerIcon('breakpoints-activate', Codicon.activateBreakpoints, localize('breakpointsActivate', 'Icon for the activate action in the breakpoints view.'));
export const debugConsoleEvaluationInput = registerIcon('debug-console-evaluation-input', Codicon.arrowSmallRight, localize('debugConsoleEvaluationInput', 'Icon for the debug evaluation input marker.'));
export const debugConsoleEvaluationPrompt = registerIcon('debug-console-evaluation-prompt', Codicon.chevronRight, localize('debugConsoleEvaluationPrompt', 'Icon for the debug evaluation prompt.'));

View File

@@ -39,7 +39,7 @@ export class DebugProgressContribution implements IWorkbenchContribution {
if (viewsService.isViewContainerVisible(VIEWLET_ID)) {
progressService.withProgress({ location: VIEWLET_ID }, () => promise);
}
const source = debugService.getConfigurationManager().getDebuggerLabel(session.configuration.type);
const source = debugService.getAdapterManager().getDebuggerLabel(session.configuration.type);
progressService.withProgress({
location: ProgressLocation.Notification,
title: progressStartEvent.body.title,

View File

@@ -13,6 +13,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { matchesFuzzy } from 'vs/base/common/filters';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -55,7 +57,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '',
highlights: { label: highlights },
buttons: [{
iconClass: 'codicon-gear',
iconClass: ThemeIcon.asClassName(debugConfigure),
tooltip: localize('customizeLaunchConfig', "Configure Launch Configuration")
}],
trigger: () => {
@@ -64,7 +66,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
return TriggerAction.CLOSE_PICKER;
},
accept: async () => {
await this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name);
await configManager.selectConfiguration(config.launch, config.name);
try {
await this.debugService.startDebugging(config.launch);
} catch (error) {
@@ -86,6 +88,26 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
});
}
configManager.getRecentDynamicConfigurations().forEach(({ name, type }) => {
const highlights = matchesFuzzy(filter, name, true);
if (highlights) {
picks.push({
label: name,
highlights: { label: highlights },
accept: async () => {
await configManager.selectConfiguration(undefined, name, undefined, { type });
try {
const { launch, getConfig } = configManager.selectedConfiguration;
const config = await getConfig();
await this.debugService.startDebugging(launch, config);
} catch (error) {
this.notificationService.error(error);
}
}
});
}
});
dynamicProviders.forEach(provider => {
picks.push({
label: `$(folder) ${provider.label}...`,
@@ -93,6 +115,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
accept: async () => {
const pick = await provider.pick();
if (pick) {
await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: pick.config.type });
this.debugService.startDebugging(pick.launch, pick.config);
}
}

View File

@@ -5,13 +5,13 @@
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { URI as uri } from 'vs/base/common/uri';
import { URI, URI as uri } from 'vs/base/common/uri';
import { distinct } from 'vs/base/common/arrays';
import * as errors from 'vs/base/common/errors';
import severity from 'vs/base/common/severity';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
@@ -32,7 +32,7 @@ import { IAction, Action } from 'vs/base/common/actions';
import { deepClone, equals } from 'vs/base/common/objects';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID, IAdapterManager, IExceptionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils';
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
import { RunOnceScheduler } from 'vs/base/common/async';
@@ -48,6 +48,9 @@ import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager';
import { ITextModel } from 'vs/editor/common/model';
export class DebugService implements IDebugService {
declare readonly _serviceBrand: undefined;
@@ -62,13 +65,14 @@ export class DebugService implements IDebugService {
private telemetry: DebugTelemetry;
private taskRunner: DebugTaskRunner;
private configurationManager: ConfigurationManager;
private adapterManager: AdapterManager;
private toDispose: IDisposable[];
private debugType!: IContextKey<string>;
private debugState!: IContextKey<string>;
private inDebugMode!: IContextKey<boolean>;
private debugUx!: IContextKey<string>;
private breakpointsExist!: IContextKey<boolean>;
private breakpointsToSendOnResourceSaved: Set<string>;
private breakpointsToSendOnResourceSaved: Set<URI>;
private initializing = false;
private previousState: State | undefined;
private sessionCancellationTokens = new Map<string, CancellationTokenSource>();
@@ -92,18 +96,20 @@ export class DebugService implements IDebugService {
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,
@IActivityService private readonly activityService: IActivityService,
@ICommandService private readonly commandService: ICommandService,
@IQuickInputService private readonly quickInputService: IQuickInputService
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
this.toDispose = [];
this.breakpointsToSendOnResourceSaved = new Set<string>();
this.breakpointsToSendOnResourceSaved = new Set<URI>();
this._onDidChangeState = new Emitter<State>();
this._onDidNewSession = new Emitter<IDebugSession>();
this._onWillNewSession = new Emitter<IDebugSession>();
this._onDidEndSession = new Emitter<IDebugSession>();
this.configurationManager = this.instantiationService.createInstance(ConfigurationManager);
this.adapterManager = this.instantiationService.createInstance(AdapterManager);
this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager);
this.toDispose.push(this.configurationManager);
contextKeyService.bufferChangeEvents(() => {
@@ -111,7 +117,7 @@ export class DebugService implements IDebugService {
this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService);
this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService);
this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService);
this.debugUx.set((this.configurationManager.hasDebuggers() && !!this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple');
this.debugUx.set((this.adapterManager.hasDebuggers() && !!this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple');
this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService);
});
@@ -160,8 +166,8 @@ export class DebugService implements IDebugService {
this.toDispose.push(this.viewModel.onDidFocusSession(() => {
this.onStateChange();
}));
this.toDispose.push(Event.any(this.configurationManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => {
this.debugUx.set(!!(this.state !== State.Inactive || (this.configurationManager.selectedConfiguration.name && this.configurationManager.hasDebuggers())) ? 'default' : 'simple');
this.toDispose.push(Event.any(this.adapterManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => {
this.debugUx.set(!!(this.state !== State.Inactive || (this.configurationManager.selectedConfiguration.name && this.adapterManager.hasDebuggers())) ? 'default' : 'simple');
}));
this.toDispose.push(this.model.onDidChangeCallStack(() => {
const numberOfSessions = this.model.getSessions().filter(s => !s.parentSession).length;
@@ -190,6 +196,10 @@ export class DebugService implements IDebugService {
return this.configurationManager;
}
getAdapterManager(): IAdapterManager {
return this.adapterManager;
}
sourceIsNotAvailable(uri: uri): void {
this.model.sourceIsNotAvailable(uri);
}
@@ -243,7 +253,7 @@ export class DebugService implements IDebugService {
this.debugState.set(getStateLabel(state));
this.inDebugMode.set(state !== State.Inactive);
// Only show the simple ux if debug is not yet started and if no launch.json exists
this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || (this.configurationManager.hasDebuggers() && this.configurationManager.selectedConfiguration.name)) ? 'default' : 'simple');
this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || (this.adapterManager.hasDebuggers() && this.configurationManager.selectedConfiguration.name)) ? 'default' : 'simple');
});
this.previousState = state;
this._onDidChangeState.fire(state);
@@ -295,7 +305,7 @@ export class DebugService implements IDebugService {
const sessions = this.model.getSessions();
const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName);
if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) {
if (sessions.some(s => (s.configuration.name === configOrName && s.root === launch.workspace) && (!launch || !launch.workspace || !s.root || this.uriIdentityService.extUri.isEqual(s.root.uri, launch.workspace.uri)))) {
throw new Error(alreadyRunningMessage);
}
if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) {
@@ -395,7 +405,7 @@ export class DebugService implements IDebugService {
const unresolvedConfig = deepClone(config);
if (!type) {
const guess = await this.configurationManager.guessDebugger();
const guess = await this.adapterManager.guessDebugger();
if (guess) {
type = guess.type;
}
@@ -435,7 +445,7 @@ export class DebugService implements IDebugService {
}
resolvedConfig = cfg;
if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) {
if (!this.adapterManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) {
let message: string;
if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') {
message = configByProviders.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', configByProviders.request)
@@ -450,7 +460,7 @@ export class DebugService implements IDebugService {
actionList.push(new Action(
'installAdditionalDebuggers',
nls.localize('installAdditionalDebuggers', "Install {0} Extension", resolvedConfig.type),
nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type),
undefined,
true,
async () => this.commandService.executeCommand('debug.installAdditionalDebuggers')
@@ -547,7 +557,7 @@ export class DebugService implements IDebugService {
}
private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise<void> {
const dbgr = this.configurationManager.getDebugger(session.configuration.type);
const dbgr = this.adapterManager.getDebugger(session.configuration.type);
try {
await session.initialize(dbgr!);
await session.launchOrAttach(session.configuration);
@@ -752,7 +762,7 @@ export class DebugService implements IDebugService {
}
private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {
const dbg = this.configurationManager.getDebugger(config.type);
const dbg = this.adapterManager.getDebugger(config.type);
if (dbg) {
let folder: IWorkspaceFolder | undefined = undefined;
if (launch && launch.workspace) {
@@ -798,7 +808,8 @@ export class DebugService implements IDebugService {
const lineNumber = stackFrame.range.startLineNumber;
if (lineNumber >= 1 && lineNumber <= model.getLineCount()) {
const lineContent = control.getModel().getLineContent(lineNumber);
aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent));
aria.alert(nls.localize({ key: 'debuggingPaused', comment: ['First placeholder is the stack frame name, second is the line number, third placeholder is the reason why debugging is stopped, for example "breakpoint" and the last one is the file line content.'] },
"{0}:{1}, debugging paused {2}, {3}", stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', lineContent));
}
}
}
@@ -839,6 +850,10 @@ export class DebugService implements IDebugService {
//---- breakpoints
canSetBreakpointsIn(model: ITextModel): boolean {
return this.adapterManager.canSetBreakpointsIn(model);
}
async enableOrDisableBreakpoints(enable: boolean, breakpoint?: IEnablement): Promise<void> {
if (breakpoint) {
this.model.setEnablement(breakpoint, enable);
@@ -860,10 +875,11 @@ export class DebugService implements IDebugService {
this.debugStorage.storeBreakpoints(this.model);
}
async addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], context: string): Promise<IBreakpoint[]> {
async addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], ariaAnnounce = true): Promise<IBreakpoint[]> {
const breakpoints = this.model.addBreakpoints(uri, rawBreakpoints);
breakpoints.forEach(bp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", bp.lineNumber, uri.fsPath)));
breakpoints.forEach(bp => this.telemetry.logDebugAddBreakpoint(bp, context));
if (ariaAnnounce) {
breakpoints.forEach(bp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", bp.lineNumber, uri.fsPath)));
}
// In some cases we need to store breakpoints before we send them because sending them can take a long time
// And after sending them because the debug adapter can attach adapter data to a breakpoint
@@ -877,7 +893,7 @@ export class DebugService implements IDebugService {
this.model.updateBreakpoints(data);
this.debugStorage.storeBreakpoints(this.model);
if (sendOnResourceSaved) {
this.breakpointsToSendOnResourceSaved.add(uri.toString());
this.breakpointsToSendOnResourceSaved.add(uri);
} else {
await this.sendBreakpoints(uri);
this.debugStorage.storeBreakpoints(this.model);
@@ -902,7 +918,7 @@ export class DebugService implements IDebugService {
addFunctionBreakpoint(name?: string, id?: string): void {
const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id);
this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint);
this.viewModel.setSelectedBreakpoint(newFunctionBreakpoint);
}
async renameFunctionBreakpoint(id: string, newFunctionName: string): Promise<void> {
@@ -930,6 +946,12 @@ export class DebugService implements IDebugService {
await this.sendDataBreakpoints();
}
async setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): Promise<void> {
this.model.setExceptionBreakpointCondition(exceptionBreakpoint, condition);
this.debugStorage.storeBreakpoints(this.model);
await this.sendExceptionBreakpoints();
}
async sendAllBreakpoints(session?: IDebugSession): Promise<any> {
await Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session)));
await this.sendFunctionBreakpoints(session);
@@ -982,12 +1004,17 @@ export class DebugService implements IDebugService {
this.model.removeBreakpoints(toRemove);
}
fileChangesEvent.getUpdated().forEach(event => {
if (this.breakpointsToSendOnResourceSaved.delete(event.resource.toString())) {
this.sendBreakpoints(event.resource, true);
const toSend: URI[] = [];
for (const uri of this.breakpointsToSendOnResourceSaved) {
if (fileChangesEvent.contains(uri, FileChangeType.UPDATED)) {
toSend.push(uri);
}
});
}
for (const uri of toSend) {
this.breakpointsToSendOnResourceSaved.delete(uri);
this.sendBreakpoints(uri, true);
}
}
}

View File

@@ -28,15 +28,15 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { localize } from 'vs/nls';
import { canceled } from 'vs/base/common/errors';
import { filterExceptionsFromTelemetry } from 'vs/workbench/contrib/debug/common/debugUtils';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
export class DebugSession implements IDebugSession {
@@ -51,6 +51,7 @@ export class DebugSession implements IDebugSession {
private rawListeners: IDisposable[] = [];
private fetchThreadsScheduler: RunOnceScheduler | undefined;
private repl: ReplModel;
private stoppedDetails: IRawStoppedDetails | undefined;
private readonly _onDidChangeState = new Emitter<void>();
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent | undefined>();
@@ -82,7 +83,8 @@ export class DebugSession implements IDebugSession {
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,
@IOpenerService private readonly openerService: IOpenerService,
@INotificationService private readonly notificationService: INotificationService,
@ILifecycleService lifecycleService: ILifecycleService
@ILifecycleService lifecycleService: ILifecycleService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
this._options = options || {};
if (this.hasSeparateRepl()) {
@@ -245,7 +247,8 @@ export class DebugSession implements IDebugSession {
supportsVariablePaging: true, // #9537
supportsRunInTerminalRequest: true, // #10574
locale: platform.locale,
supportsProgressReporting: true // #92253
supportsProgressReporting: true, // #92253
supportsInvalidatedEvent: true // #106745
});
this.initialized = true;
@@ -391,7 +394,18 @@ export class DebugSession implements IDebugSession {
}
if (this.raw.readyForBreakpoints) {
await this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) });
const args: DebugProtocol.SetExceptionBreakpointsArguments = this.capabilities.supportsExceptionFilterOptions ? {
filters: [],
filterOptions: exbpts.map(exb => {
if (exb.condition) {
return { filterId: exb.filter, condition: exb.condition };
}
return { filterId: exb.filter };
})
} : { filters: exbpts.map(exb => exb.filter) };
await this.raw.setExceptionBreakpoints(args);
}
}
@@ -798,6 +812,7 @@ export class DebugSession implements IDebugSession {
}));
this.rawListeners.push(this.raw.onDidStop(async event => {
this.stoppedDetails = event.body;
await this.fetchThreads(event.body);
const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined;
if (thread) {
@@ -999,6 +1014,19 @@ export class DebugSession implements IDebugSession {
this.rawListeners.push(this.raw.onDidProgressEnd(event => {
this._onDidProgressEnd.fire(event);
}));
this.rawListeners.push(this.raw.onDidInvalidated(async event => {
if (!(event.body.areas && event.body.areas.length === 1 && event.body.areas[0] === 'variables')) {
// If invalidated event only requires to update variables, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745
this.cancelAllRequests();
this.model.clearThreads(this.getId(), true);
await this.fetchThreads(this.stoppedDetails);
}
const viewModel = this.debugService.getViewModel();
if (viewModel.focusedSession === this) {
viewModel.updateViews();
}
}));
this.rawListeners.push(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event)));
}
@@ -1026,12 +1054,12 @@ export class DebugSession implements IDebugSession {
//---- sources
getSourceForUri(uri: URI): Source | undefined {
return this.sources.get(this.getUriKey(uri));
return this.sources.get(this.uriIdentityService.asCanonicalUri(uri).toString());
}
getSource(raw?: DebugProtocol.Source): Source {
let source = new Source(raw, this.getId());
const uriKey = this.getUriKey(source.uri);
let source = new Source(raw, this.getId(), this.uriIdentityService);
const uriKey = source.uri.toString();
const found = this.sources.get(uriKey);
if (found) {
source = found;
@@ -1072,11 +1100,6 @@ export class DebugSession implements IDebugSession {
this.cancellationMap.clear();
}
private getUriKey(uri: URI): string {
// TODO: the following code does not make sense if uri originates from a different platform
return platform.isLinux ? uri.toString() : uri.toString().toLowerCase();
}
// REPL
getReplElements(): IReplElement[] {
@@ -1094,7 +1117,7 @@ export class DebugSession implements IDebugSession {
async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
await this.repl.addReplExpression(this, stackFrame, name);
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
variableSetEmitter.fire();
this.debugService.getViewModel().updateViews();
}
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void {

View File

@@ -16,9 +16,9 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerThemingParticipant, IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { localize } from 'vs/nls';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -29,6 +29,7 @@ import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from '
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition';
const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety';
@@ -64,7 +65,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
this.$el = dom.$('div.debug-toolbar');
this.$el.style.top = `${layoutService.offset?.top ?? 0}px`;
this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper'));
this.dragArea = dom.append(this.$el, dom.$('div.drag-area' + ThemeIcon.asCSSSelector(icons.debugGripper)));
const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container'));
this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService);
@@ -75,7 +76,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
orientation: ActionsOrientation.HORIZONTAL,
actionViewItemProvider: (action: IAction) => {
if (action.id === FocusSessionAction.ID) {
return this.instantiationService.createInstance(FocusSessionActionViewItem, action);
return this.instantiationService.createInstance(FocusSessionActionViewItem, action, undefined);
} else if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
} else if (action instanceof SubmenuItemAction) {
@@ -94,7 +95,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
}
const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService);
if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id)) {
if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id && first.enabled === second.enabled)) {
this.actionBar.clear();
this.actionBar.push(actions, { icon: true, label: false });
this.activeActions = actions;
@@ -169,7 +170,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
const left = dom.getComputedStyle(this.$el).left;
if (left) {
const position = parseFloat(left) / window.innerWidth;
this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL);
this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL, StorageTarget.MACHINE);
}
}
@@ -180,7 +181,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
this.$el.style.backgroundColor = this.getColor(debugToolBarBackground) || '';
const widgetShadowColor = this.getColor(widgetShadow);
this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : '';
this.$el.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : '';
const contrastBorderColor = this.getColor(contrastBorder);
const borderColor = this.getColor(debugToolBarBorder);
@@ -220,7 +221,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) {
const moveToTop = y < titleAreaHeight;
this.setYCoordinate(moveToTop ? 0 : titleAreaHeight);
this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL);
this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL, StorageTarget.MACHINE);
}
}
@@ -351,51 +352,51 @@ registerThemingParticipant((theme, collector) => {
const debugIconStartColor = theme.getColor(debugIconStartForeground);
if (debugIconStartColor) {
collector.addRule(`.monaco-workbench .codicon-debug-start { color: ${debugIconStartColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStart)} { color: ${debugIconStartColor} !important; }`);
}
const debugIconPauseColor = theme.getColor(debugIconPauseForeground);
if (debugIconPauseColor) {
collector.addRule(`.monaco-workbench .codicon-debug-pause { color: ${debugIconPauseColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugPause)} { color: ${debugIconPauseColor} !important; }`);
}
const debugIconStopColor = theme.getColor(debugIconStopForeground);
if (debugIconStopColor) {
collector.addRule(`.monaco-workbench .codicon-debug-stop, .monaco-workbench .debug-view-content .codicon-record { color: ${debugIconStopColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStop)} { color: ${debugIconStopColor} !important; }`);
}
const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground);
if (debugIconDisconnectColor) {
collector.addRule(`.monaco-workbench .codicon-debug-disconnect { color: ${debugIconDisconnectColor} !important; }`);
collector.addRule(`.monaco-workbench .debug-view-content ${ThemeIcon.asCSSSelector(icons.debugDisconnect)}, .monaco-workbench .debug-toolbar ${ThemeIcon.asCSSSelector(icons.debugDisconnect)} { color: ${debugIconDisconnectColor} !important; }`);
}
const debugIconRestartColor = theme.getColor(debugIconRestartForeground);
if (debugIconRestartColor) {
collector.addRule(`.monaco-workbench .codicon-debug-restart, .monaco-workbench .codicon-debug-restart-frame { color: ${debugIconRestartColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestart)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugRestartFrame)} { color: ${debugIconRestartColor} !important; }`);
}
const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground);
if (debugIconStepOverColor) {
collector.addRule(`.monaco-workbench .codicon-debug-step-over { color: ${debugIconStepOverColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOver)} { color: ${debugIconStepOverColor} !important; }`);
}
const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground);
if (debugIconStepIntoColor) {
collector.addRule(`.monaco-workbench .codicon-debug-step-into { color: ${debugIconStepIntoColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepInto)} { color: ${debugIconStepIntoColor} !important; }`);
}
const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground);
if (debugIconStepOutColor) {
collector.addRule(`.monaco-workbench .codicon-debug-step-out { color: ${debugIconStepOutColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepOut)} { color: ${debugIconStepOutColor} !important; }`);
}
const debugIconContinueColor = theme.getColor(debugIconContinueForeground);
if (debugIconContinueColor) {
collector.addRule(`.monaco-workbench .codicon-debug-continue,.monaco-workbench .codicon-debug-reverse-continue { color: ${debugIconContinueColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugContinue)}, .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugReverseContinue)} { color: ${debugIconContinueColor} !important; }`);
}
const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground);
if (debugIconStepBackColor) {
collector.addRule(`.monaco-workbench .codicon-debug-step-back { color: ${debugIconStepBackColor} !important; }`);
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStepBack)} { color: ${debugIconStepBackColor} !important; }`);
}
});

View File

@@ -15,7 +15,7 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -33,6 +33,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { debugConsole } from 'vs/workbench/contrib/debug/browser/debugIcons';
export class DebugViewPaneContainer extends ViewPaneContainer {
@@ -171,7 +172,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
return this.startDebugActionViewItem;
}
if (action.id === FocusSessionAction.ID) {
return new FocusSessionActionViewItem(action, this.debugService, this.themeService, this.contextViewService, this.configurationService);
return new FocusSessionActionViewItem(action, undefined, this.debugService, this.themeService, this.contextViewService, this.configurationService);
}
if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
@@ -247,7 +248,7 @@ export class OpenDebugConsoleAction extends ToggleViewAction {
@IContextKeyService contextKeyService: IContextKeyService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, 'codicon-debug-console');
super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, ThemeIcon.asClassName(debugConsole));
}
}

View File

@@ -8,14 +8,17 @@ import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IExceptionInfo, IDebugSession, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
const $ = dom.$;
// theming
@@ -25,18 +28,19 @@ export const debugExceptionWidgetBackground = registerColor('debugExceptionWidge
export class ExceptionWidget extends ZoneWidget {
private _backgroundColor?: Color;
private backgroundColor: Color | undefined;
constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, private debugSession: IDebugSession | undefined,
constructor(
editor: ICodeEditor,
private exceptionInfo: IExceptionInfo,
private debugSession: IDebugSession | undefined,
@IThemeService themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super(editor, { showFrame: true, showArrow: true, frameWidth: 1, className: 'exception-widget-container' });
super(editor, { showFrame: true, showArrow: true, isAccessible: true, frameWidth: 1, className: 'exception-widget-container' });
this._backgroundColor = Color.white;
this._applyTheme(themeService.getColorTheme());
this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this)));
this.applyTheme(themeService.getColorTheme());
this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme.bind(this)));
this.create();
const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50);
@@ -44,8 +48,8 @@ export class ExceptionWidget extends ZoneWidget {
this._disposables.add(onDidLayoutChangeScheduler);
}
private _applyTheme(theme: IColorTheme): void {
this._backgroundColor = theme.getColor(debugExceptionWidgetBackground);
private applyTheme(theme: IColorTheme): void {
this.backgroundColor = theme.getColor(debugExceptionWidgetBackground);
const frameColor = theme.getColor(debugExceptionWidgetBorder);
this.style({
arrowColor: frameColor,
@@ -55,7 +59,7 @@ export class ExceptionWidget extends ZoneWidget {
protected _applyStyles(): void {
if (this.container) {
this.container.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : '';
this.container.style.backgroundColor = this.backgroundColor ? this.backgroundColor.toString() : '';
}
super._applyStyles();
}
@@ -66,14 +70,27 @@ export class ExceptionWidget extends ZoneWidget {
const fontInfo = this.editor.getOption(EditorOption.fontInfo);
container.style.fontSize = `${fontInfo.fontSize}px`;
container.style.lineHeight = `${fontInfo.lineHeight}px`;
container.tabIndex = 0;
const title = $('.title');
const label = $('.label');
dom.append(title, label);
const actions = $('.actions');
dom.append(title, actions);
label.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.');
let ariaLabel = label.textContent;
const actionBar = new ActionBar(actions);
actionBar.push(new Action('editor.closeExceptionWidget', nls.localize('close', "Close"), ThemeIcon.asClassName(widgetClose), true, async () => {
const contribution = this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);
contribution.closeExceptionWidget();
}), { label: false, icon: true });
let title = $('.title');
title.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.');
dom.append(container, title);
if (this.exceptionInfo.description) {
let description = $('.description');
description.textContent = this.exceptionInfo.description;
ariaLabel += ', ' + this.exceptionInfo.description;
dom.append(container, description);
}
@@ -83,7 +100,9 @@ export class ExceptionWidget extends ZoneWidget {
const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, true, this.debugSession ? this.debugSession.root : undefined);
stackTrace.appendChild(linkedStackTrace);
dom.append(container, stackTrace);
ariaLabel += ', ' + this.exceptionInfo.details.stackTrace;
}
container.setAttribute('aria-label', ariaLabel);
}
protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void {
@@ -96,4 +115,9 @@ export class ExceptionWidget extends ZoneWidget {
this._relayout(computedLinesNumber);
}
focus(): void {
// Focus into the container for accessibility purposes so the exception and stack trace gets read
this.container?.focus();
}
}

View File

@@ -18,6 +18,7 @@ import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/brows
import { IProcessEnvironment } from 'vs/base/common/platform';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { ILogService } from 'vs/platform/log/common/log';
import { IHostService } from 'vs/workbench/services/host/browser/host';
class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService {
@@ -26,7 +27,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ILogService logService: ILogService
@ILogService logService: ILogService,
@IHostService hostService: IHostService
) {
const connection = remoteAgentService.getConnection();
let channel: IChannel;
@@ -49,14 +51,14 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
// Reload window on reload request
this._register(this.onReload(event => {
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
window.location.reload();
hostService.reload();
}
}));
// Close window on close request
this._register(this.onClose(event => {
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
window.close();
hostService.close();
}
}));
}

View File

@@ -39,7 +39,7 @@ export class LinkDetector {
@IFileService private readonly fileService: IFileService,
@IOpenerService private readonly openerService: IOpenerService,
@IPathService private readonly pathService: IPathService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
// noop
}
@@ -98,7 +98,7 @@ export class LinkDetector {
private createWebLink(url: string): Node {
const link = this.createLink(url);
const uri = URI.parse(url);
this.decorateLink(link, () => this.openerService.open(uri, { allowTunneling: !!this.workbenchEnvironmentService.configuration.remoteAuthority }));
this.decorateLink(link, () => this.openerService.open(uri, { allowTunneling: !!this.environmentService.remoteAuthority }));
return link;
}

View File

@@ -16,7 +16,7 @@ import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from '
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { tildify } from 'vs/base/common/labels';
import { normalizeDriveLetter, tildify } from 'vs/base/common/labels';
import { isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { ltrim } from 'vs/base/common/strings';
@@ -350,7 +350,9 @@ class SessionTreeItem extends BaseTreeItem {
} else {
// on unix try to tildify absolute paths
path = normalize(path);
if (!isWindows) {
if (isWindows) {
path = normalizeDriveLetter(path);
} else {
path = tildify(path, (await this._pathService.userHome()).fsPath);
}
}
@@ -431,7 +433,7 @@ export class LoadedScriptsView extends ViewPane {
@IPathService private readonly pathService: IPathService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@ITelemetryService telemetryService: ITelemetryService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);

View File

@@ -16,10 +16,11 @@
}
.monaco-editor .debug-hover-widget .complex-value {
width: 324px;
max-width: 700px;
}
.monaco-editor .debug-hover-widget .complex-value .title {
.monaco-editor .debug-hover-widget .complex-value .title,
.monaco-editor .debug-hover-widget .complex-value .tip {
padding-left: 15px;
padding-right: 2px;
font-size: 11px;
@@ -29,9 +30,17 @@
height: 18px;
overflow: hidden;
white-space: pre;
}
.monaco-editor .debug-hover-widget .complex-value .title {
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}
.monaco-editor .debug-hover-widget .complex-value .tip {
border-top: 1px solid rgba(128, 128, 128, 0.35);
opacity: 0.5;
}
.monaco-editor .debug-hover-widget .debug-hover-tree {
line-height: 18px;
cursor: pointer;
@@ -57,12 +66,13 @@
background-color: rgba(173, 214, 255, 0.15);
}
.monaco-editor .debug-hover-widget .value {
.monaco-editor .debug-hover-widget > .monaco-scrollable-element > .value {
color: rgba(108, 108, 108, 0.8);
overflow: auto;
font-family: var(--monaco-monospace-font);
max-height: 500px;
padding: 4px 5px;
white-space: pre-wrap;
}
.monaco-editor.vs-dark .debugHoverHighlight,

View File

@@ -55,6 +55,11 @@
padding-bottom: 0;
}
.monaco-workbench.safari .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box {
margin-bottom: 0px;
}
.monaco-workbench .monaco-action-bar .start-debug-action-item .configuration.disabled .monaco-select-box {
opacity: 0.7;
font-style: italic;
@@ -79,6 +84,7 @@
/* Make icons and text the same color as the list foreground on focus selection */
.debug-pane .monaco-list:focus .monaco-list-row.selected .state.label,
.debug-pane .monaco-list:focus .monaco-list-row.selected .load-all,
.debug-pane .monaco-list:focus .monaco-list-row.selected.focused .state.label,
.debug-pane .monaco-list:focus .monaco-list-row.selected .codicon,
.debug-pane .monaco-list:focus .monaco-list-row.selected.focused .codicon {
@@ -93,7 +99,7 @@
width: 100%;
}
.debug-pane .debug-call-stack-title > .pause-message {
.debug-pane .debug-call-stack-title > .state-message {
flex: 1;
text-align: right;
text-overflow: ellipsis;
@@ -102,7 +108,7 @@
margin: 0px 10px;
}
.debug-pane .debug-call-stack-title > .pause-message > .label {
.debug-pane .debug-call-stack-title > .state-message > .label {
border-radius: 3px;
padding: 1px 2px;
font-size: 9px;
@@ -312,10 +318,10 @@
justify-content: center;
}
.debug-pane .debug-breakpoints .breakpoint > .file-path {
.debug-pane .debug-breakpoints .breakpoint > .file-path,
.debug-pane .debug-breakpoints .breakpoint.exception > .condition {
opacity: 0.7;
font-size: 0.9em;
margin-left: 0.8em;
margin-left: 0.9em;
flex: 1;
text-overflow: ellipsis;
overflow: hidden;

View File

@@ -15,9 +15,17 @@
}
.monaco-editor .zone-widget .zone-widget-container.exception-widget .title {
display: flex;
}
.monaco-editor .zone-widget .zone-widget-container.exception-widget .title .label {
font-weight: bold;
}
.monaco-editor .zone-widget .zone-widget-container.exception-widget .title .actions {
flex: 1;
}
.monaco-editor .zone-widget .zone-widget-container.exception-widget .description,
.monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace {
font-family: var(--monaco-monospace-font);
@@ -27,7 +35,7 @@
margin-top: 0.5em;
}
.monaco-editor .zone-widget .zone-widget-container.exception-widget a {
.monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace a {
text-decoration: underline;
cursor: pointer;
}

View File

@@ -38,6 +38,15 @@
margin-right: 4px;
}
.monaco-workbench .repl .repl-tree .output.expression.value-and-source .count-badge-wrapper {
margin-right: 4px;
}
/* Allow the badge to be a bit shorter so it does not look cut off */
.monaco-workbench .repl .repl-tree .output.expression.value-and-source .count-badge-wrapper .monaco-count-badge {
min-height: 16px;
}
.monaco-workbench .repl .repl-tree .monaco-tl-contents .arrow {
position:absolute;
left: 2px;
@@ -115,6 +124,25 @@
}
.panel > .title .monaco-action-bar .action-item.repl-panel-filter-container {
min-width: 200px;
min-width: 300px;
margin-right: 10px;
}
.repl-panel-filter-container .repl-panel-filter-controls {
position: absolute;
top: 0px;
bottom: 0;
right: 0px;
display: flex;
align-items: center;
}
.repl-panel-filter-container .repl-panel-filter-controls > .repl-panel-filter-badge {
margin: 4px;
padding: 0px 8px;
border-radius: 2px;
}
.repl-panel-filter-container .repl-panel-filter-controls > .repl-panel-filter-badge.hidden {
display: none;
}

View File

@@ -69,6 +69,7 @@ export class RawDebugSession implements IDisposable {
private readonly _onDidProgressStart = new Emitter<DebugProtocol.ProgressStartEvent>();
private readonly _onDidProgressUpdate = new Emitter<DebugProtocol.ProgressUpdateEvent>();
private readonly _onDidProgressEnd = new Emitter<DebugProtocol.ProgressEndEvent>();
private readonly _onDidInvalidated = new Emitter<DebugProtocol.InvalidatedEvent>();
private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();
private readonly _onDidEvent = new Emitter<DebugProtocol.Event>();
@@ -150,6 +151,9 @@ export class RawDebugSession implements IDisposable {
case 'progressEnd':
this._onDidProgressEnd.fire(event as DebugProtocol.ProgressEndEvent);
break;
case 'invalidated':
this._onDidInvalidated.fire(event as DebugProtocol.InvalidatedEvent);
break;
default:
this._onDidCustomEvent.fire(event);
break;
@@ -230,6 +234,10 @@ export class RawDebugSession implements IDisposable {
return this._onDidProgressEnd.event;
}
get onDidInvalidated(): Event<DebugProtocol.InvalidatedEvent> {
return this._onDidInvalidated.event;
}
get onDidEvent(): Event<DebugProtocol.Event> {
return this._onDidEvent.event;
}
@@ -612,10 +620,10 @@ export class RawDebugSession implements IDisposable {
}
}
let env: IProcessEnvironment = {};
if (vscodeArgs.env) {
let env: IProcessEnvironment = processEnv;
if (vscodeArgs.env && Object.keys(vscodeArgs.env).length > 0) {
// merge environment variables into a copy of the process.env
env = objects.mixin(processEnv, vscodeArgs.env);
env = objects.mixin(objects.deepClone(processEnv), vscodeArgs.env);
// and delete some if necessary
Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]);
}
@@ -624,7 +632,7 @@ export class RawDebugSession implements IDisposable {
}
private send<R extends DebugProtocol.Response>(command: string, args: any, token?: CancellationToken, timeout?: number): Promise<R | undefined> {
return new Promise<DebugProtocol.Response>((completeDispatch, errorDispatch) => {
return new Promise<DebugProtocol.Response | undefined>((completeDispatch, errorDispatch) => {
if (!this.debugAdapter) {
if (this.inShutdown) {
// We are in shutdown silently complete

View File

@@ -19,8 +19,8 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { memoize } from 'vs/base/common/decorators';
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
@@ -59,10 +59,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { ReplFilter, ReplFilterState, ReplFilterActionViewItem } from 'vs/workbench/contrib/debug/browser/replFilter';
import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
const HISTORY_STORAGE_KEY = 'debug.repl.history';
const FILTER_HISTORY_STORAGE_KEY = 'debug.repl.filterHistory';
const DECORATION_KEY = 'replinputdecoration';
const FILTER_ACTION_ID = `workbench.actions.treeView.repl.filter`;
@@ -120,7 +122,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
this.filter = new ReplFilter();
this.filterState = new ReplFilterState();
this.filterState = new ReplFilterState(this);
codeEditorService.registerDecorationType(DECORATION_KEY, {});
this.registerListeners();
@@ -251,6 +253,13 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}));
}
getFilterStats(): { total: number, filtered: number } {
return {
total: this.tree.getNode().children.length,
filtered: this.tree.getNode().children.filter(c => c.visible).length
};
}
get isReadonly(): boolean {
// Do not allow to edit inactive sessions
const session = this.tree.getInput();
@@ -452,9 +461,12 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (action.id === SelectReplAction.ID) {
return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction);
const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession;
return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction, session);
} else if (action.id === FILTER_ACTION_ID) {
this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action, localize('workbench.debug.filter.placeholder', "Filter (e.g. text, !exclude)"), this.filterState);
const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[];
this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action,
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory);
return this.filterActionViewItem;
}
@@ -520,6 +532,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
// Only scroll if we were scrolled all the way down before tree refreshed #10486
revealLastElement(this.tree);
}
// Repl elements count changed, need to update filter stats on the badge
this.filterState.updateFilterStats();
}, Repl.REFRESH_DELAY);
}
@@ -582,7 +596,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
private createReplInput(container: HTMLElement): void {
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
dom.append(this.replInputContainer, $('.repl-input-chevron.codicon.codicon-chevron-right'));
dom.append(this.replInputContainer, $('.repl-input-chevron' + ThemeIcon.asCSSSelector(debugConsoleEvaluationPrompt)));
const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this });
this.historyNavigationEnablement = historyNavigationEnablement;
@@ -698,10 +712,18 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
saveState(): void {
const replHistory = this.history.getHistory();
if (replHistory.length) {
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE, StorageTarget.USER);
} else {
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
}
if (this.filterActionViewItem) {
const filterHistory = this.filterActionViewItem.getHistory();
if (filterHistory.length) {
this.storageService.store(FILTER_HISTORY_STORAGE_KEY, JSON.stringify(filterHistory), StorageScope.WORKSPACE, StorageTarget.USER);
} else {
this.storageService.remove(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
}
}
super.saveState();
}
@@ -835,7 +857,7 @@ export class ClearReplAction extends Action {
constructor(id: string, label: string,
@IViewsService private readonly viewsService: IViewsService
) {
super(id, label, 'debug-action codicon-clear-all');
super(id, label, 'debug-action ' + ThemeIcon.asClassName(debugConsoleClearAll));
}
async run(): Promise<any> {

View File

@@ -19,9 +19,11 @@ import { Event, Emitter } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { ReplEvaluationResult, ReplEvaluationInput } from 'vs/workbench/contrib/debug/common/replModel';
import { localize } from 'vs/nls';
type ParsedQuery = {
@@ -77,23 +79,48 @@ export class ReplFilter implements ITreeFilter<IReplElement> {
}
}
export interface IFilterStatsProvider {
getFilterStats(): { total: number, filtered: number };
}
export class ReplFilterState {
constructor(private filterStatsProvider: IFilterStatsProvider) { }
private readonly _onDidChange: Emitter<void> = new Emitter<void>();
get onDidChange(): Event<void> {
return this._onDidChange.event;
}
private readonly _onDidStatsChange: Emitter<void> = new Emitter<void>();
get onDidStatsChange(): Event<void> {
return this._onDidStatsChange.event;
}
private _filterText = '';
private _stats = { total: 0, filtered: 0 };
get filterText(): string {
return this._filterText;
}
get filterStats(): { total: number, filtered: number } {
return this._stats;
}
set filterText(filterText: string) {
if (this._filterText !== filterText) {
this._filterText = filterText;
this._onDidChange.fire();
this.updateFilterStats();
}
}
updateFilterStats(): void {
const { total, filtered } = this.filterStatsProvider.getFilterStats();
if (this._stats.total !== total || this._stats.filtered !== filtered) {
this._stats = { total, filtered };
this._onDidStatsChange.fire();
}
}
}
@@ -102,12 +129,14 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private delayedFilterUpdate: Delayer<void>;
private container!: HTMLElement;
private filterBadge!: HTMLElement;
private filterInputBox!: HistoryInputBox;
constructor(
action: IAction,
private placeholder: string,
private filters: ReplFilterState,
private history: string[],
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IContextViewService private readonly contextViewService: IContextViewService) {
@@ -123,6 +152,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
this.element = DOM.append(this.container, DOM.$(''));
this.element.className = this.class;
this.createInput(this.element);
this.createBadge(this.element);
this.updateClass();
}
@@ -130,6 +160,10 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
this.filterInputBox.focus();
}
getHistory(): string[] {
return this.filterInputBox.getHistory();
}
private clearFilterText(): void {
this.filterInputBox.value = '';
}
@@ -137,7 +171,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private createInput(container: HTMLElement): void {
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
placeholder: this.placeholder,
history: []
history: this.history
}));
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
this.filterInputBox.value = this.filters.filterText;
@@ -179,6 +213,34 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
}
}
private createBadge(container: HTMLElement): void {
const controlsContainer = DOM.append(container, DOM.$('.repl-panel-filter-controls'));
const filterBadge = this.filterBadge = DOM.append(controlsContainer, DOM.$('.repl-panel-filter-badge'));
this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
filterBadge.style.backgroundColor = background;
filterBadge.style.borderWidth = border ? '1px' : '';
filterBadge.style.borderStyle = border ? 'solid' : '';
filterBadge.style.borderColor = border;
filterBadge.style.color = foreground;
}));
this.updateBadge();
this._register(this.filters.onDidStatsChange(() => this.updateBadge()));
}
private updateBadge(): void {
const { total, filtered } = this.filters.filterStats;
const filterBadgeHidden = total === filtered || total === 0;
this.filterBadge.classList.toggle('hidden', filterBadgeHidden);
this.filterBadge.textContent = localize('showing filtered repl lines', "Showing {0} of {1}", filtered, total);
this.filterInputBox.inputElement.style.paddingRight = filterBadgeHidden ? '4px' : '150px';
}
protected get class(): string {
return 'panel-action-tree-filter';
}

View File

@@ -21,8 +21,11 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IReplElementSource, IDebugService, IExpression, IReplElement, IDebugConfiguration, IDebugSession, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { localize } from 'vs/nls';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { debugConsoleEvaluationInput } from 'vs/workbench/contrib/debug/browser/debugIcons';
const $ = dom.$;
@@ -40,10 +43,13 @@ interface IReplEvaluationResultTemplateData {
interface ISimpleReplElementTemplateData {
container: HTMLElement;
count: CountBadge;
countContainer: HTMLElement;
value: HTMLElement;
source: HTMLElement;
getReplElementSource(): IReplElementSource | undefined;
toDispose: IDisposable[];
elementListener: IDisposable;
}
interface IRawObjectReplTemplateData {
@@ -62,7 +68,7 @@ export class ReplEvaluationInputsRenderer implements ITreeRenderer<ReplEvaluatio
}
renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData {
dom.append(container, $('span.arrow.codicon.codicon-arrow-small-right'));
dom.append(container, $('span.arrow' + ThemeIcon.asCSSSelector(debugConsoleEvaluationInput)));
const input = dom.append(container, $('.expression'));
const label = new HighlightedLabel(input, false);
return { label };
@@ -101,7 +107,7 @@ export class ReplGroupRenderer implements ITreeRenderer<ReplGroup, FuzzyScore, I
}
}
export class ReplEvaluationResultsRenderer implements ITreeRenderer<ReplEvaluationResult, FuzzyScore, IReplEvaluationResultTemplateData> {
export class ReplEvaluationResultsRenderer implements ITreeRenderer<ReplEvaluationResult | Variable, FuzzyScore, IReplEvaluationResultTemplateData> {
static readonly ID = 'replEvaluationResult';
get templateId(): string {
@@ -117,7 +123,7 @@ export class ReplEvaluationResultsRenderer implements ITreeRenderer<ReplEvaluati
return { value };
}
renderElement(element: ITreeNode<ReplEvaluationResult, FuzzyScore>, index: number, templateData: IReplEvaluationResultTemplateData): void {
renderElement(element: ITreeNode<ReplEvaluationResult | Variable, FuzzyScore>, index: number, templateData: IReplEvaluationResultTemplateData): void {
const expression = element.element;
renderExpressionValue(expression, templateData.value, {
showHover: false,
@@ -151,9 +157,12 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplEleme
const expression = dom.append(container, $('.output.expression.value-and-source'));
data.container = container;
data.countContainer = dom.append(expression, $('.count-badge-wrapper'));
data.count = new CountBadge(data.countContainer);
data.value = dom.append(expression, $('span.value'));
data.source = dom.append(expression, $('.source'));
data.toDispose = [];
data.toDispose.push(attachBadgeStyler(data.count, this.themeService));
data.toDispose.push(dom.addDisposableListener(data.source, 'click', e => {
e.preventDefault();
e.stopPropagation();
@@ -172,11 +181,13 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplEleme
}
renderElement({ element }: ITreeNode<SimpleReplElement, FuzzyScore>, index: number, templateData: ISimpleReplElementTemplateData): void {
this.setElementCount(element, templateData);
templateData.elementListener = element.onDidChangeCount(() => this.setElementCount(element, templateData));
// value
dom.clearNode(templateData.value);
// Reset classes to clear ansi decorations since templates are reused
templateData.value.className = 'value';
const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session);
const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session.root);
templateData.value.appendChild(result);
templateData.value.classList.add((element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info');
@@ -185,9 +196,22 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplEleme
templateData.getReplElementSource = () => element.sourceData;
}
private setElementCount(element: SimpleReplElement, templateData: ISimpleReplElementTemplateData): void {
if (element.count >= 2) {
templateData.count.setCount(element.count);
templateData.countContainer.hidden = false;
} else {
templateData.countContainer.hidden = true;
}
}
disposeTemplate(templateData: ISimpleReplElementTemplateData): void {
dispose(templateData.toDispose);
}
disposeElement(_element: ITreeNode<SimpleReplElement, FuzzyScore>, _index: number, templateData: ISimpleReplElementTemplateData): void {
templateData.elementListener.dispose();
}
}
export class ReplVariablesRenderer extends AbstractExpressionsRenderer {
@@ -296,14 +320,14 @@ export class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
if (element instanceof Variable && element.name) {
return ReplVariablesRenderer.ID;
}
if (element instanceof ReplEvaluationResult) {
if (element instanceof ReplEvaluationResult || (element instanceof Variable && !element.name)) {
// Variable with no name is a top level variable which should be rendered like a repl element #17404
return ReplEvaluationResultsRenderer.ID;
}
if (element instanceof ReplEvaluationInput) {
return ReplEvaluationInputsRenderer.ID;
}
if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) {
// Variable with no name is a top level variable which should be rendered like a repl element #17404
if (element instanceof SimpleReplElement) {
return ReplSimpleElementsRenderer.ID;
}
if (element instanceof ReplGroup) {
@@ -359,13 +383,13 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider<IRe
return localize('replVariableAriaLabel', "Variable {0}, value {1}", element.name, element.value);
}
if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult) {
return localize('replValueOutputAriaLabel', "{0}", element.value);
return element.value + (element instanceof SimpleReplElement && element.count > 1 ? localize('occurred', ", occured {0} times", element.count) : '');
}
if (element instanceof RawObjectReplElement) {
return localize('replRawObjectAriaLabel', "Debug console variable {0}, value {1}", element.name, element.value);
}
if (element instanceof ReplGroup) {
return localize('replGroup', "Debug console group {0}, read eval print loop, debug", element.name);
return localize('replGroup', "Debug console group {0}", element.name);
}
return '';

Some files were not shown because too many files have changed in this diff Show More