Skip to content

Commit 457425d

Browse files
committed
feat: add doc generation
1 parent d713ad5 commit 457425d

File tree

8 files changed

+4795
-4507
lines changed

8 files changed

+4795
-4507
lines changed

package-lock.json

+3,789-3,789
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+724-709
Large diffs are not rendered by default.

src/commands/show.ts

+37
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,43 @@ export async function showProblem(node?: LeetCodeNode): Promise<void> {
5959
await showProblemInternal(node);
6060
}
6161

62+
export async function showDocumentation(node?: LeetCodeNode): Promise<void> {
63+
if (!node) {
64+
return;
65+
}
66+
console.log(`Show documentation: ${node.id}`);
67+
try {
68+
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
69+
const workspaceFolder: string = await selectWorkspaceFolder();
70+
if (!workspaceFolder) {
71+
return;
72+
}
73+
74+
const fileFolder: string = leetCodeConfig.get<string>(`filePath.doc.folder`, "").trim();
75+
const fileName: string = leetCodeConfig.get<string>(`filePath.doc.filename`, "").trim();
76+
if (fileFolder === "" || fileName === "") {
77+
await promptForOpenOutputChannel("Please configure the file path first.", DialogType.error);
78+
return;
79+
}
80+
81+
let finalPath: string = path.join(workspaceFolder, fileFolder, fileName);
82+
if (finalPath) {
83+
finalPath = await resolveRelativePath(finalPath, node, "md");
84+
if (!finalPath) {
85+
leetCodeChannel.appendLine("Showing problem canceled by user.");
86+
return;
87+
}
88+
}
89+
finalPath = wsl.useWsl() ? await wsl.toWinPath(finalPath) : finalPath;
90+
console.log(`Documentation path: ${finalPath}`);
91+
92+
await leetCodeExecutor.showDocumentationInternal(node, finalPath);
93+
}
94+
catch (error) {
95+
console.log(error);
96+
}
97+
}
98+
6299
export async function searchProblem(): Promise<void> {
63100
if (!leetCodeManager.getUser()) {
64101
promptForSignIn();

src/commands/submit.ts

+45-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { leetCodeTreeDataProvider } from "../explorer/LeetCodeTreeDataProvider";
66
import { leetCodeExecutor } from "../leetCodeExecutor";
77
import { leetCodeManager } from "../leetCodeManager";
88
import { DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils";
9-
import { getActiveFilePath } from "../utils/workspaceUtils";
9+
import { copyCodeBlock, insertSubmitResult } from "../utils/workspaceUtils";
1010
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";
1111

1212
export async function submitSolution(uri?: vscode.Uri): Promise<void> {
@@ -15,14 +15,55 @@ export async function submitSolution(uri?: vscode.Uri): Promise<void> {
1515
return;
1616
}
1717

18-
const filePath: string | undefined = await getActiveFilePath(uri);
18+
uri;
19+
20+
const editor = vscode.window.activeTextEditor;
21+
if (editor) {
22+
editor.document.save();
23+
}
24+
25+
// const filePath: string | undefined = await getActiveFilePath(uri);
26+
const filePath: string | undefined = await copyCodeBlock();
1927
if (!filePath) {
2028
return;
2129
}
2230

2331
try {
24-
const result: string = await leetCodeExecutor.submitSolution(filePath);
25-
leetCodeSubmissionProvider.show(result);
32+
const message: string = await leetCodeExecutor.submitSolution(filePath);
33+
// console.log("submit result", message);
34+
35+
// 如果 message 不包含 "Accepted",则说明提交失败
36+
if (!message.includes("Accepted")) {
37+
leetCodeSubmissionProvider.show(message);
38+
return;
39+
}
40+
41+
// 获取今天日期字符串,例如 "2021-11-16"
42+
const today = new Date().toISOString().slice(0, 10);
43+
44+
// 匹配通过测试用例数、运行时间和内存使用情况的正则表达式
45+
const regex_cases_passed = /(\d+)\/(\d+) cases passed/;
46+
const regex_runtime_percentage = /Your runtime beats (\d+\.\d+) %/;
47+
const regex_memory_percentage = /Your memory usage beats (\d+\.\d+) %/;
48+
const regex_runtime_ms = /(\d+) ms/;
49+
const regex_memory_usage = /\((\d+\.\d+) MB\)/;
50+
51+
// 提取通过测试用例数、运行时间和内存使用情况
52+
const [cases_passed, total_cases] = regex_cases_passed.exec(message)?.slice(1) ?? [];
53+
const runtime_percentage = regex_runtime_percentage.exec(message)?.[1] ?? [];
54+
const memory_percentage = regex_memory_percentage.exec(message)?.[1] ?? [];
55+
const runtime_ms = regex_runtime_ms.exec(message)?.[1] ?? [];
56+
const memory_usage = regex_memory_usage.exec(message)?.[1] ?? [];
57+
58+
// 格式化输出结果
59+
const result = `// ${today} submission
60+
// ${cases_passed}/${total_cases} cases passed
61+
// Runtime: ${runtime_ms} ms, faster than ${runtime_percentage}% of cpp online submissions.
62+
// Memory Usage: ${memory_usage} MB, less than ${memory_percentage}% of cpp online submissions.`;
63+
64+
// console.log(result);
65+
await insertSubmitResult(result);
66+
2667
} catch (error) {
2768
await promptForOpenOutputChannel("Failed to submit the solution. Please open the output channel for details.", DialogType.error);
2869
return;

src/commands/test.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { leetCodeManager } from "../leetCodeManager";
88
import { IQuickItemEx, UserStatus } from "../shared";
99
import { isWindows, usingCmd } from "../utils/osUtils";
1010
import { DialogType, promptForOpenOutputChannel, showFileSelectDialog } from "../utils/uiUtils";
11-
import { getActiveFilePath } from "../utils/workspaceUtils";
11+
// import { getActiveFilePath } from "../utils/workspaceUtils";
12+
import { copyCodeBlock } from "../utils/workspaceUtils";
1213
import * as wsl from "../utils/wslUtils";
1314
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";
1415

@@ -18,7 +19,9 @@ export async function testSolution(uri?: vscode.Uri): Promise<void> {
1819
return;
1920
}
2021

21-
const filePath: string | undefined = await getActiveFilePath(uri);
22+
// const filePath: string | undefined = await getActiveFilePath(uri);
23+
uri;
24+
const filePath: string | undefined = await copyCodeBlock();
2225
if (!filePath) {
2326
return;
2427
}

src/extension.ts

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
5757
vscode.commands.registerCommand("leetcode.manageSessions", () => session.manageSessions()),
5858
vscode.commands.registerCommand("leetcode.previewProblem", (node: LeetCodeNode) => show.previewProblem(node)),
5959
vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(node)),
60+
vscode.commands.registerCommand("leetcode.showDocumentation", (node: LeetCodeNode) => show.showDocumentation(node)),
6061
vscode.commands.registerCommand("leetcode.pickOne", () => show.pickOne()),
6162
vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem()),
6263
vscode.commands.registerCommand("leetcode.showSolution", (input: LeetCodeNode | vscode.Uri) => show.showSolution(input)),

src/leetCodeExecutor.ts

+103-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ import { DialogOptions, openUrl } from "./utils/uiUtils";
1414
import * as wsl from "./utils/wslUtils";
1515
import { toWslPath, useWsl } from "./utils/wslUtils";
1616

17+
interface IDescription {
18+
title: string;
19+
url: string;
20+
tags: string[];
21+
companies: string[];
22+
category: string;
23+
difficulty: string;
24+
likes: string;
25+
dislikes: string;
26+
body: string;
27+
}
28+
29+
const promiseWithTimeout = (promise, timeout) => {
30+
let timeoutId;
31+
const timeoutPromise = new Promise((_, reject) => {
32+
timeoutId = setTimeout(() => {
33+
reject(new Error(`Operation timed out after ${timeout} milliseconds`));
34+
}, timeout);
35+
});
36+
37+
return Promise.race([promise, timeoutPromise]).then((result) => {
38+
clearTimeout(timeoutId);
39+
return result;
40+
});
41+
};
42+
1743
class LeetCodeExecutor implements Disposable {
1844
private leetCodeRootPath: string;
1945
private nodeExecutable: string;
@@ -100,6 +126,77 @@ class LeetCodeExecutor implements Disposable {
100126
return await this.executeCommandEx(this.nodeExecutable, cmd);
101127
}
102128

129+
private parseDescription(descString: string, problem: IProblem): IDescription {
130+
const [
131+
/* title */, ,
132+
url, ,
133+
/* tags */, ,
134+
/* langs */, ,
135+
category,
136+
difficulty,
137+
likes,
138+
dislikes,
139+
/* accepted */,
140+
/* submissions */,
141+
/* testcase */, ,
142+
...body
143+
] = descString.split("\n");
144+
return {
145+
title: problem.name,
146+
url,
147+
tags: problem.tags,
148+
companies: problem.companies,
149+
category: category.slice(2),
150+
difficulty: difficulty.slice(2),
151+
likes: likes.split(": ")[1].trim(),
152+
dislikes: dislikes.split(": ")[1].trim(),
153+
body: body.join("\n").replace(/<pre>[\r\n]*([^]+?)[\r\n]*<\/pre>/g, "<pre><code>$1</code></pre>"),
154+
};
155+
}
156+
157+
public async showDocumentationInternal(problemNode: IProblem, filePath: string): Promise<void> {
158+
159+
const descString: string = await this.getDescription(problemNode.id, false);
160+
const description = this.parseDescription(descString, problemNode);
161+
const { title, url, category, difficulty, likes, dislikes, body } = description;
162+
163+
const head: string = `# [${problemNode.id}. ${title}](${url})`;
164+
const info: string = [
165+
`| Category | Difficulty | Likes | Dislikes |`,
166+
`| :------: | :--------: | :---: | :------: |`,
167+
`| ${category} | ${difficulty} | ${likes} | ${dislikes} |`,
168+
].join("\n");
169+
const tags = "**Tags**: " + description.tags.join(",");
170+
const companies = "**Companies**: " + description.companies.join(",");
171+
172+
const md_content = [
173+
head,
174+
'',
175+
"## Description",
176+
'',
177+
tags,
178+
'',
179+
companies,
180+
'',
181+
info,
182+
'',
183+
body.replace(/\t/g, " "),
184+
"## Solution\n",
185+
"**题目描述**\n",
186+
"**解题思路**\n",
187+
].join("\n");
188+
189+
// console.log("md_content", md_content);
190+
191+
if (!await fse.pathExists(filePath)) {
192+
await fse.createFile(filePath);
193+
await fse.writeFile(filePath, md_content);
194+
workspace.openTextDocument(filePath).then((document) => {
195+
window.showTextDocument(document);
196+
});
197+
}
198+
}
199+
103200
public async showProblem(problemNode: IProblem, language: string, filePath: string, showDescriptionInComment: boolean = false, needTranslation: boolean): Promise<void> {
104201
const templateType: string = showDescriptionInComment ? "-cx" : "-c";
105202
const cmd: string[] = [await this.getLeetCodeBinaryPath(), "show", problemNode.id, templateType, "-l", language];
@@ -164,7 +261,8 @@ class LeetCodeExecutor implements Disposable {
164261

165262
public async submitSolution(filePath: string): Promise<string> {
166263
try {
167-
return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]);
264+
return promiseWithTimeout(this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]), 10000);
265+
// return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]);
168266
} catch (error) {
169267
if (error.result) {
170268
return error.result;
@@ -175,9 +273,11 @@ class LeetCodeExecutor implements Disposable {
175273

176274
public async testSolution(filePath: string, testString?: string): Promise<string> {
177275
if (testString) {
178-
return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]);
276+
return promiseWithTimeout(this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]), 10000);
277+
// return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]);
179278
}
180-
return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`]);
279+
return promiseWithTimeout(this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`]), 10000);
280+
// return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`]);
181281
}
182282

