From 52a84d3fd61f693d65fe2c5d259e2d949f2d4dbf Mon Sep 17 00:00:00 2001 From: Ye Zhihao Date: Mon, 22 Apr 2019 14:22:02 +0800 Subject: [PATCH] Add side mode to webview (#271) --- README.md | 1 + docs/README_zh-CN.md | 1 + package-lock.json | 6 ++-- package.json | 8 ++++- src/commands/show.ts | 23 ++++++++++-- src/commands/submit.ts | 2 +- src/commands/test.ts | 2 +- src/extension.ts | 2 +- src/webview/LeetCodeWebview.ts | 17 ++++++--- src/webview/leetCodePreviewProvider.ts | 44 +++++++++++++++++------ src/webview/leetCodeSolutionProvider.ts | 21 +++++++---- src/webview/leetCodeSubmissionProvider.ts | 4 +-- 12 files changed, 97 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 21b3afaf..7e89a880 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ | `leetcode.outputFolder`| Specify the relative path to save the problem files. Besides using customized path, there are also several reserved words which can be used here: | N/A | | `leetcode.enableStatusBar` | Specify whether the LeetCode status bar will be shown or not. | `true` | | `leetcode.enableShortcuts` | Specify whether the submit and test shortcuts in editor or not. | `true` | +| `leetcode.enableSideMode` | Specify whether `preview`, `solution` and `submission` tab should be grouped into the second editor column when solving a problem. | `true` | | `leetcode.nodePath` | Specify the `Node.js` executable path. | `node` | ## Want Help? diff --git a/docs/README_zh-CN.md b/docs/README_zh-CN.md index 070f5d61..554103b7 100644 --- a/docs/README_zh-CN.md +++ b/docs/README_zh-CN.md @@ -141,6 +141,7 @@ | `leetcode.outputFolder` | 指定保存文件时所用的相对文件夹路径。除了用户自定义路径外,也可以使用保留项,包括: | N/A | | `leetcode.enableStatusBar` | 指定是否在 VS Code 下方显示插件状态栏。 | `true` | | `leetcode.enableShortcuts` | 指定是否在 VS Code 编辑文件下方显示提交和测试的快捷按钮。 | `true` | +| `leetcode.enableSideMode` | 指定在解决一道题时,是否将`问题预览`、`高票答案`与`提交结果`窗口集中在编辑器的第二栏。 | `true` | | `leetcode.nodePath` | 指定 `Node.js` 可执行文件的路径。 | `node` | ## 需要帮助? diff --git a/package-lock.json b/package-lock.json index 92f93c1a..9db2eaa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1782,9 +1782,9 @@ } }, "vsc-leetcode-cli": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/vsc-leetcode-cli/-/vsc-leetcode-cli-2.6.3.tgz", - "integrity": "sha512-o6rR4FRaYFV6JBV3bLEvi/OCgRknxRZyC2MQY/ayS9emDNjS/9wr1LDoWJn6tDIR7JwOFLSSTQcpc3DwMsFtkA==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/vsc-leetcode-cli/-/vsc-leetcode-cli-2.6.4.tgz", + "integrity": "sha512-vHcy2WQmQlcm2jB2m5Z+2RfuW0/4S+WjOhXE7twn2lvMgb9jqq3h+MYVHr1ijxraRwiCb5sGknz0T17zkob3gA==", "requires": { "ansi-styles": "3.2.1", "cheerio": "0.20.0", diff --git a/package.json b/package.json index 1971214b..6df14382 100644 --- a/package.json +++ b/package.json @@ -301,6 +301,12 @@ "scope": "application", "description": "Show the submit and test shortcuts in editor or not." }, + "leetcode.enableSideMode": { + "type": "boolean", + "default": true, + "scope": "application", + "description": "Determine whether to group all webview pages into the second editor column when solving problems." + }, "leetcode.nodePath": { "type": "string", "default": "node", @@ -338,6 +344,6 @@ "markdown-it": "^8.4.2", "require-from-string": "^2.0.2", "unescape-js": "^1.1.1", - "vsc-leetcode-cli": "2.6.3" + "vsc-leetcode-cli": "2.6.4" } } diff --git a/src/commands/show.ts b/src/commands/show.ts index a72c31bf..1f7b7e52 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -13,9 +13,15 @@ import { IProblem, IQuickItemEx, languages, ProblemState } from "../shared"; import { DialogOptions, DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils"; import { selectWorkspaceFolder } from "../utils/workspaceUtils"; import * as wsl from "../utils/wslUtils"; +import { leetCodePreviewProvider } from "../webview/leetCodePreviewProvider"; import { leetCodeSolutionProvider } from "../webview/leetCodeSolutionProvider"; import * as list from "./list"; +export async function previewProblem(node: IProblem, isSideMode: boolean = false): Promise { + const descString: string = await leetCodeExecutor.getDescription(node); + leetCodePreviewProvider.show(descString, node, isSideMode); +} + export async function showProblem(node?: LeetCodeNode): Promise { if (!node) { return; @@ -51,7 +57,7 @@ export async function showSolution(node?: LeetCodeNode): Promise { } try { const solution: string = await leetCodeExecutor.showSolution(node, language); - await leetCodeSolutionProvider.show(unescapeJS(solution), node); + leetCodeSolutionProvider.show(unescapeJS(solution), node); } catch (error) { leetCodeChannel.appendLine(error.toString()); await promptForOpenOutputChannel("Failed to fetch the top voted solution. Please open the output channel for details.", DialogType.error); @@ -95,7 +101,7 @@ async function showProblemInternal(node: IProblem): Promise { // SUGGESTION: group config retriving into one file const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); let outDir: string = await selectWorkspaceFolder(); - let relativePath: string = (leetCodeConfig.get("outputFolder") || "").trim(); + let relativePath: string = (leetCodeConfig.get("outputFolder", "")).trim(); const matchResult: RegExpMatchArray | null = relativePath.match(/\$\{(.*?)\}/); if (matchResult) { const resolvedPath: string | undefined = await resolveRelativePath(matchResult[1].toLocaleLowerCase(), node, language); @@ -111,12 +117,23 @@ async function showProblemInternal(node: IProblem): Promise { const originFilePath: string = await leetCodeExecutor.showProblem(node, language, outDir); const filePath: string = wsl.useWsl() ? await wsl.toWinPath(originFilePath) : originFilePath; - await vscode.window.showTextDocument(vscode.Uri.file(filePath), { preview: false, viewColumn: vscode.ViewColumn.One }); + await Promise.all([ + vscode.window.showTextDocument(vscode.Uri.file(filePath), { preview: false, viewColumn: vscode.ViewColumn.One }), + movePreviewAsideIfNeeded(node), + ]); } catch (error) { await promptForOpenOutputChannel("Failed to show the problem. Please open the output channel for details.", DialogType.error); } } +async function movePreviewAsideIfNeeded(node: IProblem): Promise { + if (vscode.workspace.getConfiguration("leetcode").get("enableSideMode", true)) { + return previewProblem(node, true); + } else { + return Promise.resolve(); + } +} + async function parseProblemsToPicks(p: Promise): Promise>> { return new Promise(async (resolve: (res: Array>) => void): Promise => { const picks: Array> = (await p).map((problem: IProblem) => Object.assign({}, { diff --git a/src/commands/submit.ts b/src/commands/submit.ts index 2035ec11..f6e2d989 100644 --- a/src/commands/submit.ts +++ b/src/commands/submit.ts @@ -21,7 +21,7 @@ export async function submitSolution(uri?: vscode.Uri): Promise { try { const result: string = await leetCodeExecutor.submitSolution(filePath); - await leetCodeSubmissionProvider.show(result); + 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 558f7409..262f7339 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -81,7 +81,7 @@ export async function testSolution(uri?: vscode.Uri): Promise { if (!result) { return; } - await leetCodeSubmissionProvider.show(result); + 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 60138085..877ff90c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -51,7 +51,7 @@ export async function activate(context: vscode.ExtensionContext): Promise vscode.commands.registerCommand("leetcode.signout", () => leetCodeManager.signOut()), vscode.commands.registerCommand("leetcode.selectSessions", () => session.selectSession()), vscode.commands.registerCommand("leetcode.createSession", () => session.createSession()), - vscode.commands.registerCommand("leetcode.previewProblem", (node: LeetCodeNode) => leetCodePreviewProvider.show(node)), + vscode.commands.registerCommand("leetcode.previewProblem", (node: LeetCodeNode) => show.previewProblem(node)), vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(node)), vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem()), vscode.commands.registerCommand("leetcode.showSolution", (node: LeetCodeNode) => show.showSolution(node)), diff --git a/src/webview/LeetCodeWebview.ts b/src/webview/LeetCodeWebview.ts index 7b0e9b8d..adac21e8 100644 --- a/src/webview/LeetCodeWebview.ts +++ b/src/webview/LeetCodeWebview.ts @@ -1,11 +1,12 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import { ConfigurationChangeEvent, Disposable, ViewColumn, WebviewPanel, window, workspace } from "vscode"; +import { commands, ConfigurationChangeEvent, Disposable, ViewColumn, WebviewPanel, window, workspace } from "vscode"; import { markdownEngine } from "./markdownEngine"; export abstract class LeetCodeWebview implements Disposable { + protected readonly viewType: string = "leetcode.webview"; protected panel: WebviewPanel | undefined; private listeners: Disposable[] = []; @@ -16,9 +17,9 @@ export abstract class LeetCodeWebview implements Disposable { } protected showWebviewInternal(): void { - const { viewType, title, viewColumn, preserveFocus } = this.getWebviewOption(); + const { title, viewColumn, preserveFocus } = this.getWebviewOption(); if (!this.panel) { - this.panel = window.createWebviewPanel(viewType, title, { viewColumn, preserveFocus }, { + this.panel = window.createWebviewPanel(this.viewType, title, { viewColumn, preserveFocus }, { enableScripts: true, enableCommandUris: true, enableFindWidget: true, @@ -30,7 +31,14 @@ export abstract class LeetCodeWebview implements Disposable { workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.listeners); } else { this.panel.title = title; - this.panel.reveal(viewColumn, preserveFocus); + if (viewColumn === ViewColumn.Two) { + // Make sure second group exists. See vscode#71608 issue + commands.executeCommand("workbench.action.focusSecondEditorGroup").then(() => { + this.panel!.reveal(viewColumn, preserveFocus); + }); + } else { + this.panel.reveal(viewColumn, preserveFocus); + } } this.panel.webview.html = this.getWebviewContent(); } @@ -57,7 +65,6 @@ export abstract class LeetCodeWebview implements Disposable { } export interface ILeetCodeWebviewOption { - viewType: string; title: string; viewColumn: ViewColumn; preserveFocus?: boolean; diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index 9189d769..11b32fc4 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -2,28 +2,44 @@ // Licensed under the MIT license. import { commands, ViewColumn } from "vscode"; -import { leetCodeExecutor } from "../leetCodeExecutor"; import { IProblem } from "../shared"; import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview"; import { markdownEngine } from "./markdownEngine"; class LeetCodePreviewProvider extends LeetCodeWebview { + protected readonly viewType: string = "leetcode.preview"; private node: IProblem; private description: IDescription; + private sideMode: boolean = false; - public async show(node: IProblem): Promise { - this.description = this.parseDescription(await leetCodeExecutor.getDescription(node), node); + public isSideMode(): boolean { + return this.sideMode; + } + + public show(descString: string, node: IProblem, isSideMode: boolean = false): void { + this.description = this.parseDescription(descString, node); this.node = node; + this.sideMode = isSideMode; this.showWebviewInternal(); + if (this.sideMode) { + this.hideSideBar(); // For better view area + } } protected getWebviewOption(): ILeetCodeWebviewOption { - return { - viewType: "leetcode.preview", - title: `${this.node.name}: Preview`, - viewColumn: ViewColumn.One, - }; + if (!this.sideMode) { + return { + title: `${this.node.name}: Preview`, + viewColumn: ViewColumn.One, + }; + } else { + return { + title: "Description", + viewColumn: ViewColumn.Two, + preserveFocus: true, + }; + } } protected getWebviewContent(): string { @@ -84,7 +100,7 @@ class LeetCodePreviewProvider extends LeetCodeWebview { ${markdownEngine.getStyles()} - ${button.style} + ${!this.sideMode ? button.style : ""} ${head} @@ -92,10 +108,10 @@ class LeetCodePreviewProvider extends LeetCodeWebview { ${tags} ${companies} ${body} - ${button.element} + ${!this.sideMode ? button.element : ""} @@ -106,6 +122,7 @@ class LeetCodePreviewProvider extends LeetCodeWebview { super.onDidDisposeWebview(); delete this.node; delete this.description; + this.sideMode = false; } protected async onDidReceiveMessage(message: IWebViewMessage): Promise { @@ -117,6 +134,11 @@ class LeetCodePreviewProvider extends LeetCodeWebview { } } + private async hideSideBar(): Promise { + await commands.executeCommand("workbench.action.focusSideBar"); + await commands.executeCommand("workbench.action.toggleSidebarVisibility"); + } + private parseDescription(descString: string, problem: IProblem): IDescription { const [ /* title */, , diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index b433d5ba..952af2cd 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -3,24 +3,33 @@ import { ViewColumn } from "vscode"; import { IProblem } from "../shared"; +import { leetCodePreviewProvider } from "./leetCodePreviewProvider"; import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview"; import { markdownEngine } from "./markdownEngine"; class LeetCodeSolutionProvider extends LeetCodeWebview { + protected readonly viewType: string = "leetcode.solution"; private solution: Solution; - public async show(solutionString: string, problem: IProblem): Promise { + public show(solutionString: string, problem: IProblem): void { this.solution = this.parseSolution(solutionString, problem); this.showWebviewInternal(); } protected getWebviewOption(): ILeetCodeWebviewOption { - return { - viewType: "leetcode.solution", - title: `${this.solution.problem}: Solution`, - viewColumn: ViewColumn.One, - }; + if (!leetCodePreviewProvider.isSideMode()) { + return { + title: `${this.solution.problem}: Solution`, + viewColumn: ViewColumn.One, + }; + } else { + return { + title: "Solution", + viewColumn: ViewColumn.Two, + preserveFocus: true, + }; + } } protected getWebviewContent(): string { diff --git a/src/webview/leetCodeSubmissionProvider.ts b/src/webview/leetCodeSubmissionProvider.ts index d12e6f41..6905099e 100644 --- a/src/webview/leetCodeSubmissionProvider.ts +++ b/src/webview/leetCodeSubmissionProvider.ts @@ -7,16 +7,16 @@ import { markdownEngine } from "./markdownEngine"; class LeetCodeSubmissionProvider extends LeetCodeWebview { + protected readonly viewType: string = "leetcode.submission"; private result: string; - public async show(result: string): Promise { + public show(result: string): void { this.result = result; this.showWebviewInternal(); } protected getWebviewOption(): ILeetCodeWebviewOption { return { - viewType: "leetcode.submission", title: "Submission", viewColumn: ViewColumn.Two, };