From 985da485e2f5583321a8df8e074a4fc77f9941c2 Mon Sep 17 00:00:00 2001 From: Bobbahbrown Date: Sun, 6 Sep 2020 01:57:51 -0300 Subject: [PATCH] Add documentation to hovers, add hovers for var uses and proc calls (#210) - Added hover handling for ScopedVar, UnscopedVar, ScopedProc, UnscopedProc - Improved handling of existing hovers, adding dm lang blocks to the hovers where appropriate - Added documentation to all existing hovers when it is available - Fixed bug with annotating Variables, which would lead to excessive whitespace being consumed --- src/dreammaker/parser.rs | 11 ++- src/langserver/main.rs | 182 ++++++++++++++++++++++++++++----------- 2 files changed, 141 insertions(+), 52 deletions(-) diff --git a/src/dreammaker/parser.rs b/src/dreammaker/parser.rs index 14dd04008..925916143 100644 --- a/src/dreammaker/parser.rs +++ b/src/dreammaker/parser.rs @@ -786,17 +786,22 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> { Punct(Assign) => { // `something=` - var let location = self.location; + // kind of goofy, but allows "enclosing" doc comments at the end of the line + // translators note: this allows comments of the form ``//! blah`` at the end of the line let (docs, expression) = require!(self.doc_comment(|this| { let expr = require!(this.expression()); let _ = require!(this.input_specifier()); + + // We have to annotate prior to consuming the statement terminator, as we + // will otherwise consume following whitespace resulting in a bad annotation range + let node = this.tree[current].path.to_owned(); + this.annotate(entry_start, || Annotation::Variable(reconstruct_path(&node, proc_kind, var_type.as_ref(), last_part))); + require!(this.statement_terminator()); success(expr) })); - let node = self.tree[current].path.to_owned(); - self.annotate(entry_start, || Annotation::Variable(reconstruct_path(&node, proc_kind, var_type.as_ref(), last_part))); - if let Some(mut var_type) = var_type { var_type.suffix(&var_suffix); self.tree.declare_var(current, last_part, location, docs, var_type, Some(expression)); diff --git a/src/langserver/main.rs b/src/langserver/main.rs index 0a94f24d8..b2517bf35 100644 --- a/src/langserver/main.rs +++ b/src/langserver/main.rs @@ -840,6 +840,91 @@ impl<'a> Engine<'a> { Ok(symbol_id) } + fn construct_proc_hover(&self, proc_name: &str, mut provided_tok: Option, scoped: bool) -> Result, jsonrpc::Error> { + let mut results = Vec::new(); + let mut proclink = String::new(); + let mut defstring = String::new(); + let mut docstring: Option = None; + while let Some(ty) = provided_tok { + if let Some(proc) = ty.procs.get(proc_name) { + let proc_value = proc.main_value(); + + // Because we need to find our declaration to get the declaration type, we partially + // form the markdown text to be used once the proc's declaration is reached + if defstring.is_empty() { + proclink = format!("[{}]({})", ty.pretty_path(), self.location_link(proc_value.location)?); + let mut message = format!("{}(", proc_name); + let mut first = true; + for each in proc_value.parameters.iter() { + use std::fmt::Write; + if first { + first = false; + } else { + message.push_str(", "); + } + let _ = write!(message, "{}", each); + } + message.push_str(")"); + defstring = message.clone(); + } + + if let Some(ref decl) = proc.declaration { + results.push(format!("{}\n```dm\n{}/{}\n```", proclink, decl.kind.name(), defstring)); + } + + if !proc_value.docs.is_empty() { + docstring = Some(proc_value.docs.text()); + } + } + + if scoped { + provided_tok = ty.parent_type_without_root(); + } else { + provided_tok = ty.parent_type(); + } + } + + if let Some(ds) = docstring { + results.push(ds); + } + + Ok(results) + } + + fn construct_var_hover(&self, var_name: &str, mut provided_tok: Option, scoped: bool) -> Result, jsonrpc::Error> { + let mut results = Vec::new(); + let mut infos = String::new(); + let mut docstring: Option = None; + while let Some(ty) = provided_tok { + if let Some(var) = ty.vars.get(var_name) { + if let Some(ref decl) = var.declaration { + // First get the path of the type containing the declaration + infos.push_str(format!("[{}]({})\n", ty.pretty_path(), self.location_link(var.value.location)?).as_str()); + + // Next toss on the declaration itself + infos.push_str(format!("```dm\nvar/{}{}\n```", decl.var_type, var_name).as_str()); + } + if !var.value.docs.is_empty() { + docstring = Some(var.value.docs.text()); + } + } + + if scoped { + provided_tok = ty.parent_type_without_root(); + } else { + provided_tok = ty.parent_type(); + } + } + if !infos.is_empty() { + results.push(infos); + } + if let Some(ds) = docstring { + results.push(ds); + } + + Ok(results) + } + // ------------------------------------------------------------------------ // Driver @@ -1096,9 +1181,11 @@ handle_method_call! { line: tdp.position.line as u32 + 1, column: tdp.position.character as u16 + 1, }; + let symbol_id = self.symbol_id_at(tdp)?; let mut results = Vec::new(); - for (_range, annotation) in annotations.get_location(location) { + let iter = annotations.get_location(location); + for (_range, annotation) in iter.clone() { #[cfg(debug_assertions)] { results.push(format!("{:?}", annotation)); } @@ -1118,48 +1205,20 @@ handle_method_call! { let mut infos = VecDeque::new(); let mut next = Some(current); + let mut docstring: Option = None; while let Some(current) = next { if let Some(var) = current.vars.get(last) { let constant = if let Some(ref constant) = var.value.constant { - format!(" \n= `{}`", constant) + format!("\n```dm\n= {}\n```", constant) } else { String::new() }; - let path = if current.path.is_empty() { - "(global)" - } else { - ¤t.path - }; - infos.push_front(format!("[{}]({}){}", path, self.location_link(var.value.location)?, constant)); + infos.push_front(format!("[{}]({}){}", current.pretty_path(), self.location_link(var.value.location)?, constant)); if let Some(ref decl) = var.declaration { - let mut declaration = String::new(); - declaration.push_str("var"); - if decl.var_type.flags.is_static() { - declaration.push_str("/static"); - } - if decl.var_type.flags.is_const() { - declaration.push_str("/const"); - } - if decl.var_type.flags.is_tmp() { - declaration.push_str("/tmp"); - } - if decl.var_type.flags.is_final() { - declaration.push_str("/SpacemanDMM_final"); - } - if decl.var_type.flags.is_private() { - declaration.push_str("/SpacemanDMM_private"); - } - if decl.var_type.flags.is_protected() { - declaration.push_str("/SpacemanDMM_protected"); - } - for bit in decl.var_type.type_path.iter() { - declaration.push('/'); - declaration.push_str(&bit); - } - declaration.push_str("/**"); - declaration.push_str(last); - declaration.push_str("**"); - infos.push_front(declaration); + infos.push_front(format!("```dm\nvar/{}{}\n```", decl.var_type, last)); + } + if !var.value.docs.is_empty() { + docstring = Some(var.value.docs.text()); } } next = current.parent_type(); @@ -1167,6 +1226,9 @@ handle_method_call! { if !infos.is_empty() { results.push(infos.into_iter().collect::>().join("\n\n")); } + if let Some(ds) = docstring { + results.push(ds); + } } Annotation::ProcHeader(path, _idx) if !path.is_empty() => { let objtree = &self.objtree; @@ -1185,15 +1247,11 @@ handle_method_call! { // the last proc for each type let mut infos = VecDeque::new(); let mut next = Some(current); + let mut docstring: Option = None; while let Some(current) = next { if let Some(proc) = current.procs.get(last) { - let path = if current.path.is_empty() { - "(global)" - } else { - ¤t.path - }; let proc_value = proc.main_value(); - let mut message = format!("[{}]({}) \n{}(", path, self.location_link(proc_value.location)?, last); + let mut message = format!("[{}]({}) \n```dm\n{}(", current.pretty_path(), self.location_link(proc_value.location)?, last); let mut first = true; for each in proc_value.parameters.iter() { use std::fmt::Write; @@ -1204,15 +1262,14 @@ handle_method_call! { } let _ = write!(message, "{}", each); } - message.push_str(")"); + message.push_str(")\n```"); infos.push_front(message); if let Some(ref decl) = proc.declaration { - let mut declaration = String::new(); - declaration.push_str(decl.kind.name()); - declaration.push_str("/**"); - declaration.push_str(last); - declaration.push_str("**"); - infos.push_front(declaration); + infos.push_front(format!("```dm\n{}/{}\n```", decl.kind.name(), last)); + } + + if !proc_value.docs.is_empty() { + docstring = Some(proc_value.docs.text()); } } next = current.parent_type(); @@ -1220,6 +1277,33 @@ handle_method_call! { if !infos.is_empty() { results.push(infos.into_iter().collect::>().join("\n\n")); } + if let Some(ds) = docstring { + results.push(ds); + } + } + Annotation::UnscopedVar(var_name) if symbol_id.is_some() => { + let (ty, proc_name) = self.find_type_context(&iter); + match self.find_unscoped_var(&iter, ty, proc_name, var_name) { + UnscopedVar::Variable { ty, .. } => { + if let Some(_decl) = ty.get_var_declaration(var_name) { + results.append(&mut self.construct_var_hover(var_name, Some(ty), false)?); + } + }, + _ => {} + } + } + Annotation::UnscopedCall(proc_name) if symbol_id.is_some() => { + let (ty, _) = self.find_type_context(&iter); + let next = ty.or(Some(self.objtree.root())); + results.append(&mut self.construct_proc_hover(proc_name, next, false)?); + } + Annotation::ScopedCall(priors, proc_name) if symbol_id.is_some() => { + let next = self.find_scoped_type(&iter, priors); + results.append(&mut self.construct_proc_hover(proc_name, next, true)?); + } + Annotation::ScopedVar(priors, var_name) if symbol_id.is_some() => { + let next = self.find_scoped_type(&iter, priors); + results.append(&mut self.construct_var_hover(var_name, next, true)?); } _ => {} }