Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug breakpoint warning improvements #151379

Merged
merged 6 commits into from Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -12,10 +12,10 @@ import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/action
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { IModelDecorationOptions, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession, DebuggerUiMessage } from 'vs/workbench/contrib/debug/common/debug';
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';
Expand All @@ -36,6 +36,8 @@ import { ILabelService } from 'vs/platform/label/common/label';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
import { onUnexpectedError } from 'vs/base/common/errors';
import { noBreakWhitespace } from 'vs/base/common/strings';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { withNullAsUndefined } from 'vs/base/common/types';

const $ = dom.$;

Expand All @@ -52,7 +54,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};

export function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): { range: Range; options: IModelDecorationOptions }[] {
export function createBreakpointDecorations(accessor: ServicesAccessor, model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): { range: Range; options: IModelDecorationOptions }[] {
const result: { range: Range; options: IModelDecorationOptions }[] = [];
breakpoints.forEach((breakpoint) => {
if (breakpoint.lineNumber > model.getLineCount()) {
Expand All @@ -65,25 +67,55 @@ export function createBreakpointDecorations(model: ITextModel, breakpoints: Read
);

result.push({
options: getBreakpointDecorationOptions(model, breakpoint, state, breakpointsActivated, showBreakpointsInOverviewRuler),
options: getBreakpointDecorationOptions(accessor, model, breakpoint, state, breakpointsActivated, showBreakpointsInOverviewRuler),
range
});
});

return result;
}

function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
const { icon, message } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined);
function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
const debugService = accessor.get(IDebugService);
const languageService = accessor.get(ILanguageService);
const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined);
let glyphMarginHoverMessage: MarkdownString | undefined;

let unverifiedMessage: string | undefined;
if (showAdapterUnverifiedMessage) {
let langId: string | undefined;
unverifiedMessage = debugService.getModel().getSessions().map(s => {
const dbg = debugService.getAdapterManager().getDebugger(s.configuration.type);
const message = dbg?.uiMessages?.[DebuggerUiMessage.UnverifiedBreakpoints];
if (message) {
if (!langId) {
// Lazily compute this, only if needed for some debug adapter
langId = withNullAsUndefined(languageService.guessLanguageIdByFilepathOrFirstLine(breakpoint.uri));
}
return langId && dbg.interestedInLanguage(langId) ? message : undefined;
}

return undefined;
})
.find(messages => !!messages);
}

if (message) {
glyphMarginHoverMessage = new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true });
if (breakpoint.condition || breakpoint.hitCondition) {
const languageId = model.getLanguageId();
glyphMarginHoverMessage = new MarkdownString().appendCodeblock(languageId, message);
glyphMarginHoverMessage.appendCodeblock(languageId, message);
if (unverifiedMessage) {
glyphMarginHoverMessage.appendMarkdown('$(warning) ' + unverifiedMessage);
}
} else {
glyphMarginHoverMessage = new MarkdownString().appendText(message);
glyphMarginHoverMessage.appendText(message);
if (unverifiedMessage) {
glyphMarginHoverMessage.appendMarkdown('\n\n$(warning) ' + unverifiedMessage);
}
}
} else if (unverifiedMessage) {
glyphMarginHoverMessage = new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true }).appendMarkdown(unverifiedMessage);
}

let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null = null;
Expand Down Expand Up @@ -469,7 +501,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
const model = activeCodeEditor.getModel();
const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri });
const debugSettings = this.configurationService.getValue<IDebugConfiguration>('debug');
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), debugSettings.showBreakpointsInOverviewRuler);
const desiredBreakpointDecorations = this.instantiationService.invokeFunction(accessor => createBreakpointDecorations(accessor, model, breakpoints, this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), debugSettings.showBreakpointsInOverviewRuler));

