完成自动补全的 output 自动申明 | 完成配置文件的前后端更新系统
This commit is contained in:
parent
290d1aec05
commit
d45c243d62
@ -1,4 +1,6 @@
|
||||
use crate::{completion::feature::{get_dot_completion, include_path_completion}, hover::feature::make_module_profile_code, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
|
||||
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 log::info;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use tower_lsp::lsp_types::*;
|
||||
@ -261,26 +263,40 @@ fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
|
||||
snippet_codes.join("")
|
||||
}
|
||||
|
||||
/// 自动补全例化模块
|
||||
fn make_module_completions(
|
||||
server: &LSPServer,
|
||||
token: &str,
|
||||
language_id: &str
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut module_completioms = Vec::<CompletionItem>::new();
|
||||
let hdl_param = server.srcs.hdl_param.clone();
|
||||
|
||||
let prefix = token.to_string().to_lowercase();
|
||||
let path_to_files = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
|
||||
let module_name_to_path = hdl_param.module_name_to_path.read().unwrap();
|
||||
|
||||
// 获取和自动补全相关的配置
|
||||
let auto_add_output_declaration = server.srcs.get_lsp_configuration_bool_value("digital-ide.function.lsp.completion.vlog.auto-add-output-declaration").unwrap_or(true);
|
||||
|
||||
// 遍历 hdlparam 中所有的 modules
|
||||
for (path_string, hdl_file) in path_to_files.iter() {
|
||||
for module in &hdl_file.fast.content {
|
||||
if !module.name.to_string().to_lowercase().starts_with(&prefix) {
|
||||
for module_name in module_name_to_path.keys() {
|
||||
if !module_name.to_string().to_lowercase().starts_with(&prefix) {
|
||||
continue;
|
||||
}
|
||||
let insert_text = make_instantiation_code(module);
|
||||
let module_profile = make_module_profile_code(module);
|
||||
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(&module_name) {
|
||||
let mut insert_text = Vec::<String>::new();
|
||||
if auto_add_output_declaration {
|
||||
if let Some(declaration_string) = make_output_declaration(&module) {
|
||||
insert_text.push(declaration_string);
|
||||
}
|
||||
}
|
||||
|
||||
let path_uri = Url::from_file_path(path_string.to_string()).unwrap().to_string();
|
||||
insert_text.push(make_instantiation_code(&module));
|
||||
|
||||
let insert_text = insert_text.join("\n");
|
||||
let module_profile = make_module_profile_code(&module);
|
||||
|
||||
let path_uri = Url::from_file_path(def_path).unwrap().to_string();
|
||||
let def_row = module.range.start.line;
|
||||
let def_col = module.range.start.character;
|
||||
let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})");
|
||||
@ -290,10 +306,11 @@ fn make_module_completions(
|
||||
value: format!("```{}\n{}\n```\n{}", language_id, module_profile, define_info)
|
||||
};
|
||||
|
||||
let detail = format!("module instantiation ({})", file_type);
|
||||
|
||||
let item = CompletionItem {
|
||||
label: module.name.to_string(),
|
||||
detail: Some("module instantiation".to_string()),
|
||||
detail: Some(detail),
|
||||
documentation: Some(Documentation::MarkupContent(module_profile)),
|
||||
kind: Some(CompletionItemKind::CLASS),
|
||||
insert_text: Some(insert_text),
|
||||
@ -309,3 +326,31 @@ fn make_module_completions(
|
||||
|
||||
module_completioms
|
||||
}
|
||||
|
||||
fn make_output_declaration(
|
||||
module: &core::hdlparam::Module
|
||||
) -> Option<String> {
|
||||
let mut output_declaration = Vec::<String>::new();
|
||||
for port in &module.ports {
|
||||
if port.dir_type == "output" || port.dir_type == "out" {
|
||||
let mut declaration = Vec::<String>::new();
|
||||
if port.net_type == "reg" || port.net_type == "wire" {
|
||||
declaration.push(port.net_type.to_string());
|
||||
} else {
|
||||
declaration.push("wire".to_string());
|
||||
}
|
||||
if port.width != "1" {
|
||||
declaration.push(port.width.to_string());
|
||||
}
|
||||
declaration.push(port.name.to_string());
|
||||
output_declaration.push(format!("{};", declaration.join(" ")));
|
||||
}
|
||||
}
|
||||
|
||||
if output_declaration.len() > 0 {
|
||||
let output_declaration = format!("// output declaration of module {}\n{}\n", module.name, output_declaration.join("\n"));
|
||||
Some(output_declaration)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@ -780,14 +780,6 @@ pub fn make_entity_profile_code(module: &crate::core::hdlparam::Module) -> Strin
|
||||
if module.ports.len() > 0 {
|
||||
codes.push("\tport (".to_string());
|
||||
|
||||
let net_mapper = |net_type: &str| {
|
||||
if net_type == "unknown" {
|
||||
0
|
||||
} else {
|
||||
net_type.len()
|
||||
}
|
||||
};
|
||||
|
||||
let max_port_length = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0);
|
||||
// let max_width_length = module.ports.iter().map(|port| width_mapper(&port.width)).max().unwrap_or(0);
|
||||
let max_dir_length = module.ports.iter().map(|port| port.dir_type.len()).max().unwrap_or(0);
|
||||
|
@ -33,6 +33,7 @@ pub mod utils;
|
||||
// LSP 服务器
|
||||
pub mod server;
|
||||
|
||||
|
||||
// 管理所有代码
|
||||
pub mod sources;
|
||||
|
||||
|
15
src/main.rs
15
src/main.rs
@ -1,6 +1,12 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use request::{ CustomParamRequest, CustomRequest, DoFastApi };
|
||||
use request::{
|
||||
test::CustomParamRequest,
|
||||
test::CustomRequest,
|
||||
fast::DoFastApi,
|
||||
config::UpdateConfigurationApi,
|
||||
primitives::DoPrimitivesJudgeApi
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
@ -43,10 +49,11 @@ async fn main() {
|
||||
let backend = Arc::new(Backend::new(client, log_handle));
|
||||
backend
|
||||
})
|
||||
.custom_method("custom/request", CustomRequest)
|
||||
.custom_method("custom/paramRequest", CustomParamRequest)
|
||||
.custom_method("custom/request", CustomRequest) // for test
|
||||
.custom_method("custom/paramRequest", CustomParamRequest) // for test
|
||||
.custom_method("api/fast", DoFastApi)
|
||||
// .custom_method("api/update-fast", UpdateFastApi)
|
||||
.custom_method("api/do-primitives-judge", DoPrimitivesJudgeApi)
|
||||
.custom_method("api/update-fast", UpdateConfigurationApi)
|
||||
.finish();
|
||||
|
||||
Server::new(stdin, stdout, socket)
|
||||
|
53
src/request/config.rs
Normal file
53
src/request/config.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::sync::Arc;
|
||||
use std::future;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tower_lsp::jsonrpc::Result;
|
||||
|
||||
use crate::server::Backend;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateConfigurationApi;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateConfigurationParams {
|
||||
configs: Vec<UpdateConfigurationItem>,
|
||||
#[serde(rename = "configType")]
|
||||
config_type: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UpdateConfigurationItem {
|
||||
name: String,
|
||||
value: Value
|
||||
}
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (UpdateConfigurationParams, ), Result<()>> for UpdateConfigurationApi {
|
||||
type Future = future::Ready<Result<()>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (UpdateConfigurationParams, )) -> Self::Future {
|
||||
let request_param = _params.0;
|
||||
let configs = request_param.configs;
|
||||
|
||||
// 用于未来进行配置分区
|
||||
#[allow(unused)]
|
||||
let config_type = request_param.config_type;
|
||||
|
||||
update_configuration(configs, &_server);
|
||||
future::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn update_configuration(
|
||||
configs: Vec<UpdateConfigurationItem>,
|
||||
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);
|
||||
}
|
||||
}
|
266
src/request/fast.rs
Normal file
266
src/request/fast.rs
Normal file
@ -0,0 +1,266 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, future};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use log::info;
|
||||
use ropey::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::jsonrpc::Result;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
use crate::core::cache_storage::CacheResult;
|
||||
use crate::core::hdlparam::FastHdlparam;
|
||||
use crate::core::sv_parser::make_fast_from_syntaxtree;
|
||||
|
||||
use crate::core::vhdl_parser::{make_fast_from_design_file, vhdl_parse};
|
||||
use crate::{core, utils::*};
|
||||
use crate::server::Backend;
|
||||
use crate::sources::recovery_sv_parse_with_retry;
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DoFastApiRequestParams {
|
||||
path: String,
|
||||
file_type: String,
|
||||
tool_chain: String
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DoFastApi;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoFastApiRequestParams, ), Result<FastHdlparam>> for DoFastApi {
|
||||
type Future = future::Ready<Result<FastHdlparam>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoFastApiRequestParams, )) -> Self::Future {
|
||||
let request_param = _params.0;
|
||||
let path = request_param.path;
|
||||
let file_type = request_param.file_type;
|
||||
let tool_chain = request_param.tool_chain;
|
||||
let hdlparam = do_fast(path, file_type, tool_chain, _server);
|
||||
future::ready(hdlparam)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option<TextDocumentItem> {
|
||||
if let Ok(url) = Url::from_file_path(path_buf) {
|
||||
if let Ok(text) = fs::read_to_string(path_buf) {
|
||||
let language_id = get_language_id_by_uri(&url);
|
||||
return Some(TextDocumentItem::new(url, language_id, -1, text));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 前端交互接口: do_fast,输入文件路径,计算出对应的 fast 结构
|
||||
pub fn do_fast(
|
||||
path: String,
|
||||
file_type: String,
|
||||
tool_chain: String,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
info!("parse fast {:?}, type: {:?}, toolchain: {:?}", path, file_type, tool_chain);
|
||||
|
||||
// 根据 file_type 和 tool_chain 计算正确的 path
|
||||
let path = {
|
||||
if file_type == "ip" && tool_chain == "xilinx" {
|
||||
let pathbuf = PathBuf::from_str(&path).unwrap();
|
||||
let basename = pathbuf.file_name().unwrap().to_str().unwrap();
|
||||
format!("{}/synth/{}.vhd", path, basename)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
let language_id = get_language_id_by_path_str(&path);
|
||||
|
||||
let parse_fast_by_language_id = |language_id: &str| {
|
||||
match language_id {
|
||||
"vhdl" => {
|
||||
do_vhdl_fast(
|
||||
&path,
|
||||
&file_type,
|
||||
&tool_chain,
|
||||
backend
|
||||
)
|
||||
}
|
||||
|
||||
"verilog" | "systemverilog" => {
|
||||
do_sv_fast(
|
||||
&path,
|
||||
&file_type,
|
||||
&tool_chain,
|
||||
backend
|
||||
)
|
||||
}
|
||||
|
||||
_ => Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
|
||||
message: Cow::Owned(format!("invalid file: {path}, expect vhdl, verilog or system verilog!")),
|
||||
data: None
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let path_buf = PathBuf::from_str(&path).unwrap();
|
||||
// 做缓存优化
|
||||
let cache = &backend.server.cache;
|
||||
match cache.try_get_fast_cache(&path_buf) {
|
||||
// 找到缓存,直接返回
|
||||
CacheResult::Ok(fast) => {
|
||||
return Ok(fast);
|
||||
}
|
||||
|
||||
// cache 没找到,那么就需要计算并存入
|
||||
CacheResult::CacheNotFound => {
|
||||
match parse_fast_by_language_id(&language_id) {
|
||||
Ok(fast) => {
|
||||
cache.update_cache(&path_buf, fast.clone());
|
||||
return Ok(fast);
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 不需要缓存的文件正常进行 fast 计算即可
|
||||
_ => {
|
||||
parse_fast_by_language_id(&language_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_sv_fast(
|
||||
path: &str,
|
||||
#[allow(unused)]
|
||||
file_type: &str,
|
||||
#[allow(unused)]
|
||||
tool_chain: &str,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
let doc = match make_textdocumenitem_from_path(&path_buf) {
|
||||
Some(doc) => doc,
|
||||
None => {
|
||||
let api_error = tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::InvalidParams,
|
||||
message: Cow::Owned(format!("cannot make doc from path : {path}")),
|
||||
data: None
|
||||
};
|
||||
|
||||
return Err(api_error);
|
||||
}
|
||||
};
|
||||
|
||||
let uri = doc.uri;
|
||||
let text = Rope::from(doc.text);
|
||||
// fast 解析不需要 include
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let parse_result = recovery_sv_parse_with_retry(
|
||||
&text,
|
||||
&uri,
|
||||
&None,
|
||||
&includes
|
||||
);
|
||||
|
||||
let sources = &backend.server.srcs;
|
||||
if let Some(syntax_tree) = parse_result {
|
||||
if let Ok(mut fast) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
|
||||
fast.file_type = file_type.to_string();
|
||||
let hdl_param = sources.hdl_param.clone();
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
}
|
||||
|
||||
Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} [do_sv_fast]")),
|
||||
data: None
|
||||
})
|
||||
}
|
||||
|
||||
fn do_vhdl_fast(
|
||||
path: &str,
|
||||
#[allow(unused)]
|
||||
file_type: &str,
|
||||
#[allow(unused)]
|
||||
tool_chain: &str,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
let sources = &backend.server.srcs;
|
||||
let pathbuf = PathBuf::from_str(path).unwrap();
|
||||
let hdl_param = sources.hdl_param.clone();
|
||||
|
||||
// TODO: 支持对于 synth 下的 vhdl 文件的解析,从而提供更加丰富的 IP 支持
|
||||
if file_type == "ip" {
|
||||
match tool_chain {
|
||||
// 此时的 pathbuf 类似 {ip_name}/synth/{ip_name}.vhd
|
||||
"xilinx" => {
|
||||
// 如果 ip 描述文件存在,则解析它,否则,创建空的写入
|
||||
// IP 描述文件一般都不会很大,所以不需要缓存
|
||||
if !pathbuf.exists() {
|
||||
let ip_name = pathbuf.file_name().unwrap().to_str().unwrap();
|
||||
let ip_name = ip_name.strip_suffix(".vhd").unwrap();
|
||||
let fake_content = vec![
|
||||
core::hdlparam::Module {
|
||||
name: ip_name.to_string(),
|
||||
params: vec![],
|
||||
ports: vec![],
|
||||
instances: vec![],
|
||||
range: core::hdlparam::Range::default()
|
||||
}
|
||||
];
|
||||
let ip_fast = core::hdlparam::FastHdlparam {
|
||||
fast_macro: core::hdlparam::Macro {
|
||||
includes: vec![],
|
||||
defines: vec![],
|
||||
errors: vec![],
|
||||
invalid: vec![]
|
||||
},
|
||||
file_type: "ip".to_string(),
|
||||
content: fake_content
|
||||
};
|
||||
hdl_param.update_fast(path.to_string(), ip_fast.clone());
|
||||
return Ok(ip_fast);
|
||||
} else if let Some(design_file) = vhdl_parse(&pathbuf) {
|
||||
if let Some(mut fast) = make_fast_from_design_file(&design_file) {
|
||||
fast.file_type = file_type.to_string();
|
||||
// IP 不需要内部的 instance,其实现是加密的,只需要暴露 module 的接口即可
|
||||
// 所以此处需要清空所有的 module 中的 instance
|
||||
for module in &mut fast.content {
|
||||
module.instances.clear();
|
||||
}
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
} else {
|
||||
return Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||||
data: None
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 没有特殊情况,则正常解析并写入
|
||||
if let Some(design_file) = vhdl_parse(&pathbuf) {
|
||||
if let Some(mut fast) = make_fast_from_design_file(&design_file) {
|
||||
fast.file_type = file_type.to_string();
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
}
|
||||
|
||||
Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||||
data: None
|
||||
})
|
||||
}
|
@ -1,347 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, future};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::info;
|
||||
use ropey::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::jsonrpc::Result;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
use crate::core::cache_storage::CacheResult;
|
||||
use crate::core::hdlparam::FastHdlparam;
|
||||
use crate::core::sv_parser::make_fast_from_syntaxtree;
|
||||
|
||||
use crate::core::vhdl_parser::{make_fast_from_design_file, vhdl_parse};
|
||||
use crate::{core, utils::*};
|
||||
use crate::server::Backend;
|
||||
use crate::sources::recovery_sv_parse_with_retry;
|
||||
|
||||
pub mod notification;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomRequest;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (), Result<i32>> for CustomRequest {
|
||||
type Future = future::Ready<Result<i32>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: ()) -> Self::Future {
|
||||
future::ready(custom_request())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_request() -> Result<i32> {
|
||||
Ok(123)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct CustomParamRequestParams {
|
||||
param: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomParamRequest;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (CustomParamRequestParams, ), Result<i32>> for CustomParamRequest {
|
||||
type Future = future::Ready<Result<i32>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (CustomParamRequestParams, )) -> Self::Future {
|
||||
future::ready(custom_param_request(_params.0.param))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_param_request(param: String) -> Result<i32> {
|
||||
info!("receive param: {:?}", param);
|
||||
Ok(123)
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DoFastApiRequestParams {
|
||||
path: String,
|
||||
file_type: String,
|
||||
tool_chain: String
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DoFastApi;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoFastApiRequestParams, ), Result<FastHdlparam>> for DoFastApi {
|
||||
type Future = future::Ready<Result<FastHdlparam>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoFastApiRequestParams, )) -> Self::Future {
|
||||
let request_param = _params.0;
|
||||
let path = request_param.path;
|
||||
let file_type = request_param.file_type;
|
||||
let tool_chain = request_param.tool_chain;
|
||||
let hdlparam = do_fast(path, file_type, tool_chain, _server);
|
||||
future::ready(hdlparam)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option<TextDocumentItem> {
|
||||
if let Ok(url) = Url::from_file_path(path_buf) {
|
||||
if let Ok(text) = fs::read_to_string(path_buf) {
|
||||
let language_id = get_language_id_by_uri(&url);
|
||||
return Some(TextDocumentItem::new(url, language_id, -1, text));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 前端交互接口: do_fast,输入文件路径,计算出对应的 fast 结构
|
||||
pub fn do_fast(
|
||||
path: String,
|
||||
file_type: String,
|
||||
tool_chain: String,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
info!("parse fast {:?}, type: {:?}, toolchain: {:?}", path, file_type, tool_chain);
|
||||
|
||||
// 根据 file_type 和 tool_chain 计算正确的 path
|
||||
let path = {
|
||||
if file_type == "ip" && tool_chain == "xilinx" {
|
||||
let pathbuf = PathBuf::from_str(&path).unwrap();
|
||||
let basename = pathbuf.file_name().unwrap().to_str().unwrap();
|
||||
format!("{}/synth/{}.vhd", path, basename)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
let language_id = get_language_id_by_path_str(&path);
|
||||
|
||||
let parse_fast_by_language_id = |language_id: &str| {
|
||||
match language_id {
|
||||
"vhdl" => {
|
||||
do_vhdl_fast(
|
||||
&path,
|
||||
&file_type,
|
||||
&tool_chain,
|
||||
backend
|
||||
)
|
||||
}
|
||||
|
||||
"verilog" | "systemverilog" => {
|
||||
do_sv_fast(
|
||||
&path,
|
||||
&file_type,
|
||||
&tool_chain,
|
||||
backend
|
||||
)
|
||||
}
|
||||
|
||||
_ => Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
|
||||
message: Cow::Owned(format!("invalid file: {path}, expect vhdl, verilog or system verilog!")),
|
||||
data: None
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let path_buf = PathBuf::from_str(&path).unwrap();
|
||||
// 做缓存优化
|
||||
let cache = &backend.server.cache;
|
||||
match cache.try_get_fast_cache(&path_buf) {
|
||||
// 找到缓存,直接返回
|
||||
CacheResult::Ok(fast) => {
|
||||
return Ok(fast);
|
||||
}
|
||||
|
||||
// cache 没找到,那么就需要计算并存入
|
||||
CacheResult::CacheNotFound => {
|
||||
match parse_fast_by_language_id(&language_id) {
|
||||
Ok(fast) => {
|
||||
cache.update_cache(&path_buf, fast.clone());
|
||||
return Ok(fast);
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 不需要缓存的文件正常进行 fast 计算即可
|
||||
_ => {
|
||||
parse_fast_by_language_id(&language_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_sv_fast(
|
||||
path: &str,
|
||||
#[allow(unused)]
|
||||
file_type: &str,
|
||||
#[allow(unused)]
|
||||
tool_chain: &str,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
let doc = match make_textdocumenitem_from_path(&path_buf) {
|
||||
Some(doc) => doc,
|
||||
None => {
|
||||
let api_error = tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::InvalidParams,
|
||||
message: Cow::Owned(format!("cannot make doc from path : {path}")),
|
||||
data: None
|
||||
};
|
||||
|
||||
return Err(api_error);
|
||||
}
|
||||
};
|
||||
|
||||
let uri = doc.uri;
|
||||
let text = Rope::from(doc.text);
|
||||
// fast 解析不需要 include
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let parse_result = recovery_sv_parse_with_retry(
|
||||
&text,
|
||||
&uri,
|
||||
&None,
|
||||
&includes
|
||||
);
|
||||
|
||||
let sources = &backend.server.srcs;
|
||||
if let Some(syntax_tree) = parse_result {
|
||||
if let Ok(mut fast) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
|
||||
fast.file_type = file_type.to_string();
|
||||
let hdl_param = sources.hdl_param.clone();
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
}
|
||||
|
||||
Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} [do_sv_fast]")),
|
||||
data: None
|
||||
})
|
||||
}
|
||||
|
||||
fn do_vhdl_fast(
|
||||
path: &str,
|
||||
#[allow(unused)]
|
||||
file_type: &str,
|
||||
#[allow(unused)]
|
||||
tool_chain: &str,
|
||||
backend: &Arc<Backend>
|
||||
) -> Result<FastHdlparam> {
|
||||
let sources = &backend.server.srcs;
|
||||
let pathbuf = PathBuf::from_str(path).unwrap();
|
||||
let hdl_param = sources.hdl_param.clone();
|
||||
|
||||
// TODO: 支持对于 synth 下的 vhdl 文件的解析,从而提供更加丰富的 IP 支持
|
||||
if file_type == "ip" {
|
||||
match tool_chain {
|
||||
// 此时的 pathbuf 类似 {ip_name}/synth/{ip_name}.vhd
|
||||
"xilinx" => {
|
||||
// 如果 ip 描述文件存在,则解析它,否则,创建空的写入
|
||||
// IP 描述文件一般都不会很大,所以不需要缓存
|
||||
if !pathbuf.exists() {
|
||||
let ip_name = pathbuf.file_name().unwrap().to_str().unwrap();
|
||||
let ip_name = ip_name.strip_suffix(".vhd").unwrap();
|
||||
let fake_content = vec![
|
||||
core::hdlparam::Module {
|
||||
name: ip_name.to_string(),
|
||||
params: vec![],
|
||||
ports: vec![],
|
||||
instances: vec![],
|
||||
range: core::hdlparam::Range::default()
|
||||
}
|
||||
];
|
||||
let ip_fast = core::hdlparam::FastHdlparam {
|
||||
fast_macro: core::hdlparam::Macro {
|
||||
includes: vec![],
|
||||
defines: vec![],
|
||||
errors: vec![],
|
||||
invalid: vec![]
|
||||
},
|
||||
file_type: "ip".to_string(),
|
||||
content: fake_content
|
||||
};
|
||||
hdl_param.update_fast(path.to_string(), ip_fast.clone());
|
||||
return Ok(ip_fast);
|
||||
} else if let Some(design_file) = vhdl_parse(&pathbuf) {
|
||||
if let Some(mut fast) = make_fast_from_design_file(&design_file) {
|
||||
fast.file_type = file_type.to_string();
|
||||
// IP 不需要内部的 instance,其实现是加密的,只需要暴露 module 的接口即可
|
||||
// 所以此处需要清空所有的 module 中的 instance
|
||||
for module in &mut fast.content {
|
||||
module.instances.clear();
|
||||
}
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
} else {
|
||||
return Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||||
data: None
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 没有特殊情况,则正常解析并写入
|
||||
if let Some(design_file) = vhdl_parse(&pathbuf) {
|
||||
if let Some(mut fast) = make_fast_from_design_file(&design_file) {
|
||||
fast.file_type = file_type.to_string();
|
||||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||||
return Ok(fast);
|
||||
}
|
||||
}
|
||||
|
||||
Err(tower_lsp::jsonrpc::Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||||
data: None
|
||||
})
|
||||
}
|
||||
|
||||
fn do_primitives_judge(name: &str, backend: &Arc<Backend>) -> bool {
|
||||
let sources = &backend.server.srcs;
|
||||
let primitive_text = sources.primitive_text.clone();
|
||||
let primitive_map = primitive_text.name_to_text.read().unwrap();
|
||||
primitive_map.contains_key(name)
|
||||
}
|
||||
|
||||
// #[derive(Clone)]
|
||||
// pub struct UpdateFastApi;
|
||||
|
||||
// impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoFastApiRequestParams, ), Result<FastHdlparam>> for UpdateFastApi {
|
||||
// type Future = future::Ready<Result<FastHdlparam>>;
|
||||
|
||||
// fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoFastApiRequestParams, )) -> Self::Future {
|
||||
// let request_param = _params.0;
|
||||
// let path = request_param.path;
|
||||
// let hdlparam = update_fast(path, _server);
|
||||
// future::ready(hdlparam)
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn update_fast(path: String, backend: &Arc<Backend>) -> Result<FastHdlparam> {
|
||||
// {
|
||||
// let fast_sync_controller = backend.server.srcs.fast_sync_controller.read().unwrap();
|
||||
// if let Some(fast_lock) = fast_sync_controller.get(&path) {
|
||||
// let fast_lock = fast_lock.clone();
|
||||
// drop(fast_sync_controller);
|
||||
// let _unused = fast_lock.write().unwrap();
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 去缓存中寻找
|
||||
// let hdl_param = backend.server.srcs.hdl_param.clone();
|
||||
// let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
|
||||
// if let Some(hdl_file) = path_to_hdl_file.get(&path) {
|
||||
// let fast = hdl_file.fast.clone();
|
||||
// Ok(fast)
|
||||
// } else {
|
||||
// do_fast(path, backend)
|
||||
// }
|
||||
// }
|
||||
pub mod config;
|
||||
pub mod primitives;
|
||||
pub mod fast;
|
||||
pub mod test;
|
37
src/request/primitives.rs
Normal file
37
src/request/primitives.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::future;
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::jsonrpc::Result;
|
||||
use crate::server::Backend;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DoPrimitivesJudgeParams {
|
||||
name: String
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DoPrimitivesJudgeApi;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoPrimitivesJudgeParams, ), Result<bool>> for DoPrimitivesJudgeApi {
|
||||
type Future = future::Ready<Result<bool>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoPrimitivesJudgeParams, )) -> Self::Future {
|
||||
let request_param = _params.0;
|
||||
let primitive_name = request_param.name;
|
||||
let is_primitive = do_primitives_judge(&primitive_name, &_server);
|
||||
future::ready(Ok(is_primitive))
|
||||
}
|
||||
}
|
||||
|
||||
fn do_primitives_judge(
|
||||
name: &str,
|
||||
backend: &Arc<Backend>
|
||||
) -> bool {
|
||||
let sources = &backend.server.srcs;
|
||||
let primitive_text = sources.primitive_text.clone();
|
||||
let primitive_map = primitive_text.name_to_text.read().unwrap();
|
||||
primitive_map.contains_key(name)
|
||||
}
|
||||
|
||||
|
44
src/request/test.rs
Normal file
44
src/request/test.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::jsonrpc::Result;
|
||||
use crate::server::Backend;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomRequest;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (), Result<i32>> for CustomRequest {
|
||||
type Future = future::Ready<Result<i32>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: ()) -> Self::Future {
|
||||
future::ready(custom_request())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_request() -> Result<i32> {
|
||||
Ok(123)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct CustomParamRequestParams {
|
||||
param: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomParamRequest;
|
||||
|
||||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (CustomParamRequestParams, ), Result<i32>> for CustomParamRequest {
|
||||
type Future = future::Ready<Result<i32>>;
|
||||
|
||||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (CustomParamRequestParams, )) -> Self::Future {
|
||||
future::ready(custom_param_request(_params.0.param))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_param_request(param: String) -> Result<i32> {
|
||||
info!("receive param: {:?}", param);
|
||||
Ok(123)
|
||||
}
|
@ -238,6 +238,14 @@ impl LanguageServer for Backend {
|
||||
completion_item: None,
|
||||
};
|
||||
|
||||
let workspace = WorkspaceServerCapabilities {
|
||||
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
|
||||
supported: Some(true),
|
||||
change_notifications: Some(OneOf::Left(true)),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let capabilities = ServerCapabilities {
|
||||
text_document_sync: Some(text_document_sync),
|
||||
completion_provider: Some(completion_provider),
|
||||
@ -246,6 +254,7 @@ impl LanguageServer for Backend {
|
||||
inlay_hint_provider: Some(OneOf::Left(true)),
|
||||
document_symbol_provider: Some(OneOf::Left(true)),
|
||||
document_highlight_provider: Some(OneOf::Left(true)),
|
||||
workspace: Some(workspace),
|
||||
..ServerCapabilities::default()
|
||||
};
|
||||
|
||||
@ -260,8 +269,6 @@ impl LanguageServer for Backend {
|
||||
self.client
|
||||
.log_message(MessageType::INFO, "Digital LSP initialized!")
|
||||
.await;
|
||||
|
||||
// self.client.send_notification::<StringNotification>(StringNotification { content: "hello from lsp server".to_string() }).await;
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<()> {
|
||||
@ -286,11 +293,6 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
|
||||
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||
info!("file did change: {:?}", params.text_document.uri);
|
||||
for change in ¶ms.content_changes {
|
||||
info!("change: {:?}", change);
|
||||
}
|
||||
|
||||
// // 如果文件太大则显示错误
|
||||
// if CacheManager::uri_is_big_file(¶ms.text_document.uri) {
|
||||
// // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
|
||||
|
@ -15,6 +15,7 @@ use log::info;
|
||||
use log::{debug, error};
|
||||
use pathdiff::diff_paths;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use serde_json::Value;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
@ -30,15 +31,6 @@ use thread::JoinHandle;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
macro_rules! unwrap_result {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(e) => e,
|
||||
Err(_) => return
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl LSPServer {
|
||||
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
|
||||
let document: TextDocumentItem = params.text_document;
|
||||
@ -180,6 +172,7 @@ pub enum ParseIR {
|
||||
SyntaxTree(sv_parser::SyntaxTree)
|
||||
}
|
||||
|
||||
|
||||
/// file metadata, including whether or not the syntax tree is up to date
|
||||
pub struct SourceMeta {
|
||||
pub id: usize,
|
||||
@ -229,7 +222,9 @@ pub struct Sources {
|
||||
/// hdlparam 后端实现
|
||||
pub hdl_param: Arc<HdlParam>,
|
||||
// 同步解析线程和发送 fast 请求的
|
||||
pub fast_sync_controller: Arc<RwLock<HashMap<String, Arc<RwLock<bool>>>>>
|
||||
pub fast_sync_controller: Arc<RwLock<HashMap<String, Arc<RwLock<bool>>>>>,
|
||||
// lsp 配置相关的
|
||||
pub lsp_configuration: Arc<RwLock<HashMap<String, Value>>>
|
||||
}
|
||||
|
||||
impl std::default::Default for Sources {
|
||||
@ -248,7 +243,8 @@ impl Sources {
|
||||
include_dirs: Arc::new(RwLock::new(Vec::new())),
|
||||
primitive_text: Arc::new(PrimitiveText::new()),
|
||||
hdl_param: Arc::new(HdlParam::new()),
|
||||
fast_sync_controller: Arc::new(RwLock::new(HashMap::<String, Arc<RwLock<bool>>>::new()))
|
||||
fast_sync_controller: Arc::new(RwLock::new(HashMap::<String, Arc<RwLock<bool>>>::new())),
|
||||
lsp_configuration: Arc::new(RwLock::new(HashMap::<String, Value>::new()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,6 +444,30 @@ impl Sources {
|
||||
.get_dot_completion(token, byte_idx, url, tree.as_ref()?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_lsp_configuration_string_value(&self, name: &str) -> Option<String> {
|
||||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||||
if let Some(Value::String(value)) = lsp_configuration.get(name) {
|
||||
return Some(value.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_lsp_configuration_i64_value(&self, name: &str) -> Option<i64> {
|
||||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||||
if let Some(Value::Number(number)) = lsp_configuration.get(name) {
|
||||
return number.as_i64();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_lsp_configuration_bool_value(&self, name: &str) -> Option<bool> {
|
||||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||||
if let Some(Value::Bool(value)) = lsp_configuration.get(name) {
|
||||
return Some(*value);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -597,6 +617,7 @@ pub fn recovery_sv_parse(
|
||||
}
|
||||
|
||||
pub fn sv_parser_pipeline(
|
||||
#[allow(unused)]
|
||||
conf: &Arc<RwLock<ProjectConfig>>,
|
||||
source_handle: &Arc<RwLock<Source>>,
|
||||
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user