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

Merge pull request #64000 from Microsoft/isidorn/debugTrees

Isidorn/debug trees
......@@ -1057,6 +1057,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.scrollHeight;
}
get renderHeight(): number {
return this.view.renderHeight;
}
domFocus(): void {
this.view.domNode.focus();
}
......
......@@ -270,6 +270,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.view.scrollHeight;
}
get renderHeight(): number {
return this.view.renderHeight;
}
domFocus(): void {
this.view.domFocus();
}
......
......@@ -245,6 +245,10 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return this.tree.scrollHeight;
}
get renderHeight(): number {
return this.tree.renderHeight;
}
domFocus(): void {
this.tree.domFocus();
}
......
......@@ -161,4 +161,4 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
return this.model.getNodeLocation(node);
}
}
\ No newline at end of file
}
......@@ -21,6 +21,7 @@ import { URI } from 'vs/base/common/uri';
import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/toggleSidebarPosition';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
export abstract class Viewlet extends Composite implements IViewlet {
......@@ -182,3 +183,13 @@ export class CollapseAction extends Action {
});
}
}
// Collapse All action for the new tree
export class CollapseAction2 extends Action {
constructor(tree: AsyncDataTree<any>, enabled: boolean, clazz: string) {
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => {
tree.collapseAll();
return Promise.resolve(undefined);
});
}
}
......@@ -6,21 +6,21 @@
import * as dom from 'vs/base/browser/dom';
import { IExpression, IDebugService, IEnablement } from 'vs/workbench/parts/debug/common/debug';
import { Expression, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITree, ContextMenuEvent, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
......@@ -114,73 +114,12 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData,
}
}
export interface IRenameBoxOptions {
export interface IInputBoxOptions {
initialValue: string;
ariaLabel: string;
placeholder?: string;
validationOptions?: IInputValidationOptions;
}
export function renderRenameBox(debugService: IDebugService, contextViewService: IContextViewService, themeService: IThemeService, tree: ITree, element: any, container: HTMLElement, options: IRenameBoxOptions): void {
let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
let inputBox = new InputBox(inputBoxContainer, contextViewService, {
validationOptions: options.validationOptions,
placeholder: options.placeholder,
ariaLabel: options.ariaLabel
});
const styler = attachInputBoxStyler(inputBox, themeService);
inputBox.value = options.initialValue ? options.initialValue : '';
inputBox.focus();
inputBox.select();
tree.clearFocus();
tree.clearSelection();
let disposed = false;
const toDispose: IDisposable[] = [inputBox, styler];
const wrapUp = once((renamed: boolean) => {
if (!disposed) {
disposed = true;
debugService.getViewModel().setSelectedExpression(undefined);
if (element instanceof Expression && renamed && inputBox.value) {
debugService.renameWatchExpression(element.getId(), inputBox.value);
} else if (element instanceof Expression && !element.name) {
debugService.removeWatchExpressions(element.getId());
} else if (element instanceof Variable) {
element.errorMessage = null;
if (renamed && element.value !== inputBox.value) {
element.setVariable(inputBox.value)
// if everything went fine we need to refresh ui elements since the variable update can change watch and variables view
.then(() => {
tree.refresh(element, false);
// Need to force watch expressions to update since a variable change can have an effect on watches
debugService.focusStackFrame(debugService.getViewModel().focusedStackFrame);
});
}
}
tree.domFocus();
tree.setFocus(element);
// need to remove the input box since this template will be reused.
container.removeChild(inputBoxContainer);
dispose(toDispose);
}
});
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.preventDefault();
e.stopPropagation();
wrapUp(isEnter);
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
}));
onFinish: (value: string, success: boolean) => void;
}
export class BaseDebugController extends WorkbenchTreeController {
......@@ -242,3 +181,117 @@ export class BaseDebugController extends WorkbenchTreeController {
return undefined;
}
}
export interface IExpressionTemplateData {
expression: HTMLElement;
name: HTMLSpanElement;
value: HTMLSpanElement;
inputBoxContainer: HTMLElement;
enableInputBox(expression: IExpression, options: IInputBoxOptions);
toDispose: IDisposable[];
}
export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpression, void, IExpressionTemplateData>, IDisposable {
protected renderedExpressions = new Map<IExpression, IExpressionTemplateData>();
private toDispose: IDisposable[];
constructor(
@IDebugService protected debugService: IDebugService,
@IContextViewService private contextViewService: IContextViewService,
@IThemeService private themeService: IThemeService
) {
this.toDispose = [];
this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
const template = this.renderedExpressions.get(expression);
if (template) {
template.enableInputBox(expression, this.getInputBoxOptions(expression));
}
}));
}
abstract get templateId(): string;
renderTemplate(container: HTMLElement): IExpressionTemplateData {
const data: IExpressionTemplateData = Object.create(null);
data.expression = dom.append(container, $('.expression'));
data.name = dom.append(data.expression, $('span.name'));
data.value = dom.append(data.expression, $('span.value'));
data.inputBoxContainer = dom.append(data.expression, $('.inputBoxContainer'));
data.enableInputBox = (expression: IExpression, options: IInputBoxOptions) => {
data.name.style.display = 'none';
data.value.style.display = 'none';
data.inputBoxContainer.style.display = 'initial';
const inputBox = new InputBox(data.inputBoxContainer, this.contextViewService, {
placeholder: options.placeholder,
ariaLabel: options.ariaLabel
});
const styler = attachInputBoxStyler(inputBox, this.themeService);
inputBox.value = options.initialValue;
inputBox.focus();
inputBox.select();
let disposed = false;
data.toDispose = [inputBox, styler];
const wrapUp = (renamed: boolean) => {
if (!disposed) {
disposed = true;
this.debugService.getViewModel().setSelectedExpression(undefined);
options.onFinish(inputBox.value, renamed);
// need to remove the input box since this template will be reused.
data.inputBoxContainer.removeChild(inputBox.element);
data.name.style.display = 'initial';
data.value.style.display = 'initial';
data.inputBoxContainer.style.display = 'none';
dispose(data.toDispose);
}
};
data.toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.preventDefault();
e.stopPropagation();
wrapUp(isEnter);
}
}));
data.toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
}));
};
return data;
}
renderElement({ element }: ITreeNode<IExpression>, index: number, data: IExpressionTemplateData): void {
this.renderedExpressions.set(element, data);
if (element === this.debugService.getViewModel().getSelectedExpression()) {
data.enableInputBox(element, this.getInputBoxOptions(element));
} else {
this.renderExpression(element, data);
}
}
protected abstract renderExpression(expression: IExpression, data: IExpressionTemplateData): void;
protected abstract getInputBoxOptions(expression: IExpression): IInputBoxOptions;
disposeTemplate(templateData: IExpressionTemplateData): void {
dispose(templateData.toDispose);
}
disposeElement(element: ITreeNode<Expression, void>): void {
this.renderedExpressions.delete(element.element);
}
dispose(): void {
this.renderedExpressions = undefined;
dispose(this.toDispose);
}
}
......@@ -19,11 +19,11 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { TogglePanelAction } from 'vs/workbench/browser/panel';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { CollapseAction2 } from 'vs/workbench/browser/viewlet';
import { first } from 'vs/base/common/arrays';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { memoize } from 'vs/base/common/decorators';
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
export abstract class AbstractDebugAction extends Action {
......@@ -797,9 +797,9 @@ export class ReverseContinueAction extends AbstractDebugAction {
}
}
export class ReplCollapseAllAction extends CollapseAction {
constructor(viewer: ITree, private toFocus: { focus(): void; }) {
super(viewer, true, undefined);
export class ReplCollapseAllAction extends CollapseAction2 {
constructor(tree: AsyncDataTree<any>, private toFocus: { focus(): void; }) {
super(tree, true, undefined);
}
public run(event?: any): Thenable<any> {
......
......@@ -4,21 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { TreeViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import * as dom from 'vs/base/browser/dom';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { normalize, isAbsolute, sep } from 'vs/base/common/paths';
import { IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { WorkbenchTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService';
import { renderViewTree, twistiePixels } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { IAccessibilityProvider, ITree, IRenderer, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { tildify } from 'vs/base/common/labels';
......@@ -28,10 +25,19 @@ import { ltrim } from 'vs/base/common/strings';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ResourceLabel, IResourceLabel, IResourceLabelOptions } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchAsyncDataTree, IListService, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugContentProvider';
const SMART = true;
type LoadedScriptsItem = BaseTreeItem;
class BaseTreeItem {
private _showedMoreThanOne: boolean;
......@@ -43,6 +49,10 @@ class BaseTreeItem {
this._showedMoreThanOne = false;
}
isLeaf(): boolean {
return Object.keys(this._children).length === 0;
}
getSession(): IDebugSession {
if (this._parent) {
return this._parent.getSession();
......@@ -140,7 +150,7 @@ class BaseTreeItem {
}
// skips intermediate single-child nodes
getLabel(separateRootFolder = true) {
getLabel(separateRootFolder = true): string {
const child = this.oneChild();
if (child) {
const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? '' : '/';
......@@ -349,10 +359,14 @@ class SessionTreeItem extends BaseTreeItem {
}
}
export class LoadedScriptsView extends TreeViewsViewletPanel {
export class LoadedScriptsView extends ViewletPanel {
private treeContainer: HTMLElement;
private loadedScriptsItemType: IContextKey<string>;
private tree: WorkbenchAsyncDataTree<any>;
private changeScheduler: RunOnceScheduler;
private treeNeedsRefreshOnVisible: boolean;
private filter: LoadedScriptsFilter;
constructor(
options: IViewletViewOptions,
......@@ -361,41 +375,56 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
@IInstantiationService private instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IEditorService private editorService: IEditorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IDebugService private debugService: IDebugService
@IDebugService private debugService: IDebugService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService);
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);
}
protected renderBody(container: HTMLElement): void {
renderBody(container: HTMLElement): void {
dom.addClass(container, 'debug-loaded-scripts');
dom.addClass(container, 'show-file-icons');
this.treeContainer = renderViewTree(container);
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer,
{
dataSource: new LoadedScriptsDataSource(),
renderer: this.instantiationService.createInstance(LoadedScriptsRenderer),
accessibilityProvider: new LoadedSciptsAccessibilityProvider()
},
this.filter = new LoadedScriptsFilter();
const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService);
this.tree = new WorkbenchAsyncDataTree(this.treeContainer, new LoadedScriptsDelegate(),
[
this.instantiationService.createInstance(LoadedScriptsRenderer)
],
new LoadedScriptsDataSource(root),
{
identityProvider: {
getId: element => element.getId()
},
filter: this.filter,
accessibilityProvider: new LoadedSciptsAccessibilityProvider(),
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"),
twistiePixels
}
},
this.contextKeyService, this.listService, this.themeService, this.configurationService
);
const callstackNavigator = new TreeResourceNavigator(this.tree);
this.disposables.push(callstackNavigator);
this.disposables.push(callstackNavigator.openResource(e => {
const element = e.element;
this.changeScheduler = new RunOnceScheduler(() => {
this.treeNeedsRefreshOnVisible = false;
if (this.tree) {
this.tree.refresh(null);
}
}, 300);
this.disposables.push(this.changeScheduler);
if (element instanceof BaseTreeItem) {
const source = element.getSource();
const loadedScriptsNavigator = new TreeResourceNavigator2(this.tree);
this.disposables.push(loadedScriptsNavigator);
this.disposables.push(loadedScriptsNavigator.openResource(e => {
if (e.element instanceof BaseTreeItem) {
const source = e.element.getSource();
if (source && source.available) {
const nullRange = { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 };
source.openInEditor(this.editorService, nullRange, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
......@@ -412,18 +441,6 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
}
}));
let nextRefreshIsRecursive = false;
const refreshScheduler = new RunOnceScheduler(() => {
if (this.tree) {
this.tree.refresh(undefined, nextRefreshIsRecursive);
nextRefreshIsRecursive = false;
}
}, 300);
this.disposables.push(refreshScheduler);
const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService);
this.tree.setInput(root);
const registerLoadedSourceListener = (session: IDebugSession) => {
this.disposables.push(session.onDidLoadedSource(event => {
let sessionRoot: SessionTreeItem;
......@@ -432,8 +449,11 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
case 'changed':
sessionRoot = root.add(session);
sessionRoot.addPath(event.source);
nextRefreshIsRecursive = true;
refreshScheduler.schedule();
if (this.isVisible) {
this.changeScheduler.schedule();
} else {
this.treeNeedsRefreshOnVisible = true;
}
if (event.reason === 'changed') {
DebugContentProvider.refreshDebugContent(event.source.uri);
}
......@@ -441,10 +461,17 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
case 'removed':
sessionRoot = root.find(session);
if (sessionRoot && sessionRoot.removePath(event.source)) {
nextRefreshIsRecursive = true;
refreshScheduler.schedule();
if (this.isVisible) {
this.changeScheduler.schedule();
} else {
this.treeNeedsRefreshOnVisible = true;
}
}
break;
default:
this.filter.setFilter(event.source.name);
this.tree.refilter();
break;
}
}));
};
......@@ -454,74 +481,101 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
this.disposables.push(this.debugService.onDidEndSession(session => {
root.remove(session.getId());
refreshScheduler.schedule();
this.changeScheduler.schedule();
}));
this.changeScheduler.schedule(0);
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
this.tree.layout(size);
}
setExpanded(expanded: boolean): void {
super.setExpanded(expanded);
if (expanded && this.treeNeedsRefreshOnVisible) {
this.changeScheduler.schedule();
}
super.layoutBody(size);
}
setVisible(visible: boolean): void {
super.setVisible(visible);
if (visible && this.treeNeedsRefreshOnVisible) {
this.changeScheduler.schedule();
}
}
/*
private tryToExpand(element: LoadedScriptsItem): void {
try {
this.tree.expand(element);
} catch (e) { }
}
*/
dispose(): void {
this.tree = undefined;
super.dispose();
}
}
// A good example of data source, renderers, action providers and accessibilty providers can be found in the callStackView.ts
class LoadedScriptsDataSource implements IDataSource {
class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
getId(tree: ITree, element: any): string {
return element.getId();
getHeight(element: LoadedScriptsItem): number {
return 22;
}
hasChildren(tree: ITree, element: any): boolean {
return element.hasChildren();
getTemplateId(element: LoadedScriptsItem): string {
if (element instanceof BaseTreeItem) {
return LoadedScriptsRenderer.ID;
}
return undefined;
}
}
getChildren(tree: ITree, element: any): Promise<any> {
return element.getChildren();
class LoadedScriptsDataSource implements IDataSource<LoadedScriptsItem> {
constructor(private root: LoadedScriptsItem) {
}
getParent(tree: ITree, element: any): Promise<any> {
return Promise.resolve(element.getParent());
hasChildren(element: LoadedScriptsItem | null): boolean {
return element === null || element.hasChildren();
}
shouldAutoexpand?(tree: ITree, element: any): boolean {
return element instanceof RootTreeItem || element instanceof SessionTreeItem;
getChildren(element: LoadedScriptsItem | null): Thenable<LoadedScriptsItem[]> {
if (element === null) {
element = this.root;
}
return element.getChildren();
}
}
interface ITemplateData {
interface ILoadedScriptsItemTemplateData {
label: ResourceLabel;
}
class LoadedScriptsRenderer implements IRenderer {
class LoadedScriptsRenderer implements ITreeRenderer<BaseTreeItem, void, ILoadedScriptsItemTemplateData> {
static readonly ID = 'lsrenderer';
constructor(
@IInstantiationService private instantiationService: IInstantiationService
) {
}
getHeight(tree: ITree, element: any): number {
return 22;
}
getTemplateId(tree: ITree, element: any): string {
return element.getTemplateId();
get templateId(): string {
return LoadedScriptsRenderer.ID;
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
let data: ITemplateData = Object.create(null);
renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData {
let data: ILoadedScriptsItemTemplateData = Object.create(null);
data.label = this.instantiationService.createInstance(ResourceLabel, container, void 0);
return data;
}
renderElement(tree: ITree, element: any, templateId: string, data: ITemplateData): void {
renderElement(node: ITreeNode<BaseTreeItem, void>, index: number, data: ILoadedScriptsItemTemplateData): void {
const element = node.element;
const label: IResourceLabel = {
name: element.getLabel()
......@@ -553,14 +607,18 @@ class LoadedScriptsRenderer implements IRenderer {
data.label.setLabel(label, options);
}
disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
disposeElement(element: ITreeNode<BaseTreeItem, void>, index: number, templateData: ILoadedScriptsItemTemplateData): void {
// noop
}
disposeTemplate(templateData: ILoadedScriptsItemTemplateData): void {
// noop
}
}
class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider {
class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider<LoadedScriptsItem> {
public getAriaLabel(tree: ITree, element: any): string {
getAriaLabel(element: LoadedScriptsItem): string {
if (element instanceof RootFolderTreeItem) {
return nls.localize('loadedScriptsRootFolderAriaLabel', "Workspace folder {0}, loaded script, debug", element.getLabel());
......@@ -577,6 +635,32 @@ class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider {
return nls.localize('loadedScriptsSourceAriaLabel', "{0}, loaded script, debug", element.getLabel());
}
}
return null;
}
}
class LoadedScriptsFilter implements ITreeFilter<BaseTreeItem> {
private filterText: string;
setFilter(filterText: string) {
this.filterText = filterText;
}
filter(element: BaseTreeItem, parentVisibility: TreeVisibility): TreeFilterResult<void> {
if (!this.filterText) {
return TreeVisibility.Visible;
}
if (element.isLeaf()) {
const name = element.getLabel();
if (name.indexOf(this.filterText) >= 0) {
return TreeVisibility.Visible;
}
return TreeVisibility.Hidden;
}
return TreeVisibility.Recurse;
}
}
\ No newline at end of file
......@@ -131,102 +131,102 @@
/* Expressions */
.monaco-workbench .monaco-tree-row .expression {
.monaco-workbench .monaco-list-row .expression {
overflow: hidden;
text-overflow: ellipsis;
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
}
.monaco-workbench.mac .monaco-tree-row .expression {
.monaco-workbench.mac .monaco-list-row .expression {
font-size: 11px;
}
.monaco-workbench.windows .monaco-tree-row .expression,
.monaco-workbench.linux .monaco-tree-row .expression {
.monaco-workbench.windows .monaco-list-row .expression,
.monaco-workbench.linux .monaco-list-row .expression {
font-size: 13px;
}
.monaco-workbench .monaco-tree-row .expression .value {
.monaco-workbench .monaco-list-row .expression .value {
margin-left: 6px;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .name {
.monaco-workbench .monaco-list-row:not(.selected) .expression .name {
color: #9B46B0;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .name.virtual {
.monaco-workbench .monaco-list-row:not(.selected) .expression .name.virtual {
opacity: 0.5;
}
.monaco-workbench > .monaco-tree-row:not(.selected) .expression .value {
.monaco-workbench > .monaco-list-row:not(.selected) .expression .value {
color: rgba(108, 108, 108, 0.8);
}
.monaco-workbench .monaco-tree-row .expression .unavailable {
.monaco-workbench .monaco-list-row .expression .unavailable {
font-style: italic;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .error {
.monaco-workbench .monaco-list-row:not(.selected) .expression .error {
color: #E51400;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .value.number {
.monaco-workbench .monaco-list-row:not(.selected) .expression .value.number {
color: #09885A;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .value.boolean {
.monaco-workbench .monaco-list-row:not(.selected) .expression .value.boolean {
color: #0000FF;
}
.monaco-workbench .monaco-tree-row:not(.selected) .expression .value.string {
.monaco-workbench .monaco-list-row:not(.selected) .expression .value.string {
color: #A31515;
}
.vs-dark .monaco-workbench > .monaco-tree-row:not(.selected) .expression .value {
.vs-dark .monaco-workbench > .monaco-list-row:not(.selected) .expression .value {
color: rgba(204, 204, 204, 0.6);
}
.vs-dark .monaco-workbench .monaco-tree-row:not(.selected) .expression .error {
.vs-dark .monaco-workbench .monaco-list-row:not(.selected) .expression .error {
color: #F48771;
}
.vs-dark .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.number {
.vs-dark .monaco-workbench .monaco-list-row:not(.selected) .expression .value.number {
color: #B5CEA8;
}
.hc-black .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.number {
.hc-black .monaco-workbench .monaco-list-row:not(.selected) .expression .value.number {
color: #89d185;
}
.hc-black .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.boolean {
.hc-black .monaco-workbench .monaco-list-row:not(.selected) .expression .value.boolean {
color: #75bdfe;
}
.hc-black .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.string {
.hc-black .monaco-workbench .monaco-list-row:not(.selected) .expression .value.string {
color: #f48771;
}
.vs-dark .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.boolean {
.vs-dark .monaco-workbench .monaco-list-row:not(.selected) .expression .value.boolean {
color: #4E94CE;
}
.vs-dark .monaco-workbench .monaco-tree-row:not(.selected) .expression .value.string {
.vs-dark .monaco-workbench .monaco-list-row:not(.selected) .expression .value.string {
color: #CE9178;
}
.hc-black .monaco-workbench .monaco-tree-row:not(.selected) .expression .error {
.hc-black .monaco-workbench .monaco-list-row:not(.selected) .expression .error {
color: #F48771;
}
/* Dark theme */
.vs-dark .monaco-workbench .monaco-tree-row:not(.selected) .expression .name {
.vs-dark .monaco-workbench .monaco-list-row:not(.selected) .expression .name {
color: #C586C0;
}
/* High Contrast Theming */
.hc-black .monaco-workbench .monaco-tree-row:not(.selected) .expression .name {
.hc-black .monaco-workbench .monaco-list-row:not(.selected) .expression .name {
color: inherit;
}
......
......@@ -32,26 +32,19 @@
.monaco-editor .debug-hover-widget .debug-hover-tree {
line-height: 18px;
max-height: 324px;
}
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-row > .content {
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-row .monaco-tl-contents {
user-select: text;
white-space: pre;
}
/* Disable tree highlight in debug hover tree. */
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) {
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-rows .monaco-list-row:hover:not(.highlighted):not(.selected):not(.focused) {
background-color: inherit;
}
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row {
cursor: default;
}
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children {
cursor: pointer;
}
.monaco-editor .debug-hover-widget pre {
margin-top: 0;
margin-bottom: 0;
......@@ -114,4 +107,4 @@
.monaco-editor.vs-dark .debugHoverHighlight,
.monaco-editor.hc-theme .debugHoverHighlight {
background-color: rgba(38, 79, 120, 0.25);
}
\ No newline at end of file
}
......@@ -9,6 +9,10 @@
height: 100%;
}
.debug-view-content {
height: 100%;
}
/* Actionbar actions */
.monaco-workbench .debug-action.configure {
......@@ -97,10 +101,6 @@
/* Debug viewlet trees */
.debug-viewlet .monaco-tree .monaco-tree-row > .content {
line-height: 22px;
}
.debug-viewlet .line-number {
background: rgba(136, 136, 136, 0.3);
border-radius: 2px;
......@@ -110,30 +110,13 @@
line-height: 20px;
}
.debug-viewlet .monaco-tree .monaco-tree-row.selected .line-number,
.debug-viewlet .monaco-list .monaco-list-row.selected .line-number,
.debug-viewlet .monaco-tree .monaco-tree-row.selected .thread > .state > .label,
.debug-viewlet .monaco-tree .monaco-tree-row.selected .session > .state > .label {
.debug-viewlet .monaco-list .monaco-list-row.selected .thread > .state > .label,
.debug-viewlet .monaco-list .monaco-list-row.selected .session > .state > .label {
background-color: #ffffff;
color: #666;
}
.debug-viewlet .monaco-tree .monaco-tree-row .content .monaco-action-bar {
visibility: hidden;
flex-shrink: 0;
}
.debug-viewlet .monaco-tree .monaco-tree-row .content .monaco-action-bar .action-label {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
.debug-viewlet .monaco-tree .monaco-tree-row:hover .content .monaco-action-bar,
.debug-viewlet .monaco-tree.focused .monaco-tree-row.focused .content .monaco-action-bar {
visibility: visible;
}
.debug-viewlet .disabled {
opacity: 0.35;
}
......@@ -235,7 +218,7 @@
display: none;
}
.debug-viewlet .debug-call-stack > .monaco-tree-row:not(.selected) .stack-frame > .file {
.debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file {
color: rgba(108, 108, 108, 0.8);
}
......@@ -244,7 +227,7 @@
text-overflow: ellipsis;
}
.vs-dark .debug-viewlet .debug-call-stack > .monaco-tree-row:not(.selected) .stack-frame > .file {
.vs-dark .debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file {
color: rgba(204, 204, 204, 0.6);
}
......@@ -262,7 +245,7 @@
opacity: 0.35;
}
.monaco-workbench .debug-viewlet .monaco-tree-row .expression {
.monaco-workbench .debug-viewlet .monaco-list-row .expression {
white-space: pre;
}
......@@ -292,7 +275,7 @@
100% { background-color: rgba(86, 156, 214, .2) }
}
.debug-viewlet .monaco-tree-row .expression .value.changed {
.debug-viewlet .monaco-list-row .expression .value.changed {
padding: 2px;
margin: 4px;
border-radius: 4px;
......@@ -338,17 +321,13 @@
background: url('add.svg') center center no-repeat;
}
.debug-viewlet .focused .monaco-tree-row.selected:not(.highlighted) > .content.actions .debug-action.add-watch-expression {
background: url("add-focus.svg") center center no-repeat;
}
.vs-dark .debug-viewlet .debug-action.add-watch-expression,
.vs-dark .debug-viewlet .debug-action.add-function-breakpoint,
.hc-black .debug-viewlet .debug-action.add-watch-expression {
background: url('add-inverse.svg') center center no-repeat;
}
.vs-dark .debug-viewlet .monaco-tree-row .expression .value.changed {
.vs-dark .debug-viewlet .monaco-list-row .expression .value.changed {
animation-name: debugViewletValueChanged;
}
......@@ -397,10 +376,6 @@
text-overflow: ellipsis
}
.debug-viewlet .debug-action.remove {
background: url('remove.svg') center center no-repeat;
}
.debug-viewlet .debug-action.remove-all {
background: url('remove-all.svg') center center no-repeat;
}
......@@ -409,16 +384,6 @@
background: url('breakpoints-activate.svg') center center no-repeat;
}
.debug-viewlet .focused .monaco-tree-row.selected:not(.highlighted) > .content.actions .debug-action.remove,
.vs-dark .debug-viewlet .focused .monaco-tree-row.selected:not(.highlighted) > .content.actions .debug-action.remove {
background: url("remove-focus.svg") center center no-repeat;
}
.vs-dark .debug-viewlet .debug-action.remove,
.hc-black .debug-viewlet .debug-action.remove {
background: url('remove-inverse.svg') center center no-repeat;
}
.vs-dark .debug-viewlet .debug-action.remove-all,
.hc-black .debug-viewlet .debug-action.remove-all {
background: url('remove-all-inverse.svg') center center no-repeat;
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.icon-white{fill:#fff;}</style></defs><title>remove-focus</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="iconBg"><path class="icon-white" d="M9.414,8l3.293,3.293-1.414,1.414L8,9.414,4.707,12.707,3.293,11.293,6.586,8,3.293,4.707,4.707,3.293,8,6.586l3.293-3.293,1.414,1.414Z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>remove</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M10.828,8l3.293,3.293-2.828,2.828L8,10.828,4.707,14.121,1.879,11.293,5.172,8,1.879,4.707,4.707,1.879,8,5.172l3.293-3.293,2.828,2.828Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M9.414,8l3.293,3.293-1.414,1.414L8,9.414,4.707,12.707,3.293,11.293,6.586,8,3.293,4.707,4.707,3.293,8,6.586l3.293-3.293,1.414,1.414Z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>remove</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M10.828,8l3.293,3.293-2.828,2.828L8,10.828,4.707,14.121,1.879,11.293,5.172,8,1.879,4.707,4.707,1.879,8,5.172l3.293-3.293,2.828,2.828Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M9.414,8l3.293,3.293-1.414,1.414L8,9.414,4.707,12.707,3.293,11.293,6.586,8,3.293,4.707,4.707,3.293,8,6.586l3.293-3.293,1.414,1.414Z"/></g></svg>
\ No newline at end of file
......@@ -99,7 +99,7 @@ export interface IReplElementSource {
export interface IExpressionContainer extends ITreeElement {
readonly hasChildren: boolean;
getChildren(): Promise<ReadonlyArray<IExpression>>;
getChildren(): Promise<IExpression[]>;
}
export interface IExpression extends IReplElement, IExpressionContainer {
......@@ -158,10 +158,10 @@ export interface IDebugSession extends ITreeElement {
rawUpdate(data: IRawModelUpdate): void;
getThread(threadId: number): IThread;
getAllThreads(): ReadonlyArray<IThread>;
getAllThreads(): IThread[];
clearThreads(removeThreads: boolean, reference?: number): void;
getReplElements(): ReadonlyArray<IReplElement>;
getReplElements(): IReplElement[];
removeReplExpressions(): void;
addReplExpression(stackFrame: IStackFrame, name: string): Promise<void>;
......@@ -286,7 +286,7 @@ export interface IStackFrame extends ITreeElement {
readonly frameId: number;
readonly range: IRange;
readonly source: Source;
getScopes(): Promise<ReadonlyArray<IScope>>;
getScopes(): Promise<IScope[]>;
getMostSpecificScopes(range: IRange): Promise<ReadonlyArray<IScope>>;
getSpecificSourceName(): string;
restart(): Promise<any>;
......@@ -380,13 +380,17 @@ export interface IViewModel extends ITreeElement {
onDidSelectExpression: Event<IExpression>;
}
export interface IEvaluate {
evaluate(session: IDebugSession, stackFrame: IStackFrame, context: string): Promise<void>;
}
export interface IDebugModel extends ITreeElement {
getSessions(includeInactive?: boolean): ReadonlyArray<IDebugSession>;
getSessions(includeInactive?: boolean): IDebugSession[];
getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): ReadonlyArray<IBreakpoint>;
areBreakpointsActivated(): boolean;
getFunctionBreakpoints(): ReadonlyArray<IFunctionBreakpoint>;
getExceptionBreakpoints(): ReadonlyArray<IExceptionBreakpoint>;
getWatchExpressions(): ReadonlyArray<IExpression>;
getWatchExpressions(): ReadonlyArray<IExpression & IEvaluate>;
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent>;
onDidChangeCallStack: Event<void>;
......
......@@ -18,7 +18,7 @@ export class ReplModel {
constructor(private session: IDebugSession) { }
getReplElements(): ReadonlyArray<IReplElement> {
getReplElements(): IReplElement[] {
return this.replElements;
}
......
......@@ -8,9 +8,7 @@ import * as lifecycle from 'vs/base/common/lifecycle';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import * as dom from 'vs/base/browser/dom';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ICancelableEvent, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
......@@ -19,37 +17,38 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/parts/debug/common/debug';
import { Expression } from 'vs/workbench/parts/debug/common/debugModel';
import { renderExpressionValue } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { VariablesDataSource, VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { getExactExpressionStartAndEnd } from 'vs/workbench/parts/debug/common/debugUtils';
import { AsyncDataTree, IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
const $ = dom.$;
const MAX_ELEMENTS_SHOWN = 18;
const MAX_TREE_HEIGHT = 324;
export class DebugHoverWidget implements IContentWidget {
public static readonly ID = 'debug.hoverWidget';
static readonly ID = 'debug.hoverWidget';
// editor.IContentWidget.allowEditorOverflow
public allowEditorOverflow = true;
allowEditorOverflow = true;
private _isVisible: boolean;
private domNode: HTMLElement;
private tree: WorkbenchTree;
private tree: AsyncDataTree<IExpression>;
private showAtPosition: Position;
private highlightDecorations: string[];
private complexValueContainer: HTMLElement;
private treeContainer: HTMLElement;
private complexValueTitle: HTMLElement;
private valueContainer: HTMLElement;
private stoleFocus: boolean;
private toDispose: lifecycle.IDisposable[];
private scrollbar: DomScrollableElement;
private dataSource: DebugHoverDataSource;
constructor(
private editor: ICodeEditor,
......@@ -68,17 +67,14 @@ export class DebugHoverWidget implements IContentWidget {
this.domNode = $('.debug-hover-widget');
this.complexValueContainer = dom.append(this.domNode, $('.complex-value'));
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
this.treeContainer.setAttribute('role', 'tree');
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
controller: this.instantiationService.createInstance(DebugHoverController, this.editor)
}, {
indentPixels: 6,
horizontalScrollMode: ScrollbarVisibility.Auto,
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
const treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
treeContainer.setAttribute('role', 'tree');
this.dataSource = new DebugHoverDataSource();
this.tree = new AsyncDataTree(treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)],
this.dataSource, {
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
accessibilityProvider: new DebugHoverAccessibilityProvider(),
});
this.valueContainer = $('.value');
......@@ -108,12 +104,7 @@ export class DebugHoverWidget implements IContentWidget {
}
private registerListeners(): void {
this.toDispose.push(this.tree.onDidExpandItem(() => {
this.layoutTree();
}));
this.toDispose.push(this.tree.onDidCollapseItem(() => {
this.layoutTree();
}));
this.toDispose.push(this.tree.onMouseClick(event => this.onMouseClick(event.element)));
this.toDispose.push(dom.addStandardDisposableListener(this.domNode, 'keydown', (e: IKeyboardEvent) => {
if (e.equals(KeyCode.Escape)) {
......@@ -127,19 +118,19 @@ export class DebugHoverWidget implements IContentWidget {
}));
}
public isVisible(): boolean {
isVisible(): boolean {
return this._isVisible;
}
public getId(): string {
getId(): string {
return DebugHoverWidget.ID;
}
public getDomNode(): HTMLElement {
getDomNode(): HTMLElement {
return this.domNode;
}
public showAt(range: Range, focus: boolean): Promise<void> {
showAt(range: Range, focus: boolean): Promise<void> {
const pos = range.getStartPosition();
const session = this.debugService.getViewModel().focusedSession;
......@@ -237,11 +228,12 @@ export class DebugHoverWidget implements IContentWidget {
this.valueContainer.hidden = true;
this.complexValueContainer.hidden = false;
this.dataSource.expression = expression;
return this.tree.setInput(expression).then(() => {
return this.tree.refresh(null).then(() => {
this.complexValueTitle.textContent = expression.value;
this.complexValueTitle.title = expression.value;
this.layoutTree();
this.tree.layout(MAX_TREE_HEIGHT);
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
if (focus) {
......@@ -251,26 +243,15 @@ export class DebugHoverWidget implements IContentWidget {
});
}
private layoutTree(): void {
const navigator = this.tree.getNavigator();
let visibleElementsCount = 0;
while (navigator.next()) {
visibleElementsCount++;
}
if (visibleElementsCount === 0) {
this.doShow(this.showAtPosition, this.tree.getInput(), false, true);
} else {
const height = Math.min(visibleElementsCount, MAX_ELEMENTS_SHOWN) * 18 + 10; // add 10 px for the horizontal scroll bar
if (this.treeContainer.clientHeight !== height) {
this.treeContainer.style.height = `${height}px`;
this.tree.layout();
}
private onMouseClick(element: IExpression): void {
if (element && element.hasChildren) {
this.tree.setFocus([]);
this.tree.setSelection([]);
this.editor.focus();
}
}
public hide(): void {
hide(): void {
if (!this._isVisible) {
return;
}
......@@ -284,7 +265,7 @@ export class DebugHoverWidget implements IContentWidget {
}
}
public getPosition(): IContentWidgetPosition {
getPosition(): IContentWidgetPosition {
return this._isVisible ? {
position: this.showAtPosition,
preference: [
......@@ -294,35 +275,40 @@ export class DebugHoverWidget implements IContentWidget {
} : null;
}
public dispose(): void {
dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
}
}
class DebugHoverController extends WorkbenchTreeController {
class DebugHoverAccessibilityProvider implements IAccessibilityProvider<IExpression> {
getAriaLabel(element: IExpression): string {
return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", element.name, element.value);
}
}
constructor(
private editor: ICodeEditor,
@IConfigurationService configurationService: IConfigurationService
) {
super({ openMode: OpenMode.SINGLE_CLICK }, configurationService);
class DebugHoverDataSource implements IDataSource<IExpression> {
expression: IExpression;
hasChildren(element: IExpression | null): boolean {
return element === null || element.hasChildren;
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean {
if (element.reference > 0) {
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
this.editor.focus();
getChildren(element: IExpression | null): Thenable<IExpression[]> {
if (element === null) {
element = this.expression;
}
return true;
return element.getChildren();
}
}
class VariablesHoverRenderer extends VariablesRenderer {
public getHeight(tree: ITree, element: any): number {
class DebugHoverDelegate implements IListVirtualDelegate<IExpression> {
getHeight(element: IExpression): number {
return 18;
}
getTemplateId(element: IExpression): string {
return VariablesRenderer.ID;
}
}
......@@ -793,7 +793,7 @@ export class DebugSession implements IDebugSession {
// REPL
getReplElements(): ReadonlyArray<IReplElement> {
getReplElements(): IReplElement[] {
return this.repl.getReplElements();
}
......
......@@ -5,10 +5,8 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { IDebugService, IStackFrame, IReplElement } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debug';
import { clipboard } from 'electron';
import { isWindows } from 'vs/base/common/platform';
......@@ -61,29 +59,6 @@ export class CopyAction extends Action {
}
const lineDelimiter = isWindows ? '\r\n' : '\n';
export class CopyAllAction extends Action {
static readonly ID = 'workbench.debug.action.copyAll';
static LABEL = nls.localize('copyAll', "Copy All");
constructor(id: string, label: string, private tree: ITree) {
super(id, label);
}
public run(): Promise<any> {
let text = '';
const navigator = this.tree.getNavigator();
// skip first navigator element - the root node
while (navigator.next()) {
if (text && text.length > 0 && text[text.length - 1] !== lineDelimiter) {
text += lineDelimiter;
}
text += (<IReplElement>navigator.current()).toString();
}
clipboard.writeText(removeAnsiEscapeCodes(text));
return Promise.resolve(undefined);
}
}
export class CopyStackTraceAction extends Action {
static readonly ID = 'workbench.action.debug.copyStackTrace';
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IAction, Action } from 'vs/base/common/actions';
import * as lifecycle from 'vs/base/common/lifecycle';
import { isFullWidthCharacter, removeAnsiEscapeCodes, endsWith } from 'vs/base/common/strings';
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import * as dom from 'vs/base/browser/dom';
import severity from 'vs/base/common/severity';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
import { IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug';
import { RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { ReplCollapseAllAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector';
import { handleANSIOutput } from 'vs/workbench/parts/debug/browser/debugANSIHandling';
import { ILabelService } from 'vs/platform/label/common/label';
import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession';
const $ = dom.$;
export class ReplExpressionsDataSource implements IDataSource {
public getId(tree: ITree, element: any): string {
return element.getId();
}
public hasChildren(tree: ITree, element: any): boolean {
return element instanceof DebugSession || (<IExpressionContainer>element).hasChildren;
}
public getChildren(tree: ITree, element: any): Promise<any> {
if (element instanceof DebugSession) {
return Promise.resolve(element.getReplElements());
}
if (element instanceof RawObjectReplElement) {
return element.getChildren();
}
if (element instanceof SimpleReplElement) {
return Promise.resolve(null);
}
return (<IExpression>element).getChildren();
}
public getParent(tree: ITree, element: any): Promise<any> {
return Promise.resolve(null);
}
}
interface IExpressionTemplateData {
input: HTMLElement;
output: HTMLElement;
value: HTMLElement;
annotation: HTMLElement;
}
interface ISimpleReplElementTemplateData {
container: HTMLElement;
value: HTMLElement;
source: HTMLElement;
getReplElementSource(): IReplElementSource;
toDispose: lifecycle.IDisposable[];
}
interface IRawObjectReplTemplateData {
container: HTMLElement;
expression: HTMLElement;
name: HTMLElement;
value: HTMLElement;
annotation: HTMLElement;
}
export class ReplExpressionsRenderer implements IRenderer {
private static readonly VARIABLE_TEMPLATE_ID = 'variable';
private static readonly EXPRESSION_TEMPLATE_ID = 'expressionRepl';
private static readonly SIMPLE_REPL_ELEMENT_TEMPLATE_ID = 'simpleReplElement';
private static readonly RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID = 'rawObject';
private static readonly LINE_HEIGHT_PX = 18;
private width: number;
private characterWidth: number;
private linkDetector: LinkDetector;
constructor(
@IEditorService private editorService: IEditorService,
@IInstantiationService private instantiationService: IInstantiationService,
@ILabelService private labelService: ILabelService
) {
this.linkDetector = this.instantiationService.createInstance(LinkDetector);
}
public getHeight(tree: ITree, element: any): number {
if (element instanceof Variable && (element.hasChildren || (element.name !== null))) {
return ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
if (element instanceof Expression && element.hasChildren) {
return 2 * ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
let availableWidth = this.width;
if (element instanceof SimpleReplElement && element.sourceData) {
availableWidth -= `${element.sourceData.source.name}:${element.sourceData.lineNumber}`.length * this.characterWidth;
}
return this.getHeightForString(element.value, availableWidth) + (element instanceof Expression ? this.getHeightForString(element.name, availableWidth) : 0);
}
private getHeightForString(s: string, availableWidth: number): number {
if (!s || !s.length || !availableWidth || availableWidth <= 0 || !this.characterWidth || this.characterWidth <= 0) {
return ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
// Last new line should be ignored since the repl elements are by design split by rows
if (endsWith(s, '\n')) {
s = s.substr(0, s.length - 1);
}
const lines = removeAnsiEscapeCodes(s).split('\n');
const numLines = lines.reduce((lineCount: number, line: string) => {
let lineLength = 0;
for (let i = 0; i < line.length; i++) {
lineLength += isFullWidthCharacter(line.charCodeAt(i)) ? 2 : 1;
}
return lineCount + Math.floor(lineLength * this.characterWidth / availableWidth);
}, lines.length);
return ReplExpressionsRenderer.LINE_HEIGHT_PX * numLines;
}
public setWidth(fullWidth: number, characterWidth: number): void {
this.width = fullWidth;
this.characterWidth = characterWidth;
}
public getTemplateId(tree: ITree, element: any): string {
if (element instanceof Variable && element.name) {
return ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID;
}
if (element instanceof Expression) {
return ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID;
}
if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) {
// Variable with no name is a top level variable which should be rendered like a repl element #17404
return ReplExpressionsRenderer.SIMPLE_REPL_ELEMENT_TEMPLATE_ID;
}
if (element instanceof RawObjectReplElement) {
return ReplExpressionsRenderer.RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID;
}
return null;
}
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID) {
let data: IVariableTemplateData = Object.create(null);
data.expression = dom.append(container, $('.expression'));
data.name = dom.append(data.expression, $('span.name'));
data.value = dom.append(data.expression, $('span.value'));
return data;
}
if (templateId === ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID) {
let data: IExpressionTemplateData = Object.create(null);
dom.addClass(container, 'input-output-pair');
data.input = dom.append(container, $('.input.expression'));
data.output = dom.append(container, $('.output.expression'));
data.value = dom.append(data.output, $('span.value'));
data.annotation = dom.append(data.output, $('span'));
return data;
}
if (templateId === ReplExpressionsRenderer.SIMPLE_REPL_ELEMENT_TEMPLATE_ID) {
let data: ISimpleReplElementTemplateData = Object.create(null);
dom.addClass(container, 'output');
let expression = dom.append(container, $('.output.expression.value-and-source'));
data.container = container;
data.value = dom.append(expression, $('span.value'));
data.source = dom.append(expression, $('.source'));
data.toDispose = [];
data.toDispose.push(dom.addDisposableListener(data.source, 'click', e => {
e.preventDefault();
e.stopPropagation();
const source = data.getReplElementSource();
if (source) {
source.source.openInEditor(this.editorService, {
startLineNumber: source.lineNumber,
startColumn: source.column,
endLineNumber: source.lineNumber,
endColumn: source.column
});
}
}));
return data;
}
if (templateId === ReplExpressionsRenderer.RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID) {
let data: IRawObjectReplTemplateData = Object.create(null);
dom.addClass(container, 'output');
data.container = container;
data.expression = dom.append(container, $('.output.expression'));
data.name = dom.append(data.expression, $('span.name'));
data.value = dom.append(data.expression, $('span.value'));
data.annotation = dom.append(data.expression, $('span'));
return data;
}
}
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
if (templateId === ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID) {
renderVariable(element, templateData, false);
} else if (templateId === ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID) {
this.renderExpression(tree, element, templateData);
} else if (templateId === ReplExpressionsRenderer.SIMPLE_REPL_ELEMENT_TEMPLATE_ID) {
this.renderSimpleReplElement(element, templateData);
} else if (templateId === ReplExpressionsRenderer.RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID) {
this.renderRawObjectReplElement(tree, element, templateData);
}
}
private renderExpression(tree: ITree, expression: IExpression, templateData: IExpressionTemplateData): void {
templateData.input.textContent = expression.name;
renderExpressionValue(expression, templateData.value, {
preserveWhitespace: !expression.hasChildren,
showHover: false,
colorize: true
});
if (expression.hasChildren) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.title = nls.localize('stateCapture', "Object state is captured from first evaluation");
}
}
private renderSimpleReplElement(element: SimpleReplElement, templateData: ISimpleReplElementTemplateData): void {
// value
dom.clearNode(templateData.value);
// Reset classes to clear ansi decorations since templates are reused
templateData.value.className = 'value';
let result = handleANSIOutput(element.value, this.linkDetector);
templateData.value.appendChild(result);
dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info');
templateData.source.textContent = element.sourceData ? `${element.sourceData.source.name}:${element.sourceData.lineNumber}` : '';
templateData.source.title = element.sourceData ? this.labelService.getUriLabel(element.sourceData.source.uri) : '';
templateData.getReplElementSource = () => element.sourceData;
}
private renderRawObjectReplElement(tree: ITree, element: RawObjectReplElement, templateData: IRawObjectReplTemplateData): void {
// key
if (element.name) {
templateData.name.textContent = `${element.name}:`;
} else {
templateData.name.textContent = '';
}
// value
renderExpressionValue(element.value, templateData.value, {
preserveWhitespace: true,
showHover: false
});
// annotation if any
if (element.annotation) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.title = element.annotation;
} else {
templateData.annotation.className = '';
templateData.annotation.title = '';
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
if (templateData.toDispose) {
lifecycle.dispose(templateData.toDispose);
}
}
}
export class ReplExpressionsAccessibilityProvider implements IAccessibilityProvider {
public getAriaLabel(tree: ITree, element: any): string {
if (element instanceof Variable) {
return nls.localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", (<Variable>element).name, (<Variable>element).value);
}
if (element instanceof Expression) {
return nls.localize('replExpressionAriaLabel', "Expression {0} has value {1}, read eval print loop, debug", (<Expression>element).name, (<Expression>element).value);
}
if (element instanceof SimpleReplElement) {
return nls.localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", (<SimpleReplElement>element).value);
}
if (element instanceof RawObjectReplElement) {
return nls.localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", (<RawObjectReplElement>element).name, (<RawObjectReplElement>element).value);
}
return null;
}
}
export class ReplExpressionsActionProvider implements IActionProvider {
constructor(private clearReplAction: Action, private toFocus: { focus(): void }) {
// noop
}
public hasActions(tree: ITree, element: any): boolean {
return false;
}
public getActions(tree: ITree, element: any): IAction[] {
return [];
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return true;
}
public getSecondaryActions(tree: ITree, element: any): IAction[] {
const actions: IAction[] = [];
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL));
actions.push(new CopyAllAction(CopyAllAction.ID, CopyAllAction.LABEL, tree));
actions.push(new ReplCollapseAllAction(tree, this.toFocus));
actions.push(new Separator());
actions.push(this.clearReplAction);
return actions;
}
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
return null;
}
}
export class ReplExpressionsController extends BaseDebugController {
private lastSelectedString: string | null = null;
public toFocusOnClick: { focus(): void };
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
const mouseEvent = <IMouseEvent>eventish;
// input and output are one element in the tree => we only expand if the user clicked on the output.
if ((element.reference > 0 || (element instanceof RawObjectReplElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) {
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
}
const selection = window.getSelection();
if (selection.type !== 'Range' || this.lastSelectedString === selection.toString()) {
// only focus the input if the user is not currently selecting.
this.toFocusOnClick.focus();
}
this.lastSelectedString = selection.toString();
return true;
}
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
return super.onContextMenu(tree, element, event, false);
}
}
......@@ -123,7 +123,7 @@ export class MockDebugService implements IDebugService {
}
export class MockSession implements IDebugSession {
getReplElements(): ReadonlyArray<IReplElement> {
getReplElements(): IReplElement[] {
return [];
}
......@@ -179,7 +179,7 @@ export class MockSession implements IDebugSession {
setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }) { }
getAllThreads(): ReadonlyArray<IThread> {
getAllThreads(): IThread[] {
return [];
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册