完成 linter 后端请求接口和基本数据结构

This commit is contained in:
锦恢 2024-12-05 23:53:38 +08:00
parent 8e2b373702
commit 7824b74c9a
35 changed files with 576 additions and 372 deletions

View File

@ -1,4 +1,4 @@
use crate::server::LSPServer;
use crate::server::LspServer;
use crate::utils::*;
#[allow(unused)]
use log::info;
@ -7,7 +7,7 @@ use tower_lsp::lsp_types::*;
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn code_lens(&self, params: CodeLensParams) -> Option<Vec<CodeLens>> {
let language_id = get_language_id_by_uri(&params.text_document.uri);
match language_id.as_str() {

View File

@ -1,6 +1,6 @@
use std::{path::PathBuf, str::FromStr};
use crate::{core, server::LSPServer};
use crate::{core, server::LspServer};
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
@ -9,7 +9,7 @@ use super::to_escape_path;
pub fn code_lens(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
params: &CodeLensParams
) -> Option<Vec<CodeLens>> {

View File

@ -1,12 +1,12 @@
use crate::server::LSPServer;
use crate::server::LspServer;
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
pub fn code_lens(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
params: &CodeLensParams
) -> Option<Vec<CodeLens>> {

View File

@ -4,7 +4,7 @@ use log::info;
use ropey::RopeSlice;
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url};
use crate::{server::LSPServer, utils::{resolve_path, to_escape_path}};
use crate::{server::LspServer, utils::{resolve_path, to_escape_path}};
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
let line_text = line.as_str().unwrap_or("");
@ -88,7 +88,7 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op
}
pub fn get_dot_completion(
server: &LSPServer,
server: &LspServer,
line: &RopeSlice,
url: &Url,
pos: &Position,
@ -142,7 +142,7 @@ fn is_port_completion(line: &RopeSlice, pos: &Position) -> bool {
}
fn get_position_port_param_completion(
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
line: &RopeSlice,
url: &Url,

View File

@ -1,4 +1,4 @@
use crate::{server::LSPServer, utils::get_language_id_by_uri};
use crate::{server::LspServer, utils::get_language_id_by_uri};
use tower_lsp::lsp_types::*;
pub mod keyword;
@ -10,7 +10,7 @@ pub use sys_tasks::provide_vlog_sys_tasks_completions;
mod vhdl;
mod sv;
impl LSPServer {
impl LspServer {
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
let language_id = get_language_id_by_uri(&params.text_document_position.text_document.uri);
match language_id.as_str() {

View File

@ -1,12 +1,10 @@
use std::collections::HashSet;
use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LspServer, sources::LSPSupport, utils::get_language_id_by_uri};
use log::info;
use ropey::{Rope, RopeSlice};
use tower_lsp::lsp_types::*;
pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
pub fn completion(server: &LspServer, params: &CompletionParams) -> Option<CompletionResponse> {
let doc = &params.text_document_position;
let uri = &params.text_document_position.text_document.uri;
let pos = doc.position;
@ -305,7 +303,7 @@ fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
/// 自动补全例化模块
fn make_module_completions(
server: &LSPServer,
server: &LspServer,
token: &str,
language_id: &str
) -> Vec<CompletionItem> {

View File

@ -6,13 +6,13 @@ use ropey::{Rope, RopeSlice};
use tower_lsp::lsp_types::*;
use crate::{hover::feature::make_vhdl_module_profile_code, utils::{from_lsp_pos, to_escape_path}};
#[allow(unused)]
use crate::{server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
use crate::{server::LspServer, sources::LSPSupport, utils::get_language_id_by_uri};
/// Called when the client requests a completion.
/// This function looks in the source code to find suitable options and then returns them
pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
pub fn completion(server: &LspServer, params: &CompletionParams) -> Option<CompletionResponse> {
let doc = &params.text_document_position;
let uri = &params.text_document_position.text_document.uri;
let language_id = get_language_id_by_uri(uri);
@ -214,7 +214,7 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
}
fn make_module_completions(
server: &LSPServer,
server: &LspServer,
token: &str,
language_id: &str
) -> Vec<CompletionItem> {

View File

@ -5,7 +5,7 @@ use regex::Regex;
use ropey::RopeSlice;
use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url};
use crate::{core::{self, hdlparam::{self, FastHdlparam}}, server::LSPServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}};
use crate::{core::{self, hdlparam::{self, FastHdlparam}}, server::LspServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}};
/// 跳转到 include 的文件
pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
@ -73,7 +73,7 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op
/// 跳转到宏定义
pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
pub fn goto_macro_definition(server: &LspServer, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
let macro_text_regex = Regex::new(r"[`0-9a-zA-Z]").unwrap();
if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) {
if macro_text.starts_with("`") {
@ -103,7 +103,7 @@ pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position
fn goto_instantiation<'a>(
server: &LSPServer,
server: &LspServer,
fast: &'a FastHdlparam,
token_name: &str,
pos: &Position,
@ -207,7 +207,7 @@ fn goto_instantiation<'a>(
}
pub fn goto_position_port_param_definition(
server: &LSPServer,
server: &LspServer,
line: &RopeSlice,
url: &Url,
pos: Position
@ -235,7 +235,7 @@ pub fn goto_position_port_param_definition(
}
pub fn goto_module_declaration_definition(
server: &LSPServer,
server: &LspServer,
token_name: &str
) -> Option<GotoDefinitionResponse> {
let hdl_param = server.srcs.hdl_param.clone();
@ -276,7 +276,7 @@ pub fn goto_module_declaration_definition(
fn goto_common_module_declaration_definition(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
@ -301,7 +301,7 @@ fn goto_common_module_declaration_definition(
fn goto_ip_module_declaration_definition(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
@ -331,7 +331,7 @@ fn goto_ip_module_declaration_definition(
fn goto_primitives_module_declaration_definition(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]

View File

@ -1,6 +1,6 @@
use crate::core::hdlparam::{self, FastHdlparam};
use crate::utils::get_language_id_by_uri;
use crate::server::LSPServer;
use crate::server::LspServer;
#[allow(unused)]
use log::info;
@ -19,7 +19,7 @@ pub use extract_defs::*;
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri);
match language_id.as_str() {

View File

@ -1,5 +1,5 @@
use crate::utils::get_definition_token;
use crate::server::LSPServer;
use crate::server::LspServer;
use crate::sources::LSPSupport;
#[allow(unused)]
@ -8,7 +8,7 @@ use tower_lsp::lsp_types::*;
use super::{feature::*, Definition, Scope};
pub fn goto_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
pub fn goto_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let doc = &params.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
let file_id = server.srcs.get_id(doc).to_owned();

View File

@ -3,11 +3,11 @@ use std::{path::PathBuf, str::FromStr};
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
use crate::{server::LSPServer, utils::{from_lsp_pos, get_definition_token, srcpos_to_location, to_escape_path}};
use crate::{server::LspServer, utils::{from_lsp_pos, get_definition_token, srcpos_to_location, to_escape_path}};
use super::{Definition, Scope};
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let doc = &params.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
let file_id = server.srcs.get_id(doc).to_owned();

View File

@ -1,10 +1,10 @@
use crate::server::ProjectConfig;
use regex::Regex;
use std::fs::OpenOptions;
use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri};
use log::info;
use ropey::Rope;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use serde::Deserialize;
use tower_lsp::lsp_types::*;
use walkdir::DirEntry;
pub mod verible;
pub mod verilator;
@ -16,202 +16,148 @@ pub use verilator::*;
pub use vivado::*;
pub use modelsim::*;
/// description
/// 诊断功能需要提供两套函数,一套函数用于从给定路径读取文件并给出诊断结果;一套用于从 lsp 的文件缓冲区直接读取文本然后给出诊断结果。
/// 前者用于扫描整个项目使用,后者在用户实时修改代码时,给出实时的诊断信息。
/// 获取诊断核心函数
pub fn provide_diagnostics(
uri: Url,
rope: &Rope,
#[allow(unused_variables)] files: Vec<Url>,
configuration: &ProjectConfig,
#[allow(unused_variables)]
files: Vec<Url>,
configuration: &LspConfiguration,
) -> PublishDiagnosticsParams {
let mut diagnostics = Vec::<Diagnostic>::new();
let language_id = get_language_id_by_uri(&uri);
// 选择对应语言的 lsp
let linter_configuration = match language_id.as_str() {
"vhdl" => Some(&configuration.vhdl_linter_configuration),
"verilog" => Some(&configuration.vlog_linter_configuration),
"systemverilog" => Some(&configuration.svlog_linter_configuration),
_ => None
};
if linter_configuration.is_none() {
info!("未知语言 {} 试图发起诊断", language_id);
return PublishDiagnosticsParams {
uri, diagnostics,
version: None
};
}
let linter_configuration = linter_configuration.unwrap();
// 根据配置决定使用哪一个诊断器
// 外层代码需要保证只有一个 linter.enable 为 true
if linter_configuration.verilator.linter.enabled {
info!("verilator linter enter");
} else if linter_configuration.verible.linter.enabled {
info!("verilator linter enter");
} else if linter_configuration.modelsim.linter.enabled {
info!("modelsim linter enter");
} else if linter_configuration.vivado.linter.enabled {
info!("vivado linter enter");
}
PublishDiagnosticsParams {
uri, diagnostics,
version: None
}
}
/// 根据输入的名字选择诊断器
/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"`
/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"`
/// - `linter_path` 为第三方的可执行文件的路径
pub fn update_diagnostics_configuration(
server: &LspServer,
linter_name: &str,
language_id: &str,
linter_path: &str
) {
let mut configuration = server.configuration.write().unwrap();
// 选择对应语言的 lsp
let linter_configuration = match language_id {
"vhdl" => Some(&mut configuration.vhdl_linter_configuration),
"verilog" => Some(&mut configuration.vlog_linter_configuration),
"systemverilog" => Some(&mut configuration.svlog_linter_configuration),
_ => None
};
if linter_configuration.is_none() {
info!("未知语言 {} 试图配置诊断器", language_id);
return;
}
let linter_configuration = linter_configuration.unwrap();
linter_configuration.verilator.linter.enabled = false;
linter_configuration.verible.linter.enabled = false;
linter_configuration.modelsim.linter.enabled = false;
linter_configuration.vivado.linter.enabled = false;
if linter_configuration.verilator.linter.name == linter_name {
linter_configuration.verilator.linter.enabled = true;
linter_configuration.verilator.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verilator.linter.path);
} else if linter_configuration.verible.linter.name == linter_name {
linter_configuration.verible.linter.enabled = true;
linter_configuration.verible.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verible.linter.path);
} else if linter_configuration.modelsim.linter.name == linter_name {
linter_configuration.modelsim.linter.enabled = true;
linter_configuration.modelsim.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.modelsim.linter.path);
} else if linter_configuration.vivado.linter.name == linter_name {
linter_configuration.vivado.linter.enabled = true;
linter_configuration.vivado.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.vivado.linter.path);
if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) {
let diagnostics = {
if configuration.verilator.syntax.enabled {
if let Ok(path) = uri.to_file_path() {
match verilator_syntax(
rope,
path,
&configuration.verilator.syntax.path,
&configuration.verilator.syntax.args,
) {
Some(diags) => diags,
None => Vec::new(),
}
} else {
Vec::new()
}
} else if configuration.verible.syntax.enabled {
match verible_syntax(rope, &configuration.verible.syntax.path, &configuration.verible.syntax.args) {
Some(diags) => diags,
None => Vec::new(),
}
} else {
Vec::new()
}
};
PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
}
} else {
PublishDiagnosticsParams {
uri,
diagnostics: Vec::new(),
version: None,
}
}
}
pub fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
#[derive(Debug, Deserialize)]
#[derive(serde::Serialize)]
pub enum DigitalLinterMode {
/// 全局诊断,将所有设计源直接进行诊断,并报错,无论文件是否打开
FULL,
/// 单文件关闭时,对应报错去除,打开哪个文件就对哪个文件进行诊断
SINGLE,
/// 全局关闭,即整个工程都不进行工程报错
NONE
}
/// convert captured severity string to DiagnosticSeverity
fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
match severity {
"Error" => Some(DiagnosticSeverity::ERROR),
s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING),
// NOTE: afaik, verilator doesn't have an info or hint severity
_ => Some(DiagnosticSeverity::INFORMATION),
}
#[derive(Debug, Deserialize)]
#[derive(serde::Serialize)]
pub struct DigitalLinterConfiguration {
// verible 相关的工具配置
pub verible: VeribleConfiguration,
// verilator 相关的工具配置
pub verilator: VerilatorConfiguration,
// modelsim 相关的工具配置
pub modelsim: ModelsimConfiguration,
// vivado 相关的工具配置
pub vivado: VivadoConfiguration
}
/// syntax checking using verilator --lint-only
fn verilator_syntax(
rope: &Rope,
file_path: PathBuf,
verilator_syntax_path: &str,
verilator_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verilator_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verilator_syntax_args)
.arg(file_path.to_str()?)
.spawn()
.ok()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)",
)
.unwrap()
});
// write file to stdin, read output from stdout
rope.write_to(child.stdin.as_mut()?).ok()?;
let output = child.wait_with_output().ok()?;
if !output.status.success() {
let mut diags: Vec<Diagnostic> = Vec::new();
let raw_output = String::from_utf8(output.stderr).ok()?;
let filtered_output = raw_output
.lines()
.filter(|line| line.starts_with('%'))
.collect::<Vec<&str>>();
for error in filtered_output {
let caps = match re.captures(error) {
Some(caps) => caps,
None => continue,
};
// check if diagnostic is for this file, since verilator can provide diagnostics for
// included files
if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") {
continue;
}
let severity = verilator_severity(caps.name("severity")?.as_str());
let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?;
let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
let pos = Position::new(line - 1, col - 1);
let msg = match severity {
Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(),
Some(DiagnosticSeverity::WARNING) => format!(
"{}: {}",
caps.name("warning_type")?.as_str(),
caps.name("message")?.as_str()
),
_ => "".to_string(),
};
diags.push(Diagnostic::new(
Range::new(pos, pos),
severity,
None,
Some("verilator".to_string()),
msg,
None,
None,
));
impl Default for DigitalLinterConfiguration {
fn default() -> Self {
DigitalLinterConfiguration {
verible: VeribleConfiguration::default(),
verilator: VerilatorConfiguration::default(),
modelsim: ModelsimConfiguration::default(),
vivado: VivadoConfiguration::default()
}
Some(diags)
} else {
None
}
}
/// syntax checking using verible-verilog-syntax
fn verible_syntax(
rope: &Rope,
verible_syntax_path: &str,
verible_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verible_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verible_syntax_args)
.arg("-")
.spawn()
.ok()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"^.+:(?P<line>\d*):(?P<startcol>\d*)(?:-(?P<endcol>\d*))?:\s(?P<message>.*)\s.*$",
)
.unwrap()
});
// write file to stdin, read output from stdout
rope.write_to(child.stdin.as_mut()?).ok()?;
let output = child.wait_with_output().ok()?;
if !output.status.success() {
let mut diags: Vec<Diagnostic> = Vec::new();
let raw_output = String::from_utf8(output.stdout).ok()?;
for error in raw_output.lines() {
let caps = re.captures(error)?;
let line: u32 = caps.name("line")?.as_str().parse().ok()?;
let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?;
let endcol: Option<u32> = match caps.name("endcol").map(|e| e.as_str().parse()) {
Some(Ok(e)) => Some(e),
None => None,
Some(Err(_)) => return None,
};
let start_pos = Position::new(line - 1, startcol - 1);
let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1);
diags.push(Diagnostic::new(
Range::new(start_pos, end_pos),
Some(DiagnosticSeverity::ERROR),
None,
Some("verible".to_string()),
caps.name("message")?.as_str().to_string(),
None,
None,
));
}
Some(diags)
} else {
None
}
}

