如何在vscode中用rust刷leetcode
步骤
安装 vscode 插件
- vscode-leetcode( leetcode 题库)
- CodeLLDB ( Debug Rust )
- rust-analyzer ( Rust 编译器代码提示)
- pucelle.run-on-save (新建的题目保存触发脚本修改 lib.rs ,从而触发编译器检查)
- 注: emeraldwalk的那个插件不支持配置正则搜索文件夹触发脚本,只能配置文件名后缀
创建 leetcode 项目
- 使用
cargo new leetcode-rs --lib
创建一个 rust 项目,这一步是因为 rust-analyzer 插件是以项目为基础去进行编译检查的 - 如果不使用 cargo ,也可以在文件夹下新建一个
rust-project.json
文件去替代Cargo.toml
组织项目配置。
json
// rust-project.json
{
// rustc 会根据该属性去寻找 rust 标准库(例如 std)的位置。 可以通过 rustc --print sysroot 来获取 sysroot 的值。
"sysroot": "xxx",
// 描述项目文件的具体信息
"crates": [
{
"root_module": "xxx.rs", // 入口文件的路径
"edition": "2024", // crate 的版本
"deps": [] // 项目依赖
}
]
}
修改 leetcode 插件配置
- 因为 leetcode 插件默认新建题目的文件名都是
${id}.${kebab-case-name}.${ext}
格式(1.两数之和.rs),rust 项目规范中文件名不能包含点 . ,所以这里要替换以下默认的格式。同时配置 leetcoe 文件夹目录。
json
// settings.json
"leetcode.workspaceFolder": "/leetcode",
"leetcode.filePath": {
"default": {
// 在 src 下新建 solutions 文件夹作为 leetcode 的目录
"folder": "src/solutions",
// 将文件名 . 改为 _
"filename": "${id}_${kebab-case-name}.${ext}"
}
},
使用 automod 宏自动添加引用新题目到模块
- 项目安装 automod :
cargo add automod
- 在项目 lib.rs 下使用宏自动引入 solutions 下的模块
- 这一步如果不是用 cargo 新建的项目,那么自己写脚本通过 run-on-save 让每次保存的时候更新
rust-project.json
里面的 crates 信息
rust
// src/lib.rs
const CURRENT: &str = "xxx.rs";
pub mod solutions {
automod::dir!("src/solutions");
}
使用 run-on-save
这一步的目的是为了新建题目文件的时候可以通过保存自动去修改 lib.rs 文件内容从而让 automod 引入新的题目模块和触发 rust-analyser 去做代码检查和提示
需要在项目下配置 run-on-save
json// 新建 .vscode 文件夹和 settings.json 文件 // .vscode/settings.json { "runOnSave.commands": [ { // 匹配 src 文件夹下的任何 rs 文件改动时触发 command "globMatch": "leetcode-rs/src/**/*.rs", // 编写 onsave.sh 脚本去修改 src/lib.rs 文件从而触发 rust-analyser 重新分析项目,这里我把脚本都丢进.vscode 文件夹内 "command": "sh .vscode/onsave.sh ${fileBasename}", "runIn": "backend", }, ], }
编写 onsave.sh 脚本,window 可以让 ai 重新改写下
bash
#!/bin/bash
# 触发保存的文件名
FILE=$1
# 目标文件路径
TARGET_FILE="leetcode-rs/src/lib.rs"
if [[ $(grep -c "$FILE" "$TARGET_FILE") -eq 0 ]]; then
# 修改 CURRENT 变量为 触发更新的文件名称
sed -i '' -E "s/(CURRENT: &str = \")[^\"]*(\";.*)/\1$FILE\2/" "$TARGET_FILE"
# 修改 src/lib.rs 里面的 automod::dir!(pub "src/solutions"); 语句
# 通过添加和移除 pub 前缀来触发 rust-analyser 重新分析项目
if [[ $(grep -c '::dir!(pub' "$TARGET_FILE") -eq 1 ]]; then
sed -i '' "s|::dir!(pub |::dir!(|" "$TARGET_FILE"
else
sed -i '' "s|::dir!(|::dir!(pub |" "$TARGET_FILE"
fi
fi
修改 leetcode 插件自动添加测试用例
- leetcode 插件不支持动态配置新建题目文件的模版,这里我们需要去修改 leetcode 插件源码
- mac 目录: ~/.vscode/extensions/leetcode.vscode-leetcode-xxx/out/src/leetCodeExecutor.js
- window 目录: C:\Users<用户名>.vscode\extensions\leetcode.vscode-leetcode-xxx...
- 测试的时候也可以通过
RUSTFLAGS="-A unused" cargo test -- --nocapture
命令忽略 unused 告警
js
showProblem(){
// more...
if (!(yield fse.pathExists(filePath))) {
yield fse.createFile(filePath);
const codeTemplate = yield this.executeCommandWithProgressEx("Fetching problem data...", this.nodeExecutable, cmd);
yield fse.writeFile(filePath, codeTemplate);
}
// 在这里后添加插桩代码
if (language == 'rust') {
let code = yield fse.readFile(filePath, 'utf-8');
if (code) {
const solutionIdx = code.indexOf('impl Solution')
const beforeStr = code.slice(0, solutionIdx)
if (!beforeStr.includes('#[allow(unused)]')) {
code = beforeStr + '\n#[allow(unused)]\n' + code.slice(solutionIdx)
}
if (!code.includes("mod test")) {
const regex = /.*pub\s+fn\s+(\w+)/s; // 匹配最后一个 pub fn Name
const match = code.match(regex);
const funcName = match ? match[1] : 'fn'
const rustCode = `
#[allow(unused)]
struct Solution;
#[cfg(test)]
mod test {
#[allow(unused)]
use super::*;
#[test]
fn test_${funcName}() {
//assert_eq!(Solution::${funcName}(vec![]),[]);
assert!(true)
}
}
`
code += rustCode
}
yield fse.writeFile(filePath, code);
}
}
// 插桩结束
// more...
}