Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): improve jump to error when runing in vscode #4012

Merged
43 changes: 38 additions & 5 deletions apps/vscode-wing/src/console/console-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
ViewColumn,
Uri,
ExtensionContext,
Position,
Range,
} from "vscode";

import {
Expand Down Expand Up @@ -156,6 +158,42 @@ export const createConsoleManager = (
logger.appendLine(err);
},
});

instance.client.onOpenFileInEditor({
onData: async (data) => {
const path = data?.path;
const line = data?.line - 1 || 0;
const column = data?.column || 0;

const openEditor = window.visibleTextEditors.find((editor) => {
return editor.document.uri.fsPath === path;
});

if (!openEditor || !openEditor.viewColumn) {
await commands.executeCommand(
"vscode.open",
Uri.file(path),
new Position(line, column)
);
return;
}

await commands.executeCommand("workbench.action.focusFirstEditorGroup");
for (let i = 0; i < openEditor.viewColumn - 1; i++) {
await commands.executeCommand("workbench.action.focusNextGroup");
}
await window.showTextDocument(openEditor.document, {
selection: new Range(
new Position(line, column),
new Position(line, column)
),
});
},
onError: (err) => {
logger.appendLine(err);
},
});

instances[instance.id] = instance;

await setActiveInstance(instance.id);
Expand Down Expand Up @@ -244,11 +282,6 @@ export const createConsoleManager = (
});

context.subscriptions.push(webviewPanel, explorerView, testsExplorerView);

const node = await resourcesExplorer.getChildren();
if (node[0]?.id) {
await explorerView?.reveal(new ResourceItem(node[0].id));
}
activeInstanceId = instanceId;
};

Expand Down
9 changes: 9 additions & 0 deletions apps/vscode-wing/src/console/services/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface Client {
runAllTests: () => Promise<any>;
listResources: () => Promise<ExplorerItem>;
onInvalidateQuery: (options: SubscriptionOptions) => void;
onOpenFileInEditor: (options: SubscriptionOptions) => void;
close: () => void;
}

Expand Down Expand Up @@ -98,6 +99,13 @@ export const createClient = (host: string): Client => {
return client["app.invalidateQuery"].subscribe(undefined, options);
};

const onOpenFileInEditor = (options: SubscriptionOptions) => {
return client["app.openFileInEditorSubscription"].subscribe(
undefined,
options
);
};

