From ec0653701edc8af9df587b056a21514f9975f0b5 Mon Sep 17 00:00:00 2001 From: LordGolias Date: Mon, 22 Jul 2024 18:46:19 +0000 Subject: [PATCH] Added completion --- README.md | 2 +- client/README.md | 1 + src/addon.rs | 55 ++++++++------ src/analyze.rs | 12 +++- src/completion.rs | 177 ++++++++++++++++++++-------------------------- src/lib.rs | 9 ++- src/main.rs | 109 ++++++++++++++++++++++++++-- 7 files changed, 232 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index d2e0f74..f74fe3a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ pnpm i ## How to publish -Push to main, create a new tag, and create new release in github. +Create a new semver tag from main and push it. # Authors diff --git a/client/README.md b/client/README.md index 5ef8d89..078a1c0 100644 --- a/client/README.md +++ b/client/README.md @@ -10,6 +10,7 @@ It provides a full-feature functionality, including but not limited to: * Full support for macro expansion (e.g. `#define`) * Compreensive type checker, including function signatures via `params` * Full support for listing available functions via `CfgFunction` in mission +* Full support for code completion, including description and signature of the completions * External Addons ## Quick start diff --git a/src/addon.rs b/src/addon.rs index e083f34..e398469 100644 --- a/src/addon.rs +++ b/src/addon.rs @@ -10,7 +10,7 @@ use sqf::span::Spanned; use sqf::types::Type; use sqf::{self, UncasedStr, MISSION_INIT_SCRIPTS}; use sqf::{get_path, preprocessor}; -use tower_lsp::lsp_types::Url; +use tower_lsp::lsp_types::{CompletionItem, Url}; use crate::analyze::compute; use crate::semantic_token::SemanticTokenLocation; @@ -33,7 +33,7 @@ fn identify_(mut addon_path: PathBuf, name: &str) -> Option<(PathBuf, Functions) while addon_path.components().count() > 3 && addon_path.pop() { let configuration = preprocessor::Configuration::with_path(addon_path.join(name)); let Ok((functions, _)) = analyze_file(configuration) else { - continue + continue; }; return Some((addon_path.join(name), functions)); } @@ -42,7 +42,9 @@ fn identify_(mut addon_path: PathBuf, name: &str) -> Option<(PathBuf, Functions) /// searches for all addons and mission description.ext within a project pub fn find(url: &Url) -> Vec<(PathBuf, Functions)> { - let Ok(addon_path) = url.to_file_path() else {return vec![]}; + let Ok(addon_path) = url.to_file_path() else { + return vec![]; + }; let mut r = find_(addon_path.clone(), "config.cpp"); r.extend(find_(addon_path, "description.ext")); @@ -51,7 +53,7 @@ pub fn find(url: &Url) -> Vec<(PathBuf, Functions)> { pub fn find_(addon_path: PathBuf, name: &str) -> Vec<(PathBuf, Functions)> { let Some(first) = identify_(addon_path, name) else { - return vec![] + return vec![]; }; let mut down1 = first.0.clone(); // addons/A/config.cpp down1.pop(); // addons/A/ @@ -67,7 +69,9 @@ pub fn find_(addon_path: PathBuf, name: &str) -> Vec<(PathBuf, Functions)> { } fn list_directories(path: impl AsRef) -> Vec { - let Ok(entries) = std::fs::read_dir(path) else { return vec![] }; + let Ok(entries) = std::fs::read_dir(path) else { + return vec![]; + }; entries .flatten() .flat_map(|entry| { @@ -84,7 +88,7 @@ fn list_directories(path: impl AsRef) -> Vec { type R = ( Option, Vec, - Option<(State, Vec)>, + Option<(State, Vec, Vec)>, ); fn process_file(content: String, configuration: Configuration, functions: &Functions) -> R { @@ -93,32 +97,41 @@ fn process_file(content: String, configuration: Configuration, functions: &Funct let mission = functions .iter() .filter_map(|(k, path)| { - let Ok(path) = get_path(&path.inner, &configuration.base_path, &configuration.addons) else { - return None + let Ok(path) = get_path(&path.inner, &configuration.base_path, &configuration.addons) + else { + return None; }; Some(( k.clone(), - ( - Origin(path, None), - Some(Output::Type(Type::Code)), - ), + (Origin(path, None), Some(Output::Type(Type::Code))), )) }) .collect(); - let (state, semantic_state, new_errors) = match compute(&content, configuration, mission) { - Ok(a) => a, - Err(e) => { - errors.push(e); - return (Some(content), errors, None); - } - }; + let (state, semantic_state, completion, new_errors) = + match compute(&content, configuration, mission) { + Ok(a) => a, + Err(e) => { + errors.push(e); + return (Some(content), errors, None); + } + }; errors.extend(new_errors); - (Some(content), errors, Some((state, semantic_state))) + ( + Some(content), + errors, + Some((state, semantic_state, completion)), + ) } -type R2 = HashMap, (Option>, (State, Vec))>; +type R2 = HashMap< + Arc, + ( + Option>, + (State, Vec, Vec), + ), +>; type R1 = (R2, HashMap, (String, Vec)>); diff --git a/src/analyze.rs b/src/analyze.rs index e0b360f..2acbf63 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -2,10 +2,17 @@ use sqf::analyzer::{analyze, MissionNamespace, State}; use sqf::error::Error; use sqf::parser::parse; use sqf::preprocessor::AstIterator; +use tower_lsp::lsp_types::CompletionItem; +use crate::completion; use crate::semantic_token::{semantic_tokens, SemanticTokenLocation}; -type Return = (State, Vec, Vec); +type Return = ( + State, + Vec, + Vec, + Vec, +); pub fn compute( text: &str, @@ -30,5 +37,6 @@ pub fn compute( state.namespace.mission = mission; analyze(&ast, &mut state); errors.extend(state.errors.clone()); - Ok((state, semantic_tokens, errors)) + let complete = completion::completion(&state.namespace); + Ok((state, semantic_tokens, complete, errors)) } diff --git a/src/completion.rs b/src/completion.rs index 1b2ca13..332c72d 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,106 +1,79 @@ -use std::collections::HashMap; +use sqf::analyzer::{Namespace, BINARY, NULLARY, UNARY}; +use tower_lsp::lsp_types::{ + CompletionItem, CompletionItemKind, Documentation, MarkupContent, MarkupKind, +}; -use crate::chumsky::{Expr, Func, Spanned}; -pub enum ImCompleteCompletionItem { - Variable(String), - Function(String, Vec), -} -/// return (need_to_continue_search, founded reference) -pub fn completion( - ast: &HashMap, - ident_offset: usize, -) -> HashMap { - let mut map = HashMap::new(); - for (_, v) in ast.iter() { - if v.name.1.end < ident_offset { - map.insert( - v.name.0.clone(), - ImCompleteCompletionItem::Function( - v.name.0.clone(), - v.args.clone().into_iter().map(|(name, _)| name).collect(), - ), - ); - } - } - - // collect params variable - for (_, v) in ast.iter() { - if v.span.end > ident_offset && v.span.start < ident_offset { - v.args.iter().for_each(|(item, _)| { - map.insert( - item.clone(), - ImCompleteCompletionItem::Variable(item.clone()), - ); - }); - get_completion_of(&v.body, &mut map, ident_offset); - } - } - map -} - -pub fn get_completion_of( - expr: &Spanned, - definition_map: &mut HashMap, - ident_offset: usize, -) -> bool { - match &expr.0 { - Expr::Error => true, - Expr::Value(_) => true, - // Expr::List(exprs) => exprs - // .iter() - // .for_each(|expr| get_definition(expr, definition_ass_list)), - Expr::Local(local) => !(ident_offset >= local.1.start && ident_offset < local.1.end), - Expr::Let(name, lhs, rest, _name_span) => { - definition_map.insert( - name.clone(), - ImCompleteCompletionItem::Variable(name.clone()), - ); - match get_completion_of(lhs, definition_map, ident_offset) { - true => get_completion_of(rest, definition_map, ident_offset), - false => false, - } - } - Expr::Then(first, second) => match get_completion_of(first, definition_map, ident_offset) { - true => get_completion_of(second, definition_map, ident_offset), - false => false, - }, - Expr::Binary(lhs, _op, rhs) => match get_completion_of(lhs, definition_map, ident_offset) { - true => get_completion_of(rhs, definition_map, ident_offset), - false => false, - }, - Expr::Call(callee, args) => { - match get_completion_of(callee, definition_map, ident_offset) { - true => {} - false => return false, - } - for expr in &args.0 { - match get_completion_of(expr, definition_map, ident_offset) { - true => continue, - false => return false, - } - } - true - } - Expr::If(test, consequent, alternative) => { - match get_completion_of(test, definition_map, ident_offset) { - true => {} - false => return false, - } - match get_completion_of(consequent, definition_map, ident_offset) { - true => {} - false => return false, +pub(super) fn completion(namespace: &Namespace) -> Vec { + namespace + .stack + .iter() + .map(|stack| stack.variables.keys()) + .flatten() + .map(|var| CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::VARIABLE), + ..Default::default() + }) + .chain(NULLARY.iter().map(|(var, (type_, detail))| CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::CONSTANT), + detail: Some(detail.to_string()), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("* `{type_:?}`: {}", detail.to_string()), + })), + ..Default::default() + })) + .chain(UNARY.iter().map(|(var, variants)| { + CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::FUNCTION), + detail: variants + .iter() + .next() + .and_then(|(_, value)| value.get(0).map(|x| x.1.to_string())), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: variants + .iter() + .map(|(type_, value)| { + value.iter().map(move |(t, explanation)| { + format!("* `{} {:?} -> {:?}`: {}", var, type_, t, explanation) + }) + }) + .flatten() + .collect::>() + .join("\n"), + })), + ..Default::default() } - get_completion_of(alternative, definition_map, ident_offset) - } - Expr::Print(expr) => get_completion_of(expr, definition_map, ident_offset), - Expr::List(lst) => { - for expr in lst { - match get_completion_of(expr, definition_map, ident_offset) { - true => continue, - false => return false, - } + })) + .chain(BINARY.iter().map(|(var, variants)| { + CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::FUNCTION), + detail: variants + .iter() + .next() + .and_then(|(_, value)| value.get(0).map(|x| x.1.to_string())), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: variants + .iter() + .map(|(type_, value)| { + value.iter().map(|(t, explanation)| { + format!( + "* `{:?} {} {:?} -> {:?}`: {}", + type_.0, var, type_.1, t, explanation, + ) + }) + }) + .flatten() + .collect::>() + .join("\n"), + })), + ..Default::default() } - true - } - } + })) + .collect() } diff --git a/src/lib.rs b/src/lib.rs index 3615f97..0bd478f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod addon; pub mod analyze; +mod completion; pub mod definition; pub mod hover; pub mod semantic_token; @@ -21,13 +22,15 @@ mod tests { ..Default::default() }; - let (state_semantic, errors) = + let (state_semantic_completion, errors) = match analyze::compute(&content, configuration, Default::default()) { - Ok((state, semantic, errors)) => (Some((state, semantic)), errors), + Ok((state, semantic, completion, errors)) => { + (Some((state, semantic, completion)), errors) + } Err(e) => (None, vec![e]), }; - let (state, semantic_tokens) = state_semantic.unwrap(); + let (state, semantic_tokens, _) = state_semantic_completion.unwrap(); assert_eq!(errors.len(), 1); assert_eq!(state.explanations.len(), 4); diff --git a/src/main.rs b/src/main.rs index e96ab8d..86a2db5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use ropey::Rope; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sqf::analyzer::State; +use sqf::analyzer::{State, BINARY, NULLARY, UNARY}; use sqf::error::{Error, ErrorType}; use sqf::UncasedStr; use sqf_analyzer_server::{addon, hover}; @@ -21,7 +21,13 @@ use tower_lsp::{Client, LanguageServer, LspService, Server}; use sqf_analyzer_server::semantic_token::SemanticTokenLocation; use sqf_analyzer_server::{analyze::compute, definition, semantic_token::LEGEND_TYPE}; -type States = DashMap), Option>)>; +type States = DashMap< + String, + ( + (State, Vec, Vec), + Option>, + ), +>; #[derive(Debug)] struct Backend { @@ -46,7 +52,13 @@ impl LanguageServer for Backend { text_document_sync: Some(TextDocumentSyncCapability::Kind( TextDocumentSyncKind::FULL, )), - completion_provider: None, + completion_provider: Some(CompletionOptions { + resolve_provider: Some(false), + trigger_characters: Some(vec![".".to_string()]), + work_done_progress_options: Default::default(), + all_commit_characters: None, + completion_item: None, + }), execute_command_provider: Some(ExecuteCommandOptions { commands: vec!["dummy.do_something".to_string()], work_done_progress_options: Default::default(), @@ -255,6 +267,10 @@ impl LanguageServer for Backend { Ok(None) } + + async fn completion(&self, params: CompletionParams) -> Result> { + Ok(self.completion(params)) + } } #[derive(Debug, Deserialize, Serialize)] @@ -427,7 +443,9 @@ impl Backend { }; let (state_semantic, errors) = match compute(¶ms.text, configuration, mission) { - Ok((state, semantic, errors)) => (Some((state, semantic)), errors), + Ok((state, semantic, completion, errors)) => { + (Some((state, semantic, completion)), errors) + } Err(e) => (None, vec![e]), }; @@ -601,6 +619,89 @@ impl Backend { .collect::>(); Some(semantic_tokens) } + + fn completion(&self, params: CompletionParams) -> Option { + let uri = params.text_document_position.text_document.uri.as_str(); + let state = &self.states.get(uri)?.0 .0; + //let rope = self.documents.get(uri)?; + + //let offset = position_to_offset(params.text_document_position.position, &rope)?; + + let vars = state + .namespace + .stack + .iter() + .map(|stack| stack.variables.keys()) + .flatten() + .map(|var| CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::VARIABLE), + ..Default::default() + }) + .chain(NULLARY.iter().map(|(var, (type_, detail))| CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::CONSTANT), + detail: Some(detail.to_string()), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("* `{type_:?}`: {}", detail.to_string()), + })), + ..Default::default() + })) + .chain(UNARY.iter().map(|(var, variants)| { + CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::FUNCTION), + detail: variants + .iter() + .next() + .and_then(|(_, value)| value.get(0).map(|x| x.1.to_string())), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: variants + .iter() + .map(|(type_, value)| { + value.iter().map(move |(t, explanation)| { + format!("* `{} {:?} -> {:?}`: {}", var, type_, t, explanation) + }) + }) + .flatten() + .collect::>() + .join("\n"), + })), + ..Default::default() + } + })) + .chain(BINARY.iter().map(|(var, variants)| { + CompletionItem { + label: var.to_string(), + kind: Some(CompletionItemKind::FUNCTION), + detail: variants + .iter() + .next() + .and_then(|(_, value)| value.get(0).map(|x| x.1.to_string())), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: variants + .iter() + .map(|(type_, value)| { + value.iter().map(|(t, explanation)| { + format!( + "* `{:?} {} {:?} -> {:?}`: {}", + type_.0, var, type_.1, t, explanation, + ) + }) + }) + .flatten() + .collect::>() + .join("\n"), + })), + ..Default::default() + } + })); + + Some(CompletionResponse::Array(vars.collect())) + } } fn to_diagnostic(item: Error, rope: &Rope) -> Option {