breakpointWidget.ts 13.3 KB
Newer Older
I
isidor 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import 'vs/css!../browser/media/breakpointWidget';
7
import * as nls from 'vs/nls';
I
isidor 已提交
8
import * as errors from 'vs/base/common/errors';
I
isidor 已提交
9
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
J
Johannes Rieken 已提交
10
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
I
isidor 已提交
11 12
import * as lifecycle from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
13
import { Position } from 'vs/editor/common/core/position';
I
isidor 已提交
14
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
15
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
J
Johannes Rieken 已提交
16
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
I
isidor 已提交
17
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_IN_BREAKPOINT_WIDGET } from 'vs/workbench/parts/debug/common/debug';
I
isidor 已提交
18
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
B
Benjamin Pasero 已提交
19
import { IThemeService } from 'vs/platform/theme/common/themeService';
I
isidor 已提交
20 21 22 23 24 25
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
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';
I
isidor 已提交
26
import { SuggestRegistry, ISuggestResult, SuggestContext } from 'vs/editor/common/modes';
27 28 29
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModel } from 'vs/editor/common/model';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
30 31
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
32 33
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
34
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
35
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
I
isidor 已提交
36
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
I
isidor 已提交
37

J
Joao Moreno 已提交
38
const $ = dom.$;
I
isidor 已提交
39 40
const IPrivateBreakpointWidgetService = createDecorator<IPrivateBreakpointWidgetService>('privateBreakopintWidgetService');
export interface IPrivateBreakpointWidgetService {
I
isidor 已提交
41 42 43
	_serviceBrand: any;
	close(success: boolean): void;
}
44
const DECORATION_KEY = 'breakpointwidgetdecoration';
I
isidor 已提交
45

I
isidor 已提交
46
export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService {
I
isidor 已提交
47
	public _serviceBrand: any;
48

I
isidor 已提交
49
	private selectContainer: HTMLElement;
50
	private input: CodeEditorWidget;
51
	private toDispose: lifecycle.IDisposable[];
I
isidor 已提交
52 53 54
	private conditionInput = '';
	private hitCountInput = '';
	private logMessageInput = '';
55
	private breakpoint: IBreakpoint;
I
isidor 已提交
56

57
	constructor(editor: ICodeEditor, private lineNumber: number, private context: Context,
I
isidor 已提交
58
		@IContextViewService private contextViewService: IContextViewService,
B
Benjamin Pasero 已提交
59
		@IDebugService private debugService: IDebugService,
I
isidor 已提交
60 61 62
		@IThemeService private themeService: IThemeService,
		@IContextKeyService private contextKeyService: IContextKeyService,
		@IInstantiationService private instantiationService: IInstantiationService,
63 64
		@IModelService private modelService: IModelService,
		@ICodeEditorService private codeEditorService: ICodeEditorService,
I
isidor 已提交
65
	) {
66
		super(editor, { showFrame: true, showArrow: false, frameWidth: 1 });
67

68
		this.toDispose = [];
69
		const uri = this.editor.getModel().uri;
I
isidor 已提交
70 71
		const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, uri });
		this.breakpoint = breakpoints.length ? breakpoints[0] : undefined;
72

73 74 75 76 77 78 79 80 81 82
		if (this.context === undefined) {
			if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) {
				this.context = Context.LOG_MESSAGE;
			} else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) {
				this.context = Context.HIT_COUNT;
			} else {
				this.context = Context.CONDITION;
			}
		}

83
		this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => {
I
isidor 已提交
84
			if (this.breakpoint && e && e.removed && e.removed.indexOf(this.breakpoint) >= 0) {
85 86 87
				this.dispose();
			}
		}));
88 89
		this.codeEditorService.registerDecorationType(DECORATION_KEY, {});

I
isidor 已提交
90 91 92
		this.create();
	}

93 94 95 96 97 98 99 100 101 102
	private get placeholder(): string {
		switch (this.context) {
			case Context.LOG_MESSAGE:
				return nls.localize('breakpointWidgetLogMessagePlaceholder', "Message to log when breakpoint is hit. Expressions within {} are interpolated. 'Enter' to accept, 'esc' to cancel.");
			case Context.HIT_COUNT:
				return nls.localize('breakpointWidgetHitCountPlaceholder', "Break when hit count condition is met. 'Enter' to accept, 'esc' to cancel.");
			default:
				return nls.localize('breakpointWidgetExpressionPlaceholder', "Break when expression evaluates to true. 'Enter' to accept, 'esc' to cancel.");
		}
	}