try {
this.ignoreDecorationsChangedEvent = true;
Expand Down
20 changes: 15 additions & 5 deletions src/vs/workbench/contrib/debug/browser/breakpointsView.ts
Expand Up @@ -23,6 +23,7 @@ import * as resources from 'vs/base/common/resources';
import { Constants } from 'vs/base/common/uint';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { localize } from 'vs/nls';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
Expand Down Expand Up @@ -106,6 +107,7 @@ export class BreakpointsView extends ViewPane {
@ILabelService private readonly labelService: ILabelService,
@IMenuService menuService: IMenuService,
@IHoverService private readonly hoverService: IHoverService,
@ILanguageService private readonly languageService: ILanguageService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);

Expand Down Expand Up @@ -285,12 +287,19 @@ export class BreakpointsView extends ViewPane {
return;
}

const hasSomeUnverified = this.debugService.getModel().getBreakpoints().some(bp => !bp.verified);

const currentType = this.debugService.getViewModel().focusedSession?.configuration.type;
const message = currentType && this.debugService.getAdapterManager().getDebuggerUiMessages(currentType)[DebuggerUiMessage.UnverifiedBreakpoints];
const dbg = currentType ? this.debugService.getAdapterManager().getDebugger(currentType) : undefined;
const message = dbg?.uiMessages && dbg.uiMessages[DebuggerUiMessage.UnverifiedBreakpoints];
const debuggerHasUnverifiedBps = message && this.debugService.getModel().getBreakpoints().filter(bp => {
if (bp.verified) {
return false;
}

const langId = this.languageService.guessLanguageIdByFilepathOrFirstLine(bp.uri);
return langId && dbg.interestedInLanguage(langId);
});

if (message && hasSomeUnverified) {
if (message && debuggerHasUnverifiedBps?.length) {
if (delayed) {
const mdown = new MarkdownString(undefined, { isTrusted: true }).appendMarkdown(message);
this.hintContainer.setLabel('$(warning)', undefined, { title: { markdown: mdown, markdownNotSupportedFallback: message } });
Expand Down Expand Up @@ -1075,7 +1084,7 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}

export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService?: ILabelService): { message?: string; icon: ThemeIcon } {
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService?: ILabelService): { message?: string; icon: ThemeIcon; showAdapterUnverifiedMessage?: boolean } {
const debugActive = state === State.Running || state === State.Stopped;

const breakpointIcon = breakpoint instanceof DataBreakpoint ? icons.dataBreakpoint : breakpoint instanceof FunctionBreakpoint ? icons.functionBreakpoint : breakpoint.logMessage ? icons.logBreakpoint : icons.breakpoint;
Expand All @@ -1094,6 +1103,7 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated:
return {
icon: breakpointIcon.unverified,
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? localize('unverifiedLogpoint', "Unverified Logpoint") : localize('unverifiedBreakpoint', "Unverified Breakpoint")),
showAdapterUnverifiedMessage: true
};
}

Expand Down
17 changes: 4 additions & 13 deletions src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts
Expand Up @@ -23,7 +23,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Breakpoints } from 'vs/workbench/contrib/debug/common/breakpoints';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, DebuggerUiMessage, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { breakpointsExtPoint, debuggersExtPoint, launchSchema, presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
Expand Down Expand Up @@ -273,15 +273,6 @@ export class AdapterManager extends Disposable implements IAdapterManager {
return undefined;
}

getDebuggerUiMessages(type: string): { [key in DebuggerUiMessage]?: string } {
const dbgr = this.getDebugger(type);
if (dbgr) {
return dbgr.uiMessages || {};
}

return {};
}

get onDidRegisterDebugger(): Event<void> {
return this._onDidRegisterDebugger.event;
}
Expand Down Expand Up @@ -312,10 +303,10 @@ export class AdapterManager extends Disposable implements IAdapterManager {
return adapter && adapter.enabled ? adapter : undefined;
}

isDebuggerInterestedInLanguage(language: string): boolean {
someDebuggerInterestedInLanguage(languageId: string): boolean {
return !!this.debuggers
.filter(d => d.enabled)
.find(a => language && a.languages && a.languages.indexOf(language) >= 0);
.find(a => a.interestedInLanguage(languageId));
}

async guessDebugger(gettingConfigurations: boolean): Promise<Debugger | undefined> {
Expand All @@ -331,7 +322,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
}
const adapters = this.debuggers
.filter(a => a.enabled)
.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
.filter(a => language && a.interestedInLanguage(language));
if (adapters.length === 1) {
return adapters[0];
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/debug/browser/disassemblyView.ts
Expand Up @@ -800,10 +800,10 @@ export class DisassemblyViewContribution implements IWorkbenchContribution {
const language = activeTextEditorControl.getModel()?.getLanguageId();
// TODO: instead of using idDebuggerInterestedInLanguage, have a specific ext point for languages
// support disassembly
this._languageSupportsDisassemleRequest?.set(!!language && debugService.getAdapterManager().isDebuggerInterestedInLanguage(language));
this._languageSupportsDisassemleRequest?.set(!!language && debugService.getAdapterManager().someDebuggerInterestedInLanguage(language));

this._onDidChangeModelLanguage = activeTextEditorControl.onDidChangeModelLanguage(e => {
this._languageSupportsDisassemleRequest?.set(debugService.getAdapterManager().isDebuggerInterestedInLanguage(e.newLanguage));
this._languageSupportsDisassemleRequest?.set(debugService.getAdapterManager().someDebuggerInterestedInLanguage(e.newLanguage));
});
} else {
this._languageSupportsDisassemleRequest?.set(false);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/debug/browser/welcomeView.ts
Expand Up @@ -65,7 +65,7 @@ export class WelcomeView extends ViewPane {
if (isCodeEditor(editorControl)) {
const model = editorControl.getModel();
const language = model ? model.getLanguageId() : undefined;
if (language && this.debugService.getAdapterManager().isDebuggerInterestedInLanguage(language)) {
if (language && this.debugService.getAdapterManager().someDebuggerInterestedInLanguage(language)) {
this.debugStartLanguageContext.set(language);
this.debuggerInterestedContext.set(true);
storageSevice.store(debugStartLanguageKey, language, StorageScope.WORKSPACE, StorageTarget.MACHINE);
Expand Down
11 changes: 9 additions & 2 deletions src/vs/workbench/contrib/debug/common/debug.ts
Expand Up @@ -159,6 +159,13 @@ export interface IDebugger {
getCustomTelemetryEndpoint(): ITelemetryEndpoint | undefined;
}

export interface IDebuggerMetadata {
label: string;
type: string;
uiMessages?: { [key in DebuggerUiMessage]: string };
interestedInLanguage(languageId: string): boolean;
}

export const enum State {
Inactive,
Initializing,
Expand Down Expand Up @@ -859,8 +866,8 @@ export interface IAdapterManager {
hasEnabledDebuggers(): boolean;
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined>;
getDebuggerLabel(type: string): string | undefined;
isDebuggerInterestedInLanguage(language: string): boolean;
getDebuggerUiMessages(type: string): { [key in DebuggerUiMessage]?: string };
someDebuggerInterestedInLanguage(language: string): boolean;
getDebugger(type: string): IDebuggerMetadata | undefined;

activateDebuggers(activationEvent: string, debugType?: string): Promise<void>;
registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable;
Expand Down
8 changes: 6 additions & 2 deletions src/vs/workbench/contrib/debug/common/debugger.ts
Expand Up @@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { isObject } from 'vs/base/common/types';
import { IJSONSchema, IJSONSchemaMap, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IDebuggerContribution, IDebugAdapter, IDebugger, IDebugSession, IAdapterManager, IDebugService, debuggerDisabledMessage } from 'vs/workbench/contrib/debug/common/debug';
import { IConfig, IDebuggerContribution, IDebugAdapter, IDebugger, IDebugSession, IAdapterManager, IDebugService, debuggerDisabledMessage, IDebuggerMetadata } from 'vs/workbench/contrib/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils';
Expand All @@ -21,7 +21,7 @@ import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtil
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';

export class Debugger implements IDebugger {
export class Debugger implements IDebugger, IDebuggerMetadata {

private debuggerContribution: IDebuggerContribution;
private mergedExtensionDescriptions: IExtensionDescription[] = [];
Expand Down Expand Up @@ -151,6 +151,10 @@ export class Debugger implements IDebugger {
return this.debuggerContribution.uiMessages;
}

interestedInLanguage(languageId: string): boolean {
return !!(this.languages && this.languages.indexOf(languageId) >= 0);
}

hasInitialConfiguration(): boolean {
return !!this.debuggerContribution.initialConfigurations;
}
Expand Down