mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421 (#7404)
* Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421 * readd svgs
This commit is contained in:
@@ -91,7 +91,8 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer |
|
||||
}
|
||||
if (options.linkDetector) {
|
||||
container.textContent = '';
|
||||
container.appendChild(options.linkDetector.handleLinks(value));
|
||||
const session = (expressionOrValue instanceof ExpressionContainer) ? expressionOrValue.getSession() : undefined;
|
||||
container.appendChild(options.linkDetector.linkify(value, false, session ? session.root : undefined));
|
||||
} else {
|
||||
container.textContent = value;
|
||||
}
|
||||
|
||||
@@ -312,12 +312,12 @@ export class CallStackView extends ViewletPanel {
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: false }, actions, this.contextMenuService);
|
||||
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element,
|
||||
getActionsContext: () => element && element instanceof StackFrame ? element.getId() : undefined,
|
||||
onHide: () => dispose(actionsDisposable)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).regi
|
||||
|
||||
const debugCategory = nls.localize('debugCategory', "Debug");
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }), 'Debug: Start Debugging', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory);
|
||||
|
||||
@@ -7,12 +7,13 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { RGBA, Color } from 'vs/base/common/color';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
|
||||
/**
|
||||
* @param text The content to stylize.
|
||||
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
|
||||
*/
|
||||
export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService): HTMLSpanElement {
|
||||
export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, debugSession: IDebugSession): HTMLSpanElement {
|
||||
|
||||
const root: HTMLSpanElement = document.createElement('span');
|
||||
const textLength: number = text.length;
|
||||
@@ -53,7 +54,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme
|
||||
if (sequenceFound) {
|
||||
|
||||
// Flush buffer with previous styles.
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, customFgColor, customBgColor);
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor);
|
||||
|
||||
buffer = '';
|
||||
|
||||
@@ -99,7 +100,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme
|
||||
|
||||
// Flush remaining text buffer if not empty.
|
||||
if (buffer) {
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, customFgColor, customBgColor);
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor);
|
||||
}
|
||||
|
||||
return root;
|
||||
@@ -267,6 +268,7 @@ export function appendStylizedStringToContainer(
|
||||
stringContent: string,
|
||||
cssClasses: string[],
|
||||
linkDetector: LinkDetector,
|
||||
debugSession: IDebugSession,
|
||||
customTextColor?: RGBA,
|
||||
customBackgroundColor?: RGBA
|
||||
): void {
|
||||
@@ -274,7 +276,7 @@ export function appendStylizedStringToContainer(
|
||||
return;
|
||||
}
|
||||
|
||||
const container = linkDetector.handleLinks(stringContent);
|
||||
const container = linkDetector.linkify(stringContent, true, debugSession.root);
|
||||
|
||||
container.className = cssClasses.join(' ');
|
||||
if (customTextColor) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -60,9 +60,16 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect");
|
||||
export const STOP_LABEL = nls.localize('stop', "Stop");
|
||||
export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue");
|
||||
|
||||
function getThreadAndRun(accessor: ServicesAccessor, thread: IThread | undefined, run: (thread: IThread) => Promise<void>, ): void {
|
||||
function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefined, run: (thread: IThread) => Promise<void>): void {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
if (!(thread instanceof Thread)) {
|
||||
let thread: IThread | undefined;
|
||||
if (threadId) {
|
||||
debugService.getModel().getSessions().forEach(s => {
|
||||
if (!thread) {
|
||||
thread = s.getThread(threadId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
thread = debugService.getViewModel().focusedThread;
|
||||
if (!thread) {
|
||||
const focusedSession = debugService.getViewModel().focusedSession;
|
||||
@@ -76,36 +83,58 @@ function getThreadAndRun(accessor: ServicesAccessor, thread: IThread | undefined
|
||||
}
|
||||
}
|
||||
|
||||
function getFrame(debugService: IDebugService, frameId: string | undefined): IStackFrame | undefined {
|
||||
if (!frameId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sessions = debugService.getModel().getSessions();
|
||||
for (let s of sessions) {
|
||||
for (let t of s.getAllThreads()) {
|
||||
for (let sf of t.getCallStack()) {
|
||||
if (sf.getId() === frameId) {
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: COPY_STACK_TRACE_ID,
|
||||
handler: async (accessor: ServicesAccessor, _: string, frame: IStackFrame) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => {
|
||||
const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService);
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
const eol = textResourcePropertiesService.getEOL(frame.source.uri);
|
||||
await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol));
|
||||
let frame = getFrame(accessor.get(IDebugService), frameId);
|
||||
if (frame) {
|
||||
const eol = textResourcePropertiesService.getEOL(frame.source.uri);
|
||||
await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: REVERSE_CONTINUE_ID,
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.reverseContinue());
|
||||
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
|
||||
getThreadAndRun(accessor, threadId, thread => thread.reverseContinue());
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: STEP_BACK_ID,
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.stepBack());
|
||||
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
|
||||
getThreadAndRun(accessor, threadId, thread => thread.stepBack());
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: TERMINATE_THREAD_ID,
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.terminate());
|
||||
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
|
||||
getThreadAndRun(accessor, threadId, thread => thread.terminate());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -184,8 +213,8 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.F10,
|
||||
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.next());
|
||||
handler: (accessor: ServicesAccessor, threadId: number) => {
|
||||
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -194,8 +223,8 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging
|
||||
primary: KeyCode.F11,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.stepIn());
|
||||
handler: (accessor: ServicesAccessor, threadId: number) => {
|
||||
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -204,8 +233,8 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyCode.F11,
|
||||
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.stepOut());
|
||||
handler: (accessor: ServicesAccessor, threadId: number) => {
|
||||
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -214,28 +243,16 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.F6,
|
||||
when: CONTEXT_DEBUG_STATE.isEqualTo('running'),
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = debugService.getViewModel().focusedThread;
|
||||
if (!thread) {
|
||||
const session = debugService.getViewModel().focusedSession;
|
||||
const threads = session && session.getAllThreads();
|
||||
thread = threads && threads.length ? threads[0] : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
thread.pause().then(undefined, onUnexpectedError);
|
||||
}
|
||||
handler: (accessor: ServicesAccessor, threadId: number) => {
|
||||
getThreadAndRun(accessor, threadId, thread => thread.pause());
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: DISCONNECT_ID,
|
||||
handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => {
|
||||
handler: (accessor: ServicesAccessor, sessionId: string | undefined) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
session = session || debugService.getViewModel().focusedSession;
|
||||
const session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession;
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
@@ -245,16 +262,14 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => {
|
||||
handler: (accessor: ServicesAccessor, sessionId: string | undefined) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
if (!session || !session.getId) {
|
||||
session = debugService.getViewModel().focusedSession;
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const showSubSessions = configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
|
||||
// Stop should be sent to the root parent session
|
||||
while (!showSubSessions && session && session.parentSession) {
|
||||
session = session.parentSession;
|
||||
}
|
||||
let session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession;
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const showSubSessions = configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
|
||||
// Stop should be sent to the root parent session
|
||||
while (!showSubSessions && session && session.parentSession) {
|
||||
session = session.parentSession;
|
||||
}
|
||||
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
@@ -263,13 +278,12 @@ export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: RESTART_FRAME_ID,
|
||||
handler: (accessor: ServicesAccessor, _: string, frame: IStackFrame | undefined) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
if (!frame) {
|
||||
frame = debugService.getViewModel().focusedStackFrame;
|
||||
let frame = getFrame(debugService, frameId);
|
||||
if (frame) {
|
||||
await frame.restart();
|
||||
}
|
||||
|
||||
return frame!.restart();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -278,8 +292,8 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => {
|
||||
getThreadAndRun(accessor, thread, thread => thread.continue());
|
||||
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
|
||||
getThreadAndRun(accessor, threadId, thread => thread.continue());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDebugEditorContribution, IDebugService, State, EDITOR_CONTRIBUTION_ID, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDebugEditorContribution, IDebugService, State, EDITOR_CONTRIBUTION_ID, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -284,18 +284,18 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
} else if (sameUri) {
|
||||
focusedSf.thread.exceptionInfo.then(exceptionInfo => {
|
||||
if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) {
|
||||
this.showExceptionWidget(exceptionInfo, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn);
|
||||
this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private showExceptionWidget(exceptionInfo: IExceptionInfo, lineNumber: number, column: number): void {
|
||||
private showExceptionWidget(exceptionInfo: IExceptionInfo, debugSession: IDebugSession | undefined, lineNumber: number, column: number): void {
|
||||
if (this.exceptionWidget) {
|
||||
this.exceptionWidget.dispose();
|
||||
}
|
||||
|
||||
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo);
|
||||
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession);
|
||||
this.exceptionWidget.show({ lineNumber, column }, 0);
|
||||
this.editor.revealLine(lineNumber);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspac
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
@@ -70,7 +70,7 @@ export class DebugSession implements IDebugSession {
|
||||
options: IDebugSessionOptions | undefined,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@@ -86,6 +86,7 @@ export class DebugSession implements IDebugSession {
|
||||
} else {
|
||||
this.repl = (this.parentSession as DebugSession).repl;
|
||||
}
|
||||
this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire());
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
@@ -731,7 +732,7 @@ export class DebugSession implements IDebugSession {
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak) {
|
||||
this.windowService.focusWindow();
|
||||
this.hostService.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -967,25 +968,19 @@ export class DebugSession implements IDebugSession {
|
||||
|
||||
removeReplExpressions(): void {
|
||||
this.repl.removeReplExpressions();
|
||||
this._onDidChangeREPLElements.fire();
|
||||
}
|
||||
|
||||
async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
|
||||
const expressionEvaluated = this.repl.addReplExpression(this, stackFrame, name);
|
||||
this._onDidChangeREPLElements.fire();
|
||||
await expressionEvaluated;
|
||||
this._onDidChangeREPLElements.fire();
|
||||
await this.repl.addReplExpression(this, stackFrame, name);
|
||||
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
|
||||
variableSetEmitter.fire();
|
||||
}
|
||||
|
||||
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void {
|
||||
this.repl.appendToRepl(data, severity, source);
|
||||
this._onDidChangeREPLElements.fire();
|
||||
this.repl.appendToRepl(this, data, severity, source);
|
||||
}
|
||||
|
||||
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
|
||||
this.repl.logToRepl(this, sev, args, frame);
|
||||
this._onDidChangeREPLElements.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -27,7 +27,7 @@ export class ExceptionWidget extends ZoneWidget {
|
||||
|
||||
private _backgroundColor?: Color;
|
||||
|
||||
constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo,
|
||||
constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, private debugSession: IDebugSession | undefined,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
@@ -80,7 +80,7 @@ export class ExceptionWidget extends ZoneWidget {
|
||||
if (this.exceptionInfo.details && this.exceptionInfo.details.stackTrace) {
|
||||
let stackTrace = $('.stack-trace');
|
||||
const linkDetector = this.instantiationService.createInstance(LinkDetector);
|
||||
const linkedStackTrace = linkDetector.handleLinks(this.exceptionInfo.details.stackTrace);
|
||||
const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, true, this.debugSession ? this.debugSession.root : undefined);
|
||||
stackTrace.appendChild(linkedStackTrace);
|
||||
dom.append(container, stackTrace);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
|
||||
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
// @IWindowService windowService: IWindowService, // TODO@weinand TODO@isidorn cyclic dependency?
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
) {
|
||||
const connection = remoteAgentService.getConnection();
|
||||
|
||||
@@ -3,160 +3,199 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { isAbsolute } from 'vs/base/common/path';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import * as osPath from 'vs/base/common/path';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class LinkDetector {
|
||||
private static readonly MAX_LENGTH = 500;
|
||||
private static FILE_LOCATION_PATTERNS: RegExp[] = [
|
||||
// group 0: full path with line and column
|
||||
// group 1: full path without line and column, matched by `*.*` in the end to work only on paths with extensions in the end (s.t. node:10352 would not match)
|
||||
// group 2: drive letter on windows with trailing backslash or leading slash on mac/linux
|
||||
// group 3: line number, matched by (:(\d+))
|
||||
// group 4: column number, matched by ((?::(\d+))?)
|
||||
// e.g.: at Context.<anonymous> (c:\Users\someone\Desktop\mocha-runner\test\test.js:26:11)
|
||||
/(?![\(])(?:file:\/\/)?((?:([a-zA-Z]+:)|[^\(\)<>\'\"\[\]:\s]+)(?:[\\/][^\(\)<>\'\"\[\]:]*)?\.[a-zA-Z]+[0-9]*):(\d+)(?::(\d+))?/g
|
||||
];
|
||||
const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f';
|
||||
const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug');
|
||||
|
||||
const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/;
|
||||
const WIN_RELATIVE_PATH = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/;
|
||||
const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`);
|
||||
const POSIX_PATH = /((?:\~|\.)?(?:\/[\w\.-]*)+)/;
|
||||
const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/;
|
||||
const PATH_LINK_REGEX = new RegExp(`${platform.isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g');
|
||||
|
||||
const MAX_LENGTH = 2000;
|
||||
|
||||
type LinkKind = 'web' | 'path' | 'text';
|
||||
type LinkPart = {
|
||||
kind: LinkKind;
|
||||
value: string;
|
||||
captures: string[];
|
||||
};
|
||||
|
||||
export class LinkDetector {
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches and handles absolute file links in the string provided.
|
||||
* Returns <span/> element that wraps the processed string, where matched links are replaced by <a/> and unmatched parts are surrounded by <span/> elements.
|
||||
* Matches and handles web urls, absolute and relative file links in the string provided.
|
||||
* Returns <span/> element that wraps the processed string, where matched links are replaced by <a/>.
|
||||
* 'onclick' event is attached to all anchored links that opens them in the editor.
|
||||
* Each line of the text, even if it contains no links, is wrapped in a <span> and added as a child of the returned <span>.
|
||||
* When splitLines is true, each line of the text, even if it contains no links, is wrapped in a <span>
|
||||
* and added as a child of the returned <span>.
|
||||
*/
|
||||
handleLinks(text: string): HTMLElement {
|
||||
linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder): HTMLElement {
|
||||
if (splitLines) {
|
||||
const lines = text.split('\n');
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
lines[i] = lines[i] + '\n';
|
||||
}
|
||||
if (!lines[lines.length - 1]) {
|
||||
// Remove the last element ('') that split added.
|
||||
lines.pop();
|
||||
}
|
||||
const elements = lines.map(line => this.linkify(line, false, workspaceFolder));
|
||||
if (elements.length === 1) {
|
||||
// Do not wrap single line with extra span.
|
||||
return elements[0];
|
||||
}
|
||||
const container = document.createElement('span');
|
||||
elements.forEach(e => container.appendChild(e));
|
||||
return container;
|
||||
}
|
||||
|
||||
const container = document.createElement('span');
|
||||
|
||||
// Handle the text one line at a time
|
||||
const lines = text.split('\n');
|
||||
|
||||
if (strings.endsWith(text, '\n')) {
|
||||
// Remove the last element ('') that split added
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
|
||||
// Re-introduce the newline for every line except the last (unless the last line originally ended with a newline)
|
||||
if (i < lines.length - 1 || strings.endsWith(text, '\n')) {
|
||||
line += '\n';
|
||||
}
|
||||
|
||||
// Don't handle links for lines that are too long
|
||||
if (line.length > LinkDetector.MAX_LENGTH) {
|
||||
let span = document.createElement('span');
|
||||
span.textContent = line;
|
||||
container.appendChild(span);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineContainer = document.createElement('span');
|
||||
|
||||
for (let pattern of LinkDetector.FILE_LOCATION_PATTERNS) {
|
||||
// Reset the state of the pattern
|
||||
pattern = new RegExp(pattern);
|
||||
let lastMatchIndex = 0;
|
||||
|
||||
let match = pattern.exec(line);
|
||||
|
||||
while (match !== null) {
|
||||
let resource: uri | null = isAbsolute(match[1]) ? uri.file(match[1]) : null;
|
||||
|
||||
if (!resource) {
|
||||
match = pattern.exec(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const textBeforeLink = line.substring(lastMatchIndex, match.index);
|
||||
if (textBeforeLink) {
|
||||
// textBeforeLink may have matches for other patterns, so we run handleLinks on it before adding it.
|
||||
lineContainer.appendChild(this.handleLinks(textBeforeLink));
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.textContent = line.substr(match.index, match[0].length);
|
||||
link.title = isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link");
|
||||
lineContainer.appendChild(link);
|
||||
const lineNumber = Number(match[3]);
|
||||
const columnNumber = match[4] ? Number(match[4]) : undefined;
|
||||
link.onclick = (e) => this.onLinkClick(new StandardMouseEvent(e), resource!, lineNumber, columnNumber);
|
||||
link.onmousemove = (event) => link.classList.toggle('pointer', isMacintosh ? event.metaKey : event.ctrlKey);
|
||||
link.onmouseleave = () => link.classList.remove('pointer');
|
||||
|
||||
lastMatchIndex = pattern.lastIndex;
|
||||
const currentMatch = match;
|
||||
match = pattern.exec(line);
|
||||
|
||||
// Append last string part if no more link matches
|
||||
if (!match) {
|
||||
const textAfterLink = line.substr(currentMatch.index + currentMatch[0].length);
|
||||
if (textAfterLink) {
|
||||
// textAfterLink may have matches for other patterns, so we run handleLinks on it before adding it.
|
||||
lineContainer.appendChild(this.handleLinks(textAfterLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found any matches for this pattern, don't check any more patterns. Other parts of the line will be checked for the other patterns due to the recursion.
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 1) {
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
// Adding lineContainer to container would introduce an unnecessary surrounding span since there is only one line, so instead we just return lineContainer
|
||||
return lineContainer;
|
||||
} else {
|
||||
container.textContent = line;
|
||||
}
|
||||
} else {
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
// Add this line to the container
|
||||
container.appendChild(lineContainer);
|
||||
} else {
|
||||
// No links were added, but we still need to surround the unmodified line with a span before adding it
|
||||
let span = document.createElement('span');
|
||||
span.textContent = line;
|
||||
container.appendChild(span);
|
||||
for (const part of this.detectLinks(text)) {
|
||||
try {
|
||||
switch (part.kind) {
|
||||
case 'text':
|
||||
container.appendChild(document.createTextNode(part.value));
|
||||
break;
|
||||
case 'web':
|
||||
container.appendChild(this.createWebLink(part.value));
|
||||
break;
|
||||
case 'path':
|
||||
const path = part.captures[0];
|
||||
const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0;
|
||||
const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0;
|
||||
container.appendChild(this.createPathLink(part.value, path, lineNumber, columnNumber, workspaceFolder));
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
container.appendChild(document.createTextNode(part.value));
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private onLinkClick(event: IMouseEvent, resource: uri, line: number, column: number = 0): void {
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.type === 'Range') {
|
||||
return; // do not navigate when user is selecting
|
||||
}
|
||||
if (!(isMacintosh ? event.metaKey : event.ctrlKey)) {
|
||||
return;
|
||||
private createWebLink(url: string): Node {
|
||||
const link = this.createLink(url);
|
||||
const uri = URI.parse(url);
|
||||
this.decorateLink(link, () => this.openerService.open(uri));
|
||||
return link;
|
||||
}
|
||||
|
||||
private createPathLink(text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: IWorkspaceFolder | undefined): Node {
|
||||
if (path[0] === '/' && path[1] === '/') {
|
||||
// Most likely a url part which did not match, for example ftp://path.
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
this.editorService.openEditor({
|
||||
resource,
|
||||
options: {
|
||||
selection: {
|
||||
startLineNumber: line,
|
||||
startColumn: column
|
||||
}
|
||||
if (path[0] === '.') {
|
||||
if (!workspaceFolder) {
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
const uri = workspaceFolder.toResource(path);
|
||||
const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } };
|
||||
const link = this.createLink(text);
|
||||
this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options }));
|
||||
return link;
|
||||
}
|
||||
|
||||
if (path[0] === '~') {
|
||||
path = osPath.join(this.environmentService.userHome, path.substring(1));
|
||||
}
|
||||
|
||||
const link = this.createLink(text);
|
||||
const uri = URI.file(osPath.normalize(path));
|
||||
this.fileService.resolve(uri).then(stat => {
|
||||
if (stat.isDirectory) {
|
||||
return;
|
||||
}
|
||||
const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } };
|
||||
this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options }));
|
||||
});
|
||||
return link;
|
||||
}
|
||||
|
||||
private createLink(text: string): HTMLElement {
|
||||
const link = document.createElement('a');
|
||||
link.textContent = text;
|
||||
return link;
|
||||
}
|
||||
|
||||
private decorateLink(link: HTMLElement, onclick: () => void) {
|
||||
link.classList.add('link');
|
||||
link.title = platform.isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link");
|
||||
link.onmousemove = (event) => link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey);
|
||||
link.onmouseleave = () => link.classList.remove('pointer');
|
||||
link.onclick = (event) => {
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.type === 'Range') {
|
||||
return; // do not navigate when user is selecting
|
||||
}
|
||||
if (!(platform.isMacintosh ? event.metaKey : event.ctrlKey)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
onclick();
|
||||
};
|
||||
}
|
||||
|
||||
private detectLinks(text: string): LinkPart[] {
|
||||
if (text.length > MAX_LENGTH) {
|
||||
return [{ kind: 'text', value: text, captures: [] }];
|
||||
}
|
||||
|
||||
const regexes: RegExp[] = [WEB_LINK_REGEX, PATH_LINK_REGEX];
|
||||
const kinds: LinkKind[] = ['web', 'path'];
|
||||
const result: LinkPart[] = [];
|
||||
|
||||
const splitOne = (text: string, regexIndex: number) => {
|
||||
if (regexIndex >= regexes.length) {
|
||||
result.push({ value: text, kind: 'text', captures: [] });
|
||||
return;
|
||||
}
|
||||
const regex = regexes[regexIndex];
|
||||
let currentIndex = 0;
|
||||
let match;
|
||||
regex.lastIndex = 0;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const stringBeforeMatch = text.substring(currentIndex, match.index);
|
||||
if (stringBeforeMatch) {
|
||||
splitOne(stringBeforeMatch, regexIndex + 1);
|
||||
}
|
||||
const value = match[0];
|
||||
result.push({
|
||||
value: value,
|
||||
kind: kinds[regexIndex],
|
||||
captures: match.slice(1)
|
||||
});
|
||||
currentIndex = match.index + value.length;
|
||||
}
|
||||
const stringAfterMatches = text.substring(currentIndex);
|
||||
if (stringAfterMatches) {
|
||||
splitOne(stringAfterMatches, regexIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
splitOne(text, 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,11 +167,11 @@
|
||||
|
||||
/* Links */
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value a {
|
||||
.monaco-workbench .monaco-list-row .expression .value a.link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value a.pointer {
|
||||
.monaco-workbench .monaco-list-row .expression .value a.link.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -701,7 +701,7 @@ class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplElement, Fuz
|
||||
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, this.themeService);
|
||||
const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session);
|
||||
templateData.value.appendChild(result);
|
||||
|
||||
dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info');
|
||||
|
||||
@@ -101,6 +101,10 @@ export class ExpressionContainer implements IExpressionContainer {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getSession(): IDebugSession | undefined {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import { basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
const MAX_REPL_LENGTH = 10000;
|
||||
let topReplElementCounter = 0;
|
||||
|
||||
export class SimpleReplElement implements IReplElement {
|
||||
constructor(
|
||||
public session: IDebugSession,
|
||||
private id: string,
|
||||
public value: string,
|
||||
public severity: severity,
|
||||
@@ -107,6 +109,8 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl
|
||||
|
||||
export class ReplModel {
|
||||
private replElements: IReplElement[] = [];
|
||||
private readonly _onDidChangeElements = new Emitter<void>();
|
||||
readonly onDidChangeElements = this._onDidChangeElements.event;
|
||||
|
||||
getReplElements(): IReplElement[] {
|
||||
return this.replElements;
|
||||
@@ -119,12 +123,12 @@ export class ReplModel {
|
||||
this.addReplElement(result);
|
||||
}
|
||||
|
||||
appendToRepl(data: string | IExpression, sev: severity, source?: IReplElementSource): void {
|
||||
appendToRepl(session: IDebugSession, data: string | IExpression, sev: severity, source?: IReplElementSource): void {
|
||||
const clearAnsiSequence = '\u001b[2J';
|
||||
if (typeof data === 'string' && data.indexOf(clearAnsiSequence) >= 0) {
|
||||
// [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php
|
||||
this.removeReplExpressions();
|
||||
this.appendToRepl(nls.localize('consoleCleared', "Console was cleared"), severity.Ignore);
|
||||
this.appendToRepl(session, nls.localize('consoleCleared', "Console was cleared"), severity.Ignore);
|
||||
data = data.substr(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length);
|
||||
}
|
||||
|
||||
@@ -133,7 +137,7 @@ export class ReplModel {
|
||||
if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !endsWith(previousElement.value, '\n') && !endsWith(previousElement.value, '\r\n')) {
|
||||
previousElement.value += data;
|
||||
} else {
|
||||
const element = new SimpleReplElement(`topReplElement:${topReplElementCounter++}`, data, sev, source);
|
||||
const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source);
|
||||
this.addReplElement(element);
|
||||
}
|
||||
} else {
|
||||
@@ -149,6 +153,7 @@ export class ReplModel {
|
||||
if (this.replElements.length > MAX_REPL_LENGTH) {
|
||||
this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
|
||||
}
|
||||
this._onDidChangeElements.fire();
|
||||
}
|
||||
|
||||
logToRepl(session: IDebugSession, sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
|
||||
@@ -185,12 +190,12 @@ export class ReplModel {
|
||||
|
||||
// flush any existing simple values logged
|
||||
if (simpleVals.length) {
|
||||
this.appendToRepl(simpleVals.join(' '), sev, source);
|
||||
this.appendToRepl(session, simpleVals.join(' '), sev, source);
|
||||
simpleVals = [];
|
||||
}
|
||||
|
||||
// show object
|
||||
this.appendToRepl(new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
|
||||
this.appendToRepl(session, new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
|
||||
}
|
||||
|
||||
// string: watch out for % replacement directive
|
||||
@@ -220,13 +225,14 @@ export class ReplModel {
|
||||
// flush simple values
|
||||
// always append a new line for output coming from an extension such that separate logs go to separate lines #23695
|
||||
if (simpleVals.length) {
|
||||
this.appendToRepl(simpleVals.join(' ') + '\n', sev, source);
|
||||
this.appendToRepl(session, simpleVals.join(' ') + '\n', sev, source);
|
||||
}
|
||||
}
|
||||
|
||||
removeReplExpressions(): void {
|
||||
if (this.replElements.length > 0) {
|
||||
this.replElements = [];
|
||||
this._onDidChangeElements.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
|
||||
export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient {
|
||||
|
||||
constructor(
|
||||
@IMainProcessService readonly mainProcessService: IMainProcessService,
|
||||
@IWindowsService private readonly windowsService: IWindowsService
|
||||
@IElectronService private readonly electronService: IElectronService
|
||||
) {
|
||||
super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName));
|
||||
}
|
||||
|
||||
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
|
||||
// TODO@Isidor use debug IPC channel
|
||||
return (this.windowsService as WindowsService).openExtensionDevelopmentHostWindow(args, env);
|
||||
// TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060)
|
||||
return this.electronService.openExtensionDevelopmentHostWindow(args, env);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,14 @@ import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService';
|
||||
import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
suite('Debug - ANSI Handling', () => {
|
||||
|
||||
let model: DebugModel;
|
||||
let session: DebugSession;
|
||||
let linkDetector: LinkDetector;
|
||||
let themeService: IThemeService;
|
||||
|
||||
@@ -24,6 +29,9 @@ suite('Debug - ANSI Handling', () => {
|
||||
* Instantiate services for use by the functions being tested.
|
||||
*/
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
|
||||
|
||||
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
|
||||
linkDetector = instantiationService.createInstance(LinkDetector);
|
||||
|
||||
|
||||
@@ -464,11 +464,12 @@ suite('Debug - Model', () => {
|
||||
// Repl output
|
||||
|
||||
test('repl output', () => {
|
||||
const session = createMockSession(model);
|
||||
const repl = new ReplModel();
|
||||
repl.appendToRepl('first line\n', severity.Error);
|
||||
repl.appendToRepl('second line ', severity.Error);
|
||||
repl.appendToRepl('third line ', severity.Error);
|
||||
repl.appendToRepl('fourth line', severity.Error);
|
||||
repl.appendToRepl(session, 'first line\n', severity.Error);
|
||||
repl.appendToRepl(session, 'second line ', severity.Error);
|
||||
repl.appendToRepl(session, 'third line ', severity.Error);
|
||||
repl.appendToRepl(session, 'fourth line', severity.Error);
|
||||
|
||||
let elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 2);
|
||||
@@ -477,14 +478,14 @@ suite('Debug - Model', () => {
|
||||
assert.equal(elements[1].value, 'second line third line fourth line');
|
||||
assert.equal(elements[1].severity, severity.Error);
|
||||
|
||||
repl.appendToRepl('1', severity.Warning);
|
||||
repl.appendToRepl(session, '1', severity.Warning);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[2].value, '1');
|
||||
assert.equal(elements[2].severity, severity.Warning);
|
||||
|
||||
const keyValueObject = { 'key1': 2, 'key2': 'value' };
|
||||
repl.appendToRepl(new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
|
||||
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
|
||||
const element = <RawObjectReplElement>repl.getReplElements()[3];
|
||||
assert.equal(element.value, 'Object');
|
||||
assert.deepEqual(element.valueObj, keyValueObject);
|
||||
@@ -492,11 +493,11 @@ suite('Debug - Model', () => {
|
||||
repl.removeReplExpressions();
|
||||
assert.equal(repl.getReplElements().length, 0);
|
||||
|
||||
repl.appendToRepl('1\n', severity.Info);
|
||||
repl.appendToRepl('2', severity.Info);
|
||||
repl.appendToRepl('3\n4', severity.Info);
|
||||
repl.appendToRepl('5\n', severity.Info);
|
||||
repl.appendToRepl('6', severity.Info);
|
||||
repl.appendToRepl(session, '1\n', severity.Info);
|
||||
repl.appendToRepl(session, '2', severity.Info);
|
||||
repl.appendToRepl(session, '3\n4', severity.Info);
|
||||
repl.appendToRepl(session, '5\n', severity.Info);
|
||||
repl.appendToRepl(session, '6', severity.Info);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[0], '1\n');
|
||||
@@ -512,7 +513,11 @@ suite('Debug - Model', () => {
|
||||
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
|
||||
const child3 = createMockSession(model, 'child3', { parentSession: parent });
|
||||
|
||||
let parentChanges = 0;
|
||||
parent.onDidChangeReplElements(() => ++parentChanges);
|
||||
|
||||
parent.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 1);
|
||||
assert.equal(parent.getReplElements().length, 1);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 1);
|
||||
@@ -520,6 +525,7 @@ suite('Debug - Model', () => {
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
grandChild.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
@@ -527,6 +533,7 @@ suite('Debug - Model', () => {
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
child3.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
@@ -534,6 +541,7 @@ suite('Debug - Model', () => {
|
||||
assert.equal(child3.getReplElements().length, 1);
|
||||
|
||||
child1.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 1);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
|
||||
@@ -8,6 +8,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/
|
||||
import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
suite('Debug - Link Detector', () => {
|
||||
|
||||
@@ -22,19 +24,18 @@ suite('Debug - Link Detector', () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Assert that a given Element is an anchor element with an onClick event.
|
||||
* Assert that a given Element is an anchor element.
|
||||
*
|
||||
* @param element The Element to verify.
|
||||
*/
|
||||
function assertElementIsLink(element: Element) {
|
||||
assert(element instanceof HTMLAnchorElement);
|
||||
assert.notEqual(null, (element as HTMLAnchorElement).onclick);
|
||||
}
|
||||
|
||||
test('noLinks', () => {
|
||||
const input = 'I am a string';
|
||||
const expectedOutput = '<span>I am a string</span>';
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(0, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
@@ -44,7 +45,17 @@ suite('Debug - Link Detector', () => {
|
||||
test('trailingNewline', () => {
|
||||
const input = 'I am a string\n';
|
||||
const expectedOutput = '<span>I am a string\n</span>';
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(0, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal(expectedOutput, output.outerHTML);
|
||||
});
|
||||
|
||||
test('trailingNewlineSplit', () => {
|
||||
const input = 'I am a string\n';
|
||||
const expectedOutput = '<span>I am a string\n</span>';
|
||||
const output = linkDetector.linkify(input, true);
|
||||
|
||||
assert.equal(0, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
@@ -52,65 +63,71 @@ suite('Debug - Link Detector', () => {
|
||||
});
|
||||
|
||||
test('singleLineLink', () => {
|
||||
const input = isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34';
|
||||
const expectedOutput = /^<span><a title=".*">.*\/foo\/bar.js:12:34<\/a><\/span>$/;
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34';
|
||||
const expectedOutput = isWindows ? '<span><a>C:\\foo\\bar.js:12:34<\/a><\/span>' : '<span><a>/Users/foo/bar.js:12:34<\/a><\/span>';
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(1, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal('A', output.firstElementChild!.tagName);
|
||||
assert(expectedOutput.test(output.outerHTML));
|
||||
assert.equal(expectedOutput, output.outerHTML);
|
||||
assertElementIsLink(output.firstElementChild!);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild!.textContent);
|
||||
assert.equal(isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild!.textContent);
|
||||
});
|
||||
|
||||
test('relativeLink', () => {
|
||||
const input = '\./foo/bar.js';
|
||||
const expectedOutput = '<span>\./foo/bar.js</span>';
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(0, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal(expectedOutput, output.outerHTML);
|
||||
});
|
||||
|
||||
test('relativeLinkWithWorkspace', () => {
|
||||
const input = '\./foo/bar.js';
|
||||
const expectedOutput = /^<span><a class="link" title=".*">\.\/foo\/bar\.js<\/a><\/span>$/;
|
||||
const output = linkDetector.linkify(input, false, new WorkspaceFolder({ uri: URI.file('/path/to/workspace'), name: 'ws', index: 0 }));
|
||||
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert(expectedOutput.test(output.outerHTML));
|
||||
});
|
||||
|
||||
test('singleLineLinkAndText', function () {
|
||||
const input = isWindows ? 'The link: C:/foo/bar.js:12:34' : 'The link: /Users/foo/bar.js:12:34';
|
||||
const expectedOutput = /^<span><span>The link: <\/span><a title=".*">.*\/foo\/bar.js:12:34<\/a><\/span>$/;
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const expectedOutput = /^<span>The link: <a>.*\/foo\/bar.js:12:34<\/a><\/span>$/;
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(2, output.children.length);
|
||||
assert.equal(1, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal('SPAN', output.children[0].tagName);
|
||||
assert.equal('A', output.children[1].tagName);
|
||||
assert.equal('A', output.children[0].tagName);
|
||||
assert(expectedOutput.test(output.outerHTML));
|
||||
assertElementIsLink(output.children[1]);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].textContent);
|
||||
assertElementIsLink(output.children[0]);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[0].textContent);
|
||||
});
|
||||
|
||||
test('singleLineMultipleLinks', () => {
|
||||
const input = isWindows ? 'Here is a link C:/foo/bar.js:12:34 and here is another D:/boo/far.js:56:78' :
|
||||
'Here is a link /Users/foo/bar.js:12:34 and here is another /Users/boo/far.js:56:78';
|
||||
const expectedOutput = /^<span><span>Here is a link <\/span><a title=".*">.*\/foo\/bar.js:12:34<\/a><span> and here is another <\/span><a title=".*">.*\/boo\/far.js:56:78<\/a><\/span>$/;
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const expectedOutput = /^<span>Here is a link <a>.*\/foo\/bar.js:12:34<\/a> and here is another <a>.*\/boo\/far.js:56:78<\/a><\/span>$/;
|
||||
const output = linkDetector.linkify(input);
|
||||
|
||||
assert.equal(4, output.children.length);
|
||||
assert.equal(2, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal('SPAN', output.children[0].tagName);
|
||||
assert.equal('A', output.children[0].tagName);
|
||||
assert.equal('A', output.children[1].tagName);
|
||||
assert.equal('SPAN', output.children[2].tagName);
|
||||
assert.equal('A', output.children[3].tagName);
|
||||
assert(expectedOutput.test(output.outerHTML));
|
||||
assertElementIsLink(output.children[0]);
|
||||
assertElementIsLink(output.children[1]);
|
||||
assertElementIsLink(output.children[3]);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].textContent);
|
||||
assert.equal(isWindows ? 'D:/boo/far.js:56:78' : '/Users/boo/far.js:56:78', output.children[3].textContent);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[0].textContent);
|
||||
assert.equal(isWindows ? 'D:/boo/far.js:56:78' : '/Users/boo/far.js:56:78', output.children[1].textContent);
|
||||
});
|
||||
|
||||
test('multilineNoLinks', () => {
|
||||
const input = 'Line one\nLine two\nLine three';
|
||||
const expectedOutput = /^<span><span>Line one\n<\/span><span>Line two\n<\/span><span>Line three<\/span><\/span>$/;
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const output = linkDetector.linkify(input, true);
|
||||
|
||||
assert.equal(3, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
@@ -123,7 +140,7 @@ suite('Debug - Link Detector', () => {
|
||||
test('multilineTrailingNewline', () => {
|
||||
const input = 'I am a string\nAnd I am another\n';
|
||||
const expectedOutput = '<span><span>I am a string\n<\/span><span>And I am another\n<\/span><\/span>';
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const output = linkDetector.linkify(input, true);
|
||||
|
||||
assert.equal(2, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
@@ -135,19 +152,17 @@ suite('Debug - Link Detector', () => {
|
||||
test('multilineWithLinks', () => {
|
||||
const input = isWindows ? 'I have a link for you\nHere it is: C:/foo/bar.js:12:34\nCool, huh?' :
|
||||
'I have a link for you\nHere it is: /Users/foo/bar.js:12:34\nCool, huh?';
|
||||
const expectedOutput = /^<span><span>I have a link for you\n<\/span><span><span>Here it is: <\/span><a title=".*">.*\/foo\/bar.js:12:34<\/a><span>\n<\/span><\/span><span>Cool, huh\?<\/span><\/span>$/;
|
||||
const output = linkDetector.handleLinks(input);
|
||||
const expectedOutput = /^<span><span>I have a link for you\n<\/span><span>Here it is: <a>.*\/foo\/bar.js:12:34<\/a>\n<\/span><span>Cool, huh\?<\/span><\/span>$/;
|
||||
const output = linkDetector.linkify(input, true);
|
||||
|
||||
assert.equal(3, output.children.length);
|
||||
assert.equal('SPAN', output.tagName);
|
||||
assert.equal('SPAN', output.children[0].tagName);
|
||||
assert.equal('SPAN', output.children[1].tagName);
|
||||
assert.equal('SPAN', output.children[2].tagName);
|
||||
assert.equal('SPAN', output.children[1].children[0].tagName);
|
||||
assert.equal('A', output.children[1].children[1].tagName);
|
||||
assert.equal('SPAN', output.children[1].children[2].tagName);
|
||||
assert.equal('A', output.children[1].children[0].tagName);
|
||||
assert(expectedOutput.test(output.outerHTML));
|
||||
assertElementIsLink(output.children[1].children[1]);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].children[1].textContent);
|
||||
assertElementIsLink(output.children[1].children[0]);
|
||||
assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].children[0].textContent);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user