diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index e9dc6c334..2fac367a6 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -18,6 +18,7 @@ use crate::{ literal::Literal, module::port::{PortDirection, PortHeader}, ty::{NetKind, NetType}, + typedef::TypedefId, }, }; @@ -504,6 +505,28 @@ impl HirDisplay for InContainer { } } +impl HirDisplay for InContainer { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + let InContainer { cont_id, value: typedef_id } = self; + let container = cont_id.to_container(f.db); + let typedef = container.get(*typedef_id); + + f.write_str("typedef ")?; + if let Some(ty) = typedef.ty { + InContainer::new(*cont_id, ty).hir_fmt(f)?; + if typedef.name.is_some() { + f.write_str(" ")?; + } + } + + if let Some(name) = &typedef.name { + f.write_str(name)?; + } + + Ok(()) + } +} + impl HirDisplay for InContainer { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { f.write_char('[')?; diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 8031f3022..95faafe61 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -17,23 +17,27 @@ use crate::{ }, stmt::{StmtId, StmtKind}, subroutine::{SubroutineId, SubroutineLoc, SubroutinePortId, SubroutineSrc}, + typedef::TypedefId, }, }; define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] - pub enum UnitEntry { +pub enum UnitEntry { ModuleId, FiledDeclId, + FiledTypedefId, } } pub type FiledDeclId = InFile; +pub type FiledTypedefId = InFile; define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ModuleEntry { DeclId, + TypedefId, NonAnsiPortEntry, AnsiPortEntry, InstanceId, @@ -59,6 +63,7 @@ define_enum_deriving_from! { pub enum BlockEntry { StmtId, DeclId, + TypedefId, BlockId, } } @@ -68,6 +73,7 @@ define_enum_deriving_from! { pub enum SubroutineEntry { StmtId, DeclId, + TypedefId, BlockId, SubroutinePortId, } @@ -138,6 +144,10 @@ impl UnitScope { scope.insert_opt(&decl.name, InFile::new(file_id, decl_id).into()); } + for (typedef_id, typedef) in hir_file.typedefs.iter() { + scope.insert_opt(&typedef.name, InFile::new(file_id, typedef_id).into()); + } + Arc::new(scope) } } @@ -196,6 +206,10 @@ impl ModuleScope { scope.insert(name, entry); } + for (typedef_id, typedef) in module.typedefs.iter() { + scope.insert_opt(&typedef.name, typedef_id.into()); + } + for (instance_id, instance) in module.instances.iter() { scope.insert_opt(&instance.name, instance_id.into()); } @@ -221,6 +235,10 @@ impl BlockScope { scope.insert_opt(&decl.name, decl_id.into()); } + for (typedef_id, typedef) in block.typedefs.iter() { + scope.insert_opt(&typedef.name, typedef_id.into()); + } + for (stmt_id, stmt) in block.stmts.iter() { scope.insert_opt(&stmt.label, stmt_id.into()); @@ -247,6 +265,10 @@ impl SubroutineScope { scope.insert_opt(&decl.name, decl_id.into()); } + for (typedef_id, typedef) in subroutine.typedefs.iter() { + scope.insert_opt(&typedef.name, typedef_id.into()); + } + for (stmt_id, stmt) in subroutine.stmts.iter() { scope.insert_opt(&stmt.label, stmt_id.into()); diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index 32956c2e9..c4c46767d 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -15,6 +15,7 @@ use crate::{ module::{ModuleId, instantiation::InstanceId, port::NonAnsiPortId}, stmt::StmtId, subroutine::{SubroutineId, SubroutinePortId}, + typedef::TypedefId, }, scope::{self, BlockEntry, ModuleEntry, SubroutineEntry, UnitEntry}, }; @@ -85,7 +86,7 @@ impl SemanticsImpl<'_> { let module_name = lower_ident_opt(instantiation.type_())?; match self.db.unit_scope().get(&module_name)? { UnitEntry::ModuleId(module_id) => Some(module_id), - UnitEntry::FiledDeclId(_) => None, + UnitEntry::FiledDeclId(_) | UnitEntry::FiledTypedefId(_) => None, } } @@ -98,6 +99,7 @@ impl SemanticsImpl<'_> { pub enum PathResolution { Module(ModuleId), Decl(InContainer), + Typedef(InContainer), ParamDecl(InModule), Subroutine(SubroutineId), SubroutinePort(InSubroutine), @@ -120,6 +122,7 @@ impl From for PathResolution { match entry { ModuleId(idx) => Self::Module(idx), FiledDeclId(idx) => Self::Decl(idx.into()), + FiledTypedefId(idx) => Self::Typedef(idx.into()), } } } @@ -129,6 +132,7 @@ impl From> for PathResolution { use ModuleEntry::*; match entry.value { DeclId(decl_id) => Self::Decl(entry.with_value(decl_id).into()), + TypedefId(typedef_id) => Self::Typedef(entry.with_value(typedef_id).into()), InstanceId(idx) => Self::Instance(entry.with_value(idx)), StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), SubroutineId(subroutine_id) => Self::Subroutine(subroutine_id), @@ -146,6 +150,7 @@ impl From> for PathResolution { use BlockEntry::*; match entry.value { DeclId(idx) => Self::Decl(entry.with_value(idx).into()), + TypedefId(idx) => Self::Typedef(entry.with_value(idx).into()), StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), BlockId(block_id) => Self::Block(block_id), } @@ -157,6 +162,7 @@ impl From> for PathResolution { use SubroutineEntry::*; match entry.value { DeclId(idx) => Self::Decl(entry.with_value(idx).into()), + TypedefId(idx) => Self::Typedef(entry.with_value(idx).into()), StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), BlockId(block_id) => Self::Block(block_id), SubroutinePortId(idx) => Self::SubroutinePort(entry.with_value(idx)), diff --git a/crates/ide/src/completion/engine/paren_list.rs b/crates/ide/src/completion/engine/paren_list.rs index 7c563becf..665579621 100644 --- a/crates/ide/src/completion/engine/paren_list.rs +++ b/crates/ide/src/completion/engine/paren_list.rs @@ -7,6 +7,7 @@ use syntax::{ ast::{self, AstNode}, has_text_range::HasTextRange, }; +use utils::get::Get; use utils::text_edit::TextEditItem; use super::{ @@ -37,7 +38,9 @@ pub(super) fn complete_in_paren_list( ParenListKind::ParamValueAssignment => { complete_param_value_assignment(db, position, prefix, ctx) } - ParenListKind::ParameterPortList => complete_parameter_port_list(prefix, ctx), + ParenListKind::ParameterPortList => { + complete_parameter_port_list_with_typedefs(db, position, prefix, ctx) + } ParenListKind::Arguments => expr::complete_argument_exprs(db, position, prefix, ctx), } } @@ -99,6 +102,49 @@ fn complete_parameter_port_list(prefix: &str, ctx: &CompletionContext) -> Vec Vec { + let sema = Semantics::new(db); + let file = sema.parse(position.file_id); + let Some(module) = + sema.find_node_at_offset::(file.syntax(), position.offset) + else { + return complete_parameter_port_list(prefix, ctx); + }; + let file_id = sema.find_file(module.syntax()); + let (_, file_src_map) = db.hir_file_with_source_map(file_id); + let module_src = hir::hir_def::module::ModuleSrc::from(module); + let module_id = hir::hir_def::module::ModuleId::new(file_id, file_src_map.get(module_src)); + + let mut items: Vec = db + .unit_scope() + .iter() + .filter_map(|(ident, entry)| { + matches!(entry, hir::scope::UnitEntry::FiledTypedefId(_)).then_some(ident) + }) + .chain(db.module_scope(module_id).iter().filter_map(|(ident, entry)| { + matches!(entry, hir::scope::ModuleEntry::TypedefId(_)).then_some(ident) + })) + .map(|ident| ident.to_string()) + .filter(|name| name.starts_with(prefix)) + .map(|name| CompletionItem { + label: name.clone(), + kind: CompletionItemKind::Text, + edit: Some(TextEditItem::replace(ctx.replacement, name)), + snippet_edit: None, + }) + .collect(); + + items.sort_by(|a, b| a.label.cmp(&b.label)); + items.dedup_by(|a, b| a.label == b.label); + items.extend(complete_parameter_port_list(prefix, ctx)); + items +} + fn complete_port_connections( db: &RootDb, position: FilePosition, diff --git a/crates/ide/src/completion/engine/port_list.rs b/crates/ide/src/completion/engine/port_list.rs index 30191980c..46eb23119 100644 --- a/crates/ide/src/completion/engine/port_list.rs +++ b/crates/ide/src/completion/engine/port_list.rs @@ -1,6 +1,7 @@ use hir::{ db::HirDb, hir_def::module::{ModuleId, ModuleSrc}, + scope::{ModuleEntry, UnitEntry}, semantics::Semantics, }; use ide_db::root_db::RootDb; @@ -19,19 +20,35 @@ pub(super) fn complete_in_port_list( kind: PortListKind, ) -> Vec { match kind { - PortListKind::Ansi => complete_ansi_port_list(prefix, ctx), + PortListKind::Ansi => complete_ansi_port_list(db, position, prefix, ctx), PortListKind::NonAnsi => complete_non_ansi_port_list(db, position, prefix, ctx), } } -fn complete_ansi_port_list(prefix: &str, ctx: &CompletionContext) -> Vec { +fn complete_ansi_port_list( + db: &RootDb, + position: FilePosition, + prefix: &str, + ctx: &CompletionContext, +) -> Vec { let keywords = [ "input", "output", "inout", "wire", "reg", "tri", "tri0", "tri1", "trireg", "triand", "trior", "wand", "wor", "supply0", "supply1", "integer", "real", "realtime", "time", "signed", "unsigned", ]; - keywords + let mut items = visible_typedefs_in_module_header(db, position) + .into_iter() + .filter(|name| name.starts_with(prefix)) + .map(|name| CompletionItem { + label: name.clone(), + kind: CompletionItemKind::Text, + edit: Some(TextEditItem::replace(ctx.replacement, name)), + snippet_edit: None, + }) + .collect::>(); + + items.extend(keywords .iter() .filter(|kw| kw.starts_with(prefix)) .map(|kw| CompletionItem { @@ -39,8 +56,41 @@ fn complete_ansi_port_list(prefix: &str, ctx: &CompletionContext) -> Vec Vec { + let sema = Semantics::new(db); + let file = sema.parse(position.file_id); + let root = file.syntax(); + let module = sema.find_node_at_offset::(root, position.offset); + let Some(module) = module else { + return Vec::new(); + }; + let file_id = sema.find_file(module.syntax()); + let (_, file_src_map) = db.hir_file_with_source_map(file_id); + let module_src = ModuleSrc::from(module); + let module_id = ModuleId::new(file_id, file_src_map.get(module_src)); + + let mut names: Vec = db + .unit_scope() + .iter() + .filter_map(|(ident, entry)| matches!(entry, UnitEntry::FiledTypedefId(_)).then_some(ident)) + .map(|ident| ident.to_string()) + .collect(); + + names.extend( + db.module_scope(module_id) + .iter() + .filter_map(|(ident, entry)| matches!(entry, ModuleEntry::TypedefId(_)).then_some(ident)) + .map(|ident| ident.to_string()), + ); + + names.sort(); + names.dedup(); + names } fn complete_non_ansi_port_list( diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index b147c7f93..c2edebf23 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -8,6 +8,7 @@ use hir::{ module::{ModuleId, instantiation::InstanceId, port::NonAnsiPortId}, stmt::StmtId, subroutine::{SubroutineId, SubroutinePortId}, + typedef::TypedefId, }, semantics::{Semantics, pathres::PathResolution}, source_map::{IsNamedSrc, IsSrc, ToAstNode}, @@ -37,6 +38,7 @@ pub enum DefinitionOrigin { NonAnsiPort(InModule), Decl(InContainer), + Typedef(InContainer), Instance(InModule), Stmt(InContainer), } @@ -48,6 +50,7 @@ impl_from! { DefinitionOrigin => SubroutinePort(InSubroutine), NonAnsiPort(InModule), Decl(InContainer), + Typedef(InContainer), Instance(InModule), Stmt(InContainer), } @@ -64,6 +67,7 @@ impl DefinitionOrigin { } DefinitionOrigin::NonAnsiPort(InModule { module_id, .. }) => module_id.into(), DefinitionOrigin::Decl(InContainer { cont_id, .. }) => cont_id, + DefinitionOrigin::Typedef(InContainer { cont_id, .. }) => cont_id, DefinitionOrigin::Instance(InModule { module_id, .. }) => module_id.into(), DefinitionOrigin::Stmt(InContainer { cont_id, .. }) => cont_id, } @@ -91,6 +95,9 @@ impl DefinitionOrigin { DefinitionOrigin::Decl(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).name.clone().unwrap() } + DefinitionOrigin::Typedef(InContainer { value, cont_id }) => { + cont_id.to_container(db).get(value).name.clone().unwrap() + } DefinitionOrigin::Instance(InModule { value, module_id }) => { module_id.to_container(db).get(value).name.clone().unwrap() } @@ -140,6 +147,10 @@ impl DefinitionOrigin { let range = cont_id.to_container_src_map(db).get(value).name_range().unwrap(); InFile::new(cont_id.file_id(db).into(), range) } + DefinitionOrigin::Typedef(InContainer { value, cont_id }) => { + let range = cont_id.to_container_src_map(db).get(value).name_range().unwrap(); + InFile::new(cont_id.file_id(db).into(), range) + } DefinitionOrigin::Instance(InModule { value, module_id }) => { let range = module_id.to_container_src_map(db).get(value).name_range().unwrap(); InFile::new(module_id.file_id, range) @@ -191,6 +202,10 @@ impl DefinitionOrigin { let range = cont_id.to_container_src_map(db).get(value).range(); InFile::new(cont_id.file_id(db).into(), range) } + DefinitionOrigin::Typedef(InContainer { value, cont_id }) => { + let range = cont_id.to_container_src_map(db).get(value).range(); + InFile::new(cont_id.file_id(db).into(), range) + } DefinitionOrigin::Instance(InModule { value, module_id }) => { let range = module_id.to_container_src_map(db).get(value).range(); InFile::new(module_id.file_id, range) @@ -287,6 +302,7 @@ impl Definition { match self.0 { PathResolution::Module(module_id) => module_id.into(), PathResolution::Decl(decl_id) => decl_id.into(), + PathResolution::Typedef(typedef_id) => typedef_id.into(), PathResolution::Instance(instance_id) => instance_id.into(), PathResolution::Stmt(stmt_id) => stmt_id.into(), PathResolution::Block(blk_id) => blk_id.into(), diff --git a/crates/ide/src/document_highlight.rs b/crates/ide/src/document_highlight.rs index 832d82807..705b40e6a 100644 --- a/crates/ide/src/document_highlight.rs +++ b/crates/ide/src/document_highlight.rs @@ -91,3 +91,82 @@ fn highlight_refs<'a>( Some(defs.chain(refs).collect()) } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use base_db::{change::Change, source_root::SourceRoot}; + use insta::assert_debug_snapshot; + use triomphe::Arc; + use utils::{lines::LineEnding, text_edit::TextSize}; + use vfs::{ChangeKind, ChangedFile, FileId, FileSet, VfsPath}; + + use super::*; + use crate::{ScopeVisibility, analysis_host::AnalysisHost}; + + fn setup(text: &str) -> (AnalysisHost, FilePosition) { + let marker = "/*caret*/"; + let off = text.find(marker).expect("missing /*caret*/"); + let mut owned = text.to_string(); + owned = owned.replace(marker, ""); + + let file_id = FileId(0); + let path = VfsPath::new_virtual_path("/test.v".to_string()); + + let mut file_set = FileSet::default(); + file_set.insert(file_id, path); + let root = SourceRoot::new_local(file_set); + + let mut change = Change::new(); + change.set_roots(vec![root]); + change.add_changed_file(ChangedFile { + file_id, + change_kind: ChangeKind::Create(Arc::from(owned.as_str()), LineEnding::Unix), + }); + + let mut host = AnalysisHost::default(); + host.apply_change(change); + let position = FilePosition { file_id, offset: TextSize::from(off as u32) }; + (host, position) + } + + fn fixtures_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/document_highlight/fixtures") + } + + #[test] + fn document_highlight_fixtures() { + let dir = fixtures_dir(); + let mut fixtures: Vec<(String, PathBuf)> = std::fs::read_dir(&dir) + .unwrap_or_else(|err| panic!("failed to read fixtures dir {dir:?}: {err}")) + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.extension()? != "v" { + return None; + } + let name = path.file_stem()?.to_string_lossy().to_string(); + Some((name, path)) + }) + .collect(); + + fixtures.sort_by(|a, b| a.0.cmp(&b.0)); + assert!(!fixtures.is_empty(), "no fixtures found in {dir:?}"); + + for (name, path) in fixtures { + let text = + std::fs::read_to_string(&path).unwrap_or_else(|err| panic!("read {path:?}: {err}")); + let (host, position) = setup(&text); + let highlights = host + .make_analysis() + .document_highlight( + position, + DocumentHighlightConfig { scope_visibility: ScopeVisibility::Public }, + ) + .unwrap() + .unwrap_or_default(); + assert_debug_snapshot!(name, highlights); + } + } +} diff --git a/crates/ide/src/document_highlight/fixtures/typedef_highlight_same_file.v b/crates/ide/src/document_highlight/fixtures/typedef_highlight_same_file.v new file mode 100644 index 000000000..d4f78d4ec --- /dev/null +++ b/crates/ide/src/document_highlight/fixtures/typedef_highlight_same_file.v @@ -0,0 +1,5 @@ +module top; +typedef logic [3:0] nibble_t; +nibble_t a; +/*caret*/nibble_t b; +endmodule diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index bdf4f2e53..516e98b97 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -11,6 +11,7 @@ use hir::{ file::FileItem, module::{ModuleId, ModuleItem, ModuleSrc, port::Ports}, stmt::{CaseItem, ForInit, Stmt, StmtId, StmtKind, StmtSrc}, + typedef::{Typedef, TypedefId, TypedefSrc}, }, region_tree::{RegionNode, RegionTreeIterator}, source_map::{IsNamedSrc, IsSrc}, @@ -191,7 +192,8 @@ pub(crate) fn document_symbols(db: &RootDb, file_id: FileId) -> Vec { build_declaration(&mut collector, declaration_id, file, src_map); } - FileItem::TypedefId(_) | FileItem::StructId(_) | FileItem::SubroutineId(_) => { + FileItem::TypedefId(typedef_id) => build_typedef(&mut collector, typedef_id, file, src_map), + FileItem::StructId(_) | FileItem::SubroutineId(_) => { // TODO: implement document symbols for these items } } @@ -267,7 +269,8 @@ fn collect_module_items( build_decls(collector, &port_decl.decls, SymbolKind::PortDecl, module, src_map) } ModuleItem::ContAssignId(_) => {} - ModuleItem::StructId(_) | ModuleItem::TypedefId(_) | ModuleItem::SubroutineId(_) => { + ModuleItem::TypedefId(typedef_id) => build_typedef(collector, typedef_id, module, src_map), + ModuleItem::StructId(_) | ModuleItem::SubroutineId(_) => { // TODO: implement document symbols for these items } } @@ -299,7 +302,8 @@ fn collect_block_items( build_declaration(collector, declaration_id, block, src_map) } BlockItem::StmtId(stmt_id) => build_stmt(db, collector, stmt_id, block, src_map), - BlockItem::TypedefId(_) | BlockItem::StructId(_) => { + BlockItem::TypedefId(typedef_id) => build_typedef(collector, typedef_id, block, src_map), + BlockItem::StructId(_) => { // TODO: implement document symbols for these items } } @@ -427,3 +431,19 @@ fn build_decl( collector.push_symbol_with_kind(&hir.name, src, kind); collector.pop(); } + +#[inline] +fn build_typedef( + collector: &mut SymbolCollecter, + typedef_id: Idx, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef, + SrcMap: Get, +{ + let hir = arena.get(typedef_id); + let src = src_map.get(typedef_id); + collector.push_symbol_with_kind(&hir.name, src, SymbolKind::Typedef); + collector.pop(); +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 352383749..18a592e7d 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -45,6 +45,7 @@ pub enum SymbolKind { ParamDecl, NetDecl, DataDecl, + Typedef, Instance, Block, Stmt, @@ -63,6 +64,7 @@ impl SymbolKind { ast::ParameterDeclaration => SymbolKind::ParamDecl, ast::NetDeclaration => SymbolKind::NetDecl, ast::DataDeclaration => SymbolKind::DataDecl, + ast::TypedefDeclaration => SymbolKind::Typedef, ast::Declarator => SymbolKind::DataDecl, ast::HierarchicalInstance => SymbolKind::Instance, diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 2b84cfd4a..35c38f2cf 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -9,6 +9,7 @@ use hir::{ module::{ModuleId, instantiation::InstanceId, port::NonAnsiPortId}, stmt::StmtId, subroutine::{SubroutineId, SubroutinePortId}, + typedef::TypedefId, }, source_map::{IsNamedSrc, IsSrc, ToAstNode}, }; @@ -55,6 +56,7 @@ impl ToNav for DefinitionOrigin { DefinitionOrigin::SubroutinePort(subroutine_port_id) => subroutine_port_id.to_nav(db), DefinitionOrigin::NonAnsiPort(nonansi_port_id) => nonansi_port_id.to_nav(db), DefinitionOrigin::Decl(decl_id) => decl_id.to_nav(db), + DefinitionOrigin::Typedef(typedef_id) => typedef_id.to_nav(db), DefinitionOrigin::Instance(instance_id) => instance_id.to_nav(db), DefinitionOrigin::Stmt(stmt_id) => stmt_id.to_nav(db), } @@ -171,6 +173,21 @@ impl ToNav for InContainer { } } +impl ToNav for InContainer { + fn to_nav(&self, db: &RootDb) -> NavTarget { + let InContainer { value: typedef_id, cont_id } = *self; + + let file_id = cont_id.file_id(db); + let src = cont_id.to_container_src_map(db).get(typedef_id); + + let cont = cont_id.to_container(db); + let typedef = cont.get(typedef_id); + let cont_name = cont.name().cloned(); + + build(file_id, src.name_range(), src.range(), typedef.name.clone(), SymbolKind::Typedef, cont_name) + } +} + impl ToNav for InModule { fn to_nav(&self, db: &RootDb) -> NavTarget { let InModule { value: instance_id, module_id } = *self; diff --git a/crates/ide/src/render.rs b/crates/ide/src/render.rs index c6b8534fb..a42fb8a33 100644 --- a/crates/ide/src/render.rs +++ b/crates/ide/src/render.rs @@ -1,6 +1,7 @@ use base_db::source_db::SourceDb; use hir::{ container::{ContainerId, ContainerParent, InFile}, + display::HirDisplay, hir_def::{DEFAULT_NAME, literal::Literal}, region_tree::RegionParent, semantics::Semantics, @@ -10,10 +11,7 @@ use itertools::Itertools; use syntax::{SVInt, SyntaxCursorExt, ast::AstNode, trivia::TriviaExt}; use utils::text_edit::TextSize; -use crate::{ - definitions::{Definition, DefinitionOrigin}, - markup::Markup, -}; +use crate::{definitions::{Definition, DefinitionOrigin}, markup::Markup}; pub(crate) fn render_literal(literal: &Literal) -> Option { let mut res = Markup::new(); @@ -121,6 +119,10 @@ pub(crate) fn render_definition(sema: &Semantics, def: Definition) -> Ma fn render_def_origin(sema: &Semantics, origin: &DefinitionOrigin) -> Markup { let mut res = Markup::new(); + if let Some(signature) = render_signature(sema, origin) { + res.push_with_code_fence(&signature); + } + res.merge(render_containers(sema, origin)); if let Some(markup) = render_side_comments(sema, origin) { @@ -133,6 +135,14 @@ fn render_def_origin(sema: &Semantics, origin: &DefinitionOrigin) -> Mar res } +fn render_signature(sema: &Semantics, origin: &DefinitionOrigin) -> Option { + let db = sema.db; + match origin { + DefinitionOrigin::Typedef(typedef) => typedef.display_signature(db).ok(), + _ => None, + } +} + fn render_side_comments(sema: &Semantics<'_, RootDb>, origin: &DefinitionOrigin) -> Option { let db = sema.db; let InFile { value: range, file_id } = origin.range(db); diff --git a/crates/ide/src/semantic_tokens.rs b/crates/ide/src/semantic_tokens.rs index a9dad46d9..02247d760 100644 --- a/crates/ide/src/semantic_tokens.rs +++ b/crates/ide/src/semantic_tokens.rs @@ -58,6 +58,7 @@ pub struct SemaToken { pub enum SemaTokenTag { Port(SemaTokenPort), Instance, + Type, None, } @@ -71,9 +72,11 @@ pub enum SemaTokenPort { bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct SemaTokenModifier: u32 { + const DECL = 1 << 0; const READ = 1 << 1; const WRITE = 1 << 2; const REF = 1 << 3; + const DEF = 1 << 4; } } @@ -173,6 +176,19 @@ fn collect_file( }; } + for (typedef_id, typedef) in hir_file.typedefs.iter() { + let _: Option<()> = try { + let _name = typedef.name.as_ref()?; + let range = file_src_map.get(typedef_id).name_range()?; + check_range!(collector, range); + collector.tokens.add(SemaToken { + range, + tag: SemaTokenTag::Type, + mods: SemaTokenModifier::DECL | SemaTokenModifier::DEF, + }); + }; + } + for (stmt_id, stmt) in hir_file.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { let range = file_src_map.get(stmt_id).range(); @@ -254,6 +270,19 @@ fn collect_module( }; } + for (typedef_id, typedef) in module.typedefs.iter() { + let _: Option<()> = try { + let _name = typedef.name.as_ref()?; + let range = module_src_map.get(typedef_id).name_range()?; + check_range!(collector, range); + collector.tokens.add(SemaToken { + range, + tag: SemaTokenTag::Type, + mods: SemaTokenModifier::DECL | SemaTokenModifier::DEF, + }); + }; + } + for (stmt_id, stmt) in module.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { let range = module_src_map.get(stmt_id).range(); @@ -299,6 +328,19 @@ fn collect_block( }; } + for (typedef_id, typedef) in block.typedefs.iter() { + let _: Option<()> = try { + let _name = typedef.name.as_ref()?; + let range = block_src_map.get(typedef_id).name_range()?; + check_range!(collector, range); + collector.tokens.add(SemaToken { + range, + tag: SemaTokenTag::Type, + mods: SemaTokenModifier::DECL | SemaTokenModifier::DEF, + }); + }; + } + for (stmt_id, stmt) in block.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { let range = block_src_map.get(stmt_id).range(); @@ -343,8 +385,88 @@ fn collect_ident_like( SemaToken { range, tag: SemaTokenTag::Instance, mods: SemaTokenModifier::empty() }; collector.tokens.add(sema_token); } + PathResolution::Typedef(_) => { + collector.tokens.add(SemaToken { + range, + tag: SemaTokenTag::Type, + mods: SemaTokenModifier::REF, + }); + } _ => {} } Some(()) } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use base_db::{change::Change, source_root::SourceRoot}; + use insta::assert_debug_snapshot; + use triomphe::Arc; + use utils::{lines::LineEnding, text_edit::TextRange}; + use vfs::{ChangeKind, ChangedFile, FileId, FileSet, VfsPath}; + + use super::*; + use crate::analysis_host::AnalysisHost; + + fn setup(text: &str) -> (AnalysisHost, FileId) { + let file_id = FileId(0); + let path = VfsPath::new_virtual_path("/test.v".to_string()); + + let mut file_set = FileSet::default(); + file_set.insert(file_id, path); + let root = SourceRoot::new_local(file_set); + + let mut change = Change::new(); + change.set_roots(vec![root]); + change.add_changed_file(ChangedFile { + file_id, + change_kind: ChangeKind::Create(Arc::from(text), LineEnding::Unix), + }); + + let mut host = AnalysisHost::default(); + host.apply_change(change); + (host, file_id) + } + + fn fixtures_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/semantic_tokens/fixtures") + } + + #[test] + fn semantic_token_fixtures() { + let dir = fixtures_dir(); + let mut fixtures: Vec<(String, PathBuf)> = std::fs::read_dir(&dir) + .unwrap_or_else(|err| panic!("failed to read fixtures dir {dir:?}: {err}")) + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.extension()? != "v" { + return None; + } + let name = path.file_stem()?.to_string_lossy().to_string(); + Some((name, path)) + }) + .collect(); + + fixtures.sort_by(|a, b| a.0.cmp(&b.0)); + assert!(!fixtures.is_empty(), "no fixtures found in {dir:?}"); + + for (name, path) in fixtures { + let text = + std::fs::read_to_string(&path).unwrap_or_else(|err| panic!("read {path:?}: {err}")); + let (host, file_id) = setup(&text); + let tokens = host + .make_analysis() + .semantic_tokens( + file_id, + SemaTokenConfig { port: SemaTokenPortConfig { clk_rst: false, io: false } }, + Some(TextRange::up_to(utils::text_edit::TextSize::of(text.as_str()))), + ) + .unwrap(); + assert_debug_snapshot!(name, tokens); + } + } +} diff --git a/crates/ide/src/semantic_tokens/fixtures/typedef_tokens.v b/crates/ide/src/semantic_tokens/fixtures/typedef_tokens.v new file mode 100644 index 000000000..9dbe78ebd --- /dev/null +++ b/crates/ide/src/semantic_tokens/fixtures/typedef_tokens.v @@ -0,0 +1,5 @@ +module top; +typedef logic [3:0] nibble_t; +nibble_t a; +nibble_t b; +endmodule diff --git a/crates/ide/src/snapshots/ide__document_highlight__tests__typedef_highlight_same_file.snap b/crates/ide/src/snapshots/ide__document_highlight__tests__typedef_highlight_same_file.snap new file mode 100644 index 000000000..c76a2299d --- /dev/null +++ b/crates/ide/src/snapshots/ide__document_highlight__tests__typedef_highlight_same_file.snap @@ -0,0 +1,25 @@ +--- +source: crates/ide/src/document_highlight.rs +assertion_line: 169 +expression: highlights +--- +[ + DocumentHighlight { + range: 32..40, + category: ReferenceCategory( + 0x0, + ), + }, + DocumentHighlight { + range: 42..50, + category: ReferenceCategory( + 0x0, + ), + }, + DocumentHighlight { + range: 54..62, + category: ReferenceCategory( + 0x0, + ), + }, +] diff --git a/crates/ide/src/snapshots/ide__semantic_tokens__tests__typedef_tokens.snap b/crates/ide/src/snapshots/ide__semantic_tokens__tests__typedef_tokens.snap new file mode 100644 index 000000000..8a34ff660 --- /dev/null +++ b/crates/ide/src/snapshots/ide__semantic_tokens__tests__typedef_tokens.snap @@ -0,0 +1,55 @@ +--- +source: crates/ide/src/semantic_tokens.rs +expression: tokens +--- +[ + SemaToken { + range: 0..32, + tag: None, + mods: SemaTokenModifier( + 0x0, + ), + }, + SemaToken { + range: 32..40, + tag: Type, + mods: SemaTokenModifier( + DECL | DEF, + ), + }, + SemaToken { + range: 40..42, + tag: None, + mods: SemaTokenModifier( + 0x0, + ), + }, + SemaToken { + range: 42..50, + tag: Type, + mods: SemaTokenModifier( + REF, + ), + }, + SemaToken { + range: 50..54, + tag: None, + mods: SemaTokenModifier( + 0x0, + ), + }, + SemaToken { + range: 54..62, + tag: Type, + mods: SemaTokenModifier( + REF, + ), + }, + SemaToken { + range: 62..76, + tag: None, + mods: SemaTokenModifier( + 0x0, + ), + }, +] diff --git a/src/lsp_ext/ext.rs b/src/lsp_ext/ext.rs index 9f69368cf..29d225bf2 100644 --- a/src/lsp_ext/ext.rs +++ b/src/lsp_ext/ext.rs @@ -75,6 +75,7 @@ define_semantic_token_kind! { (RST_PORT, "port_reset") => KEYWORD, (OTHERS_PORT, "port_generic") => PARAMETER, (INSTANCE, "instance") => VARIABLE, + (TYPE_ALIAS, "type_alias") => TYPE, (GENERIC, "generic") => TYPE_PARAMETER, } } @@ -105,6 +106,7 @@ define_semantic_token_kind! { (READ, "read") => READONLY, (WRITE, "write") => MODIFICATION, (REF, "ref") => MODIFICATION, + (DEF, "definition"), } } diff --git a/src/lsp_ext/to_proto.rs b/src/lsp_ext/to_proto.rs index 404ac28af..4a03cc22f 100644 --- a/src/lsp_ext/to_proto.rs +++ b/src/lsp_ext/to_proto.rs @@ -162,6 +162,7 @@ fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind { SymbolKind::ParamDecl => LspSymbolKind::TYPE_PARAMETER, SymbolKind::NetDecl => LspSymbolKind::PROPERTY, SymbolKind::DataDecl => LspSymbolKind::VARIABLE, + SymbolKind::Typedef => LspSymbolKind::TYPE_PARAMETER, SymbolKind::Instance => LspSymbolKind::OBJECT, SymbolKind::Block => LspSymbolKind::NAMESPACE, SymbolKind::Stmt => LspSymbolKind::NAMESPACE, @@ -590,6 +591,7 @@ pub(crate) fn semantic_tokens( SemaTokenTag::Port(SemaTokenPort::Rst) => sema_token_types::RST_PORT, SemaTokenTag::Port(SemaTokenPort::Others) => sema_token_types::OTHERS_PORT, SemaTokenTag::Instance => sema_token_types::INSTANCE, + SemaTokenTag::Type => sema_token_types::TYPE_ALIAS, SemaTokenTag::None => sema_token_types::GENERIC, }; // WORKAROUND: currently we haven't implemented client. @@ -599,6 +601,8 @@ pub(crate) fn semantic_tokens( let mut mods_set = SemaTokenModifierSet::default(); for modifier in mods { let modifier = match modifier { + SemaTokenModifier::DECL => sema_token_modifiers::DECLARATION, + SemaTokenModifier::DEF => sema_token_modifiers::DEF, SemaTokenModifier::READ => sema_token_modifiers::READ, SemaTokenModifier::WRITE => sema_token_modifiers::WRITE, SemaTokenModifier::REF => sema_token_modifiers::REF,