diff --git a/external-crates/move/crates/move-analyzer/editors/code/README.md b/external-crates/move/crates/move-analyzer/editors/code/README.md index d214232b65420..0992d87e32bb9 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/README.md +++ b/external-crates/move/crates/move-analyzer/editors/code/README.md @@ -91,6 +91,7 @@ Move source file (a file with a `.move` file extension) and: - go to references - type on hover - outline view showing symbol tree for Move source files + - inlay type hints (local declarations and lambda parameters) - If the opened Move source file is located within a buildable project you can build and (locally) test this project using `Move: Build a Move package` and `Move: Test a Move package` commands from VSCode's command palette diff --git a/external-crates/move/crates/move-analyzer/editors/code/package.json b/external-crates/move/crates/move-analyzer/editors/code/package.json index 330910af5f60e..987eada34d08b 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/package.json +++ b/external-crates/move/crates/move-analyzer/editors/code/package.json @@ -5,7 +5,7 @@ "publisher": "mysten", "icon": "images/move.png", "license": "Apache-2.0", - "version": "1.0.3", + "version": "1.0.4", "preview": true, "repository": { "url": "https://github.com/MystenLabs/sui.git", diff --git a/external-crates/move/crates/move-analyzer/src/inlay_hints.rs b/external-crates/move/crates/move-analyzer/src/inlay_hints.rs index 58f509ee217e1..ad002f205319a 100644 --- a/external-crates/move/crates/move-analyzer/src/inlay_hints.rs +++ b/external-crates/move/crates/move-analyzer/src/inlay_hints.rs @@ -3,13 +3,16 @@ use crate::{ context::Context, - symbols::{type_to_ide_string, DefInfo, Symbols}, + symbols::{on_hover_markup, type_to_ide_string, DefInfo, DefLoc, Symbols}, }; use lsp_server::Request; use lsp_types::{ - InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position, + InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, + InlayHintTooltip, Position, }; +use move_compiler::{naming::ast as N, shared::Identifier}; + /// Handles inlay hints request of the language server pub fn on_inlay_hint_request(context: &Context, request: &Request, symbols: &Symbols) { let parameters = serde_json::from_value::(request.params.clone()) @@ -48,7 +51,7 @@ pub fn on_inlay_hint_request(context: &Context, request: &Request, symbols: &Sym label: InlayHintLabel::LabelParts(vec![colon_label, type_label]), kind: Some(InlayHintKind::TYPE), text_edits: None, - tooltip: None, + tooltip: additional_hint_info(t, symbols), padding_left: None, padding_right: None, data: None, @@ -69,3 +72,46 @@ pub fn on_inlay_hint_request(context: &Context, request: &Request, symbols: &Sym eprintln!("could not send inlay thing response: {:?}", err); } } + +/// Helper function to compute additional optional info for the hint. +/// At this point it's just the on-hover information as support +/// for adding location of type definition does not seem to quite +/// work in the current version of VSCode +/// +/// TODO: revisit adding location of type definition once current problems +/// are resolved (the main problem is that adding it enables a drop-down menu +/// containing options that are not supported for the type definition, such +/// as go-to-declaration, and which jump to weird locations in the file). +fn additional_hint_info(sp!(_, t): &N::Type, symbols: &Symbols) -> Option { + if let N::Type_::Ref(_, t) = t { + return additional_hint_info(t, symbols); + } + let N::Type_::Apply(_, sp!(_, type_name), _) = t else { + return None; + }; + let N::TypeName_::ModuleType(mod_ident, struct_name) = type_name else { + return None; + }; + + let Some(mod_defs) = symbols + .file_mods() + .values() + .flatten() + .find(|m| m.ident() == &mod_ident.value) + else { + return None; + }; + + let Some(struct_def) = mod_defs.structs().get(&struct_name.value()) else { + return None; + }; + + let struct_def_loc = DefLoc::new(mod_defs.fhash(), struct_def.name_start()); + let Some(struct_def_info) = symbols.def_info(&struct_def_loc) else { + return None; + }; + + Some(InlayHintTooltip::MarkupContent(on_hover_markup( + struct_def_info, + ))) +} diff --git a/external-crates/move/crates/move-analyzer/src/symbols.rs b/external-crates/move/crates/move-analyzer/src/symbols.rs index c5f75f93fb42c..aeb341deca192 100644 --- a/external-crates/move/crates/move-analyzer/src/symbols.rs +++ b/external-crates/move/crates/move-analyzer/src/symbols.rs @@ -136,6 +136,10 @@ pub struct DefLoc { } impl DefLoc { + pub fn new(fhash: FileHash, start: Position) -> Self { + Self { fhash, start } + } + pub fn fhash(&self) -> FileHash { self.fhash } @@ -281,6 +285,12 @@ pub struct StructDef { positional: bool, } +impl StructDef { + pub fn name_start(&self) -> Position { + self.name_start.clone() + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FunctionDef { name: Symbol, @@ -429,6 +439,10 @@ impl ModuleDefs { pub fn untyped_defs(&self) -> &BTreeSet { &self.untyped_defs } + + pub fn ident(&self) -> &ModuleIdent_ { + &self.ident + } } impl fmt::Display for DefInfo { @@ -942,10 +956,7 @@ impl UseDef { use_name: &Symbol, type_def_loc: Option, ) -> Self { - let def_loc = DefLoc { - fhash: def_fhash, - start: def_start, - }; + let def_loc = DefLoc::new(def_fhash, def_start); // Normally, we compute the length of the identifier as the length // of the string that represents it as this string is the same // in the source file and in the AST. However, for aliased module @@ -1725,7 +1736,7 @@ fn get_mod_outer_defs( &fpos.file_hash(), ); def_info.insert( - DefLoc { fhash, start }, + DefLoc::new(fhash, start), DefInfo::Field(mod_ident.value, *name, *fname, t.clone(), doc_string), ); field_types.push(t.clone()); @@ -1766,10 +1777,7 @@ fn get_mod_outer_defs( &pos.file_hash(), ); def_info.insert( - DefLoc { - fhash, - start: name_start, - }, + DefLoc::new(fhash, name_start), DefInfo::Struct( mod_ident.value, *name, @@ -1810,10 +1818,7 @@ fn get_mod_outer_defs( &pos.file_hash(), ); def_info.insert( - DefLoc { - fhash, - start: name_start, - }, + DefLoc::new(fhash, name_start), DefInfo::Const( mod_ident.value, *name, @@ -1884,13 +1889,7 @@ fn get_mod_outer_defs( .collect(), }, ); - def_info.insert( - DefLoc { - fhash: loc.file_hash(), - start: name_start, - }, - fun_info, - ); + def_info.insert(DefLoc::new(loc.file_hash(), name_start), fun_info); } let mut use_def_map = UseDefMap::new(); @@ -1946,10 +1945,7 @@ fn get_mod_outer_defs( ), ); def_info.insert( - DefLoc { - fhash: mod_defs.fhash, - start: mod_defs.start, - }, + DefLoc::new(mod_defs.fhash, mod_defs.start), DefInfo::Module(mod_ident_to_ide_string(&ident), doc_comment), ); } @@ -2451,10 +2447,9 @@ impl<'a> ParsingSymbolicator<'a> { else { return; }; - mod_defs.untyped_defs.insert(DefLoc { - fhash: var.loc().file_hash(), - start: def_start, - }); + mod_defs + .untyped_defs + .insert(DefLoc::new(var.loc().file_hash(), def_start)); } } } @@ -2510,10 +2505,7 @@ impl<'a> TypingSymbolicator<'a> { let name_start = get_start_loc(&pos, self.files, self.file_id_mapping).unwrap(); let fun_info = self .def_info - .get(&DefLoc { - fhash: pos.file_hash(), - start: name_start, - }) + .get(&DefLoc::new(pos.file_hash(), name_start)) .unwrap(); let fun_type_def = def_info_to_type_def_loc(self.mod_outer_defs, fun_info); let use_def = UseDef::new( @@ -2536,10 +2528,7 @@ impl<'a> TypingSymbolicator<'a> { let name_start = get_start_loc(&pos, self.files, self.file_id_mapping).unwrap(); let const_info = self .def_info - .get(&DefLoc { - fhash: pos.file_hash(), - start: name_start, - }) + .get(&DefLoc::new(pos.file_hash(), name_start)) .unwrap(); let ident_type_def_loc = def_info_to_type_def_loc(self.mod_outer_defs, const_info); self.use_defs.insert( @@ -2565,10 +2554,7 @@ impl<'a> TypingSymbolicator<'a> { let name_start = get_start_loc(&pos, self.files, self.file_id_mapping).unwrap(); let struct_info = self .def_info - .get(&DefLoc { - fhash: pos.file_hash(), - start: name_start, - }) + .get(&DefLoc::new(pos.file_hash(), name_start)) .unwrap(); let struct_type_def = def_info_to_type_def_loc(self.mod_outer_defs, struct_info); self.use_defs.insert( @@ -3095,8 +3081,9 @@ impl<'a> TypingSymbolicator<'a> { ident_type_def_loc, ), ); - self.def_info.insert(DefLoc { fhash, start }, type_def_info); - let exists = tp_scope.insert(tname, DefLoc { fhash, start }); + self.def_info + .insert(DefLoc::new(fhash, start), type_def_info); + let exists = tp_scope.insert(tname, DefLoc::new(fhash, start)); debug_assert!(exists.is_none()); } None => { @@ -3143,10 +3130,7 @@ impl<'a> TypingSymbolicator<'a> { let def_fhash = self.mod_outer_defs.get(&mod_ident_str).unwrap().fhash; let const_info = self .def_info - .get(&DefLoc { - fhash: def_fhash, - start: const_def.name_start, - }) + .get(&DefLoc::new(def_fhash, const_def.name_start)) .unwrap(); let ident_type_def_loc = def_info_to_type_def_loc(self.mod_outer_defs, const_info); self.use_defs.insert( @@ -3295,10 +3279,7 @@ impl<'a> TypingSymbolicator<'a> { let def_fhash = self.mod_outer_defs.get(&mod_ident_str).unwrap().fhash; let field_info = self .def_info - .get(&DefLoc { - fhash: def_fhash, - start: fdef.start, - }) + .get(&DefLoc::new(def_fhash, fdef.start)) .unwrap(); let ident_type_def_loc = def_info_to_type_def_loc(self.mod_outer_defs, field_info); @@ -3386,10 +3367,7 @@ impl<'a> TypingSymbolicator<'a> { } match get_start_loc(pos, self.files, self.file_id_mapping) { Some(name_start) => { - let def_loc = DefLoc { - fhash: pos.file_hash(), - start: name_start, - }; + let def_loc = DefLoc::new(pos.file_hash(), name_start); scope.insert( *name, LocalDef { @@ -3417,10 +3395,7 @@ impl<'a> TypingSymbolicator<'a> { ), ); self.def_info.insert( - DefLoc { - fhash: pos.file_hash(), - start: name_start, - }, + DefLoc::new(pos.file_hash(), name_start), DefInfo::Local(*name, def_type, with_let, mutable), ); } @@ -3490,10 +3465,7 @@ fn add_fun_use_def( if let Some(func_def) = mod_defs.functions.get(fun_def_name) { let def_fhash = mod_outer_defs.get(&mod_ident_str).unwrap().fhash; let fun_info = def_info - .get(&DefLoc { - fhash: def_fhash, - start: func_def.start, - }) + .get(&DefLoc::new(def_fhash, func_def.start)) .unwrap(); let ident_type_def_loc = def_info_to_type_def_loc(mod_outer_defs, fun_info); let ud = UseDef::new( @@ -3533,10 +3505,7 @@ fn add_struct_use_def( if let Some(def) = mod_defs.structs.get(use_name) { let def_fhash = mod_outer_defs.get(&mod_ident_str).unwrap().fhash; let struct_info = def_info - .get(&DefLoc { - fhash: def_fhash, - start: def.name_start, - }) + .get(&DefLoc::new(def_fhash, def.name_start)) .unwrap(); let ident_type_def_loc = def_info_to_type_def_loc(mod_outer_defs, struct_info); let ud = UseDef::new( @@ -3605,7 +3574,7 @@ fn find_struct( mod_defs.structs.get(struct_name).map(|struct_def| { let fhash = mod_defs.fhash; let start = struct_def.name_start; - DefLoc { fhash, start } + DefLoc::new(fhash, start) }) } @@ -3708,23 +3677,27 @@ pub fn on_go_to_def_request(context: &Context, request: &Request, symbols: &Symb col, request.id.clone(), |u| { - // TODO: Do we need beginning and end of the definition? Does not seem to make a - // difference from the IDE perspective as the cursor goes to the beginning anyway (at - // least in VSCode). - let range = Range { - start: u.def_loc.start, - end: u.def_loc.start, - }; - let path = symbols.file_name_mapping.get(&u.def_loc.fhash).unwrap(); - let loc = Location { - uri: Url::from_file_path(path).unwrap(), - range, - }; + let loc = def_ide_location(&u.def_loc, symbols); Some(serde_json::to_value(loc).unwrap()) }, ); } +pub fn def_ide_location(def_loc: &DefLoc, symbols: &Symbols) -> Location { + // TODO: Do we need beginning and end of the definition? Does not seem to make a + // difference from the IDE perspective as the cursor goes to the beginning anyway (at + // least in VSCode). + let range = Range { + start: def_loc.start, + end: def_loc.start, + }; + let path = symbols.file_name_mapping.get(&def_loc.fhash).unwrap(); + Location { + uri: Url::from_file_path(path).unwrap(), + range, + } +} + /// Handles go-to-type-def request of the language server pub fn on_go_to_type_def_request(context: &Context, request: &Request, symbols: &Symbols) { let parameters = serde_json::from_value::(request.params.clone()) @@ -3848,20 +3821,25 @@ pub fn on_hover_request(context: &Context, request: &Request, symbols: &Symbols) return Some(serde_json::to_value(Option::::None).unwrap()); }; // use rust for highlighting in Markdown until there is support for Move - let contents = HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: if let Some(s) = &def_info_doc_string(info) { - format!("```rust\n{}\n```\n{}", info, s) - } else { - format!("```rust\n{}\n```", info) - }, - }); + let contents = HoverContents::Markup(on_hover_markup(info)); let range = None; Some(serde_json::to_value(Hover { contents, range }).unwrap()) }, ); } +pub fn on_hover_markup(info: &DefInfo) -> MarkupContent { + let value = if let Some(s) = &def_info_doc_string(info) { + format!("```rust\n{}\n```\n{}", info, s) + } else { + format!("```rust\n{}\n```", info) + }; + MarkupContent { + kind: MarkupKind::Markdown, + value, + } +} + /// Helper function to handle language server queries related to identifier uses pub fn on_use_request( context: &Context,