合并 vhdl

This commit is contained in:
锦恢 2024-10-08 17:12:56 +08:00
parent 65983f7a43
commit 58ef3cafc8
14 changed files with 976 additions and 704 deletions

571
Cargo.lock generated

File diff suppressed because it is too large Load Diff

2
Cross.toml Normal file
View File

@ -0,0 +1,2 @@
[target.x86_64-apple-darwin]
image = "ghcr.io/shepherdjerred/macos-cross-compiler:latest"

14
build_all.sh Normal file
View 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

View File

@ -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 = &params.text_document_position;
let uri = &params.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 &params.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
}
}

View File

@ -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;
}

View File

@ -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, &parameter_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)
}

View File

@ -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 = &params.text_document_position_params.text_document.uri;
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(&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),
)))
}

View File

@ -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,
&params.text_document_position_params
),
// "vhdl" => vhdl::document_highlight(
// self,
// &token,
// &file,
// pos,
// &uri
// ),
"verilog" | "systemverilog" => sv::document_highlight(
self,

View File

@ -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 = &params.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
}

View File

@ -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 = &params.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),
))
}

View File

@ -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 = &params.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 = &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();
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(&ltrim) {
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,
})
}

View File

@ -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