Skip to content

Commit

Permalink
feat: initial completion
Browse files Browse the repository at this point in the history
  • Loading branch information
jamestrew committed Dec 23, 2023
1 parent e2ddb39 commit 5a1790f
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 12 deletions.
46 changes: 42 additions & 4 deletions src/eval/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ mod test;
use std::collections::HashMap;
use std::sync::{Arc, RwLock, Weak};

use tower_lsp::lsp_types::{Position, Range};
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, Position, Range};

use crate::ast::Block;
use crate::eval::object::{Builtin, Object};
use crate::lexer::keyword_completions;
use crate::spanned::{rng_str, OpsRange, Spanned};

pub struct Scope {
Expand Down Expand Up @@ -185,10 +186,10 @@ impl Env {
let ident = ident.as_str();
let def_env = pos_env.def_env(ident)?;

Some(def_env.collect_scope_refs(ident))
Some(def_env.collect_ident_refs(ident))
}

fn collect_scope_refs(&self, ident: &str) -> Vec<Range> {
fn collect_ident_refs(&self, ident: &str) -> Vec<Range> {
let env = self.0.read().unwrap();
let mut refs: Vec<Range> = env
.refs
Expand All @@ -199,9 +200,46 @@ impl Env {

env.children
.iter()
.for_each(|child| refs.extend(child.collect_scope_refs(ident)));
.for_each(|child| refs.extend(child.collect_ident_refs(ident)));
refs
}

pub fn get_completions(&self, pos: &Position) -> Vec<CompletionItem> {
let mut items = Builtin::completion_items();
items.extend(keyword_completions());
if let Some(pos_env) = self.pos_env(pos) {
items.extend(pos_env.collect_def_items(pos));
}
items
}

fn collect_def_items(&self, pos: &Position) -> Vec<CompletionItem> {
let env = self.0.read().unwrap();
let mut items = env
.store
.iter()
.filter(|(ident, obj)| !Builtin::includes(ident) && obj.start <= *pos)
.map(|(ident, obj)| {
let kind = if matches!(***obj, Object::Function(_, _)) {
CompletionItemKind::FUNCTION
} else {
CompletionItemKind::VALUE
};
CompletionItem {
label: ident.clone(),
kind: Some(kind),
..Default::default()
}
})
.collect::<Vec<_>>();

if let Some(weak_parent) = &env.parent {
if let Some(parent) = weak_parent.upgrade() {
items.extend(Env(parent).collect_def_items(pos));
}
}
items
}
}

impl std::fmt::Debug for Env {
Expand Down
78 changes: 77 additions & 1 deletion src/eval/env/test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tower_lsp::lsp_types::*;

use crate::analyze_source;
use crate::eval::object::Builtin;

const SOURCE: &str = r#"
let foo = 1 == 2;
Expand All @@ -26,7 +27,8 @@ let add_maybe = fn(x, y) {
}
};
puts(add_maybe(a, x));"#;
puts(add_maybe(a, x));
"#;

fn foo_references() -> Vec<Range> {
vec![
Expand Down Expand Up @@ -126,3 +128,77 @@ fn deep_nested_reference() {
assert_eq!(actual, expected);
}
}

fn builtin_keyword_completions() -> Vec<CompletionItem> {
use crate::lexer::keyword_completions;
Builtin::completion_items()
.into_iter()
.chain(keyword_completions())
.collect()
}

macro_rules! assert_comp_items {
($expected:expr, $actual:expr) => {
assert_eq!($expected.len(), $actual.len());
for item in $expected.iter() {
assert!(
$actual.contains(item),
"Expected item not found in actual {:#?}",
item
);
}
};
}

fn comp_item(label: &str, kind: CompletionItemKind) -> CompletionItem {
CompletionItem {
label: label.to_string(),
kind: Some(kind),
..Default::default()
}
}

#[test]
fn completion_for_empty_source() {
let (_, env) = analyze_source("");
let comps = env.get_completions(&Position::new(0, 0));
let expected = builtin_keyword_completions();
assert_comp_items!(comps, expected);
}

#[test]
fn top_of_the_file_completions() {
let (_, env) = analyze_source(SOURCE);
let comps = env.get_completions(&Position::new(0, 0));
let expected = builtin_keyword_completions();
assert_comp_items!(comps, expected);
}

#[test]
fn bottom_of_the_file_completions() {
let (_, env) = analyze_source(SOURCE);
let comps = env.get_completions(&Position::new(24, 0));
let mut expected = builtin_keyword_completions();
expected.extend(vec![
comp_item("foo", CompletionItemKind::VALUE),
comp_item("a", CompletionItemKind::VALUE),
comp_item("x", CompletionItemKind::VALUE),
comp_item("add_maybe", CompletionItemKind::FUNCTION),
]);
assert_comp_items!(comps, expected);
}

#[test]
fn inner_scope_completions() {
let (_, env) = analyze_source(SOURCE);
let comps = env.get_completions(&Position::new(7, 0));
let mut expected = builtin_keyword_completions();
expected.extend(vec![
comp_item("foo", CompletionItemKind::VALUE),
comp_item("a", CompletionItemKind::VALUE),
comp_item("b", CompletionItemKind::VALUE),
comp_item("bar", CompletionItemKind::VALUE),
comp_item("x", CompletionItemKind::VALUE),
]);
assert_comp_items!(comps, expected);
}
25 changes: 25 additions & 0 deletions src/eval/object.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind};

use crate::ast::Call;
use crate::diagnostics::{MonkeyError, SpannedDiagnostic};
use crate::spanned::Spanned;
Expand Down Expand Up @@ -89,6 +91,17 @@ impl Builtin {
}
}

fn detail(&self) -> &'static str {
match self {
Builtin::Len => "Returns the length of a collection or string.",
Builtin::Puts => "Outputs a string to the console or standard output.",
Builtin::First => "Retrieves the first element from a collection.",
Builtin::Last => "Retrieves the last element from a collection.",
Builtin::Rest => "Returns a collection containing all but the first element.",
Builtin::Push => "Appends an element to the end of a collection.",
}
}

