mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 02:48:30 -05:00
Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
This commit is contained in:
879
src/vs/workbench/contrib/debug/browser/repl.ts
Normal file
879
src/vs/workbench/contrib/debug/browser/repl.ts
Normal file
@@ -0,0 +1,879 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!vs/workbench/contrib/debug/browser/media/repl';
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IAction, IActionItem, Action } from 'vs/base/common/actions';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { HistoryNavigator } from 'vs/base/common/history';
|
||||
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
|
||||
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems';
|
||||
import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { Variable, Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { ReplCollapseAllAction, CopyAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const HISTORY_STORAGE_KEY = 'debug.repl.history';
|
||||
const IPrivateReplService = createDecorator<IPrivateReplService>('privateReplService');
|
||||
const DECORATION_KEY = 'replinputdecoration';
|
||||
|
||||
interface IPrivateReplService {
|
||||
_serviceBrand: any;
|
||||
acceptReplInput(): void;
|
||||
getVisibleContent(): string;
|
||||
selectSession(session?: IDebugSession): void;
|
||||
clearRepl(): void;
|
||||
}
|
||||
|
||||
function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
|
||||
tree.scrollTop = tree.scrollHeight - tree.renderHeight;
|
||||
}
|
||||
|
||||
const sessionsToIgnore = new Set<IDebugSession>();
|
||||
export class Repl extends Panel implements IPrivateReplService, IHistoryNavigationWidget {
|
||||
_serviceBrand: any;
|
||||
|
||||
private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show
|
||||
private static readonly REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
|
||||
|
||||
private history: HistoryNavigator<string>;
|
||||
private tree: WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
|
||||
private replDelegate: ReplDelegate;
|
||||
private container: HTMLElement;
|
||||
private replInput: CodeEditorWidget;
|
||||
private replInputContainer: HTMLElement;
|
||||
private dimension: dom.Dimension;
|
||||
private replInputHeight: number;
|
||||
private model: ITextModel;
|
||||
private historyNavigationEnablement: IContextKey<boolean>;
|
||||
private scopedInstantiationService: IInstantiationService;
|
||||
private replElementsChangeListener: IDisposable;
|
||||
private styleElement: HTMLStyleElement;
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
super(REPL_ID, telemetryService, themeService, storageService);
|
||||
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
|
||||
codeEditorService.registerDecorationType(DECORATION_KEY, {});
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.debugService.getViewModel().onDidFocusSession(session => {
|
||||
if (session) {
|
||||
sessionsToIgnore.delete(session);
|
||||
}
|
||||
this.selectSession();
|
||||
}));
|
||||
this._register(this.debugService.onWillNewSession(() => {
|
||||
// Need to listen to output events for sessions which are not yet fully initialised
|
||||
const input = this.tree.getInput();
|
||||
if (!input || input.state === State.Inactive) {
|
||||
this.selectSession();
|
||||
}
|
||||
this.updateTitleArea();
|
||||
}));
|
||||
this._register(this.themeService.onThemeChange(() => {
|
||||
if (this.isVisible()) {
|
||||
this.updateInputDecoration();
|
||||
}
|
||||
}));
|
||||
this._register(this.onDidChangeVisibility(visible => {
|
||||
if (!visible) {
|
||||
dispose(this.model);
|
||||
} else {
|
||||
this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true);
|
||||
this.replInput.setModel(this.model);
|
||||
this.updateInputDecoration();
|
||||
this.refreshReplElements(true);
|
||||
}
|
||||
}));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) {
|
||||
this.onDidFontChange();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get isReadonly(): boolean {
|
||||
// Do not allow to edit inactive sessions
|
||||
const session = this.tree.getInput();
|
||||
if (session && session.state !== State.Inactive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
showPreviousValue(): void {
|
||||
this.navigateHistory(true);
|
||||
}
|
||||
|
||||
showNextValue(): void {
|
||||
this.navigateHistory(false);
|
||||
}
|
||||
|
||||
private onDidFontChange(): void {
|
||||
if (this.styleElement) {
|
||||
const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
|
||||
const fontSize = debugConsole.fontSize;
|
||||
const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily;
|
||||
const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em';
|
||||
|
||||
// Set the font size, font family, line height and align the twistie to be centered
|
||||
this.styleElement.innerHTML = `
|
||||
.repl .repl-tree .expression {
|
||||
font-size: ${fontSize}px;
|
||||
font-family: ${fontFamily};
|
||||
}
|
||||
|
||||
.repl .repl-tree .expression {
|
||||
line-height: ${lineHeight};
|
||||
}
|
||||
|
||||
.repl .repl-tree .monaco-tl-twistie {
|
||||
background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px);
|
||||
}
|
||||
`;
|
||||
|
||||
this.tree.rerender();
|
||||
}
|
||||
}
|
||||
|
||||
private navigateHistory(previous: boolean): void {
|
||||
const historyInput = previous ? this.history.previous() : this.history.next();
|
||||
if (historyInput) {
|
||||
this.replInput.setValue(historyInput);
|
||||
aria.status(historyInput);
|
||||
// always leave cursor at the end.
|
||||
this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 });
|
||||
this.historyNavigationEnablement.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
selectSession(session?: IDebugSession): void {
|
||||
const treeInput = this.tree.getInput();
|
||||
if (!session) {
|
||||
const focusedSession = this.debugService.getViewModel().focusedSession;
|
||||
// If there is a focusedSession focus on that one, otherwise just show any other not ignored session
|
||||
if (focusedSession) {
|
||||
session = focusedSession;
|
||||
} else if (!treeInput || sessionsToIgnore.has(treeInput)) {
|
||||
session = first(this.debugService.getModel().getSessions(true), s => !sessionsToIgnore.has(s)) || undefined;
|
||||
}
|
||||
}
|
||||
if (session) {
|
||||
if (this.replElementsChangeListener) {
|
||||
this.replElementsChangeListener.dispose();
|
||||
}
|
||||
this.replElementsChangeListener = session.onDidChangeReplElements(() => {
|
||||
this.refreshReplElements(session!.getReplElements().length === 0);
|
||||
});
|
||||
|
||||
if (this.tree && treeInput !== session) {
|
||||
this.tree.setInput(session).then(() => revealLastElement(this.tree)).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
this.replInput.updateOptions({ readOnly: this.isReadonly });
|
||||
this.updateInputDecoration();
|
||||
}
|
||||
|
||||
clearRepl(): void {
|
||||
const session = this.tree.getInput();
|
||||
if (session) {
|
||||
session.removeReplExpressions();
|
||||
if (session.state === State.Inactive) {
|
||||
// Ignore inactive sessions which got cleared - so they are not shown any more
|
||||
sessionsToIgnore.add(session);
|
||||
this.selectSession();
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}
|
||||
this.replInput.focus();
|
||||
}
|
||||
|
||||
acceptReplInput(): void {
|
||||
const session = this.tree.getInput();
|
||||
if (session) {
|
||||
session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, this.replInput.getValue());
|
||||
revealLastElement(this.tree);
|
||||
this.history.add(this.replInput.getValue());
|
||||
this.replInput.setValue('');
|
||||
const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
if (shouldRelayout) {
|
||||
// Trigger a layout to shrink a potential multi line input
|
||||
this.layout(this.dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVisibleContent(): string {
|
||||
let text = '';
|
||||
const lineDelimiter = this.textResourcePropertiesService.getEOL(this.model.uri);
|
||||
const traverseAndAppend = (node: ITreeNode<IReplElement, FuzzyScore>) => {
|
||||
node.children.forEach(child => {
|
||||
text += child.element.toString() + lineDelimiter;
|
||||
if (!child.collapsed && child.children.length) {
|
||||
traverseAndAppend(child);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseAndAppend(this.tree.getNode());
|
||||
|
||||
return removeAnsiEscapeCodes(text);
|
||||
}
|
||||
|
||||
layout(dimension: dom.Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (this.tree) {
|
||||
const treeHeight = dimension.height - this.replInputHeight;
|
||||
this.tree.getHTMLElement().style.height = `${treeHeight}px`;
|
||||
this.tree.layout(treeHeight, dimension.width);
|
||||
}
|
||||
this.replInputContainer.style.height = `${this.replInputHeight}px`;
|
||||
|
||||
this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight });
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.replInput.focus();
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem | null {
|
||||
if (action.id === SelectReplAction.ID) {
|
||||
return this.instantiationService.createInstance(SelectReplActionItem, this.selectReplAction);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
if (this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s)).length > 1) {
|
||||
result.push(this.selectReplAction);
|
||||
}
|
||||
result.push(this.clearReplAction);
|
||||
|
||||
result.forEach(a => this._register(a));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Cached locals
|
||||
@memoize
|
||||
private get selectReplAction(): SelectReplAction {
|
||||
return this.scopedInstantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get clearReplAction(): ClearReplAction {
|
||||
return this.scopedInstantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get refreshScheduler(): RunOnceScheduler {
|
||||
return new RunOnceScheduler(() => {
|
||||
if (!this.tree.getInput()) {
|
||||
return;
|
||||
}
|
||||
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
|
||||
this.tree.updateChildren().then(() => {
|
||||
if (lastElementVisible) {
|
||||
// Only scroll if we were scrolled all the way down before tree refreshed #10486
|
||||
revealLastElement(this.tree);
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
}, Repl.REFRESH_DELAY);
|
||||
}
|
||||
|
||||
// --- Creation
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
this.container = dom.append(parent, $('.repl'));
|
||||
const treeContainer = dom.append(this.container, $('.repl-tree'));
|
||||
this.createReplInput(this.container);
|
||||
|
||||
this.replDelegate = new ReplDelegate(this.configurationService);
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, this.replDelegate, [
|
||||
this.instantiationService.createInstance(VariablesRenderer),
|
||||
this.instantiationService.createInstance(ReplSimpleElementsRenderer),
|
||||
new ReplExpressionsRenderer(),
|
||||
new ReplRawObjectsRenderer()
|
||||
], new ReplDataSource(), {
|
||||
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
|
||||
accessibilityProvider: new ReplAccessibilityProvider(),
|
||||
identityProvider: { getId: element => (<IReplElement>element).getId() },
|
||||
mouseSupport: false,
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e },
|
||||
horizontalScrolling: false,
|
||||
setRowLineHeight: false,
|
||||
supportDynamicHeights: true
|
||||
}) as WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
|
||||
|
||||
this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
||||
// Make sure to select the session if debugging is already active
|
||||
this.selectSession();
|
||||
this.styleElement = dom.createStyleSheet(this.container);
|
||||
this.onDidFontChange();
|
||||
}
|
||||
|
||||
private createReplInput(container: HTMLElement): void {
|
||||
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
|
||||
|
||||
const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this });
|
||||
this.historyNavigationEnablement = historyNavigationEnablement;
|
||||
this._register(scopedContextKeyService);
|
||||
CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true);
|
||||
|
||||
this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, scopedContextKeyService], [IPrivateReplService, this]));
|
||||
const options = getSimpleEditorOptions();
|
||||
options.readOnly = true;
|
||||
this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions());
|
||||
|
||||
CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, {
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
|
||||
// Disable history navigation because up and down are used to navigate through the suggest widget
|
||||
this.historyNavigationEnablement.set(false);
|
||||
|
||||
const focusedSession = this.debugService.getViewModel().focusedSession;
|
||||
if (focusedSession && focusedSession.capabilities.supportsCompletionsRequest) {
|
||||
|
||||
const model = this.replInput.getModel();
|
||||
if (model) {
|
||||
const word = model.getWordAtPosition(position);
|
||||
const overwriteBefore = word ? word.word.length : 0;
|
||||
const text = model.getLineContent(position.lineNumber);
|
||||
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
|
||||
|
||||
return focusedSession.completions(frameId, text, position, overwriteBefore).then(suggestions => {
|
||||
return { suggestions };
|
||||
}, err => {
|
||||
return { suggestions: [] };
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve({ suggestions: [] });
|
||||
}
|
||||
});
|
||||
|
||||
this._register(this.replInput.onDidScrollChange(e => {
|
||||
if (!e.scrollHeightChanged) {
|
||||
return;
|
||||
}
|
||||
this.replInputHeight = Math.max(Repl.REPL_INPUT_INITIAL_HEIGHT, Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height));
|
||||
this.layout(this.dimension);
|
||||
}));
|
||||
this._register(this.replInput.onDidChangeModelContent(() => {
|
||||
const model = this.replInput.getModel();
|
||||
this.historyNavigationEnablement.set(!!model && model.getValue() === '');
|
||||
}));
|
||||
// We add the input decoration only when the focus is in the input #61126
|
||||
this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration()));
|
||||
this._register(this.replInput.onDidBlurEditorText(() => this.updateInputDecoration()));
|
||||
|
||||
this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus')));
|
||||
this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus')));
|
||||
}
|
||||
|
||||
private onContextMenu(e: ITreeContextMenuEvent<IReplElement>): void {
|
||||
const actions: IAction[] = [];
|
||||
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL, this.clipboardService));
|
||||
actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, () => {
|
||||
this.clipboardService.writeText(this.getVisibleContent());
|
||||
return Promise.resolve(undefined);
|
||||
}));
|
||||
actions.push(new ReplCollapseAllAction(this.tree, this.replInput));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.clearReplAction);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => e.element
|
||||
});
|
||||
}
|
||||
|
||||
// --- Update
|
||||
|
||||
private refreshReplElements(noDelay: boolean): void {
|
||||
if (this.tree && this.isVisible()) {
|
||||
if (this.refreshScheduler.isScheduled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refreshScheduler.schedule(noDelay ? 0 : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private updateInputDecoration(): void {
|
||||
if (!this.replInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IDecorationOptions[] = [];
|
||||
if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) {
|
||||
const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme());
|
||||
decorations.push({
|
||||
range: {
|
||||
startLineNumber: 0,
|
||||
endLineNumber: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 1
|
||||
},
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions"),
|
||||
color: transparentForeground ? transparentForeground.toString() : undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.replInput.setDecorations(DECORATION_KEY, decorations);
|
||||
}
|
||||
|
||||
protected saveState(): void {
|
||||
const replHistory = this.history.getHistory();
|
||||
if (replHistory.length) {
|
||||
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.replInput.dispose();
|
||||
if (this.replElementsChangeListener) {
|
||||
this.replElementsChangeListener.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Repl tree
|
||||
|
||||
interface IExpressionTemplateData {
|
||||
input: HTMLElement;
|
||||
output: HTMLElement;
|
||||
value: HTMLElement;
|
||||
annotation: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
interface ISimpleReplElementTemplateData {
|
||||
container: HTMLElement;
|
||||
value: HTMLElement;
|
||||
source: HTMLElement;
|
||||
getReplElementSource(): IReplElementSource | undefined;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
interface IRawObjectReplTemplateData {
|
||||
container: HTMLElement;
|
||||
expression: HTMLElement;
|
||||
name: HTMLElement;
|
||||
value: HTMLElement;
|
||||
annotation: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
class ReplExpressionsRenderer implements ITreeRenderer<Expression, FuzzyScore, IExpressionTemplateData> {
|
||||
static readonly ID = 'expressionRepl';
|
||||
|
||||
get templateId(): string {
|
||||
return ReplExpressionsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IExpressionTemplateData {
|
||||
dom.addClass(container, 'input-output-pair');
|
||||
const input = dom.append(container, $('.input.expression'));
|
||||
const label = new HighlightedLabel(input, false);
|
||||
const output = dom.append(container, $('.output.expression'));
|
||||
const value = dom.append(output, $('span.value'));
|
||||
const annotation = dom.append(output, $('span'));
|
||||
|
||||
return { input, label, output, value, annotation };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<Expression, FuzzyScore>, index: number, templateData: IExpressionTemplateData): void {
|
||||
const expression = element.element;
|
||||
templateData.label.set(expression.name, createMatches(element.filterData));
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IExpressionTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplElement, FuzzyScore, ISimpleReplElementTemplateData> {
|
||||
static readonly ID = 'simpleReplElement';
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
get templateId(): string {
|
||||
return ReplSimpleElementsRenderer.ID;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get linkDetector(): LinkDetector {
|
||||
return this.instantiationService.createInstance(LinkDetector);
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ISimpleReplElementTemplateData {
|
||||
const data: ISimpleReplElementTemplateData = Object.create(null);
|
||||
dom.addClass(container, 'output');
|
||||
const 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;
|
||||
}
|
||||
|
||||
renderElement({ element }: ITreeNode<SimpleReplElement, FuzzyScore>, index: number, templateData: ISimpleReplElementTemplateData): void {
|
||||
// value
|
||||
dom.clearNode(templateData.value);
|
||||
// Reset classes to clear ansi decorations since templates are reused
|
||||
templateData.value.className = 'value';
|
||||
const 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;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISimpleReplElementTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class ReplRawObjectsRenderer implements ITreeRenderer<RawObjectReplElement, FuzzyScore, IRawObjectReplTemplateData> {
|
||||
static readonly ID = 'rawObject';
|
||||
|
||||
get templateId(): string {
|
||||
return ReplRawObjectsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IRawObjectReplTemplateData {
|
||||
dom.addClass(container, 'output');
|
||||
|
||||
const expression = dom.append(container, $('.output.expression'));
|
||||
const name = dom.append(expression, $('span.name'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
const value = dom.append(expression, $('span.value'));
|
||||
const annotation = dom.append(expression, $('span'));
|
||||
|
||||
return { container, expression, name, label, value, annotation };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<RawObjectReplElement, FuzzyScore>, index: number, templateData: IRawObjectReplTemplateData): void {
|
||||
// key
|
||||
const element = node.element;
|
||||
templateData.label.set(element.name ? `${element.name}:` : '', createMatches(node.filterData));
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IRawObjectReplTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ReplDelegate implements IListVirtualDelegate<IReplElement> {
|
||||
|
||||
constructor(private configurationService: IConfigurationService) { }
|
||||
|
||||
getHeight(element: IReplElement): number {
|
||||
// Give approximate heights. Repl has dynamic height so the tree will measure the actual height on its own.
|
||||
const fontSize = this.configurationService.getValue<IDebugConfiguration>('debug').console.fontSize;
|
||||
if (element instanceof Expression && element.hasChildren) {
|
||||
return Math.ceil(2 * 1.4 * fontSize);
|
||||
}
|
||||
|
||||
return Math.ceil(1.4 * fontSize);
|
||||
}
|
||||
|
||||
getTemplateId(element: IReplElement): string {
|
||||
if (element instanceof Variable && element.name) {
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
if (element instanceof Expression) {
|
||||
return ReplExpressionsRenderer.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 ReplSimpleElementsRenderer.ID;
|
||||
}
|
||||
|
||||
return ReplRawObjectsRenderer.ID;
|
||||
}
|
||||
|
||||
hasDynamicHeight?(element: IReplElement): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isDebugSession(obj: any): obj is IDebugSession {
|
||||
return typeof obj.getReplElements === 'function';
|
||||
}
|
||||
|
||||
class ReplDataSource implements IAsyncDataSource<IDebugSession, IReplElement> {
|
||||
|
||||
hasChildren(element: IReplElement | IDebugSession): boolean {
|
||||
if (isDebugSession(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!(<IExpressionContainer>element).hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: IReplElement | IDebugSession): Promise<IReplElement[]> {
|
||||
if (isDebugSession(element)) {
|
||||
return Promise.resolve(element.getReplElements());
|
||||
}
|
||||
if (element instanceof RawObjectReplElement) {
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
return (<IExpression>element).getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
class ReplAccessibilityProvider implements IAccessibilityProvider<IReplElement> {
|
||||
getAriaLabel(element: IReplElement): string {
|
||||
if (element instanceof Variable) {
|
||||
return nls.localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
if (element instanceof Expression) {
|
||||
return nls.localize('replExpressionAriaLabel', "Expression {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
if (element instanceof SimpleReplElement) {
|
||||
return nls.localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", element.value);
|
||||
}
|
||||
if (element instanceof RawObjectReplElement) {
|
||||
return nls.localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Repl actions and commands
|
||||
|
||||
class AcceptReplInputAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.acceptInput',
|
||||
label: nls.localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"),
|
||||
alias: 'REPL Accept Input',
|
||||
precondition: CONTEXT_IN_DEBUG_REPL,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyCode.Enter,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
|
||||
SuggestController.get(editor).acceptSelectedSuggestion();
|
||||
accessor.get(IPrivateReplService).acceptReplInput();
|
||||
}
|
||||
}
|
||||
|
||||
class ReplCopyAllAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.copyAll',
|
||||
label: nls.localize('actions.repl.copyAll', "Debug: Console Copy All"),
|
||||
alias: 'Debug Console Copy All',
|
||||
precondition: CONTEXT_IN_DEBUG_REPL,
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
clipboardService.writeText(accessor.get(IPrivateReplService).getVisibleContent());
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(AcceptReplInputAction);
|
||||
registerEditorAction(ReplCopyAllAction);
|
||||
|
||||
class SelectReplActionItem extends FocusSessionActionItem {
|
||||
protected getSessions(): ReadonlyArray<IDebugSession> {
|
||||
return this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s));
|
||||
}
|
||||
}
|
||||
|
||||
class SelectReplAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.debug.selectRepl';
|
||||
static LABEL = nls.localize('selectRepl', "Select Debug Console");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IPrivateReplService private readonly replService: IPrivateReplService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(sessionName: string): Promise<any> {
|
||||
const session = this.debugService.getModel().getSessions(true).filter(p => p.getLabel() === sessionName).pop();
|
||||
// If session is already the focused session we need to manualy update the tree since view model will not send a focused change event
|
||||
if (session && session.state !== State.Inactive && session !== this.debugService.getViewModel().focusedSession) {
|
||||
this.debugService.focusStackFrame(undefined, undefined, session, true);
|
||||
} else {
|
||||
this.replService.selectSession(session);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearReplAction extends Action {
|
||||
static readonly ID = 'workbench.debug.panel.action.clearReplAction';
|
||||
static LABEL = nls.localize('clearRepl', "Clear Console");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IPanelService private readonly panelService: IPanelService
|
||||
) {
|
||||
super(id, label, 'debug-action clear-repl');
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
const repl = <Repl>this.panelService.openPanel(REPL_ID);
|
||||
repl.clearRepl();
|
||||
aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared"));
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user