完成 linter 的诊断 pipeline 架构实现
This commit is contained in:
parent
e726fffd99
commit
723ee40a4e
@ -26,11 +26,32 @@ pub struct CacheItem {
|
|||||||
pub cache_name: String
|
pub cache_name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CacheItem {
|
||||||
|
fn default() -> Self {
|
||||||
|
CacheItem {
|
||||||
|
file_name: "".to_string(),
|
||||||
|
size: 0,
|
||||||
|
version: "".to_string(),
|
||||||
|
cache_name: "".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CacheInfo {
|
||||||
|
// version
|
||||||
|
pub version: Option<String>,
|
||||||
|
/// 解析类型文件缓存的根目录
|
||||||
|
pub parser_cache: Option<PathBuf>,
|
||||||
|
/// 第三方 linter 文件缓存的根目录
|
||||||
|
pub linter_cache: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
/// 用于进行高效 IR 缓存的模块
|
/// 用于进行高效 IR 缓存的模块
|
||||||
pub struct CacheManager {
|
pub struct CacheManager {
|
||||||
/// 缓存文件夹根目录
|
/// 缓存文件夹根目录
|
||||||
pub root_dir: PathBuf,
|
pub root_dir: PathBuf,
|
||||||
|
pub cache_info: Arc<RwLock<CacheInfo>>,
|
||||||
/// meta 文件内容
|
/// meta 文件内容
|
||||||
pub meta_name: String,
|
pub meta_name: String,
|
||||||
/// meta 内容
|
/// meta 内容
|
||||||
@ -40,23 +61,52 @@ pub struct CacheManager {
|
|||||||
|
|
||||||
impl CacheManager {
|
impl CacheManager {
|
||||||
pub fn new(root_dir: PathBuf) -> Self {
|
pub fn new(root_dir: PathBuf) -> Self {
|
||||||
// 读入 meta 文件
|
|
||||||
let meta_name = "index.cache";
|
let meta_name = "index.cache";
|
||||||
let meta_path = root_dir.join(meta_name);
|
let cache_info = Arc::new(RwLock::new(CacheInfo {
|
||||||
let meta = get_or_init_meta(&meta_path);
|
version: None,
|
||||||
|
parser_cache: None,
|
||||||
// 如果不存在 root dir,则创建
|
linter_cache: None,
|
||||||
if !root_dir.exists() {
|
}));
|
||||||
match fs::create_dir_all(&root_dir) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(err) => info!("error happen when create {root_dir:?}: {err:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheManager {
|
CacheManager {
|
||||||
root_dir,
|
root_dir,
|
||||||
|
cache_info,
|
||||||
meta_name: meta_name.to_string(),
|
meta_name: meta_name.to_string(),
|
||||||
meta: Arc::new(RwLock::new(meta))
|
meta: Arc::new(RwLock::new(HashMap::<String, CacheItem>::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self, version: &str) {
|
||||||
|
let root_dir = &self.root_dir;
|
||||||
|
let mut cache_info = self.cache_info.write().unwrap();
|
||||||
|
cache_info.version = Some(version.to_string());
|
||||||
|
|
||||||
|
let linter_cache = root_dir.join(version).join("lc");
|
||||||
|
let parser_cache = root_dir.join(version).join("pc");
|
||||||
|
|
||||||
|
// 如果不存在指定的缓存文件夹,则创建
|
||||||
|
// ~/digital-ide/{版本号}/
|
||||||
|
// 📁 lc: linter的cache
|
||||||
|
// 📁 pc: parser的cache
|
||||||
|
CacheManager::check_dir(&linter_cache);
|
||||||
|
CacheManager::check_dir(&parser_cache);
|
||||||
|
|
||||||
|
// 读入 meta 文件
|
||||||
|
let meta_path = parser_cache.join(&self.meta_name);
|
||||||
|
let meta = get_or_init_meta(&meta_path);
|
||||||
|
let mut cache_meta = self.meta.write().unwrap();
|
||||||
|
for (key, value) in meta.into_iter() {
|
||||||
|
cache_meta.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("缓存系统初始化完成,pc: {:?}, lc: {:?}", parser_cache, linter_cache);
|
||||||
|
cache_info.parser_cache = Some(parser_cache);
|
||||||
|
cache_info.linter_cache = Some(linter_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_dir(dir: &PathBuf) {
|
||||||
|
if !dir.exists() {
|
||||||
|
fs::create_dir_all(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +173,7 @@ impl CacheManager {
|
|||||||
hash_string
|
hash_string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 更新缓存,并写入磁盘
|
||||||
pub fn update_cache(&self, path: &PathBuf, fast: FastHdlparam) {
|
pub fn update_cache(&self, path: &PathBuf, fast: FastHdlparam) {
|
||||||
let path_string = path.to_str().unwrap();
|
let path_string = path.to_str().unwrap();
|
||||||
let version = self.get_version(path);
|
let version = self.get_version(path);
|
||||||
@ -139,10 +190,13 @@ impl CacheManager {
|
|||||||
let mut meta_handle = self.meta.write().unwrap();
|
let mut meta_handle = self.meta.write().unwrap();
|
||||||
meta_handle.insert(path_string.to_string(), cache_item);
|
meta_handle.insert(path_string.to_string(), cache_item);
|
||||||
|
|
||||||
|
let cache_info = self.cache_info.read().unwrap();
|
||||||
|
|
||||||
// 准备必要的独立数据塞入线程进行调度
|
// 准备必要的独立数据塞入线程进行调度
|
||||||
|
if let Some(parser_cache) = &cache_info.parser_cache {
|
||||||
let meta = (&*meta_handle).clone();
|
let meta = (&*meta_handle).clone();
|
||||||
let meta_save_path = self.root_dir.join(self.meta_name.clone());
|
let meta_save_path = parser_cache.join(&self.meta_name);
|
||||||
let cache_save_path = self.root_dir.join(cache_name);
|
let cache_save_path = parser_cache.join(cache_name);
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
info!("save meta to {meta_save_path:?}");
|
info!("save meta to {meta_save_path:?}");
|
||||||
@ -152,6 +206,7 @@ impl CacheManager {
|
|||||||
let _ = k_serialize(&cache_save_path, fast);
|
let _ = k_serialize(&cache_save_path, fast);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
#[allow(unused)]
|
||||||
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ropey::Rope;
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, Url};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -22,19 +25,21 @@ impl Default for LinterStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait AbstractLinterConfiguration {
|
pub trait AbstractLinterConfiguration {
|
||||||
fn new(language_id: &str) -> Self;
|
/// - language_id: 语言 id
|
||||||
|
/// - invoker: 调用可执行文件的名字,比如 `iverilog`,`xvlog`,`verible-verilog-syntax`
|
||||||
/// 提供一次诊断
|
/// - args: 调用执行器进行诊断的命令行参数(不包含文件路径)
|
||||||
fn provide_diagnostics(&self);
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self;
|
||||||
|
|
||||||
|
/// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果
|
||||||
|
fn provide_diagnostics(&self, uri: &Url, rope: &Rope) -> Vec<Diagnostic>;
|
||||||
|
|
||||||
/// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe , xvlog 就是 bat
|
/// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe , xvlog 就是 bat
|
||||||
fn get_windows_extension(&self) -> &str;
|
fn get_windows_extension(&self) -> &str;
|
||||||
|
|
||||||
|
/// 获取 linter 安装路径,内部实现,内部调用
|
||||||
fn get_linter_path(&self) -> &str;
|
fn get_linter_path(&self) -> &str;
|
||||||
|
|
||||||
/// 获取真实调用路径,比如
|
/// 获取真实调用路径,比如
|
||||||
///
|
|
||||||
/// iverilog.exe 或者 /path/to/verilog.exe
|
/// iverilog.exe 或者 /path/to/verilog.exe
|
||||||
fn get_invoke_name(&self, execute_name: &str) -> String {
|
fn get_invoke_name(&self, execute_name: &str) -> String {
|
||||||
let suffix = {
|
let suffix = {
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::{path::PathBuf, str::FromStr};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ropey::Rope;
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, Url};
|
||||||
|
|
||||||
use crate::utils::command::is_command_valid;
|
use crate::utils::command::is_command_valid;
|
||||||
|
|
||||||
use super::{AbstractLinterConfiguration, LinterStatus};
|
use super::{AbstractLinterConfiguration, LinterStatus};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct IverilogConfiguration {
|
pub struct IverilogConfiguration {
|
||||||
pub language_id: String,
|
pub language_id: String,
|
||||||
pub linter: IverilogLinter
|
pub linter: IverilogLinter
|
||||||
@ -15,33 +17,44 @@ pub struct IverilogConfiguration {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct IverilogLinter {
|
pub struct IverilogLinter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub exe_name: String,
|
||||||
/// 目前是否启动
|
/// 目前是否启动
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IverilogLinter {
|
impl IverilogLinter {
|
||||||
fn default() -> Self {
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||||||
Self {
|
IverilogLinter {
|
||||||
name: "iverilog".to_string(),
|
name: "iverilog".to_string(),
|
||||||
|
exe_name: invoker.to_string(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "iverilog".to_string(),
|
path: invoker.to_string(),
|
||||||
args: Vec::new(),
|
args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbstractLinterConfiguration for IverilogConfiguration {
|
impl AbstractLinterConfiguration for IverilogConfiguration {
|
||||||
fn new(language_id: &str) -> Self {
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||||||
IverilogConfiguration {
|
IverilogConfiguration {
|
||||||
language_id: language_id.to_string(),
|
language_id: language_id.to_string(),
|
||||||
linter: IverilogLinter::default()
|
linter: IverilogLinter::new(invoker, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_diagnostics(&self) {
|
fn provide_diagnostics(
|
||||||
|
&self,
|
||||||
|
uri: &Url,
|
||||||
|
#[allow(unused)]
|
||||||
|
rope: &Rope
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_windows_extension(&self) -> &str {
|
fn get_windows_extension(&self) -> &str {
|
||||||
|
@ -26,7 +26,6 @@ pub use modelsim::*;
|
|||||||
/// 诊断模块的每一个子诊断器都需要实现如下的函数
|
/// 诊断模块的每一个子诊断器都需要实现如下的函数
|
||||||
/// - provide_diagnostics: 提供一次诊断
|
/// - provide_diagnostics: 提供一次诊断
|
||||||
/// -
|
/// -
|
||||||
|
|
||||||
/// 获取诊断核心函数
|
/// 获取诊断核心函数
|
||||||
pub fn provide_diagnostics(
|
pub fn provide_diagnostics(
|
||||||
uri: Url,
|
uri: Url,
|
||||||
@ -58,27 +57,46 @@ pub fn provide_diagnostics(
|
|||||||
|
|
||||||
// 根据配置决定使用哪一个诊断器
|
// 根据配置决定使用哪一个诊断器
|
||||||
// 外层代码需要保证只有一个 linter.enable 为 true
|
// 外层代码需要保证只有一个 linter.enable 为 true
|
||||||
if linter_configuration.verilator.linter.enabled {
|
match linter_configuration {
|
||||||
info!("verilator linter enter");
|
config if config.iverilog.linter.enabled => {
|
||||||
|
info!("iverilog linter enter");
|
||||||
} else if linter_configuration.verible.linter.enabled {
|
diagnostics.append(
|
||||||
info!("verible linter enter");
|
&mut config.iverilog.provide_diagnostics(&uri, rope)
|
||||||
|
);
|
||||||
} else if linter_configuration.modelsim.linter.enabled {
|
}
|
||||||
info!("modelsim linter enter");
|
config if config.verilator.linter.enabled => {
|
||||||
|
info!("verilator linter enter");
|
||||||
} else if linter_configuration.vivado.linter.enabled {
|
diagnostics.append(
|
||||||
info!("vivado linter enter");
|
&mut config.verilator.provide_diagnostics(&uri, rope)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
config if config.verible.linter.enabled => {
|
||||||
|
info!("verible linter enter");
|
||||||
|
diagnostics.append(
|
||||||
|
&mut config.verible.provide_diagnostics(&uri, rope)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
config if config.modelsim.linter.enabled => {
|
||||||
|
info!("modelsim linter enter");
|
||||||
|
diagnostics.append(
|
||||||
|
&mut config.modelsim.provide_diagnostics(&uri, rope)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
config if config.vivado.linter.enabled => {
|
||||||
|
info!("vivado linter enter");
|
||||||
|
diagnostics.append(
|
||||||
|
&mut config.vivado.provide_diagnostics(&uri, rope)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishDiagnosticsParams {
|
PublishDiagnosticsParams {
|
||||||
uri, diagnostics,
|
uri, diagnostics,
|
||||||
version: None
|
version: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据输入的名字选择诊断器
|
/// 根据输入的名字选择诊断器,并更新所有诊断器的基本路径
|
||||||
/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"`
|
/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"`
|
||||||
/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"`
|
/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"`
|
||||||
/// - `linter_path` 为第三方的可执行文件的路径
|
/// - `linter_path` 为第三方的可执行文件的路径
|
||||||
@ -114,27 +132,44 @@ pub fn update_diagnostics_configuration(
|
|||||||
"iverilog" => {
|
"iverilog" => {
|
||||||
linter_configuration.iverilog.linter.enabled = true;
|
linter_configuration.iverilog.linter.enabled = true;
|
||||||
linter_configuration.iverilog.linter.path = linter_path.to_string();
|
linter_configuration.iverilog.linter.path = linter_path.to_string();
|
||||||
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.iverilog.linter.path);
|
let invoke_name = linter_configuration.iverilog.get_invoke_name("iverilog");
|
||||||
|
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
|
||||||
}
|
}
|
||||||
"verilator" => {
|
"verilator" => {
|
||||||
linter_configuration.verilator.linter.enabled = true;
|
linter_configuration.verilator.linter.enabled = true;
|
||||||
linter_configuration.verilator.linter.path = linter_path.to_string();
|
linter_configuration.verilator.linter.path = linter_path.to_string();
|
||||||
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verilator.linter.path);
|
let invoke_name = linter_configuration.verilator.get_invoke_name("verilator");
|
||||||
|
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
|
||||||
}
|
}
|
||||||
"verible" => {
|
"verible" => {
|
||||||
linter_configuration.verible.linter.enabled = true;
|
linter_configuration.verible.linter.enabled = true;
|
||||||
linter_configuration.verible.linter.path = linter_path.to_string();
|
linter_configuration.verible.linter.path = linter_path.to_string();
|
||||||
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verible.linter.path);
|
let invoke_name = linter_configuration.verible.get_invoke_name("verible-verilog-syntax");
|
||||||
|
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
|
||||||
}
|
}
|
||||||
"modelsim" => {
|
"modelsim" => {
|
||||||
linter_configuration.modelsim.linter.enabled = true;
|
linter_configuration.modelsim.linter.enabled = true;
|
||||||
linter_configuration.modelsim.linter.path = linter_path.to_string();
|
linter_configuration.modelsim.linter.path = linter_path.to_string();
|
||||||
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.modelsim.linter.path);
|
let invoke_name = {
|
||||||
|
if language_id == "vhdl" {
|
||||||
|
linter_configuration.modelsim.get_invoke_name("vcom")
|
||||||
|
} else {
|
||||||
|
linter_configuration.modelsim.get_invoke_name("vlog")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
|
||||||
}
|
}
|
||||||
"vivado" => {
|
"vivado" => {
|
||||||
linter_configuration.vivado.linter.enabled = true;
|
linter_configuration.vivado.linter.enabled = true;
|
||||||
linter_configuration.vivado.linter.path = linter_path.to_string();
|
linter_configuration.vivado.linter.path = linter_path.to_string();
|
||||||
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.vivado.linter.path);
|
let invoke_name = {
|
||||||
|
if language_id == "vhdl" {
|
||||||
|
linter_configuration.vivado.get_invoke_name("xvhdl")
|
||||||
|
} else {
|
||||||
|
linter_configuration.vivado.get_invoke_name("xvlog")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info!("未找到匹配的诊断器: {}", linter_name);
|
info!("未找到匹配的诊断器: {}", linter_name);
|
||||||
@ -172,12 +207,128 @@ pub struct DigitalLinterConfiguration {
|
|||||||
|
|
||||||
impl DigitalLinterConfiguration {
|
impl DigitalLinterConfiguration {
|
||||||
pub fn new(language_id: &str) -> Self {
|
pub fn new(language_id: &str) -> Self {
|
||||||
|
match language_id {
|
||||||
|
"vhdl" => {
|
||||||
DigitalLinterConfiguration {
|
DigitalLinterConfiguration {
|
||||||
iverilog: IverilogConfiguration::new(language_id),
|
iverilog: IverilogConfiguration::new(
|
||||||
verible: VeribleConfiguration::new(language_id),
|
language_id,
|
||||||
verilator: VerilatorConfiguration::new(language_id),
|
"iverilog",
|
||||||
modelsim: ModelsimConfiguration::new(language_id),
|
vec![
|
||||||
vivado: VivadoConfiguration::new(language_id)
|
"-t null".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
verible: VeribleConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verible-verilog-syntax",
|
||||||
|
vec![]
|
||||||
|
),
|
||||||
|
verilator: VerilatorConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verilator",
|
||||||
|
vec![]
|
||||||
|
),
|
||||||
|
modelsim: ModelsimConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"vcom",
|
||||||
|
vec![
|
||||||
|
"-quiet".to_string(),
|
||||||
|
"-nologo".to_string(),
|
||||||
|
"-2008".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
vivado: VivadoConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"xvhdl",
|
||||||
|
vec![
|
||||||
|
"--nolog".to_string()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"systemverilog" => {
|
||||||
|
DigitalLinterConfiguration {
|
||||||
|
iverilog: IverilogConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"iverilog",
|
||||||
|
vec![
|
||||||
|
"-g2012".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
verible: VeribleConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verible-verilog-syntax",
|
||||||
|
vec![]
|
||||||
|
),
|
||||||
|
verilator: VerilatorConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verilator",
|
||||||
|
vec![
|
||||||
|
"--lint-only".to_string(),
|
||||||
|
"-sv".to_string(),
|
||||||
|
"-Wall".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
modelsim: ModelsimConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"vlog",
|
||||||
|
vec![
|
||||||
|
"-quiet".to_string(),
|
||||||
|
"-nologo".to_string(),
|
||||||
|
"-sv".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
vivado: VivadoConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"xvlog",
|
||||||
|
vec![
|
||||||
|
"--sv".to_string(),
|
||||||
|
"--nolog".to_string()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认为 verilog
|
||||||
|
_ => {
|
||||||
|
DigitalLinterConfiguration {
|
||||||
|
iverilog: IverilogConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"iverilog",
|
||||||
|
vec![
|
||||||
|
"-t null".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
verible: VeribleConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verible-verilog-syntax",
|
||||||
|
vec![]
|
||||||
|
),
|
||||||
|
verilator: VerilatorConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"verilator",
|
||||||
|
vec![
|
||||||
|
"--lint-only".to_string(),
|
||||||
|
"-Wall".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
modelsim: ModelsimConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"vlog",
|
||||||
|
vec![
|
||||||
|
"-quiet".to_string(),
|
||||||
|
"-nologo".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
vivado: VivadoConfiguration::new(
|
||||||
|
language_id,
|
||||||
|
"xvlog",
|
||||||
|
vec![
|
||||||
|
"--nolog".to_string()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::{path::PathBuf, str::FromStr};
|
#[allow(unused)]
|
||||||
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::utils::command::is_command_valid;
|
use crate::utils::command::is_command_valid;
|
||||||
|
|
||||||
use super::{AbstractLinterConfiguration, LinterStatus};
|
use super::{AbstractLinterConfiguration, LinterStatus};
|
||||||
|
use ropey::Rope;
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, Url};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ModelsimConfiguration {
|
pub struct ModelsimConfiguration {
|
||||||
pub language_id: String,
|
pub language_id: String,
|
||||||
pub linter: ModelsimLinter
|
pub linter: ModelsimLinter
|
||||||
@ -16,6 +16,7 @@ pub struct ModelsimConfiguration {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ModelsimLinter {
|
pub struct ModelsimLinter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub exe_name: String,
|
||||||
/// 目前是否启动
|
/// 目前是否启动
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@ -23,28 +24,36 @@ pub struct ModelsimLinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Default for ModelsimLinter {
|
impl ModelsimLinter {
|
||||||
fn default() -> Self {
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "modelsim".to_string(),
|
name: "modelsim".to_string(),
|
||||||
|
exe_name: invoker.to_string(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "Modelsim-verilog-syntax".to_string(),
|
path: invoker.to_string(),
|
||||||
args: Vec::new(),
|
args,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl AbstractLinterConfiguration for ModelsimConfiguration {
|
impl AbstractLinterConfiguration for ModelsimConfiguration {
|
||||||
fn new(language_id: &str) -> Self {
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||||||
ModelsimConfiguration {
|
ModelsimConfiguration {
|
||||||
language_id: language_id.to_string(),
|
language_id: language_id.to_string(),
|
||||||
linter: ModelsimLinter::default()
|
linter: ModelsimLinter::new(invoker, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_diagnostics(&self) {
|
fn provide_diagnostics(
|
||||||
|
&self,
|
||||||
|
uri: &Url,
|
||||||
|
#[allow(unused)]
|
||||||
|
rope: &Rope
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_windows_extension(&self) -> &str {
|
fn get_windows_extension(&self) -> &str {
|
||||||
|
@ -10,7 +10,7 @@ use super::{AbstractLinterConfiguration, LinterStatus};
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VeribleConfiguration {
|
pub struct VeribleConfiguration {
|
||||||
pub language_id: String,
|
pub language_id: String,
|
||||||
pub linter: VeribleLinter,
|
pub linter: VeribleLinter,
|
||||||
@ -20,6 +20,7 @@ pub struct VeribleConfiguration {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VeribleLinter {
|
pub struct VeribleLinter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub exe_name: String,
|
||||||
/// 目前是否启动
|
/// 目前是否启动
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@ -33,13 +34,14 @@ pub struct VeribleFormat {
|
|||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VeribleLinter {
|
impl VeribleLinter {
|
||||||
fn default() -> Self {
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "verible".to_string(),
|
name: "verible".to_string(),
|
||||||
|
exe_name: invoker.to_string(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "verible-verilog-syntax".to_string(),
|
path: invoker.to_string(),
|
||||||
args: Vec::new(),
|
args,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,16 +118,23 @@ fn verible_syntax(
|
|||||||
|
|
||||||
|
|
||||||
impl AbstractLinterConfiguration for VeribleConfiguration {
|
impl AbstractLinterConfiguration for VeribleConfiguration {
|
||||||
fn new(language_id: &str) -> Self {
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||||||
VeribleConfiguration {
|
VeribleConfiguration {
|
||||||
language_id: language_id.to_string(),
|
language_id: language_id.to_string(),
|
||||||
linter: VeribleLinter::default(),
|
linter: VeribleLinter::new(invoker, args),
|
||||||
format: VeribleFormat::default()
|
format: VeribleFormat::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_diagnostics(&self) {
|
fn provide_diagnostics(
|
||||||
|
&self,
|
||||||
|
uri: &Url,
|
||||||
|
#[allow(unused)]
|
||||||
|
rope: &Rope
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_linter_path(&self) -> &str {
|
fn get_linter_path(&self) -> &str {
|
||||||
|
@ -9,7 +9,7 @@ use crate::utils::command::is_command_valid;
|
|||||||
|
|
||||||
use super::{AbstractLinterConfiguration, LinterStatus};
|
use super::{AbstractLinterConfiguration, LinterStatus};
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VerilatorConfiguration {
|
pub struct VerilatorConfiguration {
|
||||||
pub language_id: String,
|
pub language_id: String,
|
||||||
pub linter: VerilatorLinter,
|
pub linter: VerilatorLinter,
|
||||||
@ -18,23 +18,21 @@ pub struct VerilatorConfiguration {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VerilatorLinter {
|
pub struct VerilatorLinter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub exe_name: String,
|
||||||
/// 目前是否启动
|
/// 目前是否启动
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VerilatorLinter {
|
impl VerilatorLinter {
|
||||||
fn default() -> Self {
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "verilator".to_string(),
|
name: "verilator".to_string(),
|
||||||
|
exe_name: invoker.to_string(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "verilator".to_string(),
|
path: invoker.to_string(),
|
||||||
args: vec![
|
args,
|
||||||
"--lint-only".to_string(),
|
|
||||||
"--sv".to_string(),
|
|
||||||
"-Wall".to_string(),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,15 +123,22 @@ fn verilator_syntax(
|
|||||||
|
|
||||||
|
|
||||||
impl AbstractLinterConfiguration for VerilatorConfiguration {
|
impl AbstractLinterConfiguration for VerilatorConfiguration {
|
||||||
fn new(language_id: &str) -> Self {
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||||||
VerilatorConfiguration {
|
VerilatorConfiguration {
|
||||||
language_id: language_id.to_string(),
|
language_id: language_id.to_string(),
|
||||||
linter: VerilatorLinter::default()
|
linter: VerilatorLinter::new(invoker, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_diagnostics(&self) {
|
fn provide_diagnostics(
|
||||||
|
&self,
|
||||||
|
uri: &Url,
|
||||||
|
#[allow(unused)]
|
||||||
|
rope: &Rope
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_linter_path(&self) -> &str {
|
fn get_linter_path(&self) -> &str {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
#[allow(unused)]
|
||||||
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::utils::command::is_command_valid;
|
use crate::utils::command::is_command_valid;
|
||||||
|
use ropey::Rope;
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, Url};
|
||||||
|
|
||||||
use super::{AbstractLinterConfiguration, LinterStatus};
|
use super::{AbstractLinterConfiguration, LinterStatus};
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VivadoConfiguration {
|
pub struct VivadoConfiguration {
|
||||||
pub language_id: String,
|
pub language_id: String,
|
||||||
pub linter: VivadoLinter,
|
pub linter: VivadoLinter,
|
||||||
@ -13,37 +16,42 @@ pub struct VivadoConfiguration {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VivadoLinter {
|
pub struct VivadoLinter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub exe_name: String,
|
||||||
/// 目前是否启动
|
/// 目前是否启动
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VivadoLinter {
|
impl VivadoLinter {
|
||||||
fn default() -> Self {
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "vivado".to_string(),
|
name: "vivado".to_string(),
|
||||||
|
exe_name: invoker.to_string(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "Vivado".to_string(),
|
path: invoker.to_string(),
|
||||||
args: vec![
|
args,
|
||||||
"--lint-only".to_string(),
|
|
||||||
"--sv".to_string(),
|
|
||||||
"-Wall".to_string(),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbstractLinterConfiguration for VivadoConfiguration {
|
impl AbstractLinterConfiguration for VivadoConfiguration {
|
||||||
fn new(language_id: &str) -> Self {
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||||||
VivadoConfiguration {
|
VivadoConfiguration {
|
||||||
language_id: language_id.to_string(),
|
language_id: language_id.to_string(),
|
||||||
linter: VivadoLinter::default()
|
linter: VivadoLinter::new(invoker, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_diagnostics(&self) {
|
fn provide_diagnostics(
|
||||||
|
&self,
|
||||||
|
uri: &Url,
|
||||||
|
#[allow(unused)]
|
||||||
|
rope: &Rope
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_linter_path(&self) -> &str {
|
fn get_linter_path(&self) -> &str {
|
||||||
|
@ -69,9 +69,10 @@ fn update_configuration(
|
|||||||
let linter_name_configuration = &configs[0];
|
let linter_name_configuration = &configs[0];
|
||||||
let linter_path_configuration = &configs[1];
|
let linter_path_configuration = &configs[1];
|
||||||
|
|
||||||
// something like digital-ide.function.lsp.linter.vlog.diagnostor
|
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
|
||||||
let linter_name_config_name = &linter_name_configuration.name;
|
let linter_name_config_name = &linter_name_configuration.name;
|
||||||
|
|
||||||
|
// 同步到全局配置中
|
||||||
lsp_configuration.insert(
|
lsp_configuration.insert(
|
||||||
linter_name_config_name.to_string(),
|
linter_name_config_name.to_string(),
|
||||||
linter_name_configuration.value.clone()
|
linter_name_configuration.value.clone()
|
||||||
@ -82,12 +83,7 @@ fn update_configuration(
|
|||||||
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
|
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
|
||||||
let mut cookies = linter_name_config_name.split(".");
|
let mut cookies = linter_name_config_name.split(".");
|
||||||
let name = cookies.nth(4).unwrap();
|
let name = cookies.nth(4).unwrap();
|
||||||
match name {
|
name
|
||||||
"vlog" => "verilog",
|
|
||||||
"vhdl" => "vhdl",
|
|
||||||
"svlog" => "systemverilog",
|
|
||||||
_ => return
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let linter_name = linter_name_configuration.value.as_str().unwrap();
|
let linter_name = linter_name_configuration.value.as_str().unwrap();
|
||||||
|
@ -46,7 +46,6 @@ fn get_linter_status(
|
|||||||
linter_path: &str
|
linter_path: &str
|
||||||
) -> Result<LinterStatus> {
|
) -> Result<LinterStatus> {
|
||||||
let configuration = backend.server.configuration.read().unwrap();
|
let configuration = backend.server.configuration.read().unwrap();
|
||||||
|
|
||||||
// 获取对应语言的配置项目
|
// 获取对应语言的配置项目
|
||||||
let configuration = match language_id {
|
let configuration = match language_id {
|
||||||
"verilog" => &configuration.vlog_linter_configuration,
|
"verilog" => &configuration.vlog_linter_configuration,
|
||||||
@ -86,7 +85,7 @@ fn get_linter_status(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(tower_lsp::jsonrpc::Error {
|
return Err(tower_lsp::jsonrpc::Error {
|
||||||
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
|
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
|
||||||
message: Cow::Owned(format!("无效的语言 ID {}", language_id)),
|
message: Cow::Owned(format!("无效的诊断器 {}", linter_name)),
|
||||||
data: None
|
data: None
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -131,11 +131,7 @@ impl Default for LspConfiguration {
|
|||||||
impl LanguageServer for Backend {
|
impl LanguageServer for Backend {
|
||||||
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
||||||
// 申明 LSP 的基本信息和提供的能力
|
// 申明 LSP 的基本信息和提供的能力
|
||||||
let server_info = Some(ServerInfo {
|
let mut version = "0.4.0".to_string();
|
||||||
name: "Digital IDE 专用 LSP 后端服务器".to_string(),
|
|
||||||
version: Some("0.4.0".to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
let root_uri = ¶ms.root_uri;
|
let root_uri = ¶ms.root_uri;
|
||||||
|
|
||||||
let mut configure = self.server.configuration.write().unwrap();
|
let mut configure = self.server.configuration.write().unwrap();
|
||||||
@ -144,10 +140,16 @@ impl LanguageServer for Backend {
|
|||||||
if let Some(serde_json::Value::Object(options)) = params.initialization_options {
|
if let Some(serde_json::Value::Object(options)) = params.initialization_options {
|
||||||
let extension_path = options.get("extensionPath").unwrap().as_str().unwrap();
|
let extension_path = options.get("extensionPath").unwrap().as_str().unwrap();
|
||||||
let tool_chain = options.get("toolChain").unwrap().as_str().unwrap();
|
let tool_chain = options.get("toolChain").unwrap().as_str().unwrap();
|
||||||
|
version = options.get("version").unwrap().as_str().unwrap().to_string();
|
||||||
configure.tool_chain = tool_chain.to_string();
|
configure.tool_chain = tool_chain.to_string();
|
||||||
configure.extension_path = extension_path.to_string();
|
configure.extension_path = extension_path.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let server_info = Some(ServerInfo {
|
||||||
|
name: "Digital IDE 专用 LSP 后端服务器".to_string(),
|
||||||
|
version: Some(version.to_string())
|
||||||
|
});
|
||||||
|
|
||||||
info!("当前客户端初始化结果");
|
info!("当前客户端初始化结果");
|
||||||
info!("workspaceFolder: {:?}", configure.workspace_folder);
|
info!("workspaceFolder: {:?}", configure.workspace_folder);
|
||||||
info!("extensionPath: {:?}", configure.extension_path);
|
info!("extensionPath: {:?}", configure.extension_path);
|
||||||
@ -159,8 +161,8 @@ impl LanguageServer for Backend {
|
|||||||
&configure.extension_path
|
&configure.extension_path
|
||||||
);
|
);
|
||||||
|
|
||||||
// 初始化诊断器
|
// 初始化系统缓存路径
|
||||||
|
self.server.cache.start(&version);
|
||||||
|
|
||||||
let text_document_sync = TextDocumentSyncCapability::Options(
|
let text_document_sync = TextDocumentSyncCapability::Options(
|
||||||
TextDocumentSyncOptions {
|
TextDocumentSyncOptions {
|
||||||
|
@ -31,7 +31,6 @@ use sv_parser::*;
|
|||||||
use vhdl_lang::Project;
|
use vhdl_lang::Project;
|
||||||
use thread::JoinHandle;
|
use thread::JoinHandle;
|
||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
impl LspServer {
|
impl LspServer {
|
||||||
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
|
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
|
||||||
@ -100,6 +99,8 @@ impl LspServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 保存时触发的行为
|
||||||
|
/// - 提供诊断信息(主要是第三方诊断器,因为 save 后磁盘上的内容就和缓冲区中的一致了)
|
||||||
pub fn did_save(&self, params: DidSaveTextDocumentParams) -> PublishDiagnosticsParams {
|
pub fn did_save(&self, params: DidSaveTextDocumentParams) -> PublishDiagnosticsParams {
|
||||||
let urls = self.srcs.names.read().unwrap().keys().cloned().collect();
|
let urls = self.srcs.names.read().unwrap().keys().cloned().collect();
|
||||||
let file_id = self.srcs.get_id(¶ms.text_document.uri);
|
let file_id = self.srcs.get_id(¶ms.text_document.uri);
|
||||||
|
@ -3,7 +3,7 @@ use std::process::Command;
|
|||||||
pub fn is_command_valid(command: &str) -> bool {
|
pub fn is_command_valid(command: &str) -> bool {
|
||||||
// 尝试执行命令
|
// 尝试执行命令
|
||||||
match Command::new(command).output() {
|
match Command::new(command).output() {
|
||||||
Ok(output) => output.status.success(),
|
Ok(_) => true,
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit b6ae8b8a1ff22609e2a837b12dd798226f299f71
|
Subproject commit ba95abd436b0a032e0cfdcb9400ce2cfb678792f
|
Loading…
x
Reference in New Issue
Block a user