diff --git a/Cargo.lock b/Cargo.lock index 2a1b6a4..9f13197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,6 +691,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.153" @@ -1669,11 +1675,14 @@ dependencies = [ "dashmap", "filetime", "fuzzy-matcher", + "lazy_static", "rayon", "serde", "serde_json", "serde_repr", "shrinkwraprs", + "strum", + "strum_macros", "thiserror", "tokio", "tower-lsp", diff --git a/Cargo.toml b/Cargo.toml index cec5106..1a15c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,4 +28,5 @@ dyn-clone = "1.0" rayon = "1.9" bitmask-enum = "2.2.3" filetime = "0.2.23" -smallvec = "1.13" \ No newline at end of file +smallvec = "1.13" +lazy_static = "1.5" \ No newline at end of file diff --git a/crates/core/src/ast/annotation.rs b/crates/core/src/ast/annotation.rs index a1a68d2..9668ff8 100644 --- a/crates/core/src/ast/annotation.rs +++ b/crates/core/src/ast/annotation.rs @@ -1,4 +1,4 @@ -use strum_macros::{EnumString, Display, AsRefStr}; +use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; use crate::{tokens::*, AnyNode, DebugRange, NamedSyntaxNode, SyntaxNode}; use super::*; @@ -7,7 +7,7 @@ mod tags { pub struct Annotation; } -#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, AsRefStr)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, AsRefStr, EnumIter)] pub enum AnnotationKind { #[strum(serialize="@addMethod")] AddMethod, diff --git a/crates/core/src/attribs/functions.rs b/crates/core/src/attribs/functions.rs index f3ea886..033f5a7 100644 --- a/crates/core/src/attribs/functions.rs +++ b/crates/core/src/attribs/functions.rs @@ -1,10 +1,12 @@ use std::fmt::Debug; use std::str::FromStr; +use strum_macros::EnumIter; + use crate::{tokens::Keyword, AnyNode, DebugRange, NamedSyntaxNode, SyntaxNode}; use super::{AccessModifier, Specifier}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] pub enum FunctionFlavour { Cleanup, Entry, diff --git a/crates/core/src/attribs/specifier.rs b/crates/core/src/attribs/specifier.rs index fbf2ecc..9c3d9d6 100644 --- a/crates/core/src/attribs/specifier.rs +++ b/crates/core/src/attribs/specifier.rs @@ -1,8 +1,11 @@ use std::str::FromStr; + +use strum_macros::EnumIter; + use crate::{tokens::Keyword, AnyNode, DebugRange, NamedSyntaxNode, SyntaxNode}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] pub enum Specifier { Abstract, Const, diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 300870d..ee645f3 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -20,6 +20,9 @@ rayon.workspace = true bitmask-enum.workspace = true shrinkwraprs.workspace = true filetime.workspace = true +strum.workspace = true +strum_macros.workspace = true +lazy_static.workspace = true tower-lsp = "0.20.0" tokio = { version = "1.38", features = ["macros", "rt", "rt-multi-thread", "io-std", "time"] } fuzzy-matcher = "0.3.7" \ No newline at end of file diff --git a/crates/lsp/src/main.rs b/crates/lsp/src/main.rs index 548d426..0010b12 100644 --- a/crates/lsp/src/main.rs +++ b/crates/lsp/src/main.rs @@ -99,6 +99,15 @@ impl LanguageServer for Backend { async fn hover(&self, params: lsp::HoverParams) -> Result> { self.hover_impl(params).await } + + + async fn completion(&self, params: lsp::CompletionParams) -> Result> { + self.completion_impl(params).await + } + + async fn completion_resolve(&self, params: lsp::CompletionItem) -> Result { + self.completion_resolve_impl(params).await + } } diff --git a/crates/lsp/src/providers/completion.rs b/crates/lsp/src/providers/completion.rs new file mode 100644 index 0000000..2230853 --- /dev/null +++ b/crates/lsp/src/providers/completion.rs @@ -0,0 +1,143 @@ +use lazy_static::lazy_static; +use strum::IntoEnumIterator; +use tower_lsp::lsp_types as lsp; +use tower_lsp::jsonrpc::Result; + +use witcherscript::ast::AnnotationKind; +use witcherscript::attribs::{FunctionFlavour, Specifier}; +use witcherscript::tokens::Keyword; +use witcherscript_analysis::symbol_analysis::symbol_path::SymbolPathBuf; + +use crate::Backend; + + +impl Backend { + pub async fn completion_impl(&self, params: lsp::CompletionParams) -> Result> { + + Ok(None) + } + + pub async fn completion_resolve_impl(&self, params: lsp::CompletionItem) -> Result { + // return the data back for now + // this will be used for displaying documentation once that is taken care of + Ok(params) + } +} + + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +enum CompletionExpectation { + /// Default when context cannot be fully resolved or simply nothing can be suggested + #[default] + None, + /// Syntax requires a new identifier to be written + /// No suggestions should be produced in that case + NewIdentifier, + /// Valid syntax in the root of the document + Root, + /// Any expression acccessible in a given context + /// Expected type should not be taken into account at this stage + AnyExpression, + /// Cursor is placed after a dot signaling member access expression + MemberAccess { + accessor_sympath: SymbolPathBuf + }, + /// Syntax expects a known type identifier + KnownTypeIdentifier, + /// Any syntax valid inside a function body + FunctionBody { + available_local_var_sympaths: Vec + }, + /// Any syntax valid inside class's or state's definition + ClassBody { + class_sympath: SymbolPathBuf + }, + /// Any syntax valid inside struct's definition + StructBody { + struct_sympath: SymbolPathBuf + }, + /// When specifically a field is expected, for example in the `default` assignment + ClassField { + class_sympath: SymbolPathBuf + } +} + + + +trait ToCompletionItem { + fn to_completion_item(&self, text_edit_range: lsp::Range) -> lsp::CompletionItem; +} + +impl ToCompletionItem for Keyword { + fn to_completion_item(&self, _text_edit_range: lsp::Range) -> lsp::CompletionItem { + lsp::CompletionItem { + label: self.as_ref().to_string(), + kind: Some(lsp::CompletionItemKind::KEYWORD), + ..Default::default() + } + } +} + +impl ToCompletionItem for AnnotationKind { + fn to_completion_item(&self, text_edit_range: lsp::Range) -> lsp::CompletionItem { + let mut text = self.as_ref().to_string(); + if self.requires_arg() { + text += "($1)"; + } + + lsp::CompletionItem { + label: self.as_ref().to_string(), + kind: Some(lsp::CompletionItemKind::KEYWORD), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + text_edit: Some(lsp::TextEdit { + new_text: text, + range: text_edit_range + }.into()), + ..Default::default() + } + } +} + + +lazy_static! { + static ref ROOT_KEYWORDS: Vec = { + let mut kws: Vec = Vec::new(); + + kws.extend( + Specifier::iter() + .map(|s| { + let k: Keyword = s.into(); + k + }) + ); + + kws.extend( + FunctionFlavour::iter() + .map(|f| { + let k: Keyword = f.into(); + k + }) + ); + + kws.extend([ + Keyword::Class, + Keyword::State, + Keyword::Enum, + Keyword::Function, + Keyword::Struct, + Keyword::Var + ]); + + kws + }; +} + +fn root_completions(text_edit_range: lsp::Range) -> Vec { + let annotations = AnnotationKind::iter() + .map(|ak| ak.to_completion_item(text_edit_range.clone())); + + let keywords = ROOT_KEYWORDS.iter() + .map(|k| k.to_completion_item(text_edit_range.clone())); + + annotations.chain(keywords).collect() +} diff --git a/crates/lsp/src/providers/initialization.rs b/crates/lsp/src/providers/initialization.rs index 169f535..aa5d48b 100644 --- a/crates/lsp/src/providers/initialization.rs +++ b/crates/lsp/src/providers/initialization.rs @@ -139,6 +139,14 @@ impl Backend { type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), implementation_provider: None, hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + completion_item: Some(lsp::CompletionOptionsCompletionItem { + label_details_support: Some(true) + }), + ..Default::default() + }), ..lsp::ServerCapabilities::default() } }) diff --git a/crates/lsp/src/providers/mod.rs b/crates/lsp/src/providers/mod.rs index ea6d040..4b31bcb 100644 --- a/crates/lsp/src/providers/mod.rs +++ b/crates/lsp/src/providers/mod.rs @@ -10,6 +10,7 @@ pub mod document_symbols; pub mod goto; pub mod hover; pub mod workspace_symbols; +pub mod completion; pub mod custom;