Skip to content

Commit

Permalink
[move-ide] Added support for completion snippets (#17137)
Browse files Browse the repository at this point in the history
## Description 

Beyond the main objective (completion snippets support for `init`
function and objects), this PR also enhances on-hover for struct
definitions to also show abilities.

When the developer starts typing `fun i` (and continue typing to get to
`fun init`) the IDE will recognize that we are trying to write a
function definition whose name starts with `i` and will offer a code
snippet expanding this to a full init function definition:
````
module 0x0::some_module {
    fun init(ctx: &mut TxContext) {   
    }
}
````
Furthermore, if the module contains a struct definition being a
one-time-witness candidate, the IDE will be smart enough to recognize
this and offer an expanded snippet:
```
module 0x0::some_module {
    struct SOME_MODULE has drop {}
    fun init(witness: SOME_MODULE, ctx: &mut TxContext) {   
    }
}
```
Finally, this snippet will not be offered for all identifiers with a
given prefix, only those that represent function definitions. In
particular, it will not do it for local variable declaration in the
following code fragment:
```
module 0x0::some_module {
    fun foo() {
        let ini   
    }
}
```
It will also not be offered if `init` function is already defined.

A similar thing happens for objects. When the developer type the
"header" of an object struct (must have `key` ability), for example,
`public struct SomeStruct has key {`, the IDE will recognize that we are
trying to write a definition of an object and will insert a snippet
containing the `id` field:
```
public struct SomeStruct has key {
  id: UID,
  
}
```

The limitation here is that `{` triggering snippet insertion must be on
the same line as the struct definitinion. We may address at some point
but it will complicate the implementation and does not seem that
important at the moment as placing `{` on the same line is what pretty
much all the existing code does.

## Test plan 

Added test to check that abilities are shown on-hover and manually
tested snippet insertion works.
  • Loading branch information
awelc authored Apr 15, 2024
1 parent f0ce29f commit c75faf5
Show file tree
Hide file tree
Showing 5 changed files with 481 additions and 91 deletions.
22 changes: 12 additions & 10 deletions external-crates/move/crates/move-analyzer/src/bin/move-analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use lsp_types::{
HoverProviderCapability, OneOf, SaveOptions, TextDocumentSyncCapability, TextDocumentSyncKind,
TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions,
};
use move_compiler::linters::LintLevel;
use move_compiler::{command_line::compiler::FullyCompiledProgram, linters::LintLevel};
use std::{
collections::BTreeMap,
path::PathBuf,
Expand Down Expand Up @@ -52,6 +52,9 @@ fn main() {

let (connection, io_threads) = Connection::stdio();
let symbols = Arc::new(Mutex::new(symbols::empty_symbols()));
let pkg_deps = Arc::new(Mutex::new(
BTreeMap::<PathBuf, Arc<FullyCompiledProgram>>::new(),
));
let ide_files_root: VfsPath = MemoryFS::new().into();
let context = Context {
connection,
Expand Down Expand Up @@ -97,7 +100,7 @@ fn main() {
// characters, such as `::`. So when the language server encounters a completion
// request, it checks whether completions are being requested for `foo:`, and returns no
// completions in that case.)
trigger_characters: Some(vec![":".to_string(), ".".to_string()]),
trigger_characters: Some(vec![":".to_string(), ".".to_string(), "{".to_string()]),
all_commit_characters: None,
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
Expand Down Expand Up @@ -141,6 +144,7 @@ fn main() {
symbolicator_runner = symbols::SymbolicatorRunner::new(
ide_files_root.clone(),
symbols.clone(),
pkg_deps.clone(),
diag_sender,
lint,
);
Expand All @@ -153,7 +157,7 @@ fn main() {
if let Some(uri) = initialize_params.root_uri {
if let Some(p) = symbols::SymbolicatorRunner::root_dir(&uri.to_file_path().unwrap()) {
if let Ok((Some(new_symbols), _)) = symbols::get_symbols(
&mut BTreeMap::new(),
Arc::new(Mutex::new(BTreeMap::new())),
ide_files_root.clone(),
p.as_path(),
lint,
Expand Down Expand Up @@ -223,7 +227,7 @@ fn main() {
// a chance of completing pending requests (but should not accept new requests
// either which is handled inside on_requst) - instead it quits after receiving
// the exit notification from the client, which is handled below
shutdown_req_received = on_request(&context, &request, ide_files_root.clone(), shutdown_req_received);
shutdown_req_received = on_request(&context, &request, ide_files_root.clone(), pkg_deps.clone(), shutdown_req_received);
}
Ok(Message::Response(response)) => on_response(&context, &response),
Ok(Message::Notification(notification)) => {
Expand Down Expand Up @@ -256,6 +260,7 @@ fn on_request(
context: &Context,
request: &Request,
ide_files_root: VfsPath,
pkg_dependencies: Arc<Mutex<BTreeMap<PathBuf, Arc<FullyCompiledProgram>>>>,
shutdown_request_received: bool,
) -> bool {
if shutdown_request_received {
Expand All @@ -274,12 +279,9 @@ fn on_request(
return true;
}
match request.method.as_str() {
lsp_types::request::Completion::METHOD => on_completion_request(
context,
request,
ide_files_root.clone(),
&context.symbols.lock().unwrap(),
),
lsp_types::request::Completion::METHOD => {
on_completion_request(context, request, ide_files_root.clone(), pkg_dependencies)
}
lsp_types::request::GotoDefinition::METHOD => {
symbols::on_go_to_def_request(context, request, &context.symbols.lock().unwrap());
}
Expand Down
Loading

0 comments on commit c75faf5

Please sign in to comment.