103

104
	private getInputValue(breakpoint: IBreakpoint): string {
I
isidor 已提交
105 106 107 108 109 110 111
		switch (this.context) {
			case Context.LOG_MESSAGE:
				return breakpoint && breakpoint.logMessage ? breakpoint.logMessage : this.logMessageInput;
			case Context.HIT_COUNT:
				return breakpoint && breakpoint.hitCondition ? breakpoint.hitCondition : this.hitCountInput;
			default:
				return breakpoint && breakpoint.condition ? breakpoint.condition : this.conditionInput;
112
		}
I
isidor 已提交
113
	}
114

I
isidor 已提交
115
	private rememberInput(): void {
I
isidor 已提交
116
		const value = this.input.getModel().getValue();
I
isidor 已提交
117 118
		switch (this.context) {
			case Context.LOG_MESSAGE:
I
isidor 已提交
119
				this.logMessageInput = value;
I
isidor 已提交
120 121
				break;
			case Context.HIT_COUNT:
I
isidor 已提交
122
				this.hitCountInput = value;
I
isidor 已提交
123 124
				break;
			default:
I
isidor 已提交
125
				this.conditionInput = value;
I
isidor 已提交
126
		}
127 128
	}

J
Johannes Rieken 已提交
129
	protected _fillContainer(container: HTMLElement): void {
130
		this.setCssClass('breakpoint-widget');
131
		const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count"), nls.localize('logMessage', "Log Message")], this.context, this.contextViewService, null, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') });
132
		this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
I
isidor 已提交
133 134
		this.selectContainer = $('.breakpoint-select-container');
		selectBox.render(dom.append(container, this.selectContainer));
135
		selectBox.onDidSelect(e => {
I
isidor 已提交
136 137
			this.rememberInput();
			this.context = e.index;
138

139 140
			const value = this.getInputValue(this.breakpoint);
			this.input.getModel().setValue(value);
141 142
		});

I
isidor 已提交
143
		this.createBreakpointInput(dom.append(container, $('.inputContainer')));
144

I
isidor 已提交
145
		this.input.getModel().setValue(this.getInputValue(this.breakpoint));
146
		this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) });
I
isidor 已提交
147
		// Due to an electron bug we have to do the timeout, otherwise we do not get focus
I
isidor 已提交
148
		setTimeout(() => this.input.focus(), 100);
I
isidor 已提交
149
	}
I
isidor 已提交
150

I
isidor 已提交
151 152 153
	public close(success: boolean): void {
		if (success) {
			// if there is already a breakpoint on this location - remove it.
I
isidor 已提交
154

I
isidor 已提交
155 156 157 158 159
			let condition = this.breakpoint && this.breakpoint.condition;
			let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
			let logMessage = this.breakpoint && this.breakpoint.logMessage;
			this.rememberInput();

160
			if (this.conditionInput || this.context === Context.CONDITION) {
I
isidor 已提交
161 162
				condition = this.conditionInput;
			}
163
			if (this.hitCountInput || this.context === Context.HIT_COUNT) {
I
isidor 已提交
164 165
				hitCondition = this.hitCountInput;
			}
166
			if (this.logMessageInput || this.context === Context.LOG_MESSAGE) {
I
isidor 已提交
167
				logMessage = this.logMessageInput;
I
isidor 已提交
168 169
			}

I
isidor 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
			if (this.breakpoint) {
				this.debugService.updateBreakpoints(this.breakpoint.uri, {
					[this.breakpoint.getId()]: {
						condition,
						hitCondition,
						logMessage
					}
				}, false);
			} else {
				this.debugService.addBreakpoints(this.editor.getModel().uri, [{
					lineNumber: this.lineNumber,
					enabled: true,
					condition,
					hitCondition,
					logMessage
				}]).done(null, errors.onUnexpectedError);
I
isidor 已提交
186
			}
I
isidor 已提交
187 188 189 190 191 192
		}

		this.dispose();
	}

	protected _doLayout(heightInPixel: number, widthInPixel: number): void {
193
		this.input.layout({ height: 18, width: widthInPixel - 113 });
I
isidor 已提交
194 195 196 197 198 199 200
	}

	private createBreakpointInput(container: HTMLElement): void {
		const scopedContextKeyService = this.contextKeyService.createScoped(container);
		this.toDispose.push(scopedContextKeyService);

		const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection(
I
isidor 已提交
201
			[IContextKeyService, scopedContextKeyService], [IPrivateBreakpointWidgetService, this]));
