diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 74b2f2b7c1f5e..a8342fec3948d 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -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(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()); diff --git a/src/vs/workbench/parts/debug/browser/media/breakpointWidget.css b/src/vs/workbench/parts/debug/browser/media/breakpointWidget.css index 8d190b91ca032..165dd70d6905d 100644 --- a/src/vs/workbench/parts/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/parts/debug/browser/media/breakpointWidget.css @@ -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; } diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts index d046222b670c3..a914f3a752143 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts @@ -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('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 => { + let suggestions: TPromise; + 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(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());