未验证 提交 49c611af 编写于 作者: I Isidor Nikolic 提交者: GitHub

Merge pull request #46540 from Microsoft/isidorn/breakpointEditor

debug: introduce breakpoint editor
......@@ -8,12 +8,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { Range } from 'vs/editor/common/core/range';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ServicesAccessor, registerEditorAction, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext } from 'vs/workbench/parts/debug/common/debug';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
class ToggleBreakpointAction extends EditorAction {
......@@ -210,26 +209,6 @@ class ShowDebugHoverAction extends EditorAction {
}
}
class CloseBreakpointWidgetCommand extends EditorCommand {
constructor() {
super({
id: 'closeBreakpointWidget',
precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
kbOpts: {
weight: KeybindingsRegistry.WEIGHT.editorContrib(8),
kbExpr: EditorContextKeys.focus,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}
});
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
return editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).closeBreakpointWidget();
}
}
registerEditorAction(ToggleBreakpointAction);
registerEditorAction(ConditionalBreakpointAction);
registerEditorAction(LogPointAction);
......@@ -237,4 +216,3 @@ registerEditorAction(RunToCursorAction);
registerEditorAction(SelectionToReplAction);
registerEditorAction(SelectionToWatchExpressionsAction);
registerEditorAction(ShowDebugHoverAction);
registerEditorCommand(new CloseBreakpointWidgetCommand());
......@@ -14,28 +14,11 @@
justify-content: center;
flex-direction: column;
padding: 0 10px;
flex-shrink: 0;
}
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputBoxContainer {
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer {
flex: 1;
}
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .monaco-inputbox {
border: none;
}
.monaco-editor .breakpoint-widget .input {
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
line-height: 22px;
background-color: transparent;
padding: 8px;
}
.monaco-workbench.mac .monaco-editor .breakpoint-widget .input {
font-size: 11px;
}
.monaco-workbench.windows .monaco-editor .breakpoint-widget .input,
.monaco-workbench.linux .monaco-editor .breakpoint-widget .input {
font-size: 13px;
margin-top: 6px;
margin-bottom: 6px;
}
......@@ -6,26 +6,48 @@
import 'vs/css!../browser/media/breakpointWidget';
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import { KeyCode } from 'vs/base/common/keyCodes';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Position } from 'vs/editor/common/core/position';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context } from 'vs/workbench/parts/debug/common/debug';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { once } from 'vs/base/common/functional';
import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID } from 'vs/workbench/parts/debug/common/debug';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { SimpleDebugEditor } from 'vs/workbench/parts/debug/electron-browser/simpleDebugEditor';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection';
import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IModelService } from 'vs/editor/common/services/modelService';
import uri from 'vs/base/common/uri';
import { SuggestRegistry, ISuggestResult, SuggestContext } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModel } from 'vs/editor/common/model';
import { wireCancellationToken } from 'vs/base/common/async';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDecorationOptions } from '../../../../editor/common/editorCommon';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
const $ = dom.$;
const IPrivateBreakopintWidgetService = createDecorator<IPrivateBreakopintWidgetService>('privateBreakopintWidgetService');
export interface IPrivateBreakopintWidgetService {
_serviceBrand: any;
close(success: boolean): void;
}
const DECORATION_KEY = 'breakpointwidgetdecoration';
export class BreakpointWidget extends ZoneWidget {
export class BreakpointWidget extends ZoneWidget implements IPrivateBreakopintWidgetService {
public _serviceBrand: any;
private inputBox: InputBox;
private selectContainer: HTMLElement;
private input: SimpleDebugEditor;
private toDispose: lifecycle.IDisposable[];
private conditionInput = '';
private hitCountInput = '';
......@@ -35,7 +57,11 @@ export class BreakpointWidget extends ZoneWidget {
constructor(editor: ICodeEditor, private lineNumber: number, private column: number, private context: Context,
@IContextViewService private contextViewService: IContextViewService,
@IDebugService private debugService: IDebugService,
@IThemeService private themeService: IThemeService
@IThemeService private themeService: IThemeService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IInstantiationService private instantiationService: IInstantiationService,
@IModelService private modelService: IModelService,
@ICodeEditorService private codeEditorService: ICodeEditorService,
) {
super(editor, { showFrame: true, showArrow: false, frameWidth: 1 });
......@@ -58,6 +84,8 @@ export class BreakpointWidget extends ZoneWidget {
this.dispose();
}
}));
this.codeEditorService.registerDecorationType(DECORATION_KEY, {});
this.create();
}
......@@ -72,17 +100,6 @@ export class BreakpointWidget extends ZoneWidget {
}
}
private get ariaLabel(): string {
switch (this.context) {
case Context.LOG_MESSAGE:
return nls.localize('breakpointWidgetLogMessageAriaLabel', "The program will log this message everytime this breakpoint is hit. Press Enter to accept or Escape to cancel.");
case Context.HIT_COUNT:
return nls.localize('breakpointWidgetHitCountAriaLabel', "The program will only stop here if the hit count is met. Press Enter to accept or Escape to cancel.");
default:
return nls.localize('breakpointWidgetAriaLabel', "The program will only stop here if this condition is true. Press Enter to accept or Escape to cancel.");
}
}
private getInputValue(breakpoint: IBreakpoint): string {
switch (this.context) {
case Context.LOG_MESSAGE:
......@@ -95,15 +112,16 @@ export class BreakpointWidget extends ZoneWidget {
}
private rememberInput(): void {
const value = this.input.getModel().getValue();
switch (this.context) {
case Context.LOG_MESSAGE:
this.logMessageInput = this.inputBox.value;
this.logMessageInput = value;
break;
case Context.HIT_COUNT:
this.hitCountInput = this.inputBox.value;
this.hitCountInput = value;
break;
default:
this.conditionInput = this.inputBox.value;
this.conditionInput = value;
}
}
......@@ -111,89 +129,189 @@ export class BreakpointWidget extends ZoneWidget {
this.setCssClass('breakpoint-widget');
const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count"), nls.localize('logMessage', "Log Message")], this.context, this.contextViewService);
this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
selectBox.render(dom.append(container, $('.breakpoint-select-container')));
this.selectContainer = $('.breakpoint-select-container');
selectBox.render(dom.append(container, this.selectContainer));
selectBox.onDidSelect(e => {
this.rememberInput();
this.context = e.index;
this.inputBox.setAriaLabel(this.ariaLabel);
this.inputBox.setPlaceHolder(this.placeholder);
this.inputBox.value = this.getInputValue(this.breakpoint);
const value = this.getInputValue(this.breakpoint);
this.input.getModel().setValue(value);
});
const inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
this.inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
placeholder: this.placeholder,
ariaLabel: this.ariaLabel
});
this.toDispose.push(attachInputBoxStyler(this.inputBox, this.themeService));
this.toDispose.push(this.inputBox);
this.createBreakpointInput(dom.append(container, $('.inputContainer')));
dom.addClass(this.inputBox.inputElement, isWindows ? 'windows' : isMacintosh ? 'mac' : 'linux');
this.inputBox.value = this.getInputValue(this.breakpoint);
this.input.getModel().setValue(this.getInputValue(this.breakpoint));
// Due to an electron bug we have to do the timeout, otherwise we do not get focus
setTimeout(() => this.inputBox.focus(), 0);
let disposed = false;
const wrapUp = once((success: boolean) => {
if (!disposed) {
disposed = true;
if (success) {
// if there is already a breakpoint on this location - remove it.
let condition = this.breakpoint && this.breakpoint.condition;
let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
let logMessage = this.breakpoint && this.breakpoint.logMessage;
this.rememberInput();
if (this.conditionInput) {
condition = this.conditionInput;
}
if (this.hitCountInput) {
hitCondition = this.hitCountInput;
}
if (this.logMessageInput) {
logMessage = this.logMessageInput;
}
setTimeout(() => this.input.focus(), 70);
}
if (this.breakpoint) {
this.debugService.updateBreakpoints(this.breakpoint.uri, {
[this.breakpoint.getId()]: {
condition,
hitCondition,
verified: this.breakpoint.verified,
logMessage
}
}, false);
} else {
this.debugService.addBreakpoints(this.editor.getModel().uri, [{
lineNumber: this.lineNumber,
column: this.breakpoint ? this.breakpoint.column : undefined,
enabled: true,
condition,
hitCondition,
logMessage
}]).done(null, errors.onUnexpectedError);
public close(success: boolean): void {
if (success) {
// if there is already a breakpoint on this location - remove it.
let condition = this.breakpoint && this.breakpoint.condition;
let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
let logMessage = this.breakpoint && this.breakpoint.logMessage;
this.rememberInput();
if (this.conditionInput) {
condition = this.conditionInput;
}
if (this.hitCountInput) {
hitCondition = this.hitCountInput;
}
if (this.logMessageInput) {
logMessage = this.logMessageInput;
}
if (this.breakpoint) {
this.debugService.updateBreakpoints(this.breakpoint.uri, {
[this.breakpoint.getId()]: {
condition,
hitCondition,
verified: this.breakpoint.verified,
logMessage
}
}, false);
} else {
this.debugService.addBreakpoints(this.editor.getModel().uri, [{
lineNumber: this.lineNumber,
column: this.breakpoint ? this.breakpoint.column : undefined,
enabled: true,
condition,
hitCondition,
logMessage
}]).done(null, errors.onUnexpectedError);
}
}
this.dispose();
}
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
this.input.layout({ height: 18, width: widthInPixel - 113 });
}
private createBreakpointInput(container: HTMLElement): void {
const scopedContextKeyService = this.contextKeyService.createScoped(container);
this.toDispose.push(scopedContextKeyService);
const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, scopedContextKeyService], [IPrivateBreakopintWidgetService, this]));
const options = SimpleDebugEditor.getEditorOptions();
this.input = scopedInstatiationService.createInstance(SimpleDebugEditor, container, options);
const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:breakpointinput`), true);
this.input.setModel(model);
this.toDispose.push(model);
const setDecorations = () => {
const value = this.input.getModel().getValue();
const decorations = !!value ? [] : this.createDecorations();
this.input.setDecorations(DECORATION_KEY, decorations);
};
this.input.getModel().onDidChangeContent(() => setDecorations());
this.themeService.onThemeChange(() => setDecorations());
SuggestRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, {
triggerCharacters: ['.'],
provideCompletionItems: (model: ITextModel, position: Position, _context: SuggestContext, token: CancellationToken): Thenable<ISuggestResult> => {
let suggestions: TPromise<ISuggestResult>;
if (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen()) {
suggestions = provideSuggestionItems(this.editor.getModel(), this.editor.getPosition(), 'none').then(suggestions => {
return { suggestions: suggestions.map(s => s.suggestion) };
});
} else {
suggestions = TPromise.as({ suggestions: [] });
}
this.dispose();
return wireCancellationToken(token, suggestions);
}
});
}
private createDecorations(): IDecorationOptions[] {
return [{
range: {
startLineNumber: 0,
endLineNumber: 0,
startColumn: 0,
endColumn: 1
},
renderOptions: {
after: {
contentText: this.placeholder,
color: transparent(editorForeground, 0.4)(this.themeService.getTheme()).toString()
}
}
}];
}
this.toDispose.push(dom.addStandardDisposableListener(this.inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.stopPropagation();
wrapUp(isEnter);
private isCurlyBracketOpen(): boolean {
const value = this.input.getModel().getValue();
for (let i = this.input.getPosition().column - 2; i >= 0; i--) {
if (value[i] === '{') {
return true;
}
}));
if (value[i] === '}') {
return false;
}
}
return false;
}
public dispose(): void {
super.dispose();
this.input.dispose();
lifecycle.dispose(this.toDispose);
setTimeout(() => this.editor.focus(), 0);
}
}
class AcceptBreakpointWidgetInputAction extends EditorCommand {
constructor() {
super({
id: 'breakpointWidget.action.acceptInput',
precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE, // TODO@Isidor need a more specific context key if breakpoint widget is focused
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Enter
}
});
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
accessor.get(IPrivateBreakopintWidgetService).close(true);
}
}
class CloseBreakpointWidgetCommand extends EditorCommand {
constructor() {
super({
id: 'closeBreakpointWidget',
precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}
});
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const debugContribution = editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);
if (debugContribution) {
// if focus is in outer editor we need to use the debug contribution to close
return debugContribution.closeBreakpointWidget();
}
accessor.get(IPrivateBreakopintWidgetService).close(false);
}
}
registerEditorCommand(new AcceptBreakpointWidgetInputAction());
registerEditorCommand(new CloseBreakpointWidgetCommand());
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册