Add ret type

This commit is contained in:
Aleksey Kladov 2018-08-28 21:11:17 +03:00
parent b00a4d43ec
commit 2257c08cb1
14 changed files with 208 additions and 77 deletions

View file

@ -12,7 +12,8 @@ use {
};
#[derive(Debug)]
pub struct CompletionItem {
pub struct
CompletionItem {
pub name: String,
pub snippet: Option<String>
}
@ -25,10 +26,17 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
file.incremental_reparse(&edit)?
};
let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?;
if !is_ident_expr(name_ref) {
return None;
}
let mut res = Vec::new();
if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() {
complete_keywords(&file, Some(fn_def), name_ref, &mut res);
let scopes = FnScopes::new(fn_def);
complete_fn(name_ref, &scopes, &mut res);
} else {
complete_keywords(&file, None, name_ref, &mut res);
}
if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() {
let scope = ModuleScope::new(root);
@ -43,6 +51,42 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
Some(res)
}
fn is_ident_expr(name_ref: ast::NameRef) -> bool {
match ancestors(name_ref.syntax()).filter_map(ast::Expr::cast).next() {
None => false,
Some(expr) => {
expr.syntax().range() == name_ref.syntax().range()
}
}
}
fn complete_keywords(file: &File, fn_def: Option<ast::FnDef>, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
acc.push(keyword("if", "if $0 { }"));
acc.push(keyword("match", "match $0 { }"));
acc.push(keyword("while", "while $0 { }"));
acc.push(keyword("loop", "loop {$0}"));
if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
acc.push(keyword("else", "else {$0}"));
acc.push(keyword("else if", "else if $0 { }"));
}
}
}
// if let Some(fn_def) = fn_def {
// acc.push(keyword("return", ""))
// }
fn keyword(kw: &str, snip: &str) -> CompletionItem {
CompletionItem {
name: kw.to_string(),
snippet: Some(snip.to_string()),
}
}
}
fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
acc.extend(
scopes.scope_chain(name_ref.syntax())
@ -59,29 +103,44 @@ mod tests {
use super::*;
use test_utils::{assert_eq_dbg, extract_offset};
fn do_check(code: &str, expected_completions: &str) {
fn check_scope_completion(code: &str, expected_completions: &str) {
let (off, code) = extract_offset(&code);
let file = File::parse(&code);
let completions = scope_completion(&file, off).unwrap();
let completions = scope_completion(&file, off)
.unwrap()
.into_iter()
.filter(|c| c.snippet.is_none())
.collect::<Vec<_>>();
assert_eq_dbg(expected_completions, &completions);
}
fn check_snippet_completion(code: &str, expected_completions: &str) {
let (off, code) = extract_offset(&code);
let file = File::parse(&code);
let completions = scope_completion(&file, off)
.unwrap()
.into_iter()
.filter(|c| c.snippet.is_some())
.collect::<Vec<_>>();
assert_eq_dbg(expected_completions, &completions);
}
#[test]
fn test_completion_let_scope() {
do_check(r"
check_scope_completion(r"
fn quux(x: i32) {
let y = 92;
1 + <|>;
let z = ();
}
", r#"[CompletionItem { name: "y" },
CompletionItem { name: "x" },
CompletionItem { name: "quux" }]"#);
", r#"[CompletionItem { name: "y", snippet: None },
CompletionItem { name: "x", snippet: None },
CompletionItem { name: "quux", snippet: None }]"#);
}
#[test]
fn test_completion_if_let_scope() {
do_check(r"
check_scope_completion(r"
fn quux() {
if let Some(x) = foo() {
let y = 92;
@ -91,33 +150,61 @@ mod tests {
1 + <|>
}
}
", r#"[CompletionItem { name: "b" },
CompletionItem { name: "a" },
CompletionItem { name: "quux" }]"#);
", r#"[CompletionItem { name: "b", snippet: None },
CompletionItem { name: "a", snippet: None },
CompletionItem { name: "quux", snippet: None }]"#);
}
#[test]
fn test_completion_for_scope() {
do_check(r"
check_scope_completion(r"
fn quux() {
for x in &[1, 2, 3] {
<|>
}
}
", r#"[CompletionItem { name: "x" },
CompletionItem { name: "quux" }]"#);
", r#"[CompletionItem { name: "x", snippet: None },
CompletionItem { name: "quux", snippet: None }]"#);
}
#[test]
fn test_completion_mod_scope() {
do_check(r"
check_scope_completion(r"
struct Foo;
enum Baz {}
fn quux() {
<|>
}
", r#"[CompletionItem { name: "Foo" },
CompletionItem { name: "Baz" },
CompletionItem { name: "quux" }]"#);
", r#"[CompletionItem { name: "Foo", snippet: None },
CompletionItem { name: "Baz", snippet: None },
CompletionItem { name: "quux", snippet: None }]"#);
}
#[test]
fn test_completion_kewords() {
check_snippet_completion(r"
fn quux() {
<|>
}
", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") },
CompletionItem { name: "match", snippet: Some("match $0 { }") },
CompletionItem { name: "while", snippet: Some("while $0 { }") },
CompletionItem { name: "loop", snippet: Some("loop {$0}") }]"#);
}
#[test]
fn test_completion_else() {
check_snippet_completion(r"
fn quux() {
if true {
()
} <|>
}
", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") },
CompletionItem { name: "match", snippet: Some("match $0 { }") },
CompletionItem { name: "while", snippet: Some("while $0 { }") },
CompletionItem { name: "loop", snippet: Some("loop {$0}") },
CompletionItem { name: "else", snippet: Some("else {$0}") },
CompletionItem { name: "else if", snippet: Some("else if $0 { }") }]"#);
}
}

View file

@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
unicode-xid = "0.1.0"
text_unit = "0.1.3"
text_unit = "0.1.4"
itertools = "0.7.8"
drop_bomb = "0.1.4"
parking_lot = "0.6.0"

View file

@ -523,6 +523,10 @@ impl<'a> FnDef<'a> {
pub fn body(self) -> Option<Block<'a>> {
super::child_opt(self)
}
pub fn ret_type(self) -> Option<RetType<'a>> {
super::child_opt(self)
}
}
// FnPointerType
@ -1412,6 +1416,24 @@ impl<'a> AstNode<'a> for ReferenceType<'a> {
impl<'a> ReferenceType<'a> {}
// RetType
#[derive(Debug, Clone, Copy)]
pub struct RetType<'a> {
syntax: SyntaxNodeRef<'a>,
}
impl<'a> AstNode<'a> for RetType<'a> {
fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
match syntax.kind() {
RET_TYPE => Some(RetType { syntax }),
_ => None,
}
}
fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
}
impl<'a> RetType<'a> {}
// ReturnExpr
#[derive(Debug, Clone, Copy)]
pub struct ReturnExpr<'a> {

View file

@ -119,6 +119,7 @@ Grammar(
"STRUCT_DEF",
"ENUM_DEF",
"FN_DEF",
"RET_TYPE",
"EXTERN_CRATE_ITEM",
"MODULE",
"USE_ITEM",
@ -252,8 +253,10 @@ Grammar(
options: [
["param_list", "ParamList"],
["body", "Block"],
["ret_type", "RetType"]
],
),
"RetType": (),
"StructDef": (
traits: [
"NameOwner",

View file

@ -119,8 +119,10 @@ fn abi(p: &mut Parser) {
fn opt_fn_ret_type(p: &mut Parser) -> bool {
if p.at(THIN_ARROW) {
let m = p.start();
p.bump();
types::type_(p);
m.complete(p, RET_TYPE);
true
} else {
false

View file

@ -119,6 +119,7 @@ pub enum SyntaxKind {
STRUCT_DEF,
ENUM_DEF,
FN_DEF,
RET_TYPE,
EXTERN_CRATE_ITEM,
MODULE,
USE_ITEM,
@ -380,6 +381,7 @@ impl SyntaxKind {
STRUCT_DEF => &SyntaxInfo { name: "STRUCT_DEF" },
ENUM_DEF => &SyntaxInfo { name: "ENUM_DEF" },
FN_DEF => &SyntaxInfo { name: "FN_DEF" },
RET_TYPE => &SyntaxInfo { name: "RET_TYPE" },
EXTERN_CRATE_ITEM => &SyntaxInfo { name: "EXTERN_CRATE_ITEM" },
MODULE => &SyntaxInfo { name: "MODULE" },
USE_ITEM => &SyntaxInfo { name: "USE_ITEM" },

View file

@ -17,13 +17,14 @@ ROOT@[0; 42)
PIPE@[16; 17)
PIPE@[17; 18)
WHITESPACE@[18; 19)
THIN_ARROW@[19; 21)
WHITESPACE@[21; 22)
TUPLE_TYPE@[22; 24)
L_PAREN@[22; 23)
R_PAREN@[23; 24)
err: `expected a block`
err: `expected SEMI`
RET_TYPE@[19; 24)
THIN_ARROW@[19; 21)
WHITESPACE@[21; 22)
TUPLE_TYPE@[22; 24)
L_PAREN@[22; 23)
R_PAREN@[23; 24)
err: `expected a block`
err: `expected SEMI`
WHITESPACE@[24; 25)
EXPR_STMT@[25; 39)
BLOCK_EXPR@[25; 38)

View file

@ -13,10 +13,11 @@ ROOT@[0; 21)
L_PAREN@[11; 12)
R_PAREN@[12; 13)
WHITESPACE@[13; 14)
THIN_ARROW@[14; 16)
WHITESPACE@[16; 17)
TUPLE_TYPE@[17; 19)
L_PAREN@[17; 18)
R_PAREN@[18; 19)
RET_TYPE@[14; 19)
THIN_ARROW@[14; 16)
WHITESPACE@[16; 17)
TUPLE_TYPE@[17; 19)
L_PAREN@[17; 18)
R_PAREN@[18; 19)
SEMI@[19; 20)
WHITESPACE@[20; 21)

View file

@ -21,10 +21,11 @@ ROOT@[0; 29)
L_PAREN@[19; 20)
R_PAREN@[20; 21)
WHITESPACE@[21; 22)
THIN_ARROW@[22; 24)
WHITESPACE@[24; 25)
TUPLE_TYPE@[25; 27)
L_PAREN@[25; 26)
R_PAREN@[26; 27)
RET_TYPE@[22; 27)
THIN_ARROW@[22; 24)
WHITESPACE@[24; 25)
TUPLE_TYPE@[25; 27)
L_PAREN@[25; 26)
R_PAREN@[26; 27)
SEMI@[27; 28)
WHITESPACE@[28; 29)

View file

@ -21,11 +21,12 @@ ROOT@[0; 30)
L_PAREN@[18; 19)
R_PAREN@[19; 20)
WHITESPACE@[20; 21)
THIN_ARROW@[21; 23)
WHITESPACE@[23; 24)
TUPLE_TYPE@[24; 26)
L_PAREN@[24; 25)
R_PAREN@[25; 26)
RET_TYPE@[21; 26)
THIN_ARROW@[21; 23)
WHITESPACE@[23; 24)
TUPLE_TYPE@[24; 26)
L_PAREN@[24; 25)
R_PAREN@[25; 26)
WHITESPACE@[26; 27)
BLOCK@[27; 29)
L_CURLY@[27; 28)

View file

@ -28,13 +28,14 @@ ROOT@[0; 79)
PIPE@[26; 27)
PIPE@[27; 28)
WHITESPACE@[28; 29)
THIN_ARROW@[29; 31)
WHITESPACE@[31; 32)
PATH_TYPE@[32; 35)
PATH@[32; 35)
PATH_SEGMENT@[32; 35)
NAME_REF@[32; 35)
IDENT@[32; 35) "i32"
RET_TYPE@[29; 35)
THIN_ARROW@[29; 31)
WHITESPACE@[31; 32)
PATH_TYPE@[32; 35)
PATH@[32; 35)
PATH_SEGMENT@[32; 35)
NAME_REF@[32; 35)
IDENT@[32; 35) "i32"
WHITESPACE@[35; 36)
BLOCK@[36; 42)
L_CURLY@[36; 37)

View file

@ -35,11 +35,12 @@ ROOT@[0; 32)
IDENT@[19; 22) "i32"
R_PAREN@[22; 23)
WHITESPACE@[23; 24)
THIN_ARROW@[24; 26)
WHITESPACE@[26; 27)
TUPLE_TYPE@[27; 29)
L_PAREN@[27; 28)
R_PAREN@[28; 29)
RET_TYPE@[24; 29)
THIN_ARROW@[24; 26)
WHITESPACE@[26; 27)
TUPLE_TYPE@[27; 29)
L_PAREN@[27; 28)
R_PAREN@[28; 29)
R_ANGLE@[29; 30)
SEMI@[30; 31)
WHITESPACE@[31; 32)

View file

@ -8,26 +8,27 @@ ROOT@[0; 27)
L_PAREN@[6; 7)
R_PAREN@[7; 8)
WHITESPACE@[8; 9)
THIN_ARROW@[9; 11)
WHITESPACE@[11; 12)
PATH_TYPE@[12; 23)
PATH@[12; 23)
PATH_SEGMENT@[12; 23)
NAME_REF@[12; 15)
IDENT@[12; 15) "Box"
TYPE_ARG_LIST@[15; 23)
L_ANGLE@[15; 16)
TYPE_ARG@[16; 22)
PATH_TYPE@[16; 22)
PATH@[16; 17)
PATH_SEGMENT@[16; 17)
NAME_REF@[16; 17)
IDENT@[16; 17) "T"
WHITESPACE@[17; 18)
PLUS@[18; 19)
WHITESPACE@[19; 20)
LIFETIME@[20; 22) "'f"
R_ANGLE@[22; 23)
RET_TYPE@[9; 23)
THIN_ARROW@[9; 11)
WHITESPACE@[11; 12)
PATH_TYPE@[12; 23)
PATH@[12; 23)
PATH_SEGMENT@[12; 23)
NAME_REF@[12; 15)
IDENT@[12; 15) "Box"
TYPE_ARG_LIST@[15; 23)
L_ANGLE@[15; 16)
TYPE_ARG@[16; 22)
PATH_TYPE@[16; 22)
PATH@[16; 17)
PATH_SEGMENT@[16; 17)
NAME_REF@[16; 17)
IDENT@[16; 17) "T"
WHITESPACE@[17; 18)
PLUS@[18; 19)
WHITESPACE@[19; 20)
LIFETIME@[20; 22) "'f"
R_ANGLE@[22; 23)
WHITESPACE@[23; 24)
BLOCK@[24; 26)
L_CURLY@[24; 25)

View file

@ -4,7 +4,7 @@ use languageserver_types::{
Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
Command, TextDocumentIdentifier, WorkspaceEdit,
SymbolInformation, Position, Location, TextEdit,
CompletionItem,
CompletionItem, InsertTextFormat, CompletionItemKind,
};
use serde_json::{to_value, from_value};
use url_serde;
@ -331,9 +331,17 @@ pub fn handle_completion(
Some(items) => items,
};
let items = items.into_iter()
.map(|item| CompletionItem {
label: item.name,
.. Default::default()
.map(|item| {
let mut res = CompletionItem {
label: item.name,
.. Default::default()
};
if let Some(snip) = item.snippet {
res.insert_text = Some(snip);
res.insert_text_format = Some(InsertTextFormat::Snippet);
res.kind = Some(CompletionItemKind::Keyword);
};
res
})
.collect();