pub fn variants() -> &'static [Self] {
&[
Builtin::Len,
Expand Down Expand Up @@ -251,4 +264,16 @@ impl Builtin {
None
}
}

pub fn completion_items() -> Vec<CompletionItem> {
Self::variants()
.iter()
.map(|func| CompletionItem {
label: func.ident().to_string(),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some(func.detail().to_string()),
..Default::default()
})
.collect()
}
}
47 changes: 46 additions & 1 deletion src/lexer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ops::Range;

use logos::Logos;
use tower_lsp::lsp_types::Position;
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, Position};

use crate::diagnostics::{MonkeyError, SpannedError};
use crate::parser::TokenProvider;
Expand Down Expand Up @@ -81,6 +81,51 @@ pub enum TokenKind {
NewLine,
}

pub fn keyword_completions() -> Vec<CompletionItem> {
vec![
CompletionItem {
label: "let".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "fn".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "if".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "else".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "true".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "false".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "return".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
CompletionItem {
label: "nil".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
},
]
}

impl AsRef<TokenKind> for TokenKind {
fn as_ref(&self) -> &TokenKind {
self
Expand Down
15 changes: 9 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use eval::{Env, Eval};
use parser::Parser;
use tokio::sync::Mutex;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::{Diagnostic, *};
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer};
use tracing::info;

Expand Down Expand Up @@ -148,11 +148,14 @@ impl LanguageServer for Backend {
Ok(())
}

async fn completion(&self, _: CompletionParams) -> Result<Option<CompletionResponse>> {
Ok(Some(CompletionResponse::Array(vec![
CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()),
CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()),
])))
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
let pos = params.text_document_position.position;
let env_lock = self.env.lock().await;
if let Some(env) = &*env_lock {
let comps = CompletionResponse::Array(env.get_completions(&pos));
return Ok(Some(comps));
}
Ok(None)
}

async fn hover(&self, _: HoverParams) -> Result<Option<Hover>> {
Expand Down

0 comments on commit 5a1790f

Please sign in to comment.