Various hover improvements (#5617)
* Show more info on hover for variables Signed-off-by: Nick Cameron <nrc@ncameron.org> * Move hover impls to lsp module Signed-off-by: Nick Cameron <nrc@ncameron.org> * Make hover work on names inside calls, fix doc line breaking, trim docs in tool tips Signed-off-by: Nick Cameron <nrc@ncameron.org> * Test the new hovers; fix signature syntax Signed-off-by: Nick Cameron <nrc@ncameron.org> * Hover tips for kwargs Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -551,8 +551,6 @@ struct DocInfo {
|
||||
|
||||
fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> DocInfo {
|
||||
let doc = syn::Ident::new("doc", proc_macro2::Span::call_site());
|
||||
let mut code_blocks: Vec<String> = Vec::new();
|
||||
|
||||
let raw_lines = attrs.iter().flat_map(|attr| {
|
||||
if let syn::Meta::NameValue(nv) = &attr.meta {
|
||||
if nv.path.is_ident(&doc) {
|
||||
@ -568,6 +566,7 @@ fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> DocInfo {
|
||||
});
|
||||
|
||||
// Parse any code blocks from the doc string.
|
||||
let mut code_blocks: Vec<String> = Vec::new();
|
||||
let mut code_block: Option<String> = None;
|
||||
let mut parsed_lines = Vec::new();
|
||||
for line in raw_lines {
|
||||
@ -589,71 +588,68 @@ fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> DocInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse code blocks that start with a tab or a space.
|
||||
let mut lines = Vec::new();
|
||||
for line in parsed_lines {
|
||||
if line.starts_with(" ") || line.starts_with('\t') {
|
||||
if let Some(ref mut code_block) = code_block {
|
||||
code_block.push_str(&line.trim_start_matches(" ").trim_start_matches('\t'));
|
||||
code_block.push('\n');
|
||||
} else {
|
||||
code_block = Some(format!("{}\n", line));
|
||||
}
|
||||
} else {
|
||||
if let Some(ref inner_code_block) = code_block {
|
||||
code_blocks.push(inner_code_block.trim().to_string());
|
||||
code_block = None;
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = lines.into_iter();
|
||||
|
||||
if let Some(code_block) = code_block {
|
||||
code_blocks.push(code_block.trim().to_string());
|
||||
}
|
||||
|
||||
// Skip initial blank lines; they make for excessively terse summaries.
|
||||
let summary = loop {
|
||||
match lines.next() {
|
||||
Some(s) if s.is_empty() => (),
|
||||
next => break next,
|
||||
let mut summary = None;
|
||||
let mut description: Option<String> = None;
|
||||
for line in parsed_lines {
|
||||
if line.is_empty() {
|
||||
if let Some(desc) = &mut description {
|
||||
// Handle fully blank comments as newlines we keep.
|
||||
if !desc.is_empty() && !desc.ends_with('\n') {
|
||||
if desc.ends_with(' ') {
|
||||
desc.pop().unwrap();
|
||||
}
|
||||
desc.push_str("\n\n");
|
||||
}
|
||||
} else if summary.is_some() {
|
||||
description = Some(String::new());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// Skip initial blank description lines.
|
||||
let first = loop {
|
||||
match lines.next() {
|
||||
Some(s) if s.is_empty() => (),
|
||||
next => break next,
|
||||
}
|
||||
};
|
||||
|
||||
let (summary, description) = match (summary, first) {
|
||||
(None, _) => (None, None),
|
||||
(summary, None) => (summary, None),
|
||||
(Some(summary), Some(first)) => (
|
||||
Some(summary),
|
||||
Some(
|
||||
lines
|
||||
.fold(first, |acc, comment| {
|
||||
if acc.ends_with('-') || acc.ends_with('\n') || acc.is_empty() {
|
||||
// Continuation lines and newlines.
|
||||
format!("{}{}", acc, comment)
|
||||
} else if comment.is_empty() {
|
||||
// Handle fully blank comments as newlines we keep.
|
||||
format!("{}\n", acc)
|
||||
} else {
|
||||
// Default to space-separating comment fragments.
|
||||
format!("{} {}", acc, comment)
|
||||
}
|
||||
})
|
||||
.trim_end()
|
||||
.replace('\n', "\n\n")
|
||||
.to_string(),
|
||||
),
|
||||
),
|
||||
};
|
||||
if let Some(desc) = &mut description {
|
||||
desc.push_str(&line);
|
||||
// Default to space-separating comment fragments.
|
||||
desc.push(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
if summary.is_none() {
|
||||
summary = Some(String::new());
|
||||
}
|
||||
match &mut summary {
|
||||
Some(summary) => {
|
||||
summary.push_str(&line);
|
||||
// Default to space-separating comment fragments.
|
||||
summary.push(' ');
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the summary and description.
|
||||
if let Some(s) = &mut summary {
|
||||
while s.ends_with(' ') || s.ends_with('\n') {
|
||||
s.pop().unwrap();
|
||||
}
|
||||
|
||||
if s.is_empty() {
|
||||
summary = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(d) = &mut description {
|
||||
while d.ends_with(' ') || d.ends_with('\n') {
|
||||
d.pop().unwrap();
|
||||
}
|
||||
|
||||
if d.is_empty() {
|
||||
description = None;
|
||||
}
|
||||
}
|
||||
|
||||
DocInfo {
|
||||
summary,
|
||||
@ -664,37 +660,11 @@ fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> DocInfo {
|
||||
|
||||
fn normalize_comment_string(s: String) -> Vec<String> {
|
||||
s.split('\n')
|
||||
.enumerate()
|
||||
.map(|(idx, s)| {
|
||||
// Rust-style comments are intrinsically single-line. We don't want
|
||||
// to trim away formatting such as an initial '*'.
|
||||
// We also don't want to trim away a tab character, which is
|
||||
// used to denote a code block. Code blocks can also be denoted
|
||||
// by four spaces, but we don't want to trim those away either.
|
||||
.map(|s| {
|
||||
// Rust-style comments are intrinsically single-line.
|
||||
// We only want to trim a single space character from the start of
|
||||
// a line, and only if it's the first character.
|
||||
let new = s
|
||||
.chars()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, c)| {
|
||||
if idx == 0 {
|
||||
if c == ' ' {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(c)
|
||||
})
|
||||
.collect::<String>()
|
||||
.trim_end()
|
||||
.to_string();
|
||||
let s = new.as_str();
|
||||
|
||||
if idx == 0 {
|
||||
s
|
||||
} else {
|
||||
s.strip_prefix("* ").unwrap_or_else(|| s.strip_prefix('*').unwrap_or(s))
|
||||
}
|
||||
.to_string()
|
||||
s.strip_prefix(' ').unwrap_or(s).trim_end().to_owned()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -124,10 +124,6 @@ fn test_stdlib_line_to() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// lineTo
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
@ -144,7 +140,7 @@ fn test_stdlib_line_to() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/lineTo.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -158,10 +154,6 @@ fn test_stdlib_min() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// min
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
@ -185,7 +177,7 @@ fn test_stdlib_min() {
|
||||
.unwrap();
|
||||
let _expected = quote! {};
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/min.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -199,9 +191,11 @@ fn test_stdlib_show() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
fn inner_show(
|
||||
/// The args to do shit to.
|
||||
_args: Vec<f64>
|
||||
@ -212,7 +206,7 @@ fn test_stdlib_show() {
|
||||
.unwrap();
|
||||
let _expected = quote! {};
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/show.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -226,9 +220,11 @@ fn test_stdlib_box() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
fn inner_show(
|
||||
/// The args to do shit to.
|
||||
args: Box<f64>
|
||||
@ -240,7 +236,7 @@ fn test_stdlib_box() {
|
||||
.unwrap();
|
||||
let _expected = quote! {};
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/box.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -254,9 +250,11 @@ fn test_stdlib_option() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
/// ```
|
||||
fn inner_show(
|
||||
/// The args to do shit to.
|
||||
args: Option<f64>
|
||||
@ -267,7 +265,7 @@ fn test_stdlib_option() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/option.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -281,10 +279,6 @@ fn test_stdlib_array() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// show
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
@ -300,7 +294,7 @@ fn test_stdlib_array() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/array.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -314,9 +308,11 @@ fn test_stdlib_option_input_format() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
fn inner_import(
|
||||
/// The args to do shit to.
|
||||
args: Option<kittycad::types::InputFormat>
|
||||
@ -327,7 +323,7 @@ fn test_stdlib_option_input_format() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/option_input_format.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -341,9 +337,11 @@ fn test_stdlib_return_vec_sketch() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
fn inner_import(
|
||||
/// The args to do shit to.
|
||||
args: Option<kittycad::types::InputFormat>
|
||||
@ -354,7 +352,7 @@ fn test_stdlib_return_vec_sketch() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/return_vec_sketch.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -368,9 +366,11 @@ fn test_stdlib_return_vec_box_sketch() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// import
|
||||
/// ```
|
||||
fn inner_import(
|
||||
/// The args to do shit to.
|
||||
args: Option<kittycad::types::InputFormat>
|
||||
@ -381,7 +381,7 @@ fn test_stdlib_return_vec_box_sketch() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/return_vec_box_sketch.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -395,10 +395,6 @@ fn test_stdlib_doc_comment_with_code() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
/// myFunc
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
@ -414,7 +410,7 @@ fn test_stdlib_doc_comment_with_code() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
expectorate::assert_contents("tests/doc_comment_with_code.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
@ -428,9 +424,6 @@ fn test_stdlib_fail_non_camel_case() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
@ -486,9 +479,6 @@ fn test_stdlib_fail_name_not_in_code_block() {
|
||||
/// This is some function.
|
||||
/// It does shit.
|
||||
///
|
||||
/// This is code.
|
||||
/// It does other shit.
|
||||
///
|
||||
/// ```
|
||||
/// This is another code block.
|
||||
/// yes sirrr.
|
||||
|
Reference in New Issue
Block a user