View File

@ -1,5 +1,53 @@
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimConfiguration {
pub linter: ModelsimLinter,
pub format: ModelsimFormat,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimLinter {
pub name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimFormat {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for ModelsimLinter {
fn default() -> Self {
Self {
name: "modelsim".to_string(),
enabled: true,
path: "Modelsim-verilog-syntax".to_string(),
args: Vec::new(),
}
}
}
impl Default for ModelsimFormat {
fn default() -> Self {
Self {
enabled: true,
path: "Modelsim-verilog-format".to_string(),
args: Vec::new(),
}
}
}
pub fn provide_diagnostics() {
}

View File

@ -1,15 +1,22 @@
use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleConfiguration {
pub syntax: VeribleSyntax,
pub linter: VeribleLinter,
pub format: VeribleFormat,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleSyntax {
pub struct VeribleLinter {
pub name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
@ -23,9 +30,10 @@ pub struct VeribleFormat {
pub args: Vec<String>,
}
impl Default for VeribleSyntax {
impl Default for VeribleLinter {
fn default() -> Self {
Self {
name: "verible".to_string(),
enabled: true,
path: "verible-verilog-syntax".to_string(),
args: Vec::new(),
@ -47,3 +55,60 @@ impl Default for VeribleFormat {
pub fn provide_diagnostics() {
}
/// syntax checking using verible-verilog-syntax
fn verible_syntax(
rope: &Rope,
verible_syntax_path: &str,
verible_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verible_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verible_syntax_args)
.arg("-")
.spawn()
.ok()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"^.+:(?P<line>\d*):(?P<startcol>\d*)(?:-(?P<endcol>\d*))?:\s(?P<message>.*)\s.*$",
)
.unwrap()
});
// write file to stdin, read output from stdout
rope.write_to(child.stdin.as_mut()?).ok()?;
let output = child.wait_with_output().ok()?;
if !output.status.success() {
let mut diags: Vec<Diagnostic> = Vec::new();
let raw_output = String::from_utf8(output.stdout).ok()?;
for error in raw_output.lines() {
let caps = re.captures(error)?;
let line: u32 = caps.name("line")?.as_str().parse().ok()?;
let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?;
let endcol: Option<u32> = match caps.name("endcol").map(|e| e.as_str().parse()) {
Some(Ok(e)) => Some(e),
None => None,
Some(Err(_)) => return None,
};
let start_pos = Position::new(line - 1, startcol - 1);
let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1);
diags.push(Diagnostic::new(
Range::new(start_pos, end_pos),
Some(DiagnosticSeverity::ERROR),
None,
Some("verible".to_string()),
caps.name("message")?.as_str().to_string(),
None,
None,
));
}
Some(diags)
} else {
None
}
}

View File

@ -1,22 +1,30 @@
use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct VerilatorConfiguration {
pub syntax: VerilatorSyntax,
pub linter: VerilatorLinter,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VerilatorSyntax {
pub struct VerilatorLinter {
pub name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for VerilatorSyntax {
impl Default for VerilatorLinter {
fn default() -> Self {
Self {
name: "verilator".to_string(),
enabled: true,
path: "verilator".to_string(),
args: vec![
@ -31,3 +39,87 @@ impl Default for VerilatorSyntax {
pub fn provide_diagnostics() {
}
/// convert captured severity string to DiagnosticSeverity
fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
match severity {
"Error" => Some(DiagnosticSeverity::ERROR),
s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING),
// NOTE: afaik, verilator doesn't have an info or hint severity
_ => Some(DiagnosticSeverity::INFORMATION),
}
}
/// syntax checking using verilator --lint-only
fn verilator_syntax(
rope: &Rope,
file_path: PathBuf,
verilator_syntax_path: &str,
verilator_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verilator_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verilator_syntax_args)
.arg(file_path.to_str()?)
.spawn()
.ok()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)",
)
.unwrap()
});
// write file to stdin, read output from stdout
rope.write_to(child.stdin.as_mut()?).ok()?;
let output = child.wait_with_output().ok()?;
if !output.status.success() {
let mut diags: Vec<Diagnostic> = Vec::new();
let raw_output = String::from_utf8(output.stderr).ok()?;
let filtered_output = raw_output
.lines()
.filter(|line| line.starts_with('%'))
.collect::<Vec<&str>>();
for error in filtered_output {
let caps = match re.captures(error) {
Some(caps) => caps,
None => continue,
};
// check if diagnostic is for this file, since verilator can provide diagnostics for
// included files
if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") {
continue;
}
let severity = verilator_severity(caps.name("severity")?.as_str());
let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?;
let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
let pos = Position::new(line - 1, col - 1);
let msg = match severity {
Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(),
Some(DiagnosticSeverity::WARNING) => format!(
"{}: {}",
caps.name("warning_type")?.as_str(),
caps.name("message")?.as_str()
),
_ => "".to_string(),
};
diags.push(Diagnostic::new(
Range::new(pos, pos),
severity,
None,
Some("verilator".to_string()),
msg,
None,
None,
));
}
Some(diags)
} else {
None
}
}

View File

@ -3,20 +3,23 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct VivadoConfiguration {
pub syntax: VivadoSyntax,
pub linter: VivadoLinter,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VivadoSyntax {
pub struct VivadoLinter {
pub name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for VivadoSyntax {
impl Default for VivadoLinter {
fn default() -> Self {
Self {
name: "vivado".to_string(),
enabled: true,
path: "Vivado".to_string(),
args: vec![

View File

@ -1,10 +1,10 @@
use crate::{server::LSPServer, utils::{get_definition_token, get_language_id_by_uri}};
use crate::{server::LspServer, utils::{get_definition_token, get_language_id_by_uri}};
use tower_lsp::lsp_types::*;
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn document_highlight(
&self,
params: DocumentHighlightParams,

View File

@ -3,10 +3,10 @@ use log::info;
use sv_parser::{RefNode, SyntaxTree};
use tower_lsp::lsp_types::*;
use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}};
use crate::{definition::{get_ident, Scope}, server::LspServer, sources::{LSPSupport, ParseIR, Source}};
pub fn document_highlight(
server: &LSPServer,
server: &LspServer,
token: &str,
file: &Source,
pos: Position,

View File

@ -2,10 +2,10 @@ use log::info;
use sv_parser::{RefNode, SyntaxTree};
use tower_lsp::lsp_types::*;
use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}};
use crate::{definition::{get_ident, Scope}, server::LspServer, sources::{LSPSupport, ParseIR, Source}};
pub fn document_highlight(
server: &LSPServer,
server: &LspServer,
token: &str,
file: &Source,
pos: Position,

View File

@ -1,11 +1,11 @@
use tower_lsp::lsp_types::*;
use crate::{server::LSPServer, utils::get_language_id_by_uri};
use crate::{server::LspServer, utils::get_language_id_by_uri};
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
let uri = &params.text_document.uri;
let language_id = get_language_id_by_uri(uri);

View File

@ -1,8 +1,8 @@
use crate::{definition::Scope, server::LSPServer};
use crate::{definition::Scope, server::LspServer};
use tower_lsp::lsp_types::*;
pub fn document_symbol(
server: &LSPServer,
server: &LspServer,
params: &DocumentSymbolParams
) -> Option<DocumentSymbolResponse> {
let uri = &params.text_document.uri;

View File

@ -4,9 +4,9 @@ use std::{path::PathBuf, str::FromStr};
use log::info;
use tower_lsp::lsp_types::*;
use vhdl_lang::{EntHierarchy, Token};
use crate::{definition::Scope, server::LSPServer, utils::{to_escape_path, to_lsp_range, to_symbol_kind}};
use crate::{definition::Scope, server::LspServer, utils::{to_escape_path, to_lsp_range, to_symbol_kind}};
pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
pub fn document_symbol(server: &LspServer, params: &DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
// info!("enter document symbol");
let uri = &params.text_document.uri;

View File

@ -1,60 +1,62 @@
use crate::server::LSPServer;
use crate::server::LspServer;
use crate::sources::LSPSupport;
use log::info;
use ropey::Rope;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
impl LSPServer {
impl LspServer {
pub fn formatting(&self, params: DocumentFormattingParams) -> Option<Vec<TextEdit>> {
let uri = params.text_document.uri;
info!("formatting {}", &uri);
let file_id = self.srcs.get_id(&uri).to_owned();
self.srcs.wait_parse_ready(file_id, false);
let file = self.srcs.get_file(file_id)?;
let file = file.read().ok()?;
None
// let uri = params.text_document.uri;
// info!("formatting {}", &uri);
// let file_id = self.srcs.get_id(&uri).to_owned();
// self.srcs.wait_parse_ready(file_id, false);
// let file = self.srcs.get_file(file_id)?;
// let file = file.read().ok()?;
let conf = self.configuration.read().unwrap();
if conf.verible.format.enabled {
Some(vec![TextEdit::new(
Range::new(
file.text.char_to_pos(0),
file.text.char_to_pos(file.text.len_chars()),
),
format_document(
&file.text,
None,
&conf.verible.format.path,
&conf.verible.format.args,
)?,
)])
} else {
None
}
// let conf = self.configuration.read().unwrap();
// if conf.verible.format.enabled {
// Some(vec![TextEdit::new(
// Range::new(
// file.text.char_to_pos(0),
// file.text.char_to_pos(file.text.len_chars()),
// ),
// format_document(
// &file.text,
// None,
// &conf.verible.format.path,
// &conf.verible.format.args,
// )?,
// )])
// } else {
// None
// }
}
pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option<Vec<TextEdit>> {
let uri = params.text_document.uri;
info!("range formatting {}", &uri);
let file_id = self.srcs.get_id(&uri).to_owned();
self.srcs.wait_parse_ready(file_id, false);
let file = self.srcs.get_file(file_id)?;
let file = file.read().ok()?;
None
// let uri = params.text_document.uri;
// info!("range formatting {}", &uri);
// let file_id = self.srcs.get_id(&uri).to_owned();
// self.srcs.wait_parse_ready(file_id, false);
// let file = self.srcs.get_file(file_id)?;
// let file = file.read().ok()?;
let conf = self.configuration.read().unwrap();
if conf.verible.format.enabled {
Some(vec![TextEdit::new(
file.text.char_range_to_range(0..file.text.len_chars()),
format_document(
&file.text,
Some(params.range),
&conf.verible.format.path,
&conf.verible.format.args,
)?,
)])
} else {
None
}
// let conf = self.configuration.read().unwrap();
// if conf.verible.format.enabled {
// Some(vec![TextEdit::new(
// file.text.char_range_to_range(0..file.text.len_chars()),
// format_document(
// &file.text,
// Some(params.range),
// &conf.verible.format.path,
// &conf.verible.format.args,
// )?,
// )])
// } else {
// None
// }
}
}

View File

@ -5,7 +5,7 @@ use regex::Regex;
use ropey::RopeSlice;
use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url};
use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LSPServer};
use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LspServer};
use super::{get_language_id_by_path_str, get_word_range_at_position, resolve_path, to_escape_path};
@ -198,7 +198,7 @@ fn make_macro_define_content(macro_define: &Define) -> String {
}
}
pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
pub fn hover_macro(server: &LspServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap();
if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) {
if macro_text.starts_with("`") {
@ -222,7 +222,7 @@ pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language
fn goto_instantiation<'a>(
server: &LSPServer,
server: &LspServer,
fast: &'a FastHdlparam,
token_name: &str,
pos: &Position,
@ -313,7 +313,7 @@ fn goto_instantiation<'a>(
/// 计算 position 赋值的 port 或者 param
/// 比如 .clk ( clk ) 中的 .clk
pub fn hover_position_port_param(
server: &LSPServer,
server: &LspServer,
line: &RopeSlice,
url: &Url,
pos: Position,
@ -441,7 +441,7 @@ fn make_param_desc_hover(file_type: &str, param: &crate::core::hdlparam::Paramet
}
pub fn hover_module_declaration(
server: &LSPServer,
server: &LspServer,
token_name: &str,
#[allow(unused)]
language_id: &str
@ -486,7 +486,7 @@ pub fn hover_module_declaration(
fn hover_common_module_declaration(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
@ -545,7 +545,7 @@ fn hover_common_module_declaration(
fn hover_ip_module_declaration(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
@ -613,7 +613,7 @@ fn hover_ip_module_declaration(
fn hover_primitives_module_declaration(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]

View File

@ -1,4 +1,4 @@
use crate::server::LSPServer;
use crate::server::LspServer;
use crate::utils::*;
#[allow(unused)]
use log::info;
@ -9,7 +9,7 @@ pub mod feature;
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn hover(&self, params: HoverParams) -> Option<Hover> {
let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri);
match language_id.as_str() {

View File

@ -3,7 +3,7 @@ use log::info;
use regex::Regex;
use ropey::Rope;
use tower_lsp::lsp_types::*;
use crate::{core::hdlparam::{Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport};
use crate::{core::hdlparam::{Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport};
use super::feature::*;
use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard};
@ -11,7 +11,7 @@ use crate::definition::*;
use super::{get_definition_token, get_language_id_by_uri};
pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
let doc = &params.text_document_position_params.text_document.uri;
let pos: Position = params.text_document_position_params.position;
let file_id: usize = server.srcs.get_id(doc).to_owned();
@ -224,7 +224,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
/// 计算正常 symbol 的 hover
fn hover_common_symbol(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token: &String,
symbol_definition: &GenericDec,
@ -294,7 +294,7 @@ fn hover_common_symbol(
make_hover_with_comment(&file.text, def_line, &language_id, false)
}
fn hover_for_module(server: &LSPServer, pos: Position, doc: &Url) -> bool {
fn hover_for_module(server: &LspServer, pos: Position, doc: &Url) -> bool {
let pathbuf = PathBuf::from_str(doc.path()).unwrap();
let pathbuf = to_escape_path(&pathbuf);
let path_string = pathbuf.to_str().unwrap().replace("\\", "/");
@ -309,13 +309,13 @@ fn hover_for_module(server: &LSPServer, pos: Position, doc: &Url) -> bool {
};
if let Some(_) = hdlparam.walk_module(&path_string, find_module_range) {
// info!("[LSPServer] in hover: it is module");
// info!("[LspServer] in hover: it is module");
true
} else if let Some(_) = hdlparam.walk_instantiation(&path_string, find_instance_range) {
// info!("[LSPServer] in hover: it is instance");
// info!("[LspServer] in hover: it is instance");
true
} else {
// info!("[LSPServer] in hover: it is not instance");
// info!("[LspServer] in hover: it is not instance");
false
}
}

View File

@ -4,11 +4,11 @@ use log::info;
use regex::Regex;
use ropey::Rope;
use tower_lsp::lsp_types::*;
use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport};
use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport};
use super::{from_lsp_pos, get_definition_token, get_language_id_by_uri, to_escape_path};
pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
let doc = &params.text_document_position_params.text_document.uri;
let pos: Position = params.text_document_position_params.position;
let file_id: usize = server.srcs.get_id(doc).to_owned();
@ -239,7 +239,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
fn hover_common_symbol(
#[allow(unused)]
server: &LSPServer,
server: &LspServer,
#[allow(unused)]
token: &String,
symbol_definition: &GenericDec,

View File

@ -1,4 +1,4 @@
use crate::server::LSPServer;
use crate::server::LspServer;
use crate::utils::*;
#[allow(unused)]
use log::info;
@ -7,7 +7,7 @@ use tower_lsp::lsp_types::*;
mod sv;
mod vhdl;
impl LSPServer {
impl LspServer {
pub fn inlay_hint(&self, params: InlayHintParams) -> Option<Vec<InlayHint>> {
let language_id = get_language_id_by_uri(&params.text_document.uri);
match language_id.as_str() {

View File

@ -1,6 +1,6 @@
use std::{collections::HashMap, path::PathBuf, str::FromStr};
use crate::{core, server::LSPServer};
use crate::{core, server::LspServer};
#[allow(unused)]
use log::info;
use ropey::Rope;
@ -8,7 +8,7 @@ use tower_lsp::lsp_types::*;
use super::{get_language_id_by_path_str, to_escape_path};
pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
let uri = &params.text_document.uri;
let path = PathBuf::from_str(uri.path()).unwrap();
let path = to_escape_path(&path);
@ -58,7 +58,7 @@ fn is_visible_range(
}
fn make_instparam_hints(
server: &LSPServer,
server: &LspServer,
params: &InlayHintParams,
instance: &core::hdlparam::Instance,
rope: &Rope
@ -106,7 +106,7 @@ fn find_instport_inlay_hints_position(
}
fn make_instport_hints(
server: &LSPServer,
server: &LspServer,
params: &InlayHintParams,
instance: &core::hdlparam::Instance,
rope: &Rope

View File

@ -1,8 +1,8 @@
use crate::server::LSPServer;
use crate::server::LspServer;
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
None
}

9
src/request/README.md Normal file
View File

@ -0,0 +1,9 @@
request 负责实现前后端非 LSP 协议的通信,比如前端把配置文件同步到后端,前端获取 AST 的一部分数据用于前端的界面功能。
request 是单向的,只能由前端主动发起。
```mermaid
graph TB
前端 --编码发送--> request.rs --解码处理--> 后端
```

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower_lsp::jsonrpc::Result;
use crate::server::Backend;
use crate::{diagnostics::update_diagnostics_configuration, server::Backend};
#[derive(Clone)]
pub struct UpdateConfigurationApi;
@ -33,21 +33,75 @@ impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (UpdateConfigurationParams
let configs = request_param.configs;
// 用于未来进行配置分区
#[allow(unused)]
let config_type = request_param.config_type;
update_configuration(configs, &_server);
update_configuration(configs, config_type, &_server);
future::ready(Ok(()))
}
}
/// 前端配置文件的更新
fn update_configuration(
configs: Vec<UpdateConfigurationItem>,
config_type: String,
backend: &Arc<Backend>
) {
let mut lsp_configuration = backend.server.srcs.lsp_configuration.write().unwrap();
for config in configs {
info!("name: {}, value: {}", config.name, config.value);
lsp_configuration.insert(config.name, config.value);
match config_type.as_str() {
// 所有配置同步到 lsp_configuration 中
"lsp" => {
for config in configs {
info!("update config, name: {}, value: {}", config.name, config.value);
lsp_configuration.insert(config.name, config.value);
}
},
// 针对当前项目选择的诊断器的更新
// 此时 configs 的长度必然为 2
// configs.0 含有当前选择的诊断器的名字的信息,比如 "digital-ide.function.lsp.linter.vlog.diagnostor": "vivado"
// configs.1 含有第三方诊断器的合法路径相关的信息,比如 "path": "/opt/xilinx/Vivado/2022.2/bin/xvlog"
"linter" => {
if configs.len() < 2 {
info!("update_configuration, type : {}, 发生错误原因configs 数量不为 2", config_type);
return;
}
let linter_name_configuration = &configs[0];
let linter_path_configuration = &configs[1];
// something like digital-ide.function.lsp.linter.vlog.diagnostor
let linter_name_config_name = &linter_name_configuration.name;
lsp_configuration.insert(
linter_name_config_name.to_string(),
linter_name_configuration.value.clone()
);
// 从 linter_name_configuration.name 解析出 language_id
let language_id = {
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
let mut cookies = linter_name_config_name.split(".");
let name = cookies.nth(4).unwrap();
match name {
"vlog" => "verilog",
"vhdl" => "vhdl",
"svlog" => "systemverilog",
_ => return
}
};
let linter_name = linter_name_configuration.value.as_str().unwrap();
let linter_path = linter_path_configuration.value.as_str().unwrap();
update_diagnostics_configuration(
&backend.server,
linter_name,
language_id,
linter_path
);
},
_ => {}
}
}

View File

@ -1,5 +1,5 @@
use crate::core::cache_storage::CacheManager;
use crate::diagnostics::{VeribleConfiguration, VerilatorConfiguration};
use crate::diagnostics::{DigitalLinterConfiguration, DigitalLinterMode, ModelsimConfiguration, VeribleConfiguration, VerilatorConfiguration, VivadoConfiguration};
use crate::sources::*;
use crate::completion::{keyword::*, provide_vlog_sys_tasks_completions};
use flexi_logger::LoggerHandle;
@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex, RwLock};
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer};
pub struct LSPServer {
pub struct LspServer {
/// 文件和 ast 相关的
pub srcs: Sources,
/// 缓存
@ -25,23 +25,24 @@ pub struct LSPServer {
/// vhdl 关键词的自动补全
pub vhdl_keyword_completiom_items: Vec<CompletionItem>,
/// 相关的配置项目
pub configuration: Arc<RwLock<ProjectConfig>>,
pub configuration: Arc<RwLock<LspConfiguration>>,
#[allow(unused)]
pub log_handle: Mutex<Option<LoggerHandle>>,
}
impl LSPServer {
pub fn new(log_handle: Option<LoggerHandle>) -> LSPServer {
impl LspServer {
pub fn new(log_handle: Option<LoggerHandle>) -> LspServer {
let user_home = dirs_next::home_dir().unwrap();
let dide_home = user_home.join(".digital-ide");
LSPServer {
LspServer {
srcs: Sources::new(),
cache: CacheManager::new(dide_home),
vlog_keyword_completion_items: keyword_completions(VLOG_KEYWORDS),
vhdl_keyword_completiom_items: keyword_completions(VHDL_KEYWORDS),
vlog_sys_tasks_completion_items: provide_vlog_sys_tasks_completions(),
vlog_directives: other_completions(DIRECTIVES),
configuration: Arc::new(RwLock::new(ProjectConfig::default())),
configuration: Arc::new(RwLock::new(LspConfiguration::default())),
log_handle: Mutex::new(log_handle),
}
}
@ -49,7 +50,7 @@ impl LSPServer {
pub struct Backend {
pub client: Client,
pub server: LSPServer
pub server: LspServer
}
@ -57,7 +58,7 @@ impl Backend {
pub fn new(client: Client, log_handle: LoggerHandle) -> Backend {
Backend {
client,
server: LSPServer::new(Some(log_handle)),
server: LspServer::new(Some(log_handle)),
}
}
}
@ -78,7 +79,7 @@ pub enum LogLevel {
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ProjectConfig {
pub struct LspConfiguration {
// 用户工作目录的路径
pub workspace_folder: Option<Url>,
// 插件安装的根路径
@ -93,29 +94,33 @@ pub struct ProjectConfig {
// list of directories to recursively search for SystemVerilog/Verilog sources
pub source_dirs: Vec<String>,
// verible 相关的工具配置
pub verible: VeribleConfiguration,
// verilator 相关的工具配置
pub verilator: VerilatorConfiguration,
// pub modelsim: String,
// pub vivado: String,
// 下方是和 linter 相关的配置
// 诊断模式
pub linter_mode: DigitalLinterMode,
// vlog 的诊断配置
pub vlog_linter_configuration: DigitalLinterConfiguration,
// vhdl 的诊断配置
pub vhdl_linter_configuration: DigitalLinterConfiguration,
// svlog 的诊断配置
pub svlog_linter_configuration: DigitalLinterConfiguration,
// log level
pub log_level: LogLevel
}
impl Default for ProjectConfig {
impl Default for LspConfiguration {
fn default() -> Self {
ProjectConfig {
LspConfiguration {
workspace_folder: None,
extension_path: "".to_string(),
tool_chain: "xilinx".to_string(),
auto_search_workdir: true,
include_dirs: Vec::new(),
source_dirs: Vec::new(),
verible: VeribleConfiguration::default(),
verilator: VerilatorConfiguration::default(),
linter_mode: DigitalLinterMode::FULL,
vlog_linter_configuration: DigitalLinterConfiguration::default(),
vhdl_linter_configuration: DigitalLinterConfiguration::default(),
svlog_linter_configuration: DigitalLinterConfiguration::default(),
log_level: LogLevel::Info,
}
}
@ -148,11 +153,15 @@ impl LanguageServer for Backend {
info!("extensionPath: {:?}", configure.extension_path);
info!("toolChain: {:?}", configure.tool_chain);
// 初始化原语系统
self.server.srcs.init_primitive(
&configure.tool_chain,
&configure.extension_path
);
// 初始化诊断器
let text_document_sync = TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),

View File

@ -6,9 +6,9 @@ use crate::core::vhdl_parser::vhdl_parse_str;
use crate::definition::def_types::*;
use crate::definition::get_scopes_from_syntax_tree;
use crate::definition::get_scopes_from_vhdl_fast;
use crate::diagnostics::{provide_diagnostics, is_hidden};
use crate::server::LSPServer;
use crate::server::ProjectConfig;
use crate::diagnostics::provide_diagnostics;
use crate::server::LspServer;
use crate::server::LspConfiguration;
use crate::utils::to_escape_path;
#[allow(unused)]
use log::info;
@ -33,7 +33,7 @@ use thread::JoinHandle;
use tower_lsp::lsp_types::*;
use walkdir::WalkDir;
impl LSPServer {
impl LspServer {
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
let document: TextDocumentItem = params.text_document;
let uri = document.uri.clone();
@ -216,28 +216,6 @@ pub struct SourceMeta {
pub parse_handle: JoinHandle<()>,
}
/// find SystemVerilog/Verilog sources recursively from opened files
fn find_src_paths(dirs: &[PathBuf]) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = Vec::new();
for dir in dirs {
let walker = WalkDir::new(dir).into_iter();
for entry in walker.filter_entry(|e| !is_hidden(e)) {
let entry = entry.unwrap();
if entry.file_type().is_file() && entry.path().extension().is_some() {
let extension = entry.path().extension().unwrap();
if extension == "sv" || extension == "svh" || extension == "v" || extension == "vh" {
let entry_path = entry.path().to_path_buf();
if !paths.contains(&entry_path) {
paths.push(entry_path);
}
}
}
}
}
paths
}
/// The Sources struct manages all source files
pub struct Sources {
@ -302,7 +280,7 @@ impl Sources {
}
/// 增加一个 hdl 文件,并为该文件添加单独的解析线程
pub fn add(&self, server: &LSPServer, doc: TextDocumentItem) {
pub fn add(&self, server: &LspServer, doc: TextDocumentItem) {
// 对于当前的文件增加一个解析线程,不断进行解析和同步
#[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516
let valid_parse = Arc::new((Mutex::new(false), Condvar::new()));
@ -662,7 +640,7 @@ pub fn recovery_sv_parse(
pub fn sv_parser_pipeline(
#[allow(unused)]
conf: &Arc<RwLock<ProjectConfig>>,
conf: &Arc<RwLock<LspConfiguration>>,
source_handle: &Arc<RwLock<Source>>,
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
hdl_param_handle: &Arc<HdlParam>,
@ -744,7 +722,7 @@ pub fn sv_parser_pipeline(
}
pub fn vhdl_parser_pipeline(
conf: &Arc<RwLock<ProjectConfig>>,
conf: &Arc<RwLock<LspConfiguration>>,
source_handle: &Arc<RwLock<Source>>,
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
project_handle: &Arc<RwLock<Option<VhdlProject>>>,

View File

@ -1,9 +1,9 @@
#[allow(unused)]
use log::info;
use crate::{core::hdlparam::Define, server::LSPServer};
use crate::{core::hdlparam::Define, server::LspServer};
impl LSPServer {
impl LspServer {
/// 根据输入的 macro 名字,寻找 fast 中存在的第一个 macro
/// macro 可以以 ` 开头
pub fn find_macros(&self, macro_name: &str) -> Option<(Define, String)> {