Skip to content
4 changes: 2 additions & 2 deletions src/commands/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { leetCodeExecutor } from "../leetCodeExecutor";
import { leetCodeManager } from "../leetCodeManager";
import { DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils";
import { getActiveFilePath } from "../utils/workspaceUtils";
import { leetCodeResultProvider } from "../webview/leetCodeResultProvider";
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";

export async function submitSolution(uri?: vscode.Uri): Promise<void> {
if (!leetCodeManager.getUser()) {
Expand All @@ -21,7 +21,7 @@ export async function submitSolution(uri?: vscode.Uri): Promise<void> {

try {
const result: string = await leetCodeExecutor.submitSolution(filePath);
await leetCodeResultProvider.show(result);
await leetCodeSubmissionProvider.show(result);
} catch (error) {
await promptForOpenOutputChannel("Failed to submit the solution. Please open the output channel for details.", DialogType.error);
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isWindows, usingCmd } from "../utils/osUtils";
import { DialogType, promptForOpenOutputChannel, showFileSelectDialog } from "../utils/uiUtils";
import { getActiveFilePath } from "../utils/workspaceUtils";
import * as wsl from "../utils/wslUtils";
import { leetCodeResultProvider } from "../webview/leetCodeResultProvider";
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";

export async function testSolution(uri?: vscode.Uri): Promise<void> {
try {
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function testSolution(uri?: vscode.Uri): Promise<void> {
if (!result) {
return;
}
await leetCodeResultProvider.show(result);
await leetCodeSubmissionProvider.show(result);
} catch (error) {
await promptForOpenOutputChannel("Failed to test the solution. Please open the output channel for details.", DialogType.error);
}
Expand Down
9 changes: 4 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import { leetCodeManager } from "./leetCodeManager";
import { leetCodeStatusBarController } from "./statusbar/leetCodeStatusBarController";
import { DialogType, promptForOpenOutputChannel } from "./utils/uiUtils";
import { leetCodePreviewProvider } from "./webview/leetCodePreviewProvider";
import { leetCodeResultProvider } from "./webview/leetCodeResultProvider";
import { leetCodeSolutionProvider } from "./webview/leetCodeSolutionProvider";
import { leetCodeSubmissionProvider } from "./webview/leetCodeSubmissionProvider";
import { markdownEngine } from "./webview/markdownEngine";

export async function activate(context: vscode.ExtensionContext): Promise<void> {
try {
Expand All @@ -33,17 +34,15 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
});

const leetCodeTreeDataProvider: LeetCodeTreeDataProvider = new LeetCodeTreeDataProvider(context);
leetCodePreviewProvider.initialize(context);
leetCodeResultProvider.initialize(context);
leetCodeSolutionProvider.initialize(context);

context.subscriptions.push(
leetCodeStatusBarController,
leetCodeChannel,
leetCodePreviewProvider,
leetCodeResultProvider,
leetCodeSubmissionProvider,
leetCodeSolutionProvider,
leetCodeExecutor,
markdownEngine,
vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }),
vscode.languages.registerCodeLensProvider({ scheme: "file" }, codeLensProvider),
vscode.commands.registerCommand("leetcode.deleteCache", () => cache.deleteCache()),
Expand Down
64 changes: 64 additions & 0 deletions src/webview/LeetCodeWebview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import { ConfigurationChangeEvent, Disposable, ViewColumn, WebviewPanel, window, workspace } from "vscode";
import { markdownEngine } from "./markdownEngine";

export abstract class LeetCodeWebview implements Disposable {

protected panel: WebviewPanel | undefined;
private listeners: Disposable[] = [];

public dispose(): void {
if (this.panel) {
this.panel.dispose();
}
}

protected showWebviewInternal(): void {
const { viewType, title, viewColumn, preserveFocus } = this.getWebviewOption();
if (!this.panel) {
this.panel = window.createWebviewPanel(viewType, title, { viewColumn, preserveFocus }, {
enableScripts: true,
enableCommandUris: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: markdownEngine.localResourceRoots,
});
this.panel.onDidDispose(this.onDidDisposeWebview, this, this.listeners);
this.panel.webview.onDidReceiveMessage(this.onDidReceiveMessage, this, this.listeners);
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.listeners);
} else {
this.panel.title = title;
this.panel.reveal(viewColumn, preserveFocus);
}
this.panel.webview.html = this.getWebviewContent();
}

protected onDidDisposeWebview(): void {
this.panel = undefined;
for (const listener of this.listeners) {
listener.dispose();
}
this.listeners = [];
}

protected async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise<void> {
if (this.panel && event.affectsConfiguration("markdown")) {
this.panel.webview.html = this.getWebviewContent();
}
}

protected async onDidReceiveMessage(_message: any): Promise<void> { /* no special rule */ }

protected abstract getWebviewOption(): ILeetCodeWebviewOption;

protected abstract getWebviewContent(): string;
}

export interface ILeetCodeWebviewOption {
viewType: string;
title: string;
viewColumn: ViewColumn;
preserveFocus?: boolean;
}
153 changes: 69 additions & 84 deletions src/webview/leetCodePreviewProvider.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,39 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import { commands, Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode";
import { commands, ViewColumn } from "vscode";
import { leetCodeExecutor } from "../leetCodeExecutor";
import { IProblem } from "../shared";
import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview";
import { markdownEngine } from "./markdownEngine";

class LeetCodePreviewProvider implements Disposable {
class LeetCodePreviewProvider extends LeetCodeWebview {

private context: ExtensionContext;
private node: IProblem;
private panel: WebviewPanel | undefined;

public initialize(context: ExtensionContext): void {
this.context = context;
}
private description: IDescription;

public async show(node: IProblem): Promise<void> {
// Fetch problem first before creating webview panel
const descString: string = await leetCodeExecutor.getDescription(node);

this.description = this.parseDescription(await leetCodeExecutor.getDescription(node), node);
this.node = node;
if (!this.panel) {
this.panel = window.createWebviewPanel("leetcode.preview", "Preview Problem", ViewColumn.One, {
enableScripts: true,
enableCommandUris: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: markdownEngine.localResourceRoots,
});

this.panel.webview.onDidReceiveMessage(async (message: IWebViewMessage) => {
switch (message.command) {
case "ShowProblem": {
await commands.executeCommand("leetcode.showProblem", this.node);
break;
}
}
}, this, this.context.subscriptions);

this.panel.onDidDispose(() => {
this.panel = undefined;
}, null, this.context.subscriptions);
}

const description: IDescription = this.parseDescription(descString, node);
this.panel.webview.html = this.getWebViewContent(description);
this.panel.title = `${node.name}: Preview`;
this.panel.reveal(ViewColumn.One);
this.showWebviewInternal();
}

public dispose(): void {
if (this.panel) {
this.panel.dispose();
}
}

private parseDescription(descString: string, problem: IProblem): IDescription {
const [
/* title */, ,
url, ,
/* tags */, ,
/* langs */, ,
category,
difficulty,
likes,
dislikes,
/* accepted */,
/* submissions */,
/* testcase */, ,
...body
] = descString.split("\n");
protected getWebviewOption(): ILeetCodeWebviewOption {
return {
title: problem.name,
url,
tags: problem.tags,
companies: problem.companies,
category: category.slice(2),
difficulty: difficulty.slice(2),
likes: likes.split(": ")[1].trim(),
dislikes: dislikes.split(": ")[1].trim(),
body: body.join("\n").replace(/<pre>\s*([^]+?)\s*<\/pre>/g, "<pre><code>$1</code></pre>"),
viewType: "leetcode.preview",
title: `${this.node.name}: Preview`,
viewColumn: ViewColumn.One,
};
}

private getWebViewContent(desc: IDescription): string {
const mdStyles: string = markdownEngine.getStyles();
const buttonStyle: string = `
<style>
protected getWebviewContent(): string {
const button: { element: string, script: string, style: string } = {
element: `<button id="solve">Code Now</button>`,
script: `const button = document.getElementById('solve');
button.onclick = () => vscode.postMessage({
command: 'ShowProblem',
});`,
style: `<style>
#solve {
position: fixed;
bottom: 1rem;
Expand All @@ -104,9 +50,9 @@ class LeetCodePreviewProvider implements Disposable {
#solve:active {
border: 0;
}
</style>
`;
const { title, url, category, difficulty, likes, dislikes, body } = desc;
</style>`,
};
const { title, url, category, difficulty, likes, dislikes, body } = this.description;
const head: string = markdownEngine.render(`# [${title}](${url})`);
const info: string = markdownEngine.render([
`| Category | Difficulty | Likes | Dislikes |`,
Expand All @@ -117,7 +63,7 @@ class LeetCodePreviewProvider implements Disposable {
`<details>`,
`<summary><strong>Tags</strong></summary>`,
markdownEngine.render(
desc.tags
this.description.tags
.map((t: string) => `[\`${t}\`](https://leetcode.com/tag/${t})`)
.join(" | "),
),
Expand All @@ -127,7 +73,7 @@ class LeetCodePreviewProvider implements Disposable {
`<details>`,
`<summary><strong>Companies</strong></summary>`,
markdownEngine.render(
desc.companies
this.description.companies
.map((c: string) => `\`${c}\``)
.join(" | "),
),
Expand All @@ -137,28 +83,67 @@ class LeetCodePreviewProvider implements Disposable {
<!DOCTYPE html>
<html>
<head>
${mdStyles}
${buttonStyle}
${markdownEngine.getStyles()}
${button.style}
</head>
<body>
${head}
${info}
${tags}
${companies}
${body}
<button id="solve">Code Now</button>
${button.element}
<script>
const vscode = acquireVsCodeApi();
const button = document.getElementById('solve');
button.onclick = () => vscode.postMessage({
command: 'ShowProblem',
});
${button.script}
</script>
</body>
</html>
`;
}

protected onDidDisposeWebview(): void {
super.onDidDisposeWebview();
delete this.node;
delete this.description;
}

protected async onDidReceiveMessage(message: IWebViewMessage): Promise<void> {
switch (message.command) {
case "ShowProblem": {
await commands.executeCommand("leetcode.showProblem", this.node);
break;
}
}
}

private parseDescription(descString: string, problem: IProblem): IDescription {
const [
/* title */, ,
url, ,
/* tags */, ,
/* langs */, ,
category,
difficulty,
likes,
dislikes,
/* accepted */,
/* submissions */,
/* testcase */, ,
...body
] = descString.split("\n");
return {
title: problem.name,
url,
tags: problem.tags,
companies: problem.companies,
category: category.slice(2),
difficulty: difficulty.slice(2),
likes: likes.split(": ")[1].trim(),
dislikes: dislikes.split(": ")[1].trim(),
body: body.join("\n").replace(/<pre>\s*([^]+?)\s*<\/pre>/g, "<pre><code>$1</code></pre>"),
};
}
}

interface IDescription {
Expand Down
Loading