183283
public async switchEndpoint(endpoint: string): Promise<string> {

src/utils/workspaceUtils.ts

+91
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,97 @@ export async function selectWorkspaceFolder(): Promise<string> {
5959
return wsl.useWsl() ? wsl.toWslPath(workspaceFolderSetting) : workspaceFolderSetting;
6060
}
6161

62+
export async function insertSubmitResult(result?: string): Promise<string | undefined> {
63+
if (!result) {
64+
return;
65+
}
66+
const editor = vscode.window.activeTextEditor;
67+
if (!editor) {
68+
vscode.window.showErrorMessage("Please open a solution file first.");
69+
return;
70+
}
71+
editor.document.save();
72+
73+
// 获取当前光标所在行
74+
const currentLine = editor.selection.active.line;
75+
// 向前遍历找到第一次出现 "// @lc code=start" 的行
76+
let startLine = currentLine;
77+
while (startLine >= 0) {
78+
const lineText = editor.document.lineAt(startLine).text;
79+
if (lineText.includes('// @lc code=start')) {
80+
break;
81+
}
82+
startLine--;
83+
}
84+
if (startLine < 0) {
85+
vscode.window.showErrorMessage("Please add '// @lc code=start' in your solution file.");
86+
return;
87+
}
88+
89+
// 将 result 插入到 startLine 之后
90+
const resultLines: string[] = result.split(os.EOL);
91+
const insertPosition: vscode.Position = new vscode.Position(startLine + 1, 0);
92+
await editor.edit((editBuilder) => {
93+
resultLines.forEach((line) => {
94+
editBuilder.insert(insertPosition, `${line}${os.EOL}`);
95+
});
96+
});
97+
return;
98+
}
99+
100+
export async function copyCodeBlock(): Promise<string | undefined> {
101+
// 获取当前激活的文本编辑器
102+
const editor = vscode.window.activeTextEditor;
103+
if (!editor) {
104+
vscode.window.showErrorMessage('当前没有打开的文本编辑器');
105+
return;
106+
}
107+
// 获取当前光标所在行
108+
const currentLine = editor.selection.active.line;
109+
110+
// 向前遍历找到第一次出现 "// @lc code=start" 的行
111+
let startLine = currentLine;
112+
while (startLine >= 0) {
113+
const lineText = editor.document.lineAt(startLine).text;
114+
if (lineText.includes('// @lc code=start')) {
115+
break;
116+
}
117+
startLine--;
118+
}
119+
120+
// 向后遍历找到第一次出现 "// @lc code=end" 的行
121+
let endLine = currentLine;
122+
while (endLine < editor.document.lineCount) {
123+
const lineText = editor.document.lineAt(endLine).text;
124+
if (lineText.includes('// @lc code=end')) {
125+
break;
126+
}
127+
endLine++;
128+
}
129+
130+
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
131+
const filePath: string = leetCodeConfig.get<string>(`filePath.default.codefile`, "").trim();
132+
if (filePath === "") {
133+
vscode.window.showErrorMessage("Please specify the default code file path in the settings.");
134+
return;
135+
}
136+
137+
// 如果找到了符合条件的区域,将其复制到文件中
138+
if (startLine < endLine) {
139+
const fileContent = `// ${editor.document.lineAt(1).text}\n` + // 添加当前文件正数第二行前面加上 "// "
140+
editor.document.getText(new vscode.Range(
141+
new vscode.Position(startLine, 0),
142+
new vscode.Position(endLine + 1, 0)
143+
));
144+
fse.writeFileSync(filePath, fileContent);
145+
146+
// vscode.window.showInformationMessage(`成功将代码复制到文件 ${filePath}`);
147+
} else {
148+
vscode.window.showWarningMessage('未找到符合条件的代码区域');
149+
}
150+
return filePath;
151+
}
152+
62153
export async function getActiveFilePath(uri?: vscode.Uri): Promise<string | undefined> {
63154
let textEditor: vscode.TextEditor | undefined;
64155
if (uri) {

0 commit comments

Comments
 (0)