/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as lifecycle from 'vs/base/common/lifecycle'; import * as errors from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; import severity from 'vs/base/common/severity'; import * as builder from 'vs/base/browser/builder'; import * as dom from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction } from 'vs/base/common/actions'; import { EventType } from 'vs/base/common/events'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import * as debug from 'vs/workbench/parts/debug/common/debug'; import { AbstractDebugAction, PauseAction, ContinueAction, StepBackAction, StopAction, DisconnectAction, StepOverAction, StepIntoAction, StepOutAction, RestartAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IMessageService } from 'vs/platform/message/common/message'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import IDebugService = debug.IDebugService; const $ = builder.$; const DEBUG_ACTIONS_WIDGET_POSITION_KEY = 'debug.actionswidgetposition'; export class DebugActionsWidget implements IWorkbenchContribution { private static ID = 'debug.actionsWidget'; private $el: builder.Builder; private dragArea: builder.Builder; private toDispose: lifecycle.IDisposable[]; private actionBar: ActionBar; private actions: AbstractDebugAction[]; private isVisible: boolean; private isBuilt: boolean; constructor( @IMessageService private messageService: IMessageService, @ITelemetryService private telemetryService: ITelemetryService, @IDebugService private debugService: IDebugService, @IInstantiationService private instantiationService: IInstantiationService, @IPartService private partService: IPartService, @IStorageService private storageService: IStorageService ) { this.$el = $().div().addClass('debug-actions-widget'); this.dragArea = $().div().addClass('drag-area'); this.$el.append(this.dragArea); const actionBarContainter = $().div().addClass('.action-bar-container'); this.$el.append(actionBarContainter); this.toDispose = []; this.actionBar = new ActionBar(actionBarContainter, { orientation: ActionsOrientation.HORIZONTAL }); this.toDispose.push(this.actionBar); this.registerListeners(); this.hide(); this.isBuilt = false; } private registerListeners(): void { this.toDispose.push(this.debugService.onDidChangeState(() => { this.update(); })); this.toDispose.push(this.actionBar.actionRunner.addListener2(EventType.RUN, (e: any) => { // check for error if (e.error && !errors.isPromiseCanceledError(e.error)) { this.messageService.show(severity.Error, e.error); } // log in telemetry if (this.telemetryService) { this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'debugActionsWidget' }); } })); $(window).on(dom.EventType.RESIZE, () => this.setXCoordinate(), this.toDispose); this.dragArea.on(dom.EventType.MOUSE_UP, (event: MouseEvent) => { const mouseClickEvent = new StandardMouseEvent(event); if (mouseClickEvent.detail === 2) { // double click on debug bar centers it again #8250 this.setXCoordinate(0.5 * window.innerWidth); } }); this.dragArea.on(dom.EventType.MOUSE_DOWN, (event: MouseEvent) => { const $window = $(window); this.dragArea.addClass('dragged'); $window.on('mousemove', (e: MouseEvent) => { const mouseMoveEvent = new StandardMouseEvent(e); // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); this.setXCoordinate(mouseMoveEvent.posx); }).once('mouseup', (e: MouseEvent) => { const mouseMoveEvent = new StandardMouseEvent(e); this.storageService.store(DEBUG_ACTIONS_WIDGET_POSITION_KEY, mouseMoveEvent.posx / window.innerWidth, StorageScope.WORKSPACE); this.dragArea.removeClass('dragged'); $window.off('mousemove'); }); }); } private setXCoordinate(x?: number): void { if (!this.isVisible) { return; } if (!x) { x = parseFloat(this.storageService.get(DEBUG_ACTIONS_WIDGET_POSITION_KEY, StorageScope.WORKSPACE, '0.5')) * window.innerWidth; } const halfWidgetWidth = this.$el.getHTMLElement().clientWidth / 2; x = x + halfWidgetWidth - 16; // take into account half the size of the widget x = Math.max(148, x); // do not allow the widget to overflow on the left x = Math.min(x, window.innerWidth - halfWidgetWidth - 10); // do not allow the widget to overflow on the right this.$el.style('left', `${x}px`); } public getId(): string { return DebugActionsWidget.ID; } private update(): void { const state = this.debugService.state; if (state === debug.State.Disabled || state === debug.State.Inactive) { return this.hide(); } this.actionBar.clear(); this.actionBar.push(this.getActions(), { icon: true, label: false }); this.show(); } private show(): void { if (this.isVisible) { return; } if (!this.isBuilt) { this.isBuilt = true; this.$el.build(builder.withElementById(this.partService.getWorkbenchElementId()).getHTMLElement()); } this.isVisible = true; this.$el.show(); this.setXCoordinate(); } private hide(): void { this.isVisible = false; this.$el.hide(); } private getActions(): IAction[] { if (!this.actions) { this.actions = []; this.actions.push(this.instantiationService.createInstance(ContinueAction, ContinueAction.ID, ContinueAction.LABEL)); this.actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL)); this.actions.push(this.instantiationService.createInstance(StopAction, StopAction.ID, StopAction.LABEL)); this.actions.push(this.instantiationService.createInstance(DisconnectAction, DisconnectAction.ID, DisconnectAction.LABEL)); this.actions.push(this.instantiationService.createInstance(StepOverAction, StepOverAction.ID, StepOverAction.LABEL)); this.actions.push(this.instantiationService.createInstance(StepIntoAction, StepIntoAction.ID, StepIntoAction.LABEL)); this.actions.push(this.instantiationService.createInstance(StepOutAction, StepOutAction.ID, StepOutAction.LABEL)); this.actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL)); this.actions.push(this.instantiationService.createInstance(StepBackAction, StepBackAction.ID, StepBackAction.LABEL)); this.actions.forEach(a => { this.toDispose.push(a); }); } const state = this.debugService.state; const process = this.debugService.getViewModel().focusedProcess; const attached = process && !strings.equalsIgnoreCase(process.session.configuration.type, 'extensionHost') && process.session.requestType === debug.SessionRequestType.ATTACH; return this.actions.filter(a => { if (a.id === ContinueAction.ID) { return state !== debug.State.Running; } if (a.id === PauseAction.ID) { return state === debug.State.Running; } if (a.id === StepBackAction.ID) { return process && process.session.configuration.capabilities.supportsStepBack; } if (a.id === DisconnectAction.ID) { return attached; } if (a.id === StopAction.ID) { return !attached; } return true; }).sort((first, second) => first.weight - second.weight); } public dispose(): void { this.toDispose = lifecycle.dispose(this.toDispose); if (this.$el) { this.$el.destroy(); delete this.$el; } } }