const close = () => {
wsClient.close();
};
Expand All @@ -112,6 +120,7 @@ export const createClient = (host: string): Client => {
runAllTests,
listResources,
onInvalidateQuery,
onOpenFileInEditor,
close,
};
};
1 change: 1 addition & 0 deletions apps/wing-console/console/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type {
TestsStateManager,
TestStatus,
TestItem,
FileLink,
} from "./utils/createRouter.js";
export type { Trace, State } from "./types.js";
export type { LogInterface } from "./utils/LogInterface.js";
Expand Down
31 changes: 30 additions & 1 deletion apps/wing-console/console/server/src/router/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
NodeConnection,
ConstructTreeNodeMap,
} from "../utils/constructTreeNodeMap.js";
import { createProcedure, createRouter } from "../utils/createRouter.js";
import {
FileLink,
createProcedure,
createRouter,
} from "../utils/createRouter.js";
import {
isTermsAccepted,
acceptTerms,
Expand Down Expand Up @@ -561,6 +565,31 @@ export const createAppRouter = () => {
.mutation(async ({ ctx, input }) => {
await ctx.hostUtils?.openExternal(input.url);
}),
"app.openFileInEditor": createProcedure
.input(
z.object({
path: z.string(),
line: z.number().optional(),
column: z.number().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
ctx.emitter.emit("openFileInEditor", {
path: input.path,
line: input.line,
column: input.column,
});
}),
"app.openFileInEditorSubscription": createProcedure.subscription(
({ ctx }) => {
return observable<FileLink>((emit) => {
ctx.emitter.on("openFileInEditor", emit.next);
return () => {
ctx.emitter.off("openFileInEditor", emit.next);
};
});
},
),
});

return { router };
Expand Down
7 changes: 7 additions & 0 deletions apps/wing-console/console/server/src/utils/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export interface TestsStateManager {
setTest: (test: TestItem) => void;
}

export interface FileLink {
path: string;
line?: number;
column?: number;
}

export interface RouterContext {
simulator(): Promise<testing.Simulator>;
appDetails(): Promise<{
Expand All @@ -77,6 +83,7 @@ export interface RouterContext {
emitter: Emittery<{
invalidateQuery: string | undefined;
trace: Trace;
openFileInEditor: FileLink;
}>;
appState(): State;
logger: ConsoleLogger;
Expand Down
53 changes: 23 additions & 30 deletions apps/wing-console/console/ui/src/features/console-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import classNames from "classnames";
import throttle from "lodash.throttle";
import { Fragment, useEffect, useRef, useState } from "react";

import {
OpenFileInEditorButton,
useFileLink,
} from "../shared/use-file-link.js";
import { createHtmlLink } from "../shared/use-file-link.js";

const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
hour: "2-digit",
minute: "2-digit",
Expand All @@ -23,21 +29,6 @@ interface LogEntryProps {
showIcons?: boolean;
}

export const formatAbsolutePaths = (
error: string,
className: string,
expanded: boolean = false,
) => {
return error
.replaceAll(
/\B((?:[a-z]:)?[/\\]\S+):(\d+):(\d+)/gi,
(match, path, line, column) => {
return `<a class="${className}" onclick="event.stopPropagation()" href="vscode://file/${path}:${line}:${column}">${match}</a>`;
},
)
.replaceAll(/(\r\n|\n|\r)/gm, expanded ? "<br />" : "\n");
};

const LogEntryRow = ({
log,
showIcons = true,
Expand Down Expand Up @@ -77,7 +68,7 @@ const LogEntryRow = ({
if (expandableRef.current === null) {
return;
}
const html = formatAbsolutePaths(
const html = createHtmlLink(
log.message,
"text-sky-500 underline hover:text-sky-800",
expanded,
Expand Down Expand Up @@ -154,20 +145,22 @@ const LogEntryRow = ({
/>
</button>
)}
<span
className={classNames(
log.ctx?.messageType === "info" && theme.text2,
log.ctx?.messageType === "title" && theme.text1,
log.ctx?.messageType === "success" &&
"text-green-700 dark:text-green-500",
log.ctx?.messageType === "fail" && "text-red-500",
log.ctx?.messageType === "summary" && [
"font-medium",
theme.text1,
],
)}
ref={expandableRef}
/>
<OpenFileInEditorButton>
<span
className={classNames(
log.ctx?.messageType === "info" && theme.text2,
log.ctx?.messageType === "title" && theme.text1,
log.ctx?.messageType === "success" &&
"text-green-700 dark:text-green-500",
log.ctx?.messageType === "fail" && "text-red-500",
log.ctx?.messageType === "summary" && [
"font-medium",
theme.text1,
],
)}
ref={expandableRef}
/>
</OpenFileInEditorButton>
</div>

{onResourceClick && (
Expand Down
49 changes: 49 additions & 0 deletions apps/wing-console/console/ui/src/shared/use-file-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { PropsWithChildren } from "react";

import { trpc } from "../services/trpc.js";

export const createHtmlLink = (
error: string,
className: string,
expanded: boolean = false,
) => {
return error
.replaceAll(
/\B((?:[a-z]:)?[/\\]\S+):(\d+):(\d+)/gi,
(match, path, line, column) => {
const link = `vscode://file/${path}:${line}:${column}`;
return `<a class="${className}" href="${link}" path="${path}" line="${line}" column="${column}" >${match}</a>`;
},
)
.replaceAll(/(\r\n|\n|\r)/gm, expanded ? "<br />" : "\n");
};

export const OpenFileInEditorButton = ({ children }: PropsWithChildren) => {
const openFileInEditor = trpc["app.openFileInEditor"].useMutation();

return (
<button
className="appearance-none text-left"
onClick={(event) => {
const target = event.target as HTMLElement;
if (target.tagName === "A") {
const path = target.getAttribute("path")!;
const line = target.getAttribute("line")!;
const column = target.getAttribute("column")!;

if (!path || !line || !column) {
return;
}

openFileInEditor.mutate({
path,
line: Number.parseInt(line),
column: Number.parseInt(column),
});
}
}}
>
{children}
</button>
);
};
19 changes: 12 additions & 7 deletions apps/wing-console/console/ui/src/ui/blue-screen-of-death.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import classNames from "classnames";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";

import { formatAbsolutePaths } from "../features/console-logs.js";
import {
OpenFileInEditorButton,
createHtmlLink,
} from "../shared/use-file-link.js";

export const BlueScreenOfDeath = ({
title,
Expand All @@ -22,7 +25,7 @@ export const BlueScreenOfDeath = ({
return;
}
setFormattedPathsError(
formatAbsolutePaths(
createHtmlLink(
error,
"underline text-slate-300 hover:text-slate-400",
true,
Expand All @@ -46,10 +49,12 @@ export const BlueScreenOfDeath = ({
<div className="space-y-4">
<div>{title}</div>
<div className="py-4">
<span
className="outline-none select-text whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: formattedPathsError }}
/>
<OpenFileInEditorButton>
<span
className="outline-none select-text whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: formattedPathsError }}
/>
</OpenFileInEditorButton>
</div>
{displayLinks && (
<div className="w-full text-center py-4">
Expand Down
Loading