From d4c026f66350a691c5eedaccd3cb16a4d33d553a Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 16:33:31 +0800 Subject: [PATCH 1/9] Extract leetcode webview base class & add md setings listener --- src/webview/LeetCodeWebview.ts | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/webview/LeetCodeWebview.ts diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts new file mode 100644 index 00000000..ba9dbee7 --- /dev/null +++ b/src/webview/LeetCodeWebview.ts @@ -0,0 +1,63 @@ +// Copyright (c) jdneo. All rights reserved. +// Licensed under the MIT license. + +import { ConfigurationChangeEvent, Disposable, ExtensionContext, ViewColumn, WebviewPanel, window, workspace } from "vscode"; +import { markdownEngine } from "./markdownEngine"; + +export abstract class LeetCodeWebview implements Disposable { + + protected panel: WebviewPanel | undefined; + private context: ExtensionContext; + private listener: Disposable; + + public initialize(context: ExtensionContext): void { + this.context = context; + this.listener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (event.affectsConfiguration("markdown") && this.panel) { + this.panel.webview.html = this.getWebviewContent(); + } + }, this); + } + + public dispose(): void { + this.listener.dispose(); + if (this.panel) { + this.panel.dispose(); + } + } + + protected showWebviewInternal(): this is { panel: WebviewPanel } { + if (!this.panel) { + const option: ILeetCodeWebviewOption = this.getWebviewOption(); + + this.panel = window.createWebviewPanel(option.viewType, option.title, ViewColumn.One, { + enableScripts: true, + enableCommandUris: true, + enableFindWidget: true, + retainContextWhenHidden: true, + localResourceRoots: markdownEngine.localResourceRoots, + }); + + this.panel.onDidDispose(() => { + this.panel = undefined; + }, null, this.context.subscriptions); + + if (option.onDidReceiveMessage) { + this.panel.webview.onDidReceiveMessage(option.onDidReceiveMessage, this, this.context.subscriptions); + } + } + + this.panel.webview.html = this.getWebviewContent(); + return true; + } + + protected abstract getWebviewOption(): ILeetCodeWebviewOption; + + protected abstract getWebviewContent(): string; +} + +export interface ILeetCodeWebviewOption { + viewType: string; + title: string; + onDidReceiveMessage?: (message: any) => Promise; +} From 4c34172676bca54902263741a148057c71fbf40a Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 16:34:34 +0800 Subject: [PATCH 2/9] Make solution provider extend webview base --- src/webview/leetCodeSolutionProvider.ts | 74 ++++++++++--------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index 8a378208..c344b752 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -1,60 +1,33 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; +import { ViewColumn } from "vscode"; import { IProblem } from "../shared"; +import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview"; import { markdownEngine } from "./markdownEngine"; -class LeetCodeSolutionProvider implements Disposable { +class LeetCodeSolutionProvider extends LeetCodeWebview { - private context: ExtensionContext; - private panel: WebviewPanel | undefined; - - public initialize(context: ExtensionContext): void { - this.context = context; - } + private solution: Solution; public async show(solutionString: string, problem: IProblem): Promise { - if (!this.panel) { - this.panel = window.createWebviewPanel("leetCode.solution", "Top Voted Solution", ViewColumn.Active, { - retainContextWhenHidden: true, - enableFindWidget: true, - localResourceRoots: markdownEngine.localResourceRoots, - }); - - this.panel.onDidDispose(() => { - this.panel = undefined; - }, null, this.context.subscriptions); + this.solution = this.parseSolution(solutionString); + if (this.showWebviewInternal()) { + this.panel.title = `${problem.name}: Solution`; + this.panel.reveal(ViewColumn.Active); } - - const solution: Solution = this.parseSolution(solutionString); - this.panel.title = `${problem.name}: Solution`; - this.panel.webview.html = this.getWebViewContent(solution); - this.panel.reveal(ViewColumn.Active); } - public dispose(): void { - if (this.panel) { - this.panel.dispose(); - } + protected getWebviewOption(): ILeetCodeWebviewOption { + return { + viewType: "leetcode.solution", + title: "Top Voted Solution", + }; } - private parseSolution(raw: string): Solution { - const solution: Solution = new Solution(); - // [^] matches everything including \n, yet can be replaced by . in ES2018's `m` flag - raw = raw.slice(1); // skip first empty line - [solution.title, raw] = raw.split(/\n\n([^]+)/); // parse title and skip one line - [solution.url, raw] = raw.split(/\n\n([^]+)/); // parse url and skip one line - [solution.lang, raw] = raw.match(/\* Lang:\s+(.+)\n([^]+)/)!.slice(1); - [solution.author, raw] = raw.match(/\* Author:\s+(.+)\n([^]+)/)!.slice(1); - [solution.votes, raw] = raw.match(/\* Votes:\s+(\d+)\n\n([^]+)/)!.slice(1); - solution.body = raw; - return solution; - } - - private getWebViewContent(solution: Solution): string { + protected getWebviewContent(): string { const styles: string = markdownEngine.getStyles(); - const { title, url, lang, author, votes } = solution; + const { title, url, lang, author, votes } = this.solution; const head: string = markdownEngine.render(`# [${title}](${url})`); const auth: string = `[${author}](https://leetcode.com/${author}/)`; const info: string = markdownEngine.render([ @@ -62,8 +35,8 @@ class LeetCodeSolutionProvider implements Disposable { `| :------: | :------: | :------: |`, `| ${lang} | ${auth} | ${votes} |`, ].join("\n")); - const body: string = markdownEngine.render(solution.body, { - lang: solution.lang, + const body: string = markdownEngine.render(this.solution.body, { + lang: this.solution.lang, host: "https://discuss.leetcode.com/", }); return ` @@ -80,6 +53,19 @@ class LeetCodeSolutionProvider implements Disposable { `; } + + private parseSolution(raw: string): Solution { + const solution: Solution = new Solution(); + // [^] matches everything including \n, yet can be replaced by . in ES2018's `m` flag + raw = raw.slice(1); // skip first empty line + [solution.title, raw] = raw.split(/\n\n([^]+)/); // parse title and skip one line + [solution.url, raw] = raw.split(/\n\n([^]+)/); // parse url and skip one line + [solution.lang, raw] = raw.match(/\* Lang:\s+(.+)\n([^]+)/)!.slice(1); + [solution.author, raw] = raw.match(/\* Author:\s+(.+)\n([^]+)/)!.slice(1); + [solution.votes, raw] = raw.match(/\* Votes:\s+(\d+)\n\n([^]+)/)!.slice(1); + solution.body = raw; + return solution; + } } // tslint:disable-next-line:max-classes-per-file From d0ccd530cfd8fe52e6166ddbccba3cc866943e85 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 16:34:57 +0800 Subject: [PATCH 3/9] Make preview provider extend webview base --- src/webview/leetCodePreviewProvider.ts | 115 +++++++++++-------------- 1 file changed, 48 insertions(+), 67 deletions(-) diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index 5b57434a..f5b9e384 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -1,90 +1,44 @@ // 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 { - // Fetch problem first before creating webview panel const descString: string = await leetCodeExecutor.getDescription(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.description = this.parseDescription(descString, node); + if (this.showWebviewInternal()) { + this.panel.webview.html = this.getWebviewContent(); + this.panel.title = `${node.name}: Preview`; + this.panel.reveal(ViewColumn.One); + } + } - this.panel.webview.onDidReceiveMessage(async (message: IWebViewMessage) => { + protected getWebviewOption(): ILeetCodeWebviewOption { + return { + viewType: "leetcode.preview", + title: "Preview Problem", + onDidReceiveMessage: async (message: IWebViewMessage): Promise => { 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); - } - - 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"); - 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(/
\s*([^]+?)\s*<\/pre>/g, "
$1
"), + }, }; } - private getWebViewContent(desc: IDescription): string { + protected getWebviewContent(): string { const mdStyles: string = markdownEngine.getStyles(); const buttonStyle: string = ` `; - const { title, url, category, difficulty, likes, dislikes, body } = desc; + 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 |`, @@ -117,7 +71,7 @@ class LeetCodePreviewProvider implements Disposable { `
`, `Tags`, markdownEngine.render( - desc.tags + this.description.tags .map((t: string) => `[\`${t}\`](https://leetcode.com/tag/${t})`) .join(" | "), ), @@ -127,7 +81,7 @@ class LeetCodePreviewProvider implements Disposable { `
`, `Companies`, markdownEngine.render( - desc.companies + this.description.companies .map((c: string) => `\`${c}\``) .join(" | "), ), @@ -159,6 +113,33 @@ class LeetCodePreviewProvider implements Disposable { `; } + 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(/
\s*([^]+?)\s*<\/pre>/g, "
$1
"), + }; + } } interface IDescription { From 82ec8046cbe8a6679dc7a79faa0016c78a72e6d4 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 16:36:28 +0800 Subject: [PATCH 4/9] Rename result provider to submission provider & make it extend webview base --- src/commands/submit.ts | 4 +- src/commands/test.ts | 4 +- src/extension.ts | 6 +-- src/webview/leetCodeResultProvider.ts | 54 ----------------------- src/webview/leetCodeSubmissionProvider.ts | 47 ++++++++++++++++++++ 5 files changed, 54 insertions(+), 61 deletions(-) delete mode 100644 src/webview/leetCodeResultProvider.ts create mode 100644 src/webview/leetCodeSubmissionProvider.ts diff --git a/src/commands/submit.ts b/src/commands/submit.ts index 6ed100dc..2035ec11 100644 --- a/src/commands/submit.ts +++ b/src/commands/submit.ts @@ -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 { if (!leetCodeManager.getUser()) { @@ -21,7 +21,7 @@ export async function submitSolution(uri?: vscode.Uri): Promise { 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); } diff --git a/src/commands/test.ts b/src/commands/test.ts index bc388f69..558f7409 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -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 { try { @@ -81,7 +81,7 @@ export async function testSolution(uri?: vscode.Uri): Promise { 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); } diff --git a/src/extension.ts b/src/extension.ts index 1be99807..78d2d6bf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,8 +18,8 @@ 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"; export async function activate(context: vscode.ExtensionContext): Promise { try { @@ -34,14 +34,14 @@ export async function activate(context: vscode.ExtensionContext): Promise const leetCodeTreeDataProvider: LeetCodeTreeDataProvider = new LeetCodeTreeDataProvider(context); leetCodePreviewProvider.initialize(context); - leetCodeResultProvider.initialize(context); leetCodeSolutionProvider.initialize(context); + leetCodeSubmissionProvider.initialize(context); context.subscriptions.push( leetCodeStatusBarController, leetCodeChannel, leetCodePreviewProvider, - leetCodeResultProvider, + leetCodeSubmissionProvider, leetCodeSolutionProvider, leetCodeExecutor, vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }), diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts deleted file mode 100644 index 1b6d2a54..00000000 --- a/src/webview/leetCodeResultProvider.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) jdneo. All rights reserved. -// Licensed under the MIT license. - -import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; -import { markdownEngine } from "./markdownEngine"; - -class LeetCodeResultProvider implements Disposable { - - private context: ExtensionContext; - private panel: WebviewPanel | undefined; - - public initialize(context: ExtensionContext): void { - this.context = context; - } - - public async show(result: string): Promise { - if (!this.panel) { - this.panel = window.createWebviewPanel("leetcode.result", "LeetCode Results", ViewColumn.Two, { - retainContextWhenHidden: true, - enableFindWidget: true, - localResourceRoots: markdownEngine.localResourceRoots, - }); - - this.panel.onDidDispose(() => { - this.panel = undefined; - }, null, this.context.subscriptions); - } - - this.panel.webview.html = await this.provideHtmlContent(result); - this.panel.reveal(ViewColumn.Two); - } - - public dispose(): void { - if (this.panel) { - this.panel.dispose(); - } - } - - private async provideHtmlContent(result: string): Promise { - return ` - - - - - ${markdownEngine.getStyles()} - - -
${result.trim()}
- - `; - } -} - -export const leetCodeResultProvider: LeetCodeResultProvider = new LeetCodeResultProvider(); diff --git a/src/webview/leetCodeSubmissionProvider.ts b/src/webview/leetCodeSubmissionProvider.ts new file mode 100644 index 00000000..a0f1c5b6 --- /dev/null +++ b/src/webview/leetCodeSubmissionProvider.ts @@ -0,0 +1,47 @@ +// Copyright (c) jdneo. All rights reserved. +// Licensed under the MIT license. + +import { ViewColumn } from "vscode"; +import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview"; +import { markdownEngine } from "./markdownEngine"; + +class LeetCodeSubmissionProvider extends LeetCodeWebview { + + private result: string; + + public async show(result: string): Promise { + this.result = result; + if (this.showWebviewInternal()) { + this.panel.reveal(ViewColumn.Two); + } + } + + public dispose(): void { + if (this.panel) { + this.panel.dispose(); + } + } + + protected getWebviewOption(): ILeetCodeWebviewOption { + return { + viewType: "leetcode.submission", + title: "Submission", + }; + } + + protected getWebviewContent(): string { + return ` + + + + + ${markdownEngine.getStyles()} + + +
${this.result.trim()}
+ + `; + } +} + +export const leetCodeSubmissionProvider: LeetCodeSubmissionProvider = new LeetCodeSubmissionProvider(); From df68b0edfd5d672a4a0f081fab4e23cf5a5b3ef9 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 16:54:04 +0800 Subject: [PATCH 5/9] Submission provider fix --- src/webview/LeetCodeWebview.ts | 3 ++- src/webview/leetCodeSubmissionProvider.ts | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index ba9dbee7..8d0f3f70 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -30,7 +30,7 @@ export abstract class LeetCodeWebview implements Disposable { if (!this.panel) { const option: ILeetCodeWebviewOption = this.getWebviewOption(); - this.panel = window.createWebviewPanel(option.viewType, option.title, ViewColumn.One, { + this.panel = window.createWebviewPanel(option.viewType, option.title, option.viewColumn || ViewColumn.One, { enableScripts: true, enableCommandUris: true, enableFindWidget: true, @@ -59,5 +59,6 @@ export abstract class LeetCodeWebview implements Disposable { export interface ILeetCodeWebviewOption { viewType: string; title: string; + viewColumn?: ViewColumn; onDidReceiveMessage?: (message: any) => Promise; } diff --git a/src/webview/leetCodeSubmissionProvider.ts b/src/webview/leetCodeSubmissionProvider.ts index a0f1c5b6..e165e61a 100644 --- a/src/webview/leetCodeSubmissionProvider.ts +++ b/src/webview/leetCodeSubmissionProvider.ts @@ -16,16 +16,11 @@ class LeetCodeSubmissionProvider extends LeetCodeWebview { } } - public dispose(): void { - if (this.panel) { - this.panel.dispose(); - } - } - protected getWebviewOption(): ILeetCodeWebviewOption { return { viewType: "leetcode.submission", title: "Submission", + viewColumn: ViewColumn.Two, }; } From 90c9480134db4efbf45d384ee84f735a21919975 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 18:31:14 +0800 Subject: [PATCH 6/9] Make `onDidChangeConfiguration` inheritable --- src/webview/LeetCodeWebview.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index 8d0f3f70..3b6ca044 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -11,12 +11,17 @@ export abstract class LeetCodeWebview implements Disposable { private listener: Disposable; public initialize(context: ExtensionContext): void { + const { onDidChangeConfiguration } = this.getWebviewOption(); this.context = context; - this.listener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { - if (event.affectsConfiguration("markdown") && this.panel) { - this.panel.webview.html = this.getWebviewContent(); - } - }, this); + if (onDidChangeConfiguration) { + this.listener = workspace.onDidChangeConfiguration(onDidChangeConfiguration, this); + } else { + this.listener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (event.affectsConfiguration("markdown") && this.panel) { + this.panel.webview.html = this.getWebviewContent(); + } + }, this); + } } public dispose(): void { @@ -28,9 +33,9 @@ export abstract class LeetCodeWebview implements Disposable { protected showWebviewInternal(): this is { panel: WebviewPanel } { if (!this.panel) { - const option: ILeetCodeWebviewOption = this.getWebviewOption(); + const { viewType, title, viewColumn, onDidReceiveMessage } = this.getWebviewOption(); - this.panel = window.createWebviewPanel(option.viewType, option.title, option.viewColumn || ViewColumn.One, { + this.panel = window.createWebviewPanel(viewType, title, viewColumn || ViewColumn.One, { enableScripts: true, enableCommandUris: true, enableFindWidget: true, @@ -42,8 +47,8 @@ export abstract class LeetCodeWebview implements Disposable { this.panel = undefined; }, null, this.context.subscriptions); - if (option.onDidReceiveMessage) { - this.panel.webview.onDidReceiveMessage(option.onDidReceiveMessage, this, this.context.subscriptions); + if (onDidReceiveMessage) { + this.panel.webview.onDidReceiveMessage(onDidReceiveMessage, this, this.context.subscriptions); } } @@ -61,4 +66,5 @@ export interface ILeetCodeWebviewOption { title: string; viewColumn?: ViewColumn; onDidReceiveMessage?: (message: any) => Promise; + onDidChangeConfiguration?: (event: ConfigurationChangeEvent) => Promise; } From 9f0999e28336f3290f227d53f3d6b0aed864c538 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sun, 14 Apr 2019 23:58:21 +0800 Subject: [PATCH 7/9] Make two event callbacks inheritable class method --- src/webview/LeetCodeWebview.ts | 27 +++++++++++--------------- src/webview/leetCodePreviewProvider.ts | 21 ++++++++++---------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index 3b6ca044..71d17d7c 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -11,17 +11,8 @@ export abstract class LeetCodeWebview implements Disposable { private listener: Disposable; public initialize(context: ExtensionContext): void { - const { onDidChangeConfiguration } = this.getWebviewOption(); this.context = context; - if (onDidChangeConfiguration) { - this.listener = workspace.onDidChangeConfiguration(onDidChangeConfiguration, this); - } else { - this.listener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { - if (event.affectsConfiguration("markdown") && this.panel) { - this.panel.webview.html = this.getWebviewContent(); - } - }, this); - } + this.listener = workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this); } public dispose(): void { @@ -33,7 +24,7 @@ export abstract class LeetCodeWebview implements Disposable { protected showWebviewInternal(): this is { panel: WebviewPanel } { if (!this.panel) { - const { viewType, title, viewColumn, onDidReceiveMessage } = this.getWebviewOption(); + const { viewType, title, viewColumn } = this.getWebviewOption(); this.panel = window.createWebviewPanel(viewType, title, viewColumn || ViewColumn.One, { enableScripts: true, @@ -47,15 +38,21 @@ export abstract class LeetCodeWebview implements Disposable { this.panel = undefined; }, null, this.context.subscriptions); - if (onDidReceiveMessage) { - this.panel.webview.onDidReceiveMessage(onDidReceiveMessage, this, this.context.subscriptions); - } + this.panel.webview.onDidReceiveMessage(this.onDidReceiveMessage, this, this.context.subscriptions); } this.panel.webview.html = this.getWebviewContent(); return true; } + protected async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { + if (this.panel && event.affectsConfiguration("markdown")) { + this.panel.webview.html = this.getWebviewContent(); + } + } + + protected async onDidReceiveMessage(/* message */_: any): Promise { /* no special rule */ } + protected abstract getWebviewOption(): ILeetCodeWebviewOption; protected abstract getWebviewContent(): string; @@ -65,6 +62,4 @@ export interface ILeetCodeWebviewOption { viewType: string; title: string; viewColumn?: ViewColumn; - onDidReceiveMessage?: (message: any) => Promise; - onDidChangeConfiguration?: (event: ConfigurationChangeEvent) => Promise; } diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index f5b9e384..bea02196 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -13,28 +13,27 @@ class LeetCodePreviewProvider extends LeetCodeWebview { private description: IDescription; public async show(node: IProblem): Promise { - const descString: string = await leetCodeExecutor.getDescription(node); + this.description = this.parseDescription(await leetCodeExecutor.getDescription(node), node); this.node = node; - this.description = this.parseDescription(descString, node); if (this.showWebviewInternal()) { - this.panel.webview.html = this.getWebviewContent(); this.panel.title = `${node.name}: Preview`; this.panel.reveal(ViewColumn.One); } } + protected async onDidReceiveMessage(message: IWebViewMessage): Promise { + switch (message.command) { + case "ShowProblem": { + await commands.executeCommand("leetcode.showProblem", this.node); + break; + } + } + } + protected getWebviewOption(): ILeetCodeWebviewOption { return { viewType: "leetcode.preview", title: "Preview Problem", - onDidReceiveMessage: async (message: IWebViewMessage): Promise => { - switch (message.command) { - case "ShowProblem": { - await commands.executeCommand("leetcode.showProblem", this.node); - break; - } - } - }, }; } From 85a716447e566c69c219b841858e5f49441309a0 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Mon, 15 Apr 2019 10:16:30 +0800 Subject: [PATCH 8/9] Bring `onDidDispose` to webview class member --- src/webview/LeetCodeWebview.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index 71d17d7c..8396e526 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -8,43 +8,44 @@ export abstract class LeetCodeWebview implements Disposable { protected panel: WebviewPanel | undefined; private context: ExtensionContext; - private listener: Disposable; + private configListener: Disposable | undefined; public initialize(context: ExtensionContext): void { this.context = context; - this.listener = workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this); } public dispose(): void { - this.listener.dispose(); if (this.panel) { this.panel.dispose(); } } protected showWebviewInternal(): this is { panel: WebviewPanel } { + const { viewType, title, viewColumn } = this.getWebviewOption(); if (!this.panel) { - const { viewType, title, viewColumn } = this.getWebviewOption(); - - this.panel = window.createWebviewPanel(viewType, title, viewColumn || ViewColumn.One, { + this.panel = window.createWebviewPanel(viewType, title, viewColumn || ViewColumn.Active, { enableScripts: true, enableCommandUris: true, enableFindWidget: true, retainContextWhenHidden: true, localResourceRoots: markdownEngine.localResourceRoots, }); - - this.panel.onDidDispose(() => { - this.panel = undefined; - }, null, this.context.subscriptions); - + this.panel.onDidDispose(this.onDidDisposeWebview, this, this.context.subscriptions); this.panel.webview.onDidReceiveMessage(this.onDidReceiveMessage, this, this.context.subscriptions); + this.configListener = workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this); } - this.panel.webview.html = this.getWebviewContent(); return true; } + protected onDidDisposeWebview(): void { + this.panel = undefined; + if (this.configListener) { + this.configListener.dispose(); + this.configListener = undefined; + } + } + protected async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { if (this.panel && event.affectsConfiguration("markdown")) { this.panel.webview.html = this.getWebviewContent(); From 084e844dc5561a72543432eaf7882cd216bcae4a Mon Sep 17 00:00:00 2001 From: Vigilans Date: Tue, 16 Apr 2019 13:42:50 +0800 Subject: [PATCH 9/9] Replace `this.context` field with `this.listener` & Further update in next PR --- src/extension.ts | 5 +- src/webview/LeetCodeWebview.ts | 36 +++++++------- src/webview/leetCodePreviewProvider.ts | 57 ++++++++++++----------- src/webview/leetCodeSolutionProvider.ts | 19 +++++--- src/webview/leetCodeSubmissionProvider.ts | 9 ++-- tslint.json | 4 ++ 6 files changed, 72 insertions(+), 58 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 78d2d6bf..6cf38966 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,6 +20,7 @@ import { DialogType, promptForOpenOutputChannel } from "./utils/uiUtils"; import { leetCodePreviewProvider } from "./webview/leetCodePreviewProvider"; import { leetCodeSolutionProvider } from "./webview/leetCodeSolutionProvider"; import { leetCodeSubmissionProvider } from "./webview/leetCodeSubmissionProvider"; +import { markdownEngine } from "./webview/markdownEngine"; export async function activate(context: vscode.ExtensionContext): Promise { try { @@ -33,9 +34,6 @@ export async function activate(context: vscode.ExtensionContext): Promise }); const leetCodeTreeDataProvider: LeetCodeTreeDataProvider = new LeetCodeTreeDataProvider(context); - leetCodePreviewProvider.initialize(context); - leetCodeSolutionProvider.initialize(context); - leetCodeSubmissionProvider.initialize(context); context.subscriptions.push( leetCodeStatusBarController, @@ -44,6 +42,7 @@ export async function activate(context: vscode.ExtensionContext): Promise 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()), diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index 8396e526..7b0e9b8d 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -1,18 +1,13 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import { ConfigurationChangeEvent, Disposable, ExtensionContext, ViewColumn, WebviewPanel, window, workspace } from "vscode"; +import { ConfigurationChangeEvent, Disposable, ViewColumn, WebviewPanel, window, workspace } from "vscode"; import { markdownEngine } from "./markdownEngine"; export abstract class LeetCodeWebview implements Disposable { protected panel: WebviewPanel | undefined; - private context: ExtensionContext; - private configListener: Disposable | undefined; - - public initialize(context: ExtensionContext): void { - this.context = context; - } + private listeners: Disposable[] = []; public dispose(): void { if (this.panel) { @@ -20,30 +15,32 @@ export abstract class LeetCodeWebview implements Disposable { } } - protected showWebviewInternal(): this is { panel: WebviewPanel } { - const { viewType, title, viewColumn } = this.getWebviewOption(); + protected showWebviewInternal(): void { + const { viewType, title, viewColumn, preserveFocus } = this.getWebviewOption(); if (!this.panel) { - this.panel = window.createWebviewPanel(viewType, title, viewColumn || ViewColumn.Active, { + 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.context.subscriptions); - this.panel.webview.onDidReceiveMessage(this.onDidReceiveMessage, this, this.context.subscriptions); - this.configListener = workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this); + 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(); - return true; } protected onDidDisposeWebview(): void { this.panel = undefined; - if (this.configListener) { - this.configListener.dispose(); - this.configListener = undefined; + for (const listener of this.listeners) { + listener.dispose(); } + this.listeners = []; } protected async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { @@ -52,7 +49,7 @@ export abstract class LeetCodeWebview implements Disposable { } } - protected async onDidReceiveMessage(/* message */_: any): Promise { /* no special rule */ } + protected async onDidReceiveMessage(_message: any): Promise { /* no special rule */ } protected abstract getWebviewOption(): ILeetCodeWebviewOption; @@ -62,5 +59,6 @@ export abstract class LeetCodeWebview implements Disposable { export interface ILeetCodeWebviewOption { viewType: string; title: string; - viewColumn?: ViewColumn; + viewColumn: ViewColumn; + preserveFocus?: boolean; } diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index bea02196..9189d769 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -15,32 +15,25 @@ class LeetCodePreviewProvider extends LeetCodeWebview { public async show(node: IProblem): Promise { this.description = this.parseDescription(await leetCodeExecutor.getDescription(node), node); this.node = node; - if (this.showWebviewInternal()) { - this.panel.title = `${node.name}: Preview`; - this.panel.reveal(ViewColumn.One); - } - } - - protected async onDidReceiveMessage(message: IWebViewMessage): Promise { - switch (message.command) { - case "ShowProblem": { - await commands.executeCommand("leetcode.showProblem", this.node); - break; - } - } + this.showWebviewInternal(); } protected getWebviewOption(): ILeetCodeWebviewOption { return { viewType: "leetcode.preview", - title: "Preview Problem", + title: `${this.node.name}: Preview`, + viewColumn: ViewColumn.One, }; } protected getWebviewContent(): string { - const mdStyles: string = markdownEngine.getStyles(); - const buttonStyle: string = ` - - `; + `, + }; const { title, url, category, difficulty, likes, dislikes, body } = this.description; const head: string = markdownEngine.render(`# [${title}](${url})`); const info: string = markdownEngine.render([ @@ -90,8 +83,8 @@ class LeetCodePreviewProvider extends LeetCodeWebview { - ${mdStyles} - ${buttonStyle} + ${markdownEngine.getStyles()} + ${button.style} ${head} @@ -99,19 +92,31 @@ class LeetCodePreviewProvider extends LeetCodeWebview { ${tags} ${companies} ${body} - + ${button.element} `; } + protected onDidDisposeWebview(): void { + super.onDidDisposeWebview(); + delete this.node; + delete this.description; + } + + protected async onDidReceiveMessage(message: IWebViewMessage): Promise { + switch (message.command) { + case "ShowProblem": { + await commands.executeCommand("leetcode.showProblem", this.node); + break; + } + } + } + private parseDescription(descString: string, problem: IProblem): IDescription { const [ /* title */, , diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index c344b752..b433d5ba 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -11,17 +11,15 @@ class LeetCodeSolutionProvider extends LeetCodeWebview { private solution: Solution; public async show(solutionString: string, problem: IProblem): Promise { - this.solution = this.parseSolution(solutionString); - if (this.showWebviewInternal()) { - this.panel.title = `${problem.name}: Solution`; - this.panel.reveal(ViewColumn.Active); - } + this.solution = this.parseSolution(solutionString, problem); + this.showWebviewInternal(); } protected getWebviewOption(): ILeetCodeWebviewOption { return { viewType: "leetcode.solution", - title: "Top Voted Solution", + title: `${this.solution.problem}: Solution`, + viewColumn: ViewColumn.One, }; } @@ -54,7 +52,12 @@ class LeetCodeSolutionProvider extends LeetCodeWebview { `; } - private parseSolution(raw: string): Solution { + protected onDidDisposeWebview(): void { + super.onDidDisposeWebview(); + delete this.solution; + } + + private parseSolution(raw: string, problem: IProblem): Solution { const solution: Solution = new Solution(); // [^] matches everything including \n, yet can be replaced by . in ES2018's `m` flag raw = raw.slice(1); // skip first empty line @@ -64,6 +67,7 @@ class LeetCodeSolutionProvider extends LeetCodeWebview { [solution.author, raw] = raw.match(/\* Author:\s+(.+)\n([^]+)/)!.slice(1); [solution.votes, raw] = raw.match(/\* Votes:\s+(\d+)\n\n([^]+)/)!.slice(1); solution.body = raw; + solution.problem = problem.name; return solution; } } @@ -76,6 +80,7 @@ class Solution { public author: string = ""; public votes: string = ""; public body: string = ""; // Markdown supported + public problem: string = ""; } export const leetCodeSolutionProvider: LeetCodeSolutionProvider = new LeetCodeSolutionProvider(); diff --git a/src/webview/leetCodeSubmissionProvider.ts b/src/webview/leetCodeSubmissionProvider.ts index e165e61a..d12e6f41 100644 --- a/src/webview/leetCodeSubmissionProvider.ts +++ b/src/webview/leetCodeSubmissionProvider.ts @@ -11,9 +11,7 @@ class LeetCodeSubmissionProvider extends LeetCodeWebview { public async show(result: string): Promise { this.result = result; - if (this.showWebviewInternal()) { - this.panel.reveal(ViewColumn.Two); - } + this.showWebviewInternal(); } protected getWebviewOption(): ILeetCodeWebviewOption { @@ -37,6 +35,11 @@ class LeetCodeSubmissionProvider extends LeetCodeWebview { `; } + + protected onDidDisposeWebview(): void { + super.onDidDisposeWebview(); + delete this.result; + } } export const leetCodeSubmissionProvider: LeetCodeSubmissionProvider = new LeetCodeSubmissionProvider(); diff --git a/tslint.json b/tslint.json index dec1de2e..34776be7 100644 --- a/tslint.json +++ b/tslint.json @@ -26,6 +26,10 @@ "property-declaration", "variable-declaration", "member-variable-declaration" + ], + "variable-name": [ + true, + "allow-leading-underscore" ] }, "rulesDirectory": []