合并 vhdl
This commit is contained in:
parent
65983f7a43
commit
58ef3cafc8
571
Cargo.lock
generated
571
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
2
Cross.toml
Normal file
2
Cross.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[target.x86_64-apple-darwin]
|
||||
image = "ghcr.io/shepherdjerred/macos-cross-compiler:latest"
|
14
build_all.sh
Normal file
14
build_all.sh
Normal file
@ -0,0 +1,14 @@
|
||||
# linux amd64
|
||||
cargo build --release
|
||||
# linux aarch64
|
||||
cross build --release --target aarch64-unknown-linux-gnu
|
||||
# windows amd64
|
||||
cross build --release --target x86_64-pc-windows-gnu
|
||||
# windows aarch64
|
||||
cargo xwin build --release --target aarch64-pc-windows-msvc
|
||||
# MacOS amd64
|
||||
export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=rust-lld
|
||||
cross build --release --target x86_64-apple-darwin
|
||||
# MacOS aarch64
|
||||
export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=rust-lld
|
||||
cross build --release --target aarch64-apple-darwin
|
@ -1,231 +1,181 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use tower_lsp::lsp_types::*;
|
||||
use vhdl_lang::{ast::{Designator, ObjectClass}, kind_str, AnyEntKind, Design, EntRef, InterfaceEnt, Overloaded};
|
||||
use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path}};
|
||||
#[allow(unused)]
|
||||
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> {
|
||||
let doc = ¶ms.text_document_position;
|
||||
let uri = ¶ms.text_document_position.text_document.uri;
|
||||
// let pos = doc.position;
|
||||
// let language_id = get_language_id_by_uri(uri);
|
||||
|
||||
let file_id = server.srcs.get_id(&doc.text_document.uri).to_owned();
|
||||
let file_id = server.srcs.get_id(uri).to_owned();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
let file = server.srcs.get_file(file_id)?;
|
||||
let file = file.read().ok()?;
|
||||
let line_text = file.text.line(doc.position.line as usize);
|
||||
let token = get_completion_token(
|
||||
&file.text,
|
||||
line_text.clone(),
|
||||
doc.position,
|
||||
);
|
||||
|
||||
let path = match PathBuf::from_str(&doc.text_document.uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
// info!("trigger completion token: {}", token);
|
||||
// let line_text = file.text.line(pos.line as usize);
|
||||
|
||||
let project = match projects.get("VHDLProject") {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
};
|
||||
|
||||
// let source = project.get_source(&escape_path)?;
|
||||
|
||||
let binding = escape_path;
|
||||
let file = binding.as_path();
|
||||
// 1) get source position, and source file
|
||||
let Some(source) = project.get_source(file) else {
|
||||
// Do not enable completions for files that are not part of the project
|
||||
|
||||
return Some(CompletionResponse::List(CompletionList {
|
||||
..Default::default()
|
||||
}));
|
||||
};
|
||||
let cursor = from_lsp_pos(params.text_document_position.position);
|
||||
// 2) Optimization chance: go to last recognizable token before the cursor. For example:
|
||||
// - Any primary unit (e.g. entity declaration, package declaration, ...)
|
||||
// => keyword `entity`, `package`, ...
|
||||
// - Any secondary unit (e.g. package body, architecture)
|
||||
// => keyword `architecture`, ...
|
||||
|
||||
// 3) Run the parser until the point of the cursor. Then exit with possible completions
|
||||
let options = project
|
||||
.list_completion_options(&source, cursor)
|
||||
.into_iter()
|
||||
.map(|item| completion_item_to_lsp_item(server, item))
|
||||
.collect();
|
||||
Some(CompletionResponse::List(CompletionList {
|
||||
items: options,
|
||||
is_incomplete: true,
|
||||
}))
|
||||
}
|
||||
|
||||
fn completion_item_to_lsp_item(
|
||||
#[allow(unused)]
|
||||
server: &LSPServer,
|
||||
item: vhdl_lang::CompletionItem,
|
||||
) -> CompletionItem {
|
||||
match item {
|
||||
vhdl_lang::CompletionItem::Simple(ent) => entity_to_completion_item(ent),
|
||||
vhdl_lang::CompletionItem::Work => CompletionItem {
|
||||
label: "work".to_string(),
|
||||
detail: Some("work library".to_string()),
|
||||
kind: Some(CompletionItemKind::MODULE),
|
||||
insert_text: Some("work".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
vhdl_lang::CompletionItem::Formal(ent) => {
|
||||
let mut item = entity_to_completion_item(ent);
|
||||
// TODO: Confirm vscode support
|
||||
// if server.client_supports_snippets() {
|
||||
item.insert_text_format = Some(InsertTextFormat::SNIPPET);
|
||||
item.insert_text = Some(format!("{} => $1,", item.insert_text.unwrap()));
|
||||
let response = match ¶ms.context {
|
||||
Some(context) => match context.trigger_kind {
|
||||
// CompletionTriggerKind::TRIGGER_CHARACTER => {
|
||||
// let trigger_character = context.trigger_character.clone().unwrap();
|
||||
// match trigger_character.as_str() {
|
||||
// "." => {
|
||||
// info!("trigger dot completion");
|
||||
// get_dot_completion(server, &line_text, uri, &pos, &language_id)
|
||||
// },
|
||||
// "$" => Some(CompletionList {
|
||||
// is_incomplete: false,
|
||||
// items: server.sys_tasks.clone(),
|
||||
// }),
|
||||
// "`" => Some(CompletionList {
|
||||
// is_incomplete: false,
|
||||
// items: server.directives.clone(),
|
||||
// }),
|
||||
// "/" => {
|
||||
// info!("trigger include");
|
||||
// include_path_completion(&doc.text_document.uri, &line_text, pos)
|
||||
// },
|
||||
// "\"" => {
|
||||
// info!("trigger include");
|
||||
// include_path_completion(&doc.text_document.uri, &line_text, pos)
|
||||
// }
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
item
|
||||
}
|
||||
vhdl_lang::CompletionItem::Overloaded(desi, count) => CompletionItem {
|
||||
label: desi.to_string(),
|
||||
detail: Some(format!("+{count} overloaded")),
|
||||
kind: match desi {
|
||||
Designator::Identifier(_) => Some(CompletionItemKind::FUNCTION),
|
||||
Designator::OperatorSymbol(_) => Some(CompletionItemKind::OPERATOR),
|
||||
_ => None,
|
||||
},
|
||||
insert_text: Some(desi.to_string()),
|
||||
..Default::default()
|
||||
// CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
|
||||
CompletionTriggerKind::INVOKED => {
|
||||
let mut comps = server.srcs.get_completions(
|
||||
&token,
|
||||
file.text.pos_to_byte(&doc.position),
|
||||
&doc.text_document.uri,
|
||||
)?;
|
||||
|
||||
// complete keywords
|
||||
comps.items.extend::<Vec<CompletionItem>>(
|
||||
server.key_comps
|
||||
.iter()
|
||||
.filter(|x| x.label.starts_with(&token))
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
Some(comps)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
vhdl_lang::CompletionItem::Keyword(kind) => CompletionItem {
|
||||
label: kind_str(kind).to_string(),
|
||||
detail: Some(kind_str(kind).to_string()),
|
||||
insert_text: Some(kind_str(kind).to_string()),
|
||||
kind: Some(CompletionItemKind::KEYWORD),
|
||||
..Default::default()
|
||||
},
|
||||
vhdl_lang::CompletionItem::Instantiation(ent, architectures) => {
|
||||
let work_name = "work";
|
||||
|
||||
let library_names = if let Some(lib_name) = ent.library_name() {
|
||||
vec![work_name.to_string(), lib_name.name().to_string()]
|
||||
} else {
|
||||
vec![work_name.to_string()]
|
||||
};
|
||||
let (region, is_component_instantiation) = match ent.kind() {
|
||||
AnyEntKind::Design(Design::Entity(_, region)) => (region, false),
|
||||
AnyEntKind::Component(region) => (region, true),
|
||||
// should never happen but better return some value instead of crashing
|
||||
_ => return entity_to_completion_item(ent),
|
||||
};
|
||||
// TODO: Confirm vscode support
|
||||
let template = if true { // if server.client_supports_snippets() {
|
||||
let mut line = if is_component_instantiation {
|
||||
format!("${{1:{}_inst}}: {}", ent.designator, ent.designator)
|
||||
} else {
|
||||
format!(
|
||||
"${{1:{}_inst}}: entity ${{2|{}|}}.{}",
|
||||
ent.designator,
|
||||
library_names.join(","),
|
||||
ent.designator
|
||||
)
|
||||
};
|
||||
if architectures.len() > 1 {
|
||||
line.push_str("(${3|");
|
||||
for (i, architecture) in architectures.iter().enumerate() {
|
||||
line.push_str(&architecture.designator().to_string());
|
||||
if i != architectures.len() - 1 {
|
||||
line.push(',')
|
||||
}
|
||||
}
|
||||
line.push_str("|})");
|
||||
None => {
|
||||
let trigger = prev_char(&file.text, &doc.position);
|
||||
match trigger {
|
||||
// '.' => Some(server.srcs.get_dot_completions(
|
||||
// token.trim_end_matches('.'),
|
||||
// file.text.pos_to_byte(&doc.position),
|
||||
// &doc.text_document.uri,
|
||||
// )?),
|
||||
// '$' => Some(CompletionList {
|
||||
// is_incomplete: false,
|
||||
// items: server.sys_tasks.clone(),
|
||||
// }),
|
||||
// '`' => Some(CompletionList {
|
||||
// is_incomplete: false,
|
||||
// items: server.directives.clone(),
|
||||
// }),
|
||||
_ => {
|
||||
let mut comps = server.srcs.get_completions(
|
||||
&token,
|
||||
file.text.pos_to_byte(&doc.position),
|
||||
&doc.text_document.uri,
|
||||
)?;
|
||||
comps.items.extend::<Vec<CompletionItem>>(
|
||||
server.key_comps
|
||||
.iter()
|
||||
.filter(|x| x.label.starts_with(&token))
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
Some(comps)
|
||||
}
|
||||
let (ports, generics) = region.ports_and_generics();
|
||||
let mut idx = 4;
|
||||
let mut interface_ent = |elements: Vec<InterfaceEnt>, purpose: &str| {
|
||||
line += &*format!("\n {} map(\n", purpose);
|
||||
for (i, generic) in elements.iter().enumerate() {
|
||||
line += &*format!(
|
||||
" {} => ${{{}:{}}}",
|
||||
generic.designator, idx, generic.designator
|
||||
);
|
||||
idx += 1;
|
||||
if i != elements.len() - 1 {
|
||||
line += ","
|
||||
}
|
||||
line += "\n";
|
||||
}
|
||||
line += ")";
|
||||
};
|
||||
if !generics.is_empty() {
|
||||
interface_ent(generics, "generic");
|
||||
}
|
||||
if !ports.is_empty() {
|
||||
interface_ent(ports, "port");
|
||||
}
|
||||
line += ";";
|
||||
line
|
||||
} else {
|
||||
format!("{}", ent.designator)
|
||||
};
|
||||
CompletionItem {
|
||||
label: format!("{} instantiation", ent.designator),
|
||||
insert_text: Some(template),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
kind: Some(CompletionItemKind::MODULE),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
vhdl_lang::CompletionItem::Attribute(attribute) => CompletionItem {
|
||||
label: format!("{attribute}"),
|
||||
detail: Some(format!("{attribute}")),
|
||||
insert_text: Some(format!("{attribute}")),
|
||||
kind: Some(CompletionItemKind::REFERENCE),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
};
|
||||
// eprintln!("comp response: {}", now.elapsed().as_millis());
|
||||
Some(CompletionResponse::List(response?))
|
||||
}
|
||||
|
||||
fn entity_to_completion_item(ent: EntRef) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label: ent.designator.to_string(),
|
||||
detail: Some(ent.describe()),
|
||||
kind: Some(entity_kind_to_completion_kind(ent.kind())),
|
||||
data: serde_json::to_value(ent.id.to_raw()).ok(),
|
||||
insert_text: Some(ent.designator.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn entity_kind_to_completion_kind(kind: &AnyEntKind) -> CompletionItemKind {
|
||||
match kind {
|
||||
AnyEntKind::ExternalAlias { .. } | AnyEntKind::ObjectAlias { .. } => {
|
||||
CompletionItemKind::FIELD
|
||||
fn prev_char(text: &Rope, pos: &Position) -> char {
|
||||
let char_idx = text.pos_to_char(pos);
|
||||
if char_idx > 0 {
|
||||
for i in (0..char_idx).rev() {
|
||||
let res = text.char(i);
|
||||
if !res.is_whitespace() {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) => CompletionItemKind::FILE,
|
||||
AnyEntKind::Component(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Attribute(_) => CompletionItemKind::REFERENCE,
|
||||
AnyEntKind::Overloaded(overloaded) => match overloaded {
|
||||
Overloaded::SubprogramDecl(_)
|
||||
| Overloaded::Subprogram(_)
|
||||
| Overloaded::UninstSubprogramDecl(..)
|
||||
| Overloaded::UninstSubprogram(..)
|
||||
| Overloaded::InterfaceSubprogram(_) => CompletionItemKind::FUNCTION,
|
||||
Overloaded::EnumLiteral(_) => CompletionItemKind::ENUM_MEMBER,
|
||||
Overloaded::Alias(_) => CompletionItemKind::FIELD,
|
||||
},
|
||||
AnyEntKind::Type(_) => CompletionItemKind::TYPE_PARAMETER,
|
||||
AnyEntKind::ElementDeclaration(_) => CompletionItemKind::FIELD,
|
||||
AnyEntKind::Concurrent(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Sequential(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Object(object) => match object.class {
|
||||
ObjectClass::Signal => CompletionItemKind::EVENT,
|
||||
ObjectClass::Constant => CompletionItemKind::CONSTANT,
|
||||
ObjectClass::Variable | ObjectClass::SharedVariable => CompletionItemKind::VARIABLE,
|
||||
},
|
||||
AnyEntKind::LoopParameter(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::PhysicalLiteral(_) => CompletionItemKind::UNIT,
|
||||
AnyEntKind::DeferredConstant(_) => CompletionItemKind::CONSTANT,
|
||||
AnyEntKind::Library => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Design(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::View(_) => CompletionItemKind::INTERFACE,
|
||||
' '
|
||||
} else {
|
||||
' '
|
||||
}
|
||||
}
|
||||
|
||||
fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
|
||||
let mut token = String::new();
|
||||
let mut line_iter = line.chars();
|
||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||
line_iter.next();
|
||||
}
|
||||
let mut c = line_iter.prev();
|
||||
//TODO: make this a regex
|
||||
while c.is_some()
|
||||
&& (c.unwrap().is_alphanumeric()
|
||||
|| c.unwrap() == '_'
|
||||
|| c.unwrap() == '.'
|
||||
|| c.unwrap() == '['
|
||||
|| c.unwrap() == ']')
|
||||
{
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.prev();
|
||||
}
|
||||
let mut result: String = token.chars().rev().collect();
|
||||
if result.contains('[') {
|
||||
let l_bracket_offset = result.find('[').unwrap_or(result.len());
|
||||
result.replace_range(l_bracket_offset.., "");
|
||||
}
|
||||
if &result == "." {
|
||||
// probably a instantiation, the token should be what we're instatiating
|
||||
let mut char_iter = text.chars();
|
||||
let mut token = String::new();
|
||||
for _ in 0..text.pos_to_char(&pos) {
|
||||
char_iter.next();
|
||||
}
|
||||
let mut c = char_iter.prev();
|
||||
|
||||
// go to the last semicolon
|
||||
while c.is_some() && (c.unwrap() != ';') {
|
||||
c = char_iter.prev();
|
||||
}
|
||||
// go the the start of the next symbol
|
||||
while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
c = char_iter.next();
|
||||
}
|
||||
// then extract the next symbol
|
||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
token.push(c.unwrap());
|
||||
c = char_iter.next();
|
||||
}
|
||||
token
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,7 @@ pub fn make_fast_from_design_file(design_file: &DesignFile) -> Option<FastHdlpar
|
||||
#[allow(unused)]
|
||||
fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
let mut modules = Vec::new();
|
||||
let mut last_module_name = String::new();
|
||||
let mut instance_type = HashSet::new();
|
||||
|
||||
let mut i = 0;
|
||||
@ -97,7 +98,7 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
tokens[end].pos.range.end
|
||||
};
|
||||
let module = Module {
|
||||
name: entity_name,
|
||||
name: entity_name.to_string(),
|
||||
params: Vec::new(),
|
||||
ports: Vec::new(),
|
||||
instances: Vec::new(),
|
||||
@ -112,6 +113,7 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
}
|
||||
}
|
||||
};
|
||||
last_module_name = entity_name.to_string();
|
||||
modules.push(module);
|
||||
}
|
||||
}
|
||||
@ -140,7 +142,7 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
tokens[end].pos.range.end
|
||||
};
|
||||
let module = Module {
|
||||
name,
|
||||
name: name.to_string(),
|
||||
params: Vec::new(),
|
||||
ports: Vec::new(),
|
||||
instances: Vec::new(),
|
||||
@ -155,6 +157,7 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
}
|
||||
}
|
||||
};
|
||||
last_module_name = name.to_string();
|
||||
modules.push(module);
|
||||
}
|
||||
}
|
||||
@ -214,7 +217,9 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
}
|
||||
}
|
||||
};
|
||||
modules.last_mut().unwrap().instances.push(instance);
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.instances.push(instance);
|
||||
}
|
||||
} else if kind_str(tokens[i+1].kind) == "entity" {
|
||||
let instance = Instance {
|
||||
name: get_value(&tokens[i-1]),
|
||||
@ -232,7 +237,9 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
}
|
||||
}
|
||||
};
|
||||
modules.last_mut().unwrap().instances.push(instance);
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.instances.push(instance);
|
||||
}
|
||||
i += 1;
|
||||
} else {
|
||||
let name = get_value(&tokens[i-1]);
|
||||
@ -261,7 +268,9 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
}
|
||||
}
|
||||
};
|
||||
modules.last_mut().unwrap().instances.push(instance);
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.instances.push(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,9 +280,13 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
if is_map {
|
||||
let start = params.first().unwrap().range.start.clone();
|
||||
let end = params.last().unwrap().range.start.clone();
|
||||
modules.last_mut().unwrap().instances.last_mut().unwrap().instparams = Some(Range { start, end });
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.instances.last_mut().unwrap().instparams = Some(Range { start, end });;
|
||||
}
|
||||
} else {
|
||||
modules.last_mut().unwrap().params.extend(params);
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.params.extend(params);
|
||||
}
|
||||
}
|
||||
i = next_index;
|
||||
}
|
||||
@ -283,9 +296,13 @@ fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
|
||||
if is_map {
|
||||
let start = ports.first().unwrap().range.start.clone();
|
||||
let end = ports.last().unwrap().range.start.clone();
|
||||
modules.last_mut().unwrap().instances.last_mut().unwrap().instports = Some(Range { start, end });
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.instances.last_mut().unwrap().instports = Some(Range { start, end });
|
||||
}
|
||||
} else {
|
||||
modules.last_mut().unwrap().ports.extend(ports);
|
||||
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
|
||||
module.ports.extend(ports);
|
||||
}
|
||||
}
|
||||
i = next_index;
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::core::hdlparam::{self, FastHdlparam};
|
||||
use crate::utils::get_language_id_by_uri;
|
||||
use crate::server::LSPServer;
|
||||
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
use ropey::Rope;
|
||||
use sv_parser::*;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
@ -199,3 +201,58 @@ pub fn get_scopes_from_syntax_tree(syntax_tree: &SyntaxTree, url: &Url) -> Optio
|
||||
Some(global_scope)
|
||||
}
|
||||
|
||||
pub fn get_scopes_from_vhdl_fast(fast: &FastHdlparam, text: &Rope, url: &Url) -> Option<GenericScope> {
|
||||
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
|
||||
let mut global_scope: GenericScope = GenericScope::new(url);
|
||||
global_scope.ident = String::from("global");
|
||||
|
||||
for module in &fast.content {
|
||||
let mut scope: GenericScope = GenericScope::new(url);
|
||||
scope.ident = module.name.clone();
|
||||
let mut module_range = module.range.clone();
|
||||
module_range.affine(-1, -1);
|
||||
scope.start = position_to_byte_idx(text, &module_range.start);
|
||||
scope.end = position_to_byte_idx(text, &module_range.end);
|
||||
scope.byte_idx = scope.start + 7;
|
||||
|
||||
for parameter in &module.params {
|
||||
let mut def = GenericDec::new(url);
|
||||
def.ident = parameter.name.clone();
|
||||
let mut parameter_range = parameter.range.clone();
|
||||
parameter_range.affine(-1, -1);
|
||||
def.byte_idx = position_to_byte_idx(text, ¶meter_range.start);
|
||||
def.completion_kind = CompletionItemKind::TYPE_PARAMETER;
|
||||
def.symbol_kind = SymbolKind::TYPE_PARAMETER;
|
||||
scope.defs.push(Box::new(def));
|
||||
}
|
||||
for port in &module.ports {
|
||||
let mut port_def = PortDec::new(url);
|
||||
port_def.ident = port.name.clone();
|
||||
let mut port_range = port.range.clone();
|
||||
port_range.affine(-1, -1);
|
||||
port_def.byte_idx = position_to_byte_idx(text, &port_range.start);
|
||||
port_def.type_str = port.dir_type.clone();
|
||||
scope.defs.push(Box::new(port_def));
|
||||
}
|
||||
for inst in &module.instances {
|
||||
let mut instance = ModInst::new(url);
|
||||
instance.ident = inst.name.clone();
|
||||
let mut inst_range = inst.range.clone();
|
||||
inst_range.affine(-1, -1);
|
||||
instance.byte_idx = position_to_byte_idx(text, &inst_range.start);
|
||||
instance.type_str = inst.inst_type.clone();
|
||||
instance.mod_ident = inst.inst_type.clone();
|
||||
|
||||
scope.defs.push(Box::new(instance));
|
||||
}
|
||||
scopes.push(Box::new(scope));
|
||||
}
|
||||
|
||||
global_scope.scopes.append(&mut scopes);
|
||||
Some(global_scope)
|
||||
}
|
||||
|
||||
fn position_to_byte_idx(text: &Rope, pos: &hdlparam::Position) -> usize {
|
||||
let char = text.line_to_char(pos.line as usize) + pos.character as usize;
|
||||
text.char_to_byte(char)
|
||||
}
|
||||
|
@ -1,33 +1,52 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}};
|
||||
use crate::{server::LSPServer, sources::LSPSupport, utils::get_definition_token};
|
||||
|
||||
use super::{Definition, Scope};
|
||||
|
||||
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||
let uri = ¶ms.text_document_position_params.text_document.uri;
|
||||
let doc = ¶ms.text_document_position_params.text_document.uri;
|
||||
let pos = params.text_document_position_params.position;
|
||||
let file_id = server.srcs.get_id(&uri).to_owned();
|
||||
let file_id = server.srcs.get_id(doc).to_owned();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
let file = server.srcs.get_file(file_id)?;
|
||||
let file = file.read().ok()?;
|
||||
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
let line_text = file.text.line(pos.line as usize);
|
||||
let token: String = get_definition_token(&line_text, pos);
|
||||
|
||||
let project = match projects.get("VHDLProject") {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
};
|
||||
// // match include
|
||||
// if let Some(definition) = goto_include_definition(doc, &line_text, pos) {
|
||||
// return Some(definition);
|
||||
// }
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
// // match macro
|
||||
// if let Some(definition) = goto_macro_definition(server, &line_text, pos) {
|
||||
// return Some(definition);
|
||||
// }
|
||||
|
||||
let ent = project.find_definition(&source, from_lsp_pos(pos))?;
|
||||
let location = srcpos_to_location(ent.decl_pos()?);
|
||||
Some(GotoDefinitionResponse::Scalar(location))
|
||||
// match instance
|
||||
|
||||
let scope_tree = server.srcs.scope_tree.read().ok()?;
|
||||
|
||||
// // match position port & param
|
||||
// if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) {
|
||||
// return Some(definition);
|
||||
// }
|
||||
|
||||
// // match module name
|
||||
// if let Some(definition) = goto_module_declaration_definition(server, &token) {
|
||||
// return Some(definition);
|
||||
// }
|
||||
|
||||
let byte_idx = file.text.pos_to_byte(&pos);
|
||||
let global_scope = scope_tree.as_ref()?;
|
||||
let def = global_scope.get_definition(&token, byte_idx, doc)?;
|
||||
|
||||
let def_pos = file.text.byte_to_pos(def.byte_idx());
|
||||
Some(GotoDefinitionResponse::Scalar(Location::new(
|
||||
def.url(),
|
||||
Range::new(def_pos, def_pos),
|
||||
)))
|
||||
}
|
@ -21,10 +21,13 @@ impl LSPServer {
|
||||
let language_id = get_language_id_by_uri(&uri);
|
||||
|
||||
match language_id.as_str() {
|
||||
"vhdl" => vhdl::document_highlight(
|
||||
self,
|
||||
¶ms.text_document_position_params
|
||||
),
|
||||
// "vhdl" => vhdl::document_highlight(
|
||||
// self,
|
||||
// &token,
|
||||
// &file,
|
||||
// pos,
|
||||
// &uri
|
||||
// ),
|
||||
|
||||
"verilog" | "systemverilog" => sv::document_highlight(
|
||||
self,
|
||||
|
@ -1,45 +1,55 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use log::info;
|
||||
use sv_parser::{RefNode, SyntaxTree};
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path, to_lsp_range}};
|
||||
use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}};
|
||||
|
||||
pub fn document_highlight(
|
||||
server: &LSPServer,
|
||||
params: &TextDocumentPositionParams
|
||||
token: &str,
|
||||
file: &Source,
|
||||
pos: Position,
|
||||
uri: &Url
|
||||
) -> Option<Vec<DocumentHighlight>> {
|
||||
let uri = ¶ms.text_document.uri;
|
||||
let file_id = server.srcs.get_id(&uri).to_owned();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
let scope_tree = server.srcs.scope_tree.read().ok()?;
|
||||
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
// use the byte_idx of the definition if possible, otherwise use the cursor
|
||||
let byte_idx =
|
||||
match scope_tree
|
||||
.as_ref()?
|
||||
.get_definition(token, file.text.pos_to_byte(&pos), uri)
|
||||
{
|
||||
Some(def) => def.byte_idx,
|
||||
None => file.text.pos_to_byte(&pos),
|
||||
};
|
||||
let syntax_tree = file.parse_ir.as_ref()?;
|
||||
match syntax_tree {
|
||||
ParseIR::SyntaxTree(syntax_tree) => {
|
||||
let references = all_identifiers(&syntax_tree, &token);
|
||||
Some(
|
||||
scope_tree
|
||||
.as_ref()?
|
||||
.document_highlights(&uri, &file.text, references, byte_idx),
|
||||
)
|
||||
},
|
||||
_ => {
|
||||
info!("error happen in [vhdl_document_highlight]");
|
||||
None
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
}
|
||||
}
|
||||
|
||||
let project = match projects.get("VHDLProject") {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
};
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
|
||||
let ent = project.find_declaration(&source, from_lsp_pos(params.position))?;
|
||||
|
||||
Some(
|
||||
project
|
||||
.find_all_references_in_source(&source, ent)
|
||||
.iter()
|
||||
.map(|pos| DocumentHighlight {
|
||||
range: to_lsp_range(pos.range()),
|
||||
kind: Some(DocumentHighlightKind::TEXT),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
/// return all identifiers in a syntax tree matching a given token
|
||||
fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize)> {
|
||||
let mut idents: Vec<(String, usize)> = Vec::new();
|
||||
for node in syntax_tree {
|
||||
if let RefNode::Identifier(_) = node {
|
||||
|
||||
let (ident, byte_idx) = get_ident(syntax_tree, node);
|
||||
if ident == token {
|
||||
idents.push((ident, byte_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
idents
|
||||
}
|
@ -1,105 +1,19 @@
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use crate::{server::LSPServer, utils::to_lsp_range};
|
||||
use crate::utils::{to_escape_path, to_symbol_kind};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use vhdl_lang::{EntHierarchy, Token};
|
||||
use crate::{definition::Scope, server::LSPServer};
|
||||
|
||||
pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
|
||||
// info!("enter document symbol");
|
||||
|
||||
let uri = ¶ms.text_document.uri;
|
||||
let file_id = server.srcs.get_id(&uri).to_owned();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
let file = server.srcs.get_file(file_id)?;
|
||||
let file = file.read().ok()?;
|
||||
let scope_tree = server.srcs.scope_tree.read().ok()?;
|
||||
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
|
||||
let project = match projects.get("VHDLProject") {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
};
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
|
||||
// Some files are mapped to multiple libraries, only use the first library for document symbols
|
||||
let library_name = project
|
||||
.library_mapping_of(&source)
|
||||
.into_iter()
|
||||
.next()?;
|
||||
|
||||
// TODO: Confirm vscode support
|
||||
// if server.client_has_hierarchical_document_symbol_support() {
|
||||
fn to_document_symbol(
|
||||
EntHierarchy { ent, children }: EntHierarchy,
|
||||
ctx: &Vec<Token>,
|
||||
) -> DocumentSymbol {
|
||||
// Use the declaration position, if it exists,
|
||||
// else the position of the first source range token.
|
||||
// The latter is applicable for unnamed elements, e.g., processes or loops.
|
||||
let selection_pos = ent.decl_pos().unwrap_or(ent.src_span.start_token.pos(ctx));
|
||||
let src_range = ent.src_span.pos(ctx).range();
|
||||
#[allow(deprecated)]
|
||||
DocumentSymbol {
|
||||
name: ent.describe(),
|
||||
kind: to_symbol_kind(ent.kind()),
|
||||
tags: None,
|
||||
detail: None,
|
||||
selection_range: to_lsp_range(selection_pos.range),
|
||||
range: to_lsp_range(src_range),
|
||||
children: if !children.is_empty() {
|
||||
Some(
|
||||
children
|
||||
.into_iter()
|
||||
.map(|hierarchy| to_document_symbol(hierarchy, ctx))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
deprecated: None,
|
||||
}
|
||||
}
|
||||
|
||||
Some(DocumentSymbolResponse::Nested(
|
||||
project
|
||||
.document_symbols(&library_name, &source)
|
||||
.into_iter()
|
||||
.map(|(hierarchy, tokens)| to_document_symbol(hierarchy, tokens))
|
||||
.collect(),
|
||||
))
|
||||
// } else {
|
||||
// #[allow(clippy::ptr_arg)]
|
||||
// fn to_symbol_information(ent: EntRef, ctx: &Vec<Token>) -> SymbolInformation {
|
||||
// let selection_pos = ent.decl_pos().unwrap_or(ent.src_span.start_token.pos(ctx));
|
||||
// #[allow(deprecated)]
|
||||
// SymbolInformation {
|
||||
// name: ent.describe(),
|
||||
// kind: to_symbol_kind(ent.kind()),
|
||||
// tags: None,
|
||||
// location: srcpos_to_location(selection_pos),
|
||||
// deprecated: None,
|
||||
// container_name: ent.parent_in_same_source().map(|ent| ent.describe()),
|
||||
// }
|
||||
// }
|
||||
|
||||
// Some(DocumentSymbolResponse::Flat(
|
||||
// project
|
||||
// .document_symbols(&library_name, &source)
|
||||
// .into_iter()
|
||||
// .flat_map(|(a, ctx)| {
|
||||
// a.into_flat()
|
||||
// .into_iter()
|
||||
// .map(|hierarchy| to_symbol_information(hierarchy, ctx))
|
||||
// })
|
||||
// .collect(),
|
||||
// ))
|
||||
// }
|
||||
Some(DocumentSymbolResponse::Nested(
|
||||
scope_tree.as_ref()?.document_symbols(uri, &file.text),
|
||||
))
|
||||
}
|
@ -1,43 +1,288 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard};
|
||||
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use crate::server::LSPServer;
|
||||
use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport};
|
||||
|
||||
use super::{from_lsp_pos, to_escape_path};
|
||||
use super::{feature::hover_format_digit, get_definition_token, get_language_id_by_uri, to_escape_path};
|
||||
|
||||
pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
|
||||
let uri = ¶ms.text_document_position_params.text_document.uri;
|
||||
let pos = params.text_document_position_params.position;
|
||||
let file_id = server.srcs.get_id(&uri).to_owned();
|
||||
let doc = ¶ms.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();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
let file: std::sync::Arc<std::sync::RwLock<crate::sources::Source>> = server.srcs.get_file(file_id)?;
|
||||
let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?;
|
||||
|
||||
let line_text = file.text.line(pos.line as usize);
|
||||
let token: String = get_definition_token(&line_text, pos);
|
||||
let language_id = get_language_id_by_uri(doc);
|
||||
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
// // match `include
|
||||
// if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) {
|
||||
// return Some(hover);
|
||||
// }
|
||||
|
||||
// // match macro
|
||||
// if let Some(hover) = hover_macro(server, &line_text, pos, &language_id) {
|
||||
// return Some(hover);
|
||||
// }
|
||||
|
||||
let scope_tree = server.srcs.scope_tree.read().ok()?;
|
||||
let global_scope = scope_tree.as_ref().unwrap();
|
||||
|
||||
let symbol_definition: GenericDec = global_scope
|
||||
.get_definition(&token, file.text.pos_to_byte(&pos), doc)?;
|
||||
|
||||
// // match positional port param
|
||||
// if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) {
|
||||
// return Some(hover);
|
||||
// }
|
||||
|
||||
// // match module name
|
||||
// if let Some(hover) = hover_module_declaration(server, &token, &symbol_definition, &language_id) {
|
||||
// return Some(hover);
|
||||
// }
|
||||
|
||||
// match 正常 symbol
|
||||
if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) {
|
||||
return Some(hover);
|
||||
}
|
||||
|
||||
// match digit 5'b00110
|
||||
if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) {
|
||||
return Some(hover);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_code: bool) -> Option<Hover> {
|
||||
if line == 0 {
|
||||
let language_string = LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: doc.line(line).to_string()
|
||||
};
|
||||
let markdown = MarkedString::LanguageString(language_string);
|
||||
|
||||
return Some(Hover {
|
||||
contents: HoverContents::Scalar(markdown),
|
||||
range: None,
|
||||
});
|
||||
}
|
||||
let mut hover: Vec<String> = Vec::new();
|
||||
let mut multiline: bool = false;
|
||||
let mut valid: bool = true;
|
||||
let mut current: String = doc.line(line).to_string();
|
||||
|
||||
let ltrim: String = " ".repeat(current.len() - current.trim_start().len());
|
||||
let mut line_idx = line;
|
||||
|
||||
// 寻找周围的注释
|
||||
while valid {
|
||||
hover.push(current.clone());
|
||||
line_idx -= 1;
|
||||
valid = false;
|
||||
if line_idx > 0 {
|
||||
current = doc.line(line_idx).to_string();
|
||||
let currentl = current.clone().trim_start().to_owned();
|
||||
let currentr = current.clone().trim_end().to_owned();
|
||||
if currentl.starts_with("/*") && currentr.ends_with("*/") {
|
||||
valid = true;
|
||||
} else if currentr.ends_with("*/") {
|
||||
multiline = true;
|
||||
valid = true;
|
||||
} else if currentl.starts_with("/*") {
|
||||
multiline = false;
|
||||
valid = true;
|
||||
} else {
|
||||
valid = currentl.starts_with("--") || multiline;
|
||||
}
|
||||
}
|
||||
}
|
||||
hover.reverse();
|
||||
|
||||
let multi_space_regex = Regex::new(r"\s+").unwrap();
|
||||
let line_handler = |line: &str| -> String {
|
||||
let line = multi_space_regex.replace_all(line.trim(), " ");
|
||||
let mut line = line.into_owned();
|
||||
|
||||
if line.starts_with("/*") {
|
||||
line = line.strip_prefix("/*").unwrap().trim().to_string();
|
||||
} else if line.starts_with("--") {
|
||||
line = line.strip_prefix("--").unwrap().trim().to_string();
|
||||
}
|
||||
|
||||
if line.ends_with("*/") {
|
||||
line = line.strip_suffix("*/").unwrap().trim().to_string();
|
||||
}
|
||||
|
||||
return format!("{}\n", line);
|
||||
};
|
||||
|
||||
let line_comment_extractor = |line: &str| -> Option<(String, String)> {
|
||||
let line = line.trim();
|
||||
if line.contains("--") {
|
||||
let comment_start = line.split_once("--");
|
||||
match comment_start {
|
||||
Some((code, comment)) => {
|
||||
let code = code.trim().to_string();
|
||||
let comment = comment.trim().to_string();
|
||||
Some((code, comment))
|
||||
},
|
||||
None => None
|
||||
}
|
||||
} else if line.contains("/*") {
|
||||
let comment_start = line.split_once("/*");
|
||||
match comment_start {
|
||||
Some((code, comment)) => {
|
||||
let code = code.trim().to_string();
|
||||
let comment = comment.trim().strip_suffix("*/").unwrap_or("").to_string();
|
||||
Some((code, comment))
|
||||
},
|
||||
None => None
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
|
||||
let project = match projects.get("VHDLProject") {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
// 所有的 markdown
|
||||
let mut comment_markdowns = Vec::<MarkedString>::new();
|
||||
|
||||
for (line_no, line_text) in hover.iter().enumerate() {
|
||||
let line_hover = if let Some(stripped) = line_text.strip_prefix(<rim) {
|
||||
line_handler(stripped)
|
||||
} else {
|
||||
line_handler(&line_text)
|
||||
};
|
||||
|
||||
if line_no == hover.len() - 1 {
|
||||
// 最后一个为定义所在的一行
|
||||
if let Some((code, comment)) = line_comment_extractor(&line_text) {
|
||||
comment_markdowns.push(MarkedString::LanguageString(LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: code
|
||||
}));
|
||||
comment_markdowns.insert(0, MarkedString::String(comment.trim().to_string()));
|
||||
} else {
|
||||
// 这行只有代码,没有注释
|
||||
comment_markdowns.push(MarkedString::LanguageString(LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: line_hover
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// 否则,都是上方的注释
|
||||
comment_markdowns.push(MarkedString::String(line_hover.trim().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// 合并其中的 markdown
|
||||
let mut merge_markdowns = Vec::<MarkedString>::new();
|
||||
let mut string_buffer = String::new();
|
||||
for (_, md) in comment_markdowns.iter().enumerate() {
|
||||
match md {
|
||||
MarkedString::String(markdown_string) => {
|
||||
string_buffer.push_str(format!("{}\n", markdown_string.trim()).as_str());
|
||||
}
|
||||
MarkedString::LanguageString(code) => {
|
||||
if !string_buffer.is_empty() {
|
||||
merge_markdowns.push(MarkedString::String(string_buffer.to_string()));
|
||||
string_buffer.clear();
|
||||
}
|
||||
|
||||
if !exclude_code {
|
||||
merge_markdowns.push(MarkedString::LanguageString(code.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if comment_markdowns.len() > 0 {
|
||||
return Some(Hover {
|
||||
contents: HoverContents::Array(merge_markdowns),
|
||||
range: None
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn hover_common_symbol(
|
||||
#[allow(unused)]
|
||||
server: &LSPServer,
|
||||
#[allow(unused)]
|
||||
token: &String,
|
||||
symbol_definition: &GenericDec,
|
||||
file: &RwLockReadGuard<'_, crate::sources::Source>,
|
||||
doc: &Url,
|
||||
#[allow(unused)]
|
||||
pos: Position,
|
||||
language_id: &String
|
||||
) -> Option<Hover> {
|
||||
// 根据 symbol 的类别进行额外的判断
|
||||
match symbol_definition.def_type {
|
||||
DefinitionType::ModuleInstantiation => {
|
||||
let hdlparam = server.srcs.hdl_param.clone();
|
||||
let pathbuf = PathBuf::from_str(doc.path()).unwrap();
|
||||
let pathbuf = to_escape_path(&pathbuf);
|
||||
let path_string = pathbuf.to_str().unwrap().replace("\\", "/");
|
||||
|
||||
let find_name_condition = |_: &Module, instance: &Instance| {
|
||||
symbol_definition.ident == instance.name
|
||||
};
|
||||
|
||||
|
||||
if let Some(instance) = hdlparam.walk_instantiation(&path_string, find_name_condition) {
|
||||
info!("instance {:?}", instance);
|
||||
let def_line = file.text.byte_to_line(symbol_definition.byte_idx());
|
||||
let mut markdown_comment = match make_hover_with_comment(&file.text, def_line, &language_id, true) {
|
||||
Some(hover) => {
|
||||
match hover.contents {
|
||||
HoverContents::Array(array) => array,
|
||||
_ => Vec::<MarkedString>::new()
|
||||
}
|
||||
},
|
||||
None => Vec::<MarkedString>::new()
|
||||
};
|
||||
|
||||
// 扫描到右括号
|
||||
let mut current_line = def_line;
|
||||
let mut buffer = String::new();
|
||||
let len_lines = file.text.len_lines();
|
||||
let mut matcher = BracketMatcher::new();
|
||||
|
||||
loop {
|
||||
if current_line >= len_lines {
|
||||
break;
|
||||
}
|
||||
let line_text = file.text.line(current_line).to_string();
|
||||
buffer.push_str(&line_text);
|
||||
match matcher.consume_string(&line_text) {
|
||||
BracketMatchResult::Invalid | BracketMatchResult::Complete => break,
|
||||
BracketMatchResult::Valid => {}
|
||||
}
|
||||
|
||||
current_line += 1;
|
||||
}
|
||||
markdown_comment.push(MarkedString::LanguageString(LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: buffer
|
||||
}));
|
||||
|
||||
return Some(Hover { contents: HoverContents::Array(markdown_comment), range: None });
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
let def_line = file.text.byte_to_line(symbol_definition.byte_idx());
|
||||
make_hover_with_comment(&file.text, def_line, &language_id, false)
|
||||
}
|
||||
|
||||
let ent = project.find_declaration(&source, from_lsp_pos(pos))?;
|
||||
|
||||
let value = project.format_declaration(ent)?;
|
||||
|
||||
Some(Hover {
|
||||
contents: HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: format!("```vhdl\n{value}\n```"),
|
||||
}),
|
||||
range: None,
|
||||
})
|
||||
}
|
@ -4,6 +4,7 @@ use crate::core::vhdl_parser::make_fast_from_design_file;
|
||||
use crate::core::vhdl_parser::vhdl_parse;
|
||||
use crate::definition::def_types::*;
|
||||
use crate::definition::get_scopes_from_syntax_tree;
|
||||
use crate::definition::get_scopes_from_vhdl_fast;
|
||||
use crate::diagnostics::{get_diagnostics, is_hidden};
|
||||
use crate::server::LSPServer;
|
||||
use crate::utils::to_escape_path;
|
||||
@ -12,7 +13,6 @@ use log::info;
|
||||
use log::{debug, error};
|
||||
use pathdiff::diff_paths;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use vhdl_lang::MessagePrinter;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
@ -129,7 +129,7 @@ pub struct Source {
|
||||
pub enum ParseIR {
|
||||
/// 基于 vhdl-parser 的IR,存放 VHDL
|
||||
#[allow(unused)]
|
||||
VHDLProject(vhdl_lang::Project),
|
||||
DesignFile(vhdl_lang::ast::DesignFile),
|
||||
/// 存放 sv 的 IR
|
||||
SyntaxTree(sv_parser::SyntaxTree)
|
||||
}
|
||||
@ -248,7 +248,6 @@ impl Sources {
|
||||
}));
|
||||
let source_handle = source.clone();
|
||||
let scope_handle = self.scope_tree.clone();
|
||||
let design_file_handle = self.design_file_map.clone();
|
||||
let hdl_param_handle = self.hdl_param.clone();
|
||||
let inc_dirs = self.include_dirs.clone();
|
||||
|
||||
@ -270,8 +269,10 @@ impl Sources {
|
||||
match language_id.as_str() {
|
||||
"vhdl" => {
|
||||
vhdl_parser_pipeline(
|
||||
&design_file_handle,
|
||||
&source_handle,
|
||||
&scope_handle,
|
||||
&hdl_param_handle,
|
||||
&text,
|
||||
uri,
|
||||
);
|
||||
},
|
||||
@ -601,8 +602,10 @@ pub fn sv_parser_pipeline(
|
||||
}
|
||||
|
||||
pub fn vhdl_parser_pipeline(
|
||||
design_file_handle: &Arc<RwLock<HashMap<String, vhdl_lang::Project>>>,
|
||||
source_handle: &Arc<RwLock<Source>>,
|
||||
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
|
||||
hdl_param_handle: &Arc<HdlParam>,
|
||||
doc: &Rope,
|
||||
uri: &Url
|
||||
) {
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
@ -618,19 +621,58 @@ pub fn vhdl_parser_pipeline(
|
||||
info!("error happen in [vhdl_parser_pipeline], escape_path_string is empty");
|
||||
return;
|
||||
}
|
||||
if let Some(design_file) = vhdl_parse(&escape_path) {
|
||||
let mut design_files = design_file_handle.write().unwrap();
|
||||
|
||||
let mut file = source_handle.write().unwrap();
|
||||
|
||||
let mut scope_tree = if let Some(design_file) = vhdl_parse(&escape_path) {
|
||||
//let mut design_files = design_file_handle.write().unwrap();
|
||||
|
||||
if let Some(fast) = make_fast_from_design_file(&design_file) {
|
||||
hdl_param_handle.update_fast(escape_path_string.to_string(), fast);
|
||||
}
|
||||
let mut msg_printer = MessagePrinter::default();
|
||||
if let Some(project) = design_files.get_mut("VHDLProject") {
|
||||
project.digital_lsp_update_config(&escape_path, &mut msg_printer);
|
||||
let parse_ir = ParseIR::DesignFile(design_file);
|
||||
file.parse_ir = Some(parse_ir);
|
||||
hdl_param_handle.update_fast(escape_path_string.to_string(), fast.clone());
|
||||
let scope_tree = get_scopes_from_vhdl_fast(&fast, doc, uri);
|
||||
scope_tree
|
||||
} else {
|
||||
let project = vhdl_lang::Project::new_without_config(&escape_path, &mut msg_printer);
|
||||
design_files.insert("VHDLProject".to_string(), project);
|
||||
None
|
||||
}
|
||||
|
||||
// let mut msg_printer = MessagePrinter::default();
|
||||
// if let Some(project) = design_files.get_mut("VHDLProject") {
|
||||
// project.digital_lsp_update_config(&escape_path, &mut msg_printer);
|
||||
// } else {
|
||||
// let project = vhdl_lang::Project::new_without_config(&escape_path, &mut msg_printer);
|
||||
// design_files.insert("VHDLProject".to_string(), project);
|
||||
// }
|
||||
} else {
|
||||
file.parse_ir = None;
|
||||
None
|
||||
};
|
||||
drop(file);
|
||||
|
||||
|
||||
info!("finish parse {:?}", uri.to_string());
|
||||
|
||||
// 更新 global_scope,用于 sv 的解析
|
||||
// global_scope 为全局最大的那个 scope,它的 scopes 和 defs 下的元素和每一个文件一一对应
|
||||
let mut global_scope = scope_handle.write().unwrap();
|
||||
match &mut *global_scope {
|
||||
Some(scope) => match &mut scope_tree {
|
||||
Some(tree) => {
|
||||
// 更新所有 uri 为当前 uri 的文件结构
|
||||
scope.defs.retain(|x| &x.url() != uri);
|
||||
scope.scopes.retain(|x| &x.url() != uri);
|
||||
|
||||
scope.defs.append(&mut tree.defs);
|
||||
scope.scopes.append(&mut tree.scopes);
|
||||
}
|
||||
None => (),
|
||||
},
|
||||
// 使用 scope_tree 来更新全局的 scope
|
||||
None => *global_scope = scope_tree,
|
||||
}
|
||||
// eprintln!("{:#?}", *global_scope);
|
||||
drop(global_scope);
|
||||
}
|
||||
|
||||
//TODO: add bounds checking for utf8<->utf16 conversions
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 1e390e0f4429b5004e63327b64d5095775ef27b5
|
||||
Subproject commit 21f3e497e88a108d15be75ea7e2f0f1a409c6e1d
|
@ -1 +1 @@
|
||||
Subproject commit 8593beff8c21bd3d3e417ac893b9f317f5386070
|
||||
Subproject commit e8e746628785df31545fd465489dc75eba5a6c10
|
Loading…
x
Reference in New Issue
Block a user