diff --git a/package.json b/package.json index 56343331..8b841821 100644 --- a/package.json +++ b/package.json @@ -321,6 +321,7 @@ "dependencies": { "fs-extra": "^6.0.1", "highlight.js": "^9.15.6", + "lodash": "^4.17.11", "lodash.kebabcase": "^4.1.1", "markdown-it": "^8.4.2", "require-from-string": "^2.0.2", diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 8b5ed89d..09f47530 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -1,6 +1,7 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. +import * as _ from "lodash"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; @@ -201,10 +202,10 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider c[0].toUpperCase() + c.slice(1)).join(" "); - } - private getSubCategoryNodes(map: Map, category: Category): LeetCodeNode[] { const subCategoryNodes: LeetCodeNode[] = Array.from(map.keys()).map((subCategory: string) => { return new LeetCodeNode(Object.assign({}, defaultProblem, { diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index 7e3c491c..8acdfeed 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -39,7 +39,7 @@ class LeetCodePreviewProvider implements Disposable { } this.panel.webview.html = await this.provideHtmlContent(node); - this.panel.title = node.name; + this.panel.title = `${node.name}: Preview`; this.panel.reveal(); } diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index 189eed17..4886fef6 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -1,7 +1,9 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. +import * as _ from "lodash"; import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; +import { markdownEngine } from "./markdownEngine"; class LeetCodeResultProvider implements Disposable { @@ -12,11 +14,12 @@ class LeetCodeResultProvider implements Disposable { this.context = context; } - public async show(result: string): Promise { + public async show(resultString: string): Promise { if (!this.panel) { - this.panel = window.createWebviewPanel("leetcode.result", "LeetCode Results", ViewColumn.Two, { + this.panel = window.createWebviewPanel("leetcode.result", "Submission Result", ViewColumn.Two, { retainContextWhenHidden: true, enableFindWidget: true, + localResourceRoots: markdownEngine.localResourceRoots, }); this.panel.onDidDispose(() => { @@ -24,7 +27,8 @@ class LeetCodeResultProvider implements Disposable { }, null, this.context.subscriptions); } - this.panel.webview.html = await this.provideHtmlContent(result); + const result: IResult = this.parseResult(resultString); + this.panel.webview.html = this.getWebViewContent(result); this.panel.reveal(ViewColumn.Two); } @@ -34,19 +38,67 @@ class LeetCodeResultProvider implements Disposable { } } - private async provideHtmlContent(result: string): Promise { - return ` - + private parseResult(raw: string): IResult { + raw = raw.concat(" √ "); // Append a dummy sentinel to the end of raw string + const regSplit: RegExp = / [√×✔✘vx] ([^]+?)\n(?= [√×✔✘vx] )/g; + const regKeyVal: RegExp = /(.+?): ([^]*)/; + const result: IResult = { messages: [] }; + let entry: RegExpExecArray | null; + do { + entry = regSplit.exec(raw); + if (!entry) { + continue; + } + const kvMatch: RegExpExecArray | null = regKeyVal.exec(entry[1]); + if (kvMatch) { + const key: string = _.startCase(kvMatch[1]); + let value: string = kvMatch[2]; + if (!result[key]) { + result[key] = []; + } + if (key === "Testcase") { + value = value.slice(1, -1).replace("\\n", "\n"); + } + result[key].push(value); + } else { + result.messages.push(entry[1]); + } + } while (entry); + return result; + } + + private getWebViewContent(result: IResult): string { + const styles: string = markdownEngine.getStylesHTML(); + const title: string = `## ${result.messages[0]}`; + const messages: string[] = result.messages.slice(1).map((m: string) => `* ${m}`); + const sections: string[] = Object.keys(result).filter((k: string) => k !== "messages").map((key: string) => [ + `### ${key}`, + "```", + result[key].join("\n\n"), + "```", + ].join("\n")); + const body: string = markdownEngine.render([ + title, + ...messages, + ...sections, + ].join("\n")); + return ` + + - - - LeetCode Results + ${styles} - -
${result.trim()}
+ + ${body} - `; + + `; } } +interface IResult { + [key: string]: string[]; + messages: string[]; +} + export const leetCodeResultProvider: LeetCodeResultProvider = new LeetCodeResultProvider(); diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index ac5f5765..6243af02 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -3,18 +3,15 @@ import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { IProblem } from "../shared"; -import { MarkdownEngine } from "./MarkdownEngine"; +import { markdownEngine } from "./markdownEngine"; class LeetCodeSolutionProvider implements Disposable { private context: ExtensionContext; private panel: WebviewPanel | undefined; - private mdEngine: MarkdownEngine; - private solution: Solution; public initialize(context: ExtensionContext): void { this.context = context; - this.mdEngine = new MarkdownEngine(); } public async show(solutionString: string, problem: IProblem): Promise { @@ -22,7 +19,7 @@ class LeetCodeSolutionProvider implements Disposable { this.panel = window.createWebviewPanel("leetCode.solution", "Top Voted Solution", ViewColumn.Active, { retainContextWhenHidden: true, enableFindWidget: true, - localResourceRoots: this.mdEngine.localResourceRoots, + localResourceRoots: markdownEngine.localResourceRoots, }); this.panel.onDidDispose(() => { @@ -30,9 +27,9 @@ class LeetCodeSolutionProvider implements Disposable { }, null, this.context.subscriptions); } - this.solution = this.parseSolution(solutionString); - this.panel.title = problem.name; - this.panel.webview.html = this.getWebViewContent(this.solution); + const solution: Solution = this.parseSolution(solutionString); + this.panel.title = `${problem.name}: Solution`; + this.panel.webview.html = this.getWebViewContent(solution); this.panel.reveal(ViewColumn.Active); } @@ -56,17 +53,17 @@ class LeetCodeSolutionProvider implements Disposable { } private getWebViewContent(solution: Solution): string { - const styles: string = this.mdEngine.getStylesHTML(); + const styles: string = markdownEngine.getStylesHTML(); const { title, url, lang, author, votes } = solution; - const head: string = this.mdEngine.render(`# [${title}](${url})`); + const head: string = markdownEngine.render(`# [${title}](${url})`); const auth: string = `[${author}](https://leetcode.com/${author}/)`; - const info: string = this.mdEngine.render([ + const info: string = markdownEngine.render([ `| Language | Author | Votes |`, `| :------: | :------: | :------: |`, `| ${lang} | ${auth} | ${votes} |`, ].join("\n")); - const body: string = this.mdEngine.render(solution.body, { - lang: this.solution.lang, + const body: string = markdownEngine.render(solution.body, { + lang: solution.lang, host: "https://discuss.leetcode.com/", }); return ` diff --git a/src/webview/MarkdownEngine.ts b/src/webview/markdownEngine.ts similarity index 97% rename from src/webview/MarkdownEngine.ts rename to src/webview/markdownEngine.ts index 0d56382e..b3a45a4a 100644 --- a/src/webview/MarkdownEngine.ts +++ b/src/webview/markdownEngine.ts @@ -8,7 +8,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { leetCodeChannel } from "../leetCodeChannel"; -export class MarkdownEngine { +class MarkdownEngine { private readonly engine: MarkdownIt; private readonly extRoot: string; // root path of vscode built-in markdown extension @@ -106,3 +106,5 @@ export class MarkdownEngine { }; } } + +export const markdownEngine: MarkdownEngine = new MarkdownEngine();