Skip to content

Commit

Permalink
Added completion
Browse files Browse the repository at this point in the history
  • Loading branch information
LordGolias committed Jul 22, 2024
1 parent 2304114 commit ec06537
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 133 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 34 additions & 21 deletions src/addon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
Expand All @@ -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"));
Expand All @@ -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/
Expand All @@ -67,7 +69,9 @@ pub fn find_(addon_path: PathBuf, name: &str) -> Vec<(PathBuf, Functions)> {
}

fn list_directories(path: impl AsRef<Path>) -> Vec<PathBuf> {
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| {
Expand All @@ -84,7 +88,7 @@ fn list_directories(path: impl AsRef<Path>) -> Vec<PathBuf> {
type R = (
Option<String>,
Vec<Error>,
Option<(State, Vec<SemanticTokenLocation>)>,
Option<(State, Vec<SemanticTokenLocation>, Vec<CompletionItem>)>,
);

fn process_file(content: String, configuration: Configuration, functions: &Functions) -> R {
Expand All @@ -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<Arc<Path>, (Option<Arc<UncasedStr>>, (State, Vec<SemanticTokenLocation>))>;
type R2 = HashMap<
Arc<Path>,
(
Option<Arc<UncasedStr>>,
(State, Vec<SemanticTokenLocation>, Vec<CompletionItem>),
),
>;

type R1 = (R2, HashMap<Arc<Path>, (String, Vec<Error>)>);

Expand Down
12 changes: 10 additions & 2 deletions src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SemanticTokenLocation>, Vec<Error>);
type Return = (
State,
Vec<SemanticTokenLocation>,
Vec<CompletionItem>,
Vec<Error>,
);

pub fn compute(
text: &str,
Expand All @@ -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))
}
177 changes: 75 additions & 102 deletions src/completion.rs
Original file line number Diff line number Diff line change
@@ -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<String>),
}
/// return (need_to_continue_search, founded reference)
pub fn completion(
ast: &HashMap<String, Func>,
ident_offset: usize,
) -> HashMap<String, ImCompleteCompletionItem> {
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<Expr>,
definition_map: &mut HashMap<String, ImCompleteCompletionItem>,
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<CompletionItem> {
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::<Vec<String>>()
.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::<Vec<String>>()
.join("\n"),
})),
..Default::default()
}
true
}
}
}))
.collect()
}
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod addon;
pub mod analyze;
mod completion;
pub mod definition;
pub mod hover;
pub mod semantic_token;
Expand All @@ -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);
Expand Down
Loading

0 comments on commit ec06537

Please sign in to comment.