Skip to content

Commit

Permalink
feat: LSP autocomplete constructor fields (#5732)
Browse files Browse the repository at this point in the history
# Description

## Problem

Part of #1577

## Summary


![lsp-complete-constructor-fields](https://github.com/user-attachments/assets/7b822fd8-1852-492f-9d7f-48ae45e171a9)

^ The above is just an example of it working, I'm not encouraging using
constructors of std types 😄

## Additional Context

This is the last autocompletion I had in mind while implementing
autocompletion, so I'll likely won't send any other LSP PRs for a
while...

That said, if you think of other autocompletions (that don't involve
auto-importing, because that's a big thing) let me know (or capture an
issue) and we can work on it.

## Documentation

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Aug 15, 2024
1 parent ae33811 commit e71c75a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 8 deletions.
51 changes: 43 additions & 8 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use std::{
};

use async_lsp::ResponseError;
use completion_items::{crate_completion_item, simple_completion_item};
use completion_items::{
crate_completion_item, simple_completion_item, struct_field_completion_item,
};
use fm::{FileId, PathString};
use kinds::{FunctionCompletionKind, FunctionKind, ModuleCompletionKind, RequestedItems};
use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse};
Expand Down Expand Up @@ -472,11 +474,48 @@ impl<'a> NodeFinder<'a> {
fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) {
self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes);

// Check if we need to autocomplete the field name
if constructor_expression
.fields
.iter()
.any(|(field_name, _)| field_name.span().end() as usize == self.byte_index)
{
self.complete_constructor_field_name(constructor_expression);
return;
}

for (_field_name, expression) in &constructor_expression.fields {
self.find_in_expression(expression);
}
}

fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) {
let location =
Location::new(constructor_expression.type_name.last_ident().span(), self.file);
let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else {
return;
};

let struct_type = self.interner.get_struct(struct_id);
let struct_type = struct_type.borrow();

// First get all of the struct's fields
let mut fields = HashMap::new();
let fields_as_written = struct_type.get_fields_as_written();
for (field, typ) in &fields_as_written {
fields.insert(field, typ);
}

// Remove the ones that already exists in the constructor
for (field, _) in &constructor_expression.fields {
fields.remove(&field.0.contents);
}

for (field, typ) in fields {
self.completion_items.push(struct_field_completion_item(field, typ));
}
}

fn find_in_member_access_expression(
&mut self,
member_access_expression: &MemberAccessExpression,
Expand Down Expand Up @@ -905,13 +944,9 @@ impl<'a> NodeFinder<'a> {
generics: &[Type],
prefix: &str,
) {
for (name, typ) in struct_type.get_fields(generics) {
if name_matches(&name, prefix) {
self.completion_items.push(simple_completion_item(
name,
CompletionItemKind::FIELD,
Some(typ.to_string()),
));
for (name, typ) in &struct_type.get_fields(generics) {
if name_matches(name, prefix) {
self.completion_items.push(struct_field_completion_item(name, typ));
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions tooling/lsp/src/requests/completion/completion_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ fn type_to_self_string(typ: &Type, string: &mut String) {
}
}

pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem {
simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.to_string()))
}

pub(super) fn simple_completion_item(
label: impl Into<String>,
kind: CompletionItemKind,
Expand Down
26 changes: 26 additions & 0 deletions tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1390,4 +1390,30 @@ mod completion_tests {
],
);
}

#[test]
async fn test_completes_constructor_fields() {
let src = r#"
mod foobar {
struct Foo {
bb: i32,
bbb: Field,
bbbb: bool,
bbbbb: str<6>,
}
}
fn main() {
foobar::Foo { bbb: 1, b>|<, bbbbb }
}
"#;
assert_completion(
src,
vec![
simple_completion_item("bb", CompletionItemKind::FIELD, Some("i32".to_string())),
simple_completion_item("bbbb", CompletionItemKind::FIELD, Some("bool".to_string())),
],
)
.await;
}
}

0 comments on commit e71c75a

Please sign in to comment.