Add watch without selection (#171449)

* 🎁 Support adding expression to watch list without text selection

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Remove non-empty selection from "Add to Watch" pre-conditions

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Refactor duplicate expression extraction methods into a util function

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Make cancellation token parameter optional

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Omit unnecessary cancellation token argument

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔥 Remove unused import

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
This commit is contained in:
Babak K. Shandiz 2023-01-23 21:03:57 +03:30 committed by GitHub
parent 844b64be07
commit f777adc490
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 47 deletions

View file

@ -10,7 +10,6 @@ import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/debug.contribution';
import 'vs/css!./media/debugHover';
import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import * as nls from 'vs/nls';
import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
@ -123,7 +122,7 @@ registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('jumpTo
registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('SetNextStatement', "Set Next Statement"), original: 'Set Next Statement' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
registerDebugCommandPaletteItem(RunToCursorAction.ID, { value: RunToCursorAction.LABEL, original: 'Run to Cursor' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, CONTEXT_IN_DEBUG_MODE);
registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE));
registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' });
registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));

View file

@ -10,6 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorAction2, IActionOptions, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
import * as nls from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
@ -23,6 +24,7 @@ import { PanelFocusContext } from 'vs/workbench/common/contextkeys';
import { IViewsService } from 'vs/workbench/common/views';
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_IN_DEBUG_MODE, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugConfiguration, IDebugEditorContribution, IDebugService, REPL_VIEW_ID, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -330,7 +332,7 @@ export class SelectionToWatchExpressionsAction extends EditorAction {
id: SelectionToWatchExpressionsAction.ID,
label: SelectionToWatchExpressionsAction.LABEL,
alias: 'Debug: Add to Watch',
precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, EditorContextKeys.editorTextFocus),
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus),
contextMenuOpts: {
group: 'debug',
order: 1
@ -341,13 +343,33 @@ export class SelectionToWatchExpressionsAction extends EditorAction {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const viewsService = accessor.get(IViewsService);
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
if (!editor.hasModel()) {
return;
}
const text = editor.getModel().getValueInRange(editor.getSelection());
let expression: string | undefined = undefined;
const model = editor.getModel();
const selection = editor.getSelection();
if (!selection.isEmpty()) {
expression = model.getValueInRange(selection);
} else {
const position = editor.getPosition();
const evaluatableExpression = await getEvaluatableExpressionAtPosition(languageFeaturesService, model, position);
if (!evaluatableExpression) {
return;
}
expression = evaluatableExpression.matchingExpression;
}
if (!expression) {
return;
}
await viewsService.openView(WATCH_VIEW_ID);
debugService.addWatchExpression(text);
debugService.addWatchExpression(expression);
}
}

View file

@ -19,8 +19,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import * as nls from 'vs/nls';
@ -33,7 +32,7 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
import { IDebugService, IDebugSession, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils';
import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils';
const $ = dom.$;
@ -363,7 +362,7 @@ class DebugHoverComputer {
}
const model = this.editor.getModel();
const result = await this.doCompute(model, position, token);
const result = await getEvaluatableExpressionAtPosition(this.languageFeaturesService, model, position, token);
if (!result) {
return { rangeChanged: false };
}
@ -377,44 +376,6 @@ class DebugHoverComputer {
return { rangeChanged, range: this._currentRange };
}
private async doCompute(model: ITextModel, position: Position, token: CancellationToken): Promise<{ range: IRange; matchingExpression: string } | null> {
if (this.languageFeaturesService.evaluatableExpressionProvider.has(model)) {
const supports = this.languageFeaturesService.evaluatableExpressionProvider.ordered(model);
const results = coalesce(await Promise.all(supports.map(async support => {
try {
return await support.provideEvaluatableExpression(model, position, token);
} catch (err) {
return undefined;
}
})));
if (results.length > 0) {
let matchingExpression = results[0].expression;
const range = results[0].range;
if (!matchingExpression) {
const lineContent = model.getLineContent(position.lineNumber);
matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
}
return { range, matchingExpression };
}
} else { // old one-size-fits-all strategy
const lineContent = model.getLineContent(position.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, position.column, position.column);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(start - 1, end);
return {
matchingExpression,
range: new Range(position.lineNumber, start, position.lineNumber, start + matchingExpression.length)
};
}
return null;
}
async evaluate(session: IDebugSession): Promise<IExpression | undefined> {
if (!this._currentExpression) {
this.logService.error('No expression to evaluate');

View file

@ -11,6 +11,12 @@ import { deepClone } from 'vs/base/common/objects';
import { Schemas } from 'vs/base/common/network';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextModel } from 'vs/editor/common/model';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { CancellationToken } from 'vs/base/common/cancellation';
import { coalesce } from 'vs/base/common/arrays';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
const _formatPIIRegexp = /{([^}]+)}/g;
@ -115,6 +121,44 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n
{ start: 0, end: 0 };
}
export async function getEvaluatableExpressionAtPosition(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: Position, token?: CancellationToken): Promise<{ range: IRange; matchingExpression: string } | null> {
if (languageFeaturesService.evaluatableExpressionProvider.has(model)) {
const supports = languageFeaturesService.evaluatableExpressionProvider.ordered(model);
const results = coalesce(await Promise.all(supports.map(async support => {
try {
return await support.provideEvaluatableExpression(model, position, token ?? CancellationToken.None);
} catch (err) {
return undefined;
}
})));
if (results.length > 0) {
let matchingExpression = results[0].expression;
const range = results[0].range;
if (!matchingExpression) {
const lineContent = model.getLineContent(position.lineNumber);
matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
}
return { range, matchingExpression };
}
} else { // old one-size-fits-all strategy
const lineContent = model.getLineContent(position.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, position.column, position.column);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(start - 1, end);
return {
matchingExpression,
range: new Range(position.lineNumber, start, position.lineNumber, start + matchingExpression.length)
};
}
return null;
}
// RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;