I
isidor 已提交
202

I
isidor 已提交
203 204
		const options = getSimpleEditorOptions();
		const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions();
205
		this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions);
I
isidor 已提交
206
		CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true);
207
		const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:${this.editor.getId()}:breakpointinput`), true);
I
isidor 已提交
208 209
		this.input.setModel(model);
		this.toDispose.push(model);
210 211 212 213 214 215 216
		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());
217

218
		this.toDispose.push(SuggestRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, {
219
			provideCompletionItems: (model: ITextModel, position: Position, _context: SuggestContext, token: CancellationToken): Thenable<ISuggestResult> => {
220
				let suggestionsPromise: Promise<ISuggestResult>;
221
				if (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen()) {
222
					suggestionsPromise = provideSuggestionItems(this.editor.getModel(), new Position(this.lineNumber, 1), 'none', undefined, _context, token).then(suggestions => {
223 224 225 226 227 228 229 230 231 232 233 234

						let overwriteBefore = 0;
						if (this.context === Context.CONDITION) {
							overwriteBefore = position.column - 1;
						} else {
							// Inside the currly brackets, need to count how many useful characters are behind the position so they would all be taken into account
							const value = this.input.getModel().getValue();
							while ((position.column - 2 - overwriteBefore >= 0) && value[position.column - 2 - overwriteBefore] !== '{' && value[position.column - 2 - overwriteBefore] !== ' ') {
								overwriteBefore++;
							}
						}

I
isidor 已提交
235 236
						return {
							suggestions: suggestions.map(s => {
237 238
								s.suggestion.overwriteAfter = 0;
								s.suggestion.overwriteBefore = overwriteBefore;
I
isidor 已提交
239 240 241
								return s.suggestion;
							})
						};
242 243
					});
				} else {
244
					suggestionsPromise = Promise.resolve({ suggestions: [] });
245 246
				}

247
				return suggestionsPromise;
248
			}
249
		}));
250 251
	}

252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
	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()
				}
			}
		}];
	}

269 270 271 272 273 274 275 276 277 278 279 280 281
	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;
I
isidor 已提交
282
	}
283 284 285

	public dispose(): void {
		super.dispose();
I
isidor 已提交
286
		this.input.dispose();
J
Joao Moreno 已提交
287
		lifecycle.dispose(this.toDispose);
288
		setTimeout(() => this.editor.focus(), 0);
289
	}
I
isidor 已提交
290
}
I
isidor 已提交
291 292 293 294 295 296

class AcceptBreakpointWidgetInputAction extends EditorCommand {

	constructor() {
		super({
			id: 'breakpointWidget.action.acceptInput',
I
isidor 已提交
297
			precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
I
isidor 已提交
298
			kbOpts: {
I
isidor 已提交
299
				kbExpr: CONTEXT_IN_BREAKPOINT_WIDGET,
A
Alex Dima 已提交
300
				primary: KeyCode.Enter,
301
				weight: KeybindingWeight.EditorContrib
I
isidor 已提交
302 303 304 305 306
			}
		});
	}

	public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
I
isidor 已提交
307
		accessor.get(IPrivateBreakpointWidgetService).close(true);
I
isidor 已提交
308 309 310 311 312 313 314 315 316 317 318 319
	}
}

class CloseBreakpointWidgetCommand extends EditorCommand {

	constructor() {
		super({
			id: 'closeBreakpointWidget',
			precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
			kbOpts: {
				kbExpr: EditorContextKeys.textInputFocus,
				primary: KeyCode.Escape,
A
Alex Dima 已提交
320
				secondary: [KeyMod.Shift | KeyCode.Escape],
321
				weight: KeybindingWeight.EditorContrib
I
isidor 已提交
322 323 324 325 326 327 328 329 330 331 332
			}
		});
	}

	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();
		}

I
isidor 已提交
333
		accessor.get(IPrivateBreakpointWidgetService).close(false);
I
isidor 已提交
334 335 336 337 338
	}
}

registerEditorCommand(new AcceptBreakpointWidgetInputAction());
registerEditorCommand(new CloseBreakpointWidgetCommand());