refactor: Removed panicks from completion

Signed-off-by: faldor20 <eli.jambu@yahoo.com>
This commit is contained in:
Eli Dowling 2023-12-09 15:54:01 +10:00 committed by faldor20
parent 5ccaa1dc74
commit 61b37b276e
No known key found for this signature in database
GPG key ID: F2216079B890CD57
4 changed files with 285 additions and 274 deletions

View file

@ -3,8 +3,6 @@ use std::{
future::Future, future::Future,
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
result,
slice::SliceIndex,
}; };
use bumpalo::Bump; use bumpalo::Bump;
@ -13,26 +11,15 @@ use roc_collections::MutMap;
use roc_load::{CheckedModule, LoadedModule}; use roc_load::{CheckedModule, LoadedModule};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_packaging::cache::{self, RocCacheDir}; use roc_packaging::cache::{self, RocCacheDir};
use roc_region::all::{LineColumn, LineInfo}; use roc_region::all::LineInfo;
use roc_reporting::report::RocDocAllocator; use roc_reporting::report::RocDocAllocator;
use roc_solve_problem::TypeError; use roc_solve_problem::TypeError;
use roc_types::subs::{Subs, Variable}; use roc_types::subs::{Subs, Variable};
use tokio::{sync::Mutex, task::JoinHandle};
use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, CompletionItemTag, Diagnostic, GotoDefinitionResponse,
Hover, HoverContents, Location, MarkedString, Position, Range, SemanticTokenType,
SemanticTokens, SemanticTokensResult, TextEdit, Url,
};
use crate::{ use tower_lsp::lsp_types::{
analysis::completion::{ CompletionItem, Diagnostic, GotoDefinitionResponse, Hover, HoverContents, Location,
field_completion, find_record_fields, get_completion_items, get_completions, MarkedString, Position, Range, SemanticTokenType, SemanticTokens, SemanticTokensResult,
make_completion_items, make_completion_items_string, TextEdit, Url,
},
convert::{
diag::{IntoLspDiagnostic, ProblemFmt},
ToRange, ToRocPosition,
},
}; };
mod completion; mod completion;
@ -40,6 +27,14 @@ mod parse_ast;
mod semantic_tokens; mod semantic_tokens;
mod tokens; mod tokens;
use crate::{
analysis::completion::{field_completion, get_completion_items},
convert::{
diag::{IntoLspDiagnostic, ProblemFmt},
ToRange, ToRocPosition,
},
};
use self::{parse_ast::Ast, semantic_tokens::arrange_semantic_tokens, tokens::Token}; use self::{parse_ast::Ast, semantic_tokens::arrange_semantic_tokens, tokens::Token};
pub const HIGHLIGHT_TOKENS_LEGEND: &[SemanticTokenType] = Token::LEGEND; pub const HIGHLIGHT_TOKENS_LEGEND: &[SemanticTokenType] = Token::LEGEND;
@ -64,18 +59,20 @@ pub(crate) fn global_anal(
source_url: Url, source_url: Url,
source: String, source: String,
version: u32, version: u32,
) -> (impl Future<Output = Vec<AnalyzedDocument>>, DocInfo) { ) -> (impl FnOnce() -> Vec<AnalyzedDocument>, DocInfo) {
let fi = source_url.to_file_path().unwrap(); let fi = source_url.to_file_path().unwrap();
let src_dir = find_src_dir(&fi).to_path_buf(); let src_dir = find_src_dir(&fi).to_path_buf();
let line_info = LineInfo::new(&source); let line_info = LineInfo::new(&source);
let doc_info = DocInfo { let doc_info = DocInfo {
url: source_url.clone(), url: source_url.clone(),
line_info: line_info.clone(), line_info: line_info.clone(),
source: source.clone(), source: source.clone(),
version, version,
}; };
//We will return this before the analisys has completed to enable completion
let doc_info_return = doc_info.clone(); let doc_info_return = doc_info.clone();
let documents_future = async move { let documents_future = move || {
let arena = Bump::new(); let arena = Bump::new();
let loaded = roc_load::load_and_typecheck_str( let loaded = roc_load::load_and_typecheck_str(
&arena, &arena,
@ -138,12 +135,10 @@ pub(crate) fn global_anal(
root_module: &mut root_module, root_module: &mut root_module,
}; };
writeln!(std::io::stderr(), "sources:{:?}", sources);
for (module_id, (path, source)) in sources { for (module_id, (path, source)) in sources {
documents.push(builder.build_document(path, source, module_id, version)); documents.push(builder.build_document(path, source, module_id, version));
} }
writeln!(std::io::stderr(), "documents:{:?}", documents.len());
documents documents
}; };
(documents_future, doc_info_return) (documents_future, doc_info_return)
@ -338,6 +333,23 @@ impl DocInfo {
let position = position.to_roc_position(&self.line_info); let position = position.to_roc_position(&self.line_info);
let offset = position.offset; let offset = position.offset;
let source = self.source.as_bytes().split_at(offset as usize).0; let source = self.source.as_bytes().split_at(offset as usize).0;
// writeln!(std::io::stderr(), "prefix source{:?}", self.source);
// let last_few = self
// .source
// .split_at((offset - 5) as usize)
// .1
// .split_at((offset + 5) as usize)
// .0;
// let splitter = last_few.split_at(5);
// writeln!(
// std::io::stderr(),
// "starting to get completion items at offset:{:?} content:: '{:?}|{:?}'",
// offset,
// splitter.0,
// splitter.1
// );
let mut symbol = source let mut symbol = source
.iter() .iter()
.rev() .rev()
@ -478,16 +490,17 @@ impl AnalyzedDocument {
latest_doc: &DocInfo, latest_doc: &DocInfo,
symbol_prefix: String, symbol_prefix: String,
) -> Option<Vec<CompletionItem>> { ) -> Option<Vec<CompletionItem>> {
let mut position = position; let symbol_prefix = latest_doc.get_prefix_at_position(position);
writeln!( writeln!(
std::io::stderr(), std::io::stderr(),
"starting to get completion items for prefix: {:?}", "starting to get completion items for prefix: {:?} docVersion:{:?}",
symbol_prefix symbol_prefix,
latest_doc.version
); );
let len_diff = latest_doc.source.len() as i32 - self.doc_info.source.len() as i32; let len_diff = latest_doc.source.len() as i32 - self.doc_info.source.len() as i32;
//TODO: this is a hack we can move our position back by getting the difference in the number of chars on this line and what the line was before and doing the same with the number of lines
let mut position = position.to_roc_position(&latest_doc.line_info); let mut position = position.to_roc_position(&latest_doc.line_info);
//TODO: this is kind of a hack and should be removed once we can do some minimal parsing without full type checking
position.offset = (position.offset as i32 - len_diff - 1) as u32; position.offset = (position.offset as i32 - len_diff - 1) as u32;
writeln!( writeln!(
std::io::stderr(), std::io::stderr(),
@ -503,7 +516,8 @@ impl AnalyzedDocument {
.. ..
} = self.module()?; } = self.module()?;
if symbol_prefix.contains('.') { let is_field_completion = symbol_prefix.contains('.');
if is_field_completion {
field_completion( field_completion(
position, position,
symbol_prefix, symbol_prefix,

View file

@ -1,14 +1,14 @@
use std::{io::Write, path::Prefix}; use std::io::Write;
use roc_can::{ use roc_can::{
def::Def, def::Def,
expr::{Declarations, Expr, WhenBranch}, expr::{Declarations, Expr, WhenBranch},
pattern::{ListPatterns, Pattern, RecordDestruct, TupleDestruct}, pattern::{ListPatterns, Pattern, RecordDestruct, TupleDestruct},
traverse::{walk_decl, walk_def, walk_expr, DeclarationInfo, FoundDeclaration, Visitor}, traverse::{walk_decl, walk_def, walk_expr, DeclarationInfo, Visitor},
}; };
use roc_module::symbol::{self, Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_region::all::{Loc, Position, Region}; use roc_region::all::{Loc, Position, Region};
use roc_types::subs::{GetSubsSlice, Subs, Variable}; use roc_types::subs::{Subs, Variable};
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind}; use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind};
use crate::analysis::format_var_type; use crate::analysis::format_var_type;
@ -34,20 +34,7 @@ impl Visitor for CompletionVisitor<'_> {
walk_expr(self, expr, var); walk_expr(self, expr, var);
} }
} }
fn visit_annotation(&mut self, _pat: &roc_can::def::Annotation) {
let mut stderr = std::io::stderr();
// writeln!(&mut stderr, "annotation:{:?}", _pat);
}
// fn visit_pattern(&mut self, pat: &Pattern, region: Region, opt_var: Option<Variable>) {
// if region.contains_pos(self.position) {
// // if let Some(var) = opt_var {
// // self.region_typ = Some((region, var));
// // }
// walk_pattern(self, pat);
// }
// }
fn visit_decl(&mut self, decl: DeclarationInfo<'_>) { fn visit_decl(&mut self, decl: DeclarationInfo<'_>) {
match decl { match decl {
DeclarationInfo::Value { loc_expr, .. } DeclarationInfo::Value { loc_expr, .. }
@ -74,22 +61,8 @@ impl Visitor for CompletionVisitor<'_> {
} }
} }
impl CompletionVisitor<'_> { impl CompletionVisitor<'_> {
fn make_completion_items(&mut self, found: Vec<FoundDeclaration>) -> Vec<(Symbol, Variable)> {
found
.iter()
.flat_map(|comp| match comp {
FoundDeclaration::Decl(dec) => self.decl_to_completion_item(dec),
FoundDeclaration::Def(def) => def
.pattern_vars
.iter()
.map(|(symbol, var)| (symbol.clone(), var.clone()))
.collect(),
})
.collect()
}
fn extract_defs(&mut self, def: &Def) -> Vec<(Symbol, Variable)> { fn extract_defs(&mut self, def: &Def) -> Vec<(Symbol, Variable)> {
let mut stderr = std::io::stderr(); writeln!(std::io::stderr(), "completion begin");
writeln!(&mut stderr, "completion begin");
def.pattern_vars def.pattern_vars
.iter() .iter()
.map(|(symbol, var)| (symbol.clone(), var.clone())) .map(|(symbol, var)| (symbol.clone(), var.clone()))
@ -106,10 +79,10 @@ impl CompletionVisitor<'_> {
// Expr::IngestedFile(_, _, _) => todo!(), // Expr::IngestedFile(_, _, _) => todo!(),
// Expr::Var(_, _) => todo!(), // Expr::Var(_, _) => todo!(),
// Expr::AbilityMember(_, _, _) => todo!(), // Expr::AbilityMember(_, _, _) => todo!(),
Expr::When { loc_cond, cond_var, expr_var, region, branches, branches_cond_var, exhaustive } => { Expr::When { expr_var, branches,.. } => {
let out:Vec<_> = let out:Vec<_> =
branches.iter().flat_map(|WhenBranch{ patterns, value, guard, redundant }|{ branches.iter().flat_map(|WhenBranch{ patterns, value, ..}|{
if value.region.contains_pos(self.position) { if value.region.contains_pos(self.position) {
patterns.iter().flat_map(|pattern|{ patterns.iter().flat_map(|pattern|{
//We use the expression var here because if the pattern is an identifier then it must have the type of the expession given to the when is block //We use the expression var here because if the pattern is an identifier then it must have the type of the expession given to the when is block
@ -185,7 +158,7 @@ impl CompletionVisitor<'_> {
.collect() .collect()
} }
fn as_pattern(&self, as_pat: &Pattern, as_symbol: Symbol) -> (Symbol, Variable) { fn as_pattern(&self, as_pat: &Pattern, as_symbol: Symbol) -> Option<(Symbol, Variable)> {
let var = match as_pat { let var = match as_pat {
Pattern::AppliedTag { Pattern::AppliedTag {
whole_var, whole_var,
@ -227,9 +200,9 @@ impl CompletionVisitor<'_> {
// Pattern::OpaqueNotInScope(_) => todo!(), // Pattern::OpaqueNotInScope(_) => todo!(),
// Pattern::UnsupportedPattern(_) => todo!(), // Pattern::UnsupportedPattern(_) => todo!(),
// Pattern::MalformedPattern(_, _) => todo!(), // Pattern::MalformedPattern(_, _) => todo!(),
_ => todo!(), _ => return None,
}; };
(as_symbol, var.clone()) Some((as_symbol, var.clone()))
} }
fn patterns( fn patterns(
&self, &self,
@ -263,19 +236,16 @@ impl CompletionVisitor<'_> {
elem_var, elem_var,
patterns, patterns,
} => self.list_destructure(patterns, elem_var), } => self.list_destructure(patterns, elem_var),
roc_can::pattern::Pattern::As(pat, symbol) => { roc_can::pattern::Pattern::As(pat, symbol) => self
vec![self.as_pattern(&pat.value, symbol.clone())] .as_pattern(&pat.value, symbol.clone())
.map(|a| vec![a])
.unwrap_or(vec![]),
roc_can::pattern::Pattern::RecordDestructure { destructs, .. } => {
self.record_destructs(destructs)
}
roc_can::pattern::Pattern::TupleDestructure { destructs, .. } => {
self.tuple_destructs(destructs)
} }
roc_can::pattern::Pattern::RecordDestructure {
whole_var,
ext_var,
destructs,
} => self.record_destructs(destructs),
roc_can::pattern::Pattern::TupleDestructure {
whole_var,
ext_var,
destructs,
} => self.tuple_destructs(destructs),
// roc_can::pattern::Pattern::List { // roc_can::pattern::Pattern::List {
// list_var, // list_var,
// elem_var, // elem_var,
@ -300,35 +270,15 @@ impl CompletionVisitor<'_> {
} }
fn is_match(&self, symbol: &Symbol) -> bool { fn is_match(&self, symbol: &Symbol) -> bool {
let mut stderr = std::io::stderr();
// writeln!(
// &mut stderr,
// "check if prefix {:?} matches {:?}",
// self.prefix,
// symbol.as_str(self.interns)
// );
symbol.as_str(self.interns).starts_with(&self.prefix) symbol.as_str(self.interns).starts_with(&self.prefix)
} }
fn decl_to_completion_item(&self, decl: &DeclarationInfo) -> Vec<(Symbol, Variable)> { fn decl_to_completion_item(&self, decl: &DeclarationInfo) -> Vec<(Symbol, Variable)> {
match decl { match decl {
DeclarationInfo::Value { DeclarationInfo::Value {
loc_symbol, expr_var, pattern, ..
expr_var, } => self.patterns(pattern, expr_var),
pattern,
..
} => {
let mut stderr = std::io::stderr();
// writeln!(
// &mut stderr,
// "decl:{:?}",
// loc_symbol.value.as_str(self.interns)
// );
self.patterns(pattern, expr_var)
}
DeclarationInfo::Function { DeclarationInfo::Function {
loc_symbol,
expr_var, expr_var,
pattern, pattern,
function, function,
@ -362,7 +312,7 @@ impl CompletionVisitor<'_> {
} }
} }
pub fn get_completions<'a>( fn get_completions<'a>(
position: Position, position: Position,
decls: &'a Declarations, decls: &'a Declarations,
prefix: String, prefix: String,
@ -390,31 +340,19 @@ fn make_completion_item(
) -> CompletionItem { ) -> CompletionItem {
let type_str = format_var_type(var, subs, module_id, interns); let type_str = format_var_type(var, subs, module_id, interns);
let typ = match subs.get(var.clone()).content { let typ = match subs.get(var.clone()).content {
// roc_types::subs::Content::FlexVar(_) => todo!(),
// roc_types::subs::Content::RigidVar(_) => todo!(),
// roc_types::subs::Content::FlexAbleVar(_, _) => todo!(),
// roc_types::subs::Content::RigidAbleVar(_, _) => todo!(),
// roc_types::subs::Content::RecursionVar { structure, opt_name } => todo!(),
// roc_types::subs::Content::LambdaSet(_) => todo!(),
// roc_types::subs::Content::ErasedLambda => todo!(),
roc_types::subs::Content::Structure(var) => match var { roc_types::subs::Content::Structure(var) => match var {
roc_types::subs::FlatType::Apply(_, _) => CompletionItemKind::FUNCTION, roc_types::subs::FlatType::Apply(_, _) => CompletionItemKind::FUNCTION,
roc_types::subs::FlatType::Func(_, _, _) => CompletionItemKind::FUNCTION, roc_types::subs::FlatType::Func(_, _, _) => CompletionItemKind::FUNCTION,
// roc_types::subs::FlatType::FunctionOrTagUnion(_, _, _) => todo!(),
// roc_types::subs::FlatType::RecursiveTagUnion(_, _, _) => todo!(),
// roc_types::subs::FlatType::EmptyRecord |
// roc_types::subs::FlatType::Record(_, _) => todo!(),
// roc_types::subs::FlatType::EmptyTuple |
// roc_types::subs::FlatType::Tuple(_, _) => CompletionItemKind::VARIABLE,
roc_types::subs::FlatType::EmptyTagUnion roc_types::subs::FlatType::EmptyTagUnion
| roc_types::subs::FlatType::TagUnion(_, _) => CompletionItemKind::ENUM, | roc_types::subs::FlatType::TagUnion(_, _) => CompletionItemKind::ENUM,
_ => CompletionItemKind::VARIABLE, _ => CompletionItemKind::VARIABLE,
}, },
// roc_types::subs::Content::Alias(_, _, _, _) => todo!(),
// roc_types::subs::Content::RangedNumber(_) => todo!(),
// roc_types::subs::Content::Error => todo!(),
a => { a => {
writeln!(std::io::stderr(), "unhandled variable type:{:?}", a); writeln!(
std::io::stderr(),
"No specific completionKind for variable type :{:?} defaulting to 'Variable'",
a
);
CompletionItemKind::VARIABLE CompletionItemKind::VARIABLE
} }
}; };
@ -427,6 +365,9 @@ fn make_completion_item(
..Default::default() ..Default::default()
} }
} }
///Gets completion items using the visitor pattern,
///This will walk through declarations that would be accessable from the provided position adding them to a list of completion items untill all accessable declarations have been fully explored
pub fn get_completion_items( pub fn get_completion_items(
position: Position, position: Position,
prefix: String, prefix: String,
@ -458,7 +399,7 @@ pub fn make_completion_items(
.collect() .collect()
} }
pub fn make_completion_items_string( fn make_completion_items_string(
subs: &mut Subs, subs: &mut Subs,
module_id: &ModuleId, module_id: &ModuleId,
interns: &Interns, interns: &Interns,
@ -470,55 +411,62 @@ pub fn make_completion_items_string(
.collect() .collect()
} }
pub fn find_record_fields(var: Variable, subs: &mut Subs) -> Vec<(String, Variable)> { ///Gets completion items for a record field
///Uses
fn find_record_fields(var: Variable, subs: &mut Subs) -> Vec<(String, Variable)> {
let content = subs.get(var); let content = subs.get(var);
match content.content { match content.content {
// roc_types::subs::Content::FlexVar(_) => todo!(),
// roc_types::subs::Content::RigidVar(_) => todo!(),
// roc_types::subs::Content::FlexAbleVar(_, _) => todo!(),
// roc_types::subs::Content::RigidAbleVar(_, _) => todo!(),
// roc_types::subs::Content::RecursionVar { structure, opt_name } => todo!(),
// roc_types::subs::Content::LambdaSet(_) => todo!(),
// roc_types::subs::Content::ErasedLambda => todo!(),
roc_types::subs::Content::Structure(typ) => match typ { roc_types::subs::Content::Structure(typ) => match typ {
// roc_types::subs::FlatType::Apply(_, _) => todo!(),
// roc_types::subs::FlatType::Func(_, _, _) => todo!(),
roc_types::subs::FlatType::Record(fields, ext) => { roc_types::subs::FlatType::Record(fields, ext) => {
let field_types = fields.unsorted_iterator(subs, ext); let field_types = fields.unsorted_iterator(subs, ext);
let fields: Vec<_> = match field_types { let fields: Vec<_> = match field_types {
Ok(field) => field.map(|a| { Ok(field) => field
let var = match a.1 { .map(|a| {
roc_types::types::RecordField::Demanded(var) let var = match a.1 {
| roc_types::types::RecordField::Required(var) roc_types::types::RecordField::Demanded(var)
| roc_types::types::RecordField::Optional(var) | roc_types::types::RecordField::Required(var)
| roc_types::types::RecordField::RigidRequired(var) | roc_types::types::RecordField::Optional(var)
| roc_types::types::RecordField::RigidOptional(var) => var, | roc_types::types::RecordField::RigidRequired(var)
}; | roc_types::types::RecordField::RigidOptional(var) => var,
(a.0.clone().into(), var) };
}), (a.0.clone().into(), var)
Err(err) => todo!(), })
} .collect(),
.collect(); Err(err) => {
writeln!(
std::io::stderr(),
"WARN:Error getting record field types for completion{:?}",
err
);
vec![]
}
};
fields fields
} }
_ => todo!(), _ => {
// roc_types::subs::FlatType::Tuple(_, _) => todo!(), writeln!(
// roc_types::subs::FlatType::TagUnion(_, _) => todo!(), std::io::stderr(),
// roc_types::subs::FlatType::FunctionOrTagUnion(_, _, _) => todo!(), "WARN: Trying to get field completion for a type that is not a record ",
// roc_types::subs::FlatType::RecursiveTagUnion(_, _, _) => todo!(), );
// roc_types::subs::FlatType::EmptyRecord => todo!(), vec![]
// roc_types::subs::FlatType::EmptyTuple => todo!(), }
// roc_types::subs::FlatType::EmptyTagUnion => todo!(),
}, },
// roc_types::subs::Content::Alias(_, _, _, _) => todo!(),
// roc_types::subs::Content::RangedNumber(_) => todo!(),
roc_types::subs::Content::Error => { roc_types::subs::Content::Error => {
writeln!(std::io::stderr(), "ERROR: variable was of type error",); //This is caused by typechecking our partially typed variable name causing the typechecking to be confused as the type of the parent variable
//TODO! ideally i could recover using some previous typecheck result that isn't broken
writeln!(
std::io::stderr(),
"ERROR: variable type of record was of type error cannot access field",
);
vec![] vec![]
} }
a => { a => {
writeln!(std::io::stderr(), "variable before field type:{:?}", a); writeln!(
todo!(); std::io::stderr(),
"variable before field was unsuported type:{:?}",
a
);
vec![]
} }
} }
} }
@ -579,17 +527,3 @@ pub fn field_completion(
make_completion_items_string(subs, module_id, interns, field_completions); make_completion_items_string(subs, module_id, interns, field_completions);
Some(field_completions) Some(field_completions)
} }
// fn make_completion_item_string(
// &mut self,
// label: String,
// var: &Variable,
// ) -> CompletionItem {
// let type_str = format_var_type(var.clone(), self.subs, self.module_id, self.interns);
// CompletionItem {
// label,
// detail: Some(type_str),
// kind: Some(CompletionItemKind::VARIABLE ),
// ..Default::default()
// }
// }

View file

@ -1,5 +1,11 @@
use std::{ use std::{
cell::OnceCell, collections::HashMap, future::Future, io::Write, ops::Deref, rc::Rc, sync::Arc, cell::OnceCell,
collections::HashMap,
future::Future,
io::{stderr, Write},
ops::Deref,
rc::Rc,
sync::Arc,
}; };
use tokio::{ use tokio::{
@ -98,12 +104,12 @@ impl Registry {
) { ) {
let url = document.url().clone(); let url = document.url().clone();
let document = Arc::new(document); let document = Arc::new(document);
writeln!( // writeln!(
std::io::stderr(), // std::io::stderr(),
"updating doc{:?}. version:{:?}", // "updating doc{:?}. version:{:?}",
&url, // &url,
&document.doc_info.version // &document.doc_info.version
); // );
let latest_doc = LatestDocument::new_initialised(document.clone()); let latest_doc = LatestDocument::new_initialised(document.clone());
match documents.get_mut(&url) { match documents.get_mut(&url) {
@ -137,46 +143,42 @@ impl Registry {
} }
} }
pub async fn apply_change(&self, change: DocumentChange) -> () { pub async fn apply_change(&self, analysed_docs: Vec<AnalyzedDocument>) -> () {
match change { writeln!(
DocumentChange::Modified(url, source, version) => { std::io::stderr(),
let (results, partial) = global_anal(url.clone(), source, version); "updated the latest document with docinfo"
);
writeln!(std::io::stderr(), "starting global analysis"); let mut documents = self.documents.lock().await;
let handle = tokio::task::spawn(results); writeln!(
//Update the latest document with the partial analysis std::io::stderr(),
// Only replace the set of documents and all dependencies that were re-analyzed. "finised doc analasys updating docs {:?}",
// Note that this is actually the opposite of what we want - in truth we want to analysed_docs
// re-evaluate all dependents! .iter()
.map(|a| a.doc_info.url.to_string())
.collect::<Vec<_>>()
);
let mut lock = self.documents.lock().await; for document in analysed_docs {
let doc = lock.get_mut(&url); Registry::update_document(&mut documents, document);
match doc { }
Some(a) => a.latest_document = LatestDocument::new(partial), }
None => (),
} pub async fn apply_doc_info_changes(&self, url: Url, partial: DocInfo) {
drop(lock); let mut lock = self.documents.lock().await;
let doc = lock.get_mut(&url);
match doc {
Some(a) => {
writeln!( writeln!(
std::io::stderr(), std::io::stderr(),
"updated the latest document with docinfo" "set the docInfo to version:{:?}",
partial.version
); );
let analised_docs = handle.await.unwrap(); a.latest_document = LatestDocument::new(partial);
let mut documents = self.documents.lock().await;
writeln!(
std::io::stderr(),
"finised doc analasys updating docs {:?}",
analised_docs
.iter()
.map(|a| &a.doc_info)
.collect::<Vec<_>>()
);
for document in analised_docs {
Registry::update_document(&mut documents, document);
}
} }
DocumentChange::Closed(_url) => (),
None => (),
} }
} }
@ -192,12 +194,6 @@ impl Registry {
None => None, None => None,
} }
} }
// fn document_last_good(self, url: &Url) -> Option<&AnalyzedDocument> {
// self.documents.get(url)(|x| x.last_good_document)
// }
// fn document_last_good(self, url: &Url) -> Option<&AnalyzedDocument> {
// self.documents.get(url)(|x| x.last_good_document)
// }
pub async fn diagnostics(&self, url: &Url) -> Vec<Diagnostic> { pub async fn diagnostics(&self, url: &Url) -> Vec<Diagnostic> {
let Some( document) = self.latest_document_by_url(url).await else { let Some( document) = self.latest_document_by_url(url).await else {
@ -238,10 +234,9 @@ impl Registry {
) -> Option<CompletionResponse> { ) -> Option<CompletionResponse> {
let lock = self.documents.lock().await; let lock = self.documents.lock().await;
let pair = lock.get(url)?; let pair = lock.get(url)?;
let mut stderr = std::io::stderr(); writeln!(stderr(), "got document");
writeln!(&mut stderr, "got document");
let latest_doc_info = &pair.latest_document.info; let latest_doc_info = &pair.latest_document.info;
writeln!(&mut stderr, "latest version:{:?} ", latest_doc_info.version); writeln!(stderr(), "latest version:{:?} ", latest_doc_info.version);
let symbol_prefix = latest_doc_info.get_prefix_at_position(position); let symbol_prefix = latest_doc_info.get_prefix_at_position(position);

View file

@ -2,43 +2,55 @@ use analysis::HIGHLIGHT_TOKENS_LEGEND;
use registry::{DocumentChange, Registry}; use registry::{DocumentChange, Registry};
use std::future::Future; use std::future::Future;
use std::io::Write; use std::io::Write;
use std::sync::Arc; use std::sync::{Arc, OnceLock};
use tokio::sync::{Mutex, MutexGuard, RwLock}; use std::time::Duration;
use tokio::task::JoinHandle; use tokio::sync::{oneshot, Mutex, MutexGuard, RwLock};
use tokio::task::{JoinError, JoinHandle};
use tower_lsp::jsonrpc::Result; use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::request::RegisterCapability; use tower_lsp::lsp_types::request::RegisterCapability;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server}; use tower_lsp::{Client, LanguageServer, LspService, Server};
use crate::analysis::global_anal;
mod analysis; mod analysis;
mod convert; mod convert;
mod registry; mod registry;
#[derive(Debug)] #[derive(Debug)]
struct RocLs { struct RocLs {
pub inner: Arc<Inner>,
}
#[derive(Debug)]
struct Inner {
client: Client, client: Client,
registry: Registry, registry: Registry,
// change_handle: Mutex<Option<JoinHandle<()>>>, change_handle: parking_lot::Mutex<(
tokio::sync::watch::Sender<i32>,
tokio::sync::watch::Receiver<i32>,
)>,
documents_updating: tokio::sync::Semaphore,
} }
impl std::panic::RefUnwindSafe for RocLs {} impl std::panic::RefUnwindSafe for RocLs {}
const SEMLIMIT: u32 = 20;
impl RocLs { impl RocLs {
pub fn new(client: Client) -> Self { pub fn new(client: Client) -> Self {
Self { Self {
client, inner: Arc::new(Inner {
registry: Registry::default(), client,
// change_handle: Mutex::new(None), registry: Registry::default(),
change_handle: parking_lot::Mutex::new(tokio::sync::watch::channel(0)),
///Used for identifying if the intial stage of a document update is complete
documents_updating: tokio::sync::Semaphore::new(SEMLIMIT as usize),
}),
} }
} }
///Wait for all the semaphores associated with an in-progress document_info update to be released
fn registry(&self) -> &Registry { async fn wait_for_changes(&self) {
&self.registry self.inner.documents_updating.acquire_many(SEMLIMIT).await;
} }
fn registry_write(&mut self) -> &mut Registry {
&mut self.registry
}
pub fn capabilities() -> ServerCapabilities { pub fn capabilities() -> ServerCapabilities {
let text_document_sync = TextDocumentSyncCapability::Options( let text_document_sync = TextDocumentSyncCapability::Options(
// TODO: later on make this incremental // TODO: later on make this incremental
@ -93,35 +105,92 @@ impl RocLs {
/// Records a document content change. /// Records a document content change.
async fn change(&self, fi: Url, text: String, version: i32) { async fn change(&self, fi: Url, text: String, version: i32) {
// match self.change_handle.lock().await.as_ref() {
// Some(a) => a.abort(),
// None => (),
// }
writeln!(std::io::stderr(), "starting change"); writeln!(std::io::stderr(), "starting change");
self.registry() let updating_doc_info = self.inner.documents_updating.acquire().await.unwrap();
.apply_change(DocumentChange::Modified(fi.clone(), text, version as u32)) let mut new_change = {
let change_handle = self.inner.change_handle.lock();
//This will cancel any other onging changes in favour of this one
change_handle
.0
.send(version)
.expect("change_handle disposed, this shouldn't happen");
let mut watched = change_handle.1.clone();
drop(watched.borrow_and_update());
watched
};
//We will wait just a tiny amount of time to catch any requests that come in at the exact same time
tokio::time::sleep(Duration::from_millis(20)).await;
if (new_change.has_changed().unwrap()) {
writeln!(std::io::stderr(), "newer task started almost immediately");
return;
}
writeln!(std::io::stderr(), "finished checking for cancellation");
let (results, partial) = global_anal(fi.clone(), text, version as u32);
self.inner
.registry
.apply_doc_info_changes(fi.clone(), partial)
.await; .await;
// let handle = tokio::task::spawn(self.registry().apply_change(DocumentChange::Modified( drop(updating_doc_info);
// fi.clone(),
// text, writeln!(std::io::stderr(), "finished applying");
// version as u32,
// ))); let inner_ref = self.inner.clone();
let handle: JoinHandle<core::result::Result<&str, JoinError>> =
tokio::task::spawn(async move {
let results = tokio::task::spawn_blocking(results).await?;
inner_ref.registry().apply_change(results).await;
Ok("okay")
});
writeln!(std::io::stderr(), "waiting on analisys or cancel");
//The analysis task can be cancelled by another change coming in which will update the watched variable
let cancelled = tokio::select! {
a=handle=>{
match a{
Err(a)=>
{
writeln!(std::io::stderr(), "error in task{:?}",a);
true},
Ok(_)=>false
}
},
_=new_change.changed()=>true
};
if cancelled {
writeln!(std::io::stderr(), "cancelled change");
return;
}
writeln!(std::io::stderr(), "applied_change getting diagnostics"); writeln!(std::io::stderr(), "applied_change getting diagnostics");
//We do this to briefly yeild //We do this to briefly yeild
let diagnostics = self.registry().diagnostics(&fi).await; let diagnostics = self.inner.registry().diagnostics(&fi).await;
writeln!(std::io::stderr(), "applied_change returning diagnostics"); writeln!(std::io::stderr(), "applied_change returning diagnostics");
self.client self.inner
.client
.publish_diagnostics(fi, diagnostics, Some(version)) .publish_diagnostics(fi, diagnostics, Some(version))
.await; .await;
} }
}
impl Inner {
fn registry(&self) -> &Registry {
&self.registry
}
fn registry_write(&mut self) -> &mut Registry {
&mut self.registry
}
async fn close(&self, fi: Url) { async fn close(&self, fi: Url) {
self.registry() // self.registry()
.apply_change(DocumentChange::Closed(fi)) // .apply_change(DocumentChange::Closed(fi))
.await; // .await;
} }
} }
@ -135,7 +204,8 @@ impl LanguageServer for RocLs {
} }
async fn initialized(&self, _: InitializedParams) { async fn initialized(&self, _: InitializedParams) {
self.client self.inner
.client
.log_message(MessageType::INFO, "Roc language server initialized.") .log_message(MessageType::INFO, "Roc language server initialized.")
.await; .await;
} }
@ -160,7 +230,7 @@ impl LanguageServer for RocLs {
async fn did_close(&self, params: DidCloseTextDocumentParams) { async fn did_close(&self, params: DidCloseTextDocumentParams) {
let TextDocumentIdentifier { uri } = params.text_document; let TextDocumentIdentifier { uri } = params.text_document;
self.close(uri).await; self.inner.close(uri).await;
} }
async fn shutdown(&self) -> Result<()> { async fn shutdown(&self) -> Result<()> {
@ -177,8 +247,13 @@ impl LanguageServer for RocLs {
work_done_progress_params: _, work_done_progress_params: _,
} = params; } = params;
panic_wrapper_async(|| async { self.registry().hover(&text_document.uri, position).await }) panic_wrapper_async(|| async {
.await self.inner
.registry()
.hover(&text_document.uri, position)
.await
})
.await
} }
async fn goto_definition( async fn goto_definition(
@ -196,7 +271,8 @@ impl LanguageServer for RocLs {
} = params; } = params;
panic_wrapper_async(|| async { panic_wrapper_async(|| async {
self.registry() self.inner
.registry()
.goto_definition(&text_document.uri, position) .goto_definition(&text_document.uri, position)
.await .await
}) })
@ -210,7 +286,7 @@ impl LanguageServer for RocLs {
work_done_progress_params: _, work_done_progress_params: _,
} = params; } = params;
panic_wrapper_async(|| async { self.registry().formatting(&text_document.uri) }).await panic_wrapper_async(|| async { self.inner.registry().formatting(&text_document.uri) }).await
} }
async fn semantic_tokens_full( async fn semantic_tokens_full(
@ -223,14 +299,25 @@ impl LanguageServer for RocLs {
partial_result_params: _, partial_result_params: _,
} = params; } = params;
panic_wrapper_async(|| async { self.registry().semantic_tokens(&text_document.uri) }).await panic_wrapper_async(|| async { self.inner.registry().semantic_tokens(&text_document.uri) })
.await
} }
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
let doc = params.text_document_position; let doc = params.text_document_position;
writeln!(std::io::stderr(), "starting completion"); writeln!(std::io::stderr(), "starting completion");
writeln!(
std::io::stderr(),
"permits::{:?} ",
self.inner.documents_updating.available_permits()
);
//We need to wait untill any changes that were in progress when we requested completion have applied
self.wait_for_changes().await;
writeln!(std::io::stderr(), "waited for doc update to get sorted ");
let res = panic_wrapper_async(|| async { let res = panic_wrapper_async(|| async {
self.registry() self.inner
.registry()
.completion_items(&doc.text_document.uri, doc.position) .completion_items(&doc.text_document.uri, doc.position)
.await .await
}) })
@ -239,25 +326,6 @@ impl LanguageServer for RocLs {
writeln!(std::io::stderr(), "finished completion"); writeln!(std::io::stderr(), "finished completion");
res res
} }
// async fn completion(
// &self,
// params: GotoDefinitionParams,
// ) -> Result<Option<GotoDefinitionResponse>> {
// let GotoDefinitionParams {
// text_document_position_params:
// TextDocumentPositionParams {
// text_document,
// position,
// },
// work_done_progress_params: _,
// partial_result_params: _,
// } = params;
// panic_wrapper(|| {
// self.registry()
// .goto_definition(&text_document.uri, position)
// })
// }
} }
fn panic_wrapper<T>(f: impl FnOnce() -> Option<T> + std::panic::UnwindSafe) -> Result<Option<T>> { fn panic_wrapper<T>(f: impl FnOnce() -> Option<T> + std::panic::UnwindSafe) -> Result<Option<T>> {