Skip to content

Extract leetcode webview base class & add md settings listener #270

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

Merged
merged 9 commits into from
Apr 16, 2019
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