diff --git a/crates/ty_ide/src/docstring.rs b/crates/ty_ide/src/docstring.rs index b749b1feb3..163dbe1ae2 100644 --- a/crates/ty_ide/src/docstring.rs +++ b/crates/ty_ide/src/docstring.rs @@ -65,11 +65,7 @@ impl Docstring { /// Render the docstring for markdown display pub fn render_markdown(&self) -> String { let trimmed = documentation_trim(&self.0); - // TODO: now actually parse it and "render" it to markdown. - // - // For now we just wrap the content in a plaintext codeblock - // to avoid the contents erroneously being interpreted as markdown. - format!("```text\n{trimmed}\n```") + render_markdown(&trimmed) } /// Extract parameter documentation from popular docstring formats. @@ -153,6 +149,223 @@ fn documentation_trim(docs: &str) -> String { output } +/// Given a presumed reStructuredText docstring, render it to GitHub Flavored Markdown. +/// +/// This function assumes the input has had its whitespace normalized by `documentation_trim`, +/// so leading whitespace is always a space, and newlines are always `\n`. +/// +/// The general approach here is: +/// +/// * Preserve the docstring verbatim by default, ensuring indent/linewraps are preserved +/// * Escape problematic things where necessary (bare `__dunder__` => `\_\_dunder\_\_`) +/// * Introduce code fences where appropriate +/// +/// The first rule is significant in ensuring various docstring idioms render clearly. +/// In particular ensuring things like this are faithfully rendered: +/// +/// ```text +/// param1 -- a good parameter +/// param2 -- another good parameter +/// with longer docs +/// ``` +/// +/// If we didn't go out of our way to preserve the indentation and line-breaks, markdown would +/// constantly render inputs like that into abominations like: +/// +/// ```html +///

+/// param1 -- a good parameter param2 -- another good parameter +///

+/// +/// +/// with longer docs +/// +/// ``` +fn render_markdown(docstring: &str) -> String { + // TODO: there is a convention that `singletick` is for items that can + // be looked up in-scope while ``multitick`` is for opaque inline code. + // While rendering this we should make note of all the `singletick` locations + // and (possibly in a higher up piece of logic) try to resolve the names for + // cross-linking. (Similar to `TypeDetails` in the type formatting code.) + let mut output = String::new(); + let mut first_line = true; + let mut block_indent = 0; + let mut in_doctest = false; + let mut starting_literal = false; + let mut in_literal = false; + let mut in_any_code = false; + for untrimmed_line in docstring.lines() { + // We can assume leading whitespace has been normalized + let mut line = untrimmed_line.trim_start_matches(' '); + let line_indent = untrimmed_line.len() - line.len(); + + // First thing's first, add a newline to start the new line + if !first_line { + // If we're not in a codeblock, add trailing space to the line to authentically wrap it + // (Lines ending with two spaces tell markdown to preserve a linebreak) + if !in_any_code { + output.push_str(" "); + } + // Only push newlines if we're not scanning for a real line + if !starting_literal { + output.push('\n'); + } + } + + // If we're in a literal block and we find a non-empty dedented line, end the block + // TODO: we should remove all the trailing blank lines + // (Just pop all trailing `\n` from `output`?) + if in_literal && line_indent < block_indent && !line.is_empty() { + in_literal = false; + in_any_code = false; + block_indent = 0; + output.push_str("```\n"); + } + + // We previously entered a literal block and we just found our first non-blank line + // So now we're actually in the literal block + if starting_literal && !line.is_empty() { + starting_literal = false; + in_literal = true; + in_any_code = true; + block_indent = line_indent; + // TODO: I hope people don't have literal blocks about markdown code fence syntax + // TODO: should we not be this aggressive? Let it autodetect? + // TODO: respect `.. code-block::` directives: + // + output.push_str("\n```python\n"); + } + + // If we're not in a codeblock and we see something that signals a doctest, start one + if !in_any_code && line.starts_with(">>>") { + block_indent = line_indent; + in_doctest = true; + in_any_code = true; + // TODO: is there something more specific? `pycon`? + output.push_str("```python\n"); + } + + // If we're not in a codeblock and we see something that signals a literal block, start one + if !in_any_code && let Some(without_lit) = line.strip_suffix("::") { + let trimmed_without_lit = without_lit.trim(); + if let Some(character) = trimmed_without_lit.chars().next_back() { + if character.is_whitespace() { + // Remove the marker completely + line = trimmed_without_lit; + } else { + // Only remove the first `:` + line = line.strip_suffix(":").unwrap(); + } + } else { + // Delete whole line + line = trimmed_without_lit; + } + starting_literal = true; + } + + // Add this line's indentation. + // We could subtract the block_indent here but in practice it's uglier + // TODO: should we not do this if the `line.is_empty()`? When would it matter? + for _ in 0..line_indent { + // If we're not in a codeblock use non-breaking spaces to preserve the indent + if !in_any_code { + // TODO: would the raw unicode codepoint be handled *better* or *worse* + // by various IDEs? VS Code handles this approach well, at least. + output.push_str(" "); + } else { + output.push(' '); + } + } + + if !in_any_code { + // This line is plain text, so we need to escape things that are inert in reST + // but active syntax in markdown... but not if it's inside `inline code`. + // Inline-code syntax is shared by reST and markdown which is really convenient + // except we need to find and parse it anyway to do this escaping properly! :( + // For now we assume `inline code` does not span a line (I'm not even sure if can). + // + // Things that need to be escaped: underscores + // + // e.g. we want __init__ => \_\_init\_\_ but `__init__` => `__init__` + let escape = |input: &str| input.replace('_', "\\_"); + + let mut in_inline_code = false; + let mut first_chunk = true; + let mut opening_tick_count = 0; + let mut current_tick_count = 0; + for chunk in line.split('`') { + // First chunk is definitionally not in inline-code and so always plaintext + if first_chunk { + first_chunk = false; + output.push_str(&escape(chunk)); + continue; + } + // Not in first chunk, emit the ` between the last chunk and this one + output.push('`'); + current_tick_count += 1; + + // If we're in an inline block and have enough close-ticks to terminate it, do so. + // TODO: we parse ``hello```there` as (hello)(there) which probably isn't correct + // (definitely not for markdown) but it's close enough for horse grenades in this + // MVP impl. Notably we're verbatime emitting all the `'s so as long as reST and + // markdown agree we're *fine*. The accuracy of this parsing only affects the + // accuracy of where we apply escaping (so we need to misparse and see escapables + // for any of this to matter). + if opening_tick_count > 0 && current_tick_count >= opening_tick_count { + opening_tick_count = 0; + current_tick_count = 0; + in_inline_code = false; + } + + // If this chunk is completely empty we're just in a run of ticks, continue + if chunk.is_empty() { + continue; + } + + // Ok the chunk is non-empty, our run of ticks is complete + if in_inline_code { + // The previous check for >= open_tick_count didn't trip, so these can't close + // and these ticks will be verbatim rendered in the content + current_tick_count = 0; + } else if current_tick_count > 0 { + // Ok we're now in inline code + opening_tick_count = current_tick_count; + current_tick_count = 0; + in_inline_code = true; + } + + // Finally include the content either escaped or not + if in_inline_code { + output.push_str(chunk); + } else { + output.push_str(&escape(chunk)); + } + } + // NOTE: explicitly not "flushing" the ticks here. + // We respect however the user closed their inline code. + } else if line.is_empty() { + if in_doctest { + // This is the end of a doctest + block_indent = 0; + in_any_code = false; + in_literal = false; + output.push_str("```"); + } + } else { + // Print the line verbatim, it's in code + output.push_str(line); + } + + first_line = false; + } + // Flush codeblock + if in_any_code { + output.push_str("\n```"); + } + + output +} + /// Extract parameter documentation from Google-style docstrings. fn extract_google_style_params(docstring: &str) -> HashMap { let mut param_docs = HashMap::new(); @@ -490,6 +703,511 @@ mod tests { use super::*; + // A nice doctest that is surrounded by prose + #[test] + fn dunder_escape() { + let docstring = r#" + Here _this_ and ___that__ should be escaped + Here *this* and **that** should be untouched + Here `this` and ``that`` should be untouched + + Here `_this_` and ``__that__`` should be untouched + Here `_this_` ``__that__`` should be untouched + `_this_too_should_be_untouched_` + + Here `_this_```__that__`` should be untouched but this_is_escaped + Here ``_this_```__that__` should be untouched but this_is_escaped + + Here `_this_ and _that_ should be escaped (but isn't) + Here _this_ and _that_` should be escaped + `Here _this_ and _that_ should be escaped (but isn't) + Here _this_ and _that_ should be escaped` + + Here ```_is_``__a__`_balanced_``_mess_``` + Here ```_is_`````__a__``_random_````_mess__```` + ```_is_`````__a__``_random_````_mess__```` + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + Here _this_ and ___that__ should be escaped + Here *this* and **that** should be untouched + Here `this` and ``that`` should be untouched + + Here `_this_` and ``__that__`` should be untouched + Here `_this_` ``__that__`` should be untouched + `_this_too_should_be_untouched_` + + Here `_this_```__that__`` should be untouched but this_is_escaped + Here ``_this_```__that__` should be untouched but this_is_escaped + + Here `_this_ and _that_ should be escaped (but isn't) + Here _this_ and _that_` should be escaped + `Here _this_ and _that_ should be escaped (but isn't) + Here _this_ and _that_ should be escaped` + + Here ```_is_``__a__`_balanced_``_mess_``` + Here ```_is_`````__a__``_random_````_mess__```` + ```_is_`````__a__``_random_````_mess__```` + "); + + assert_snapshot!(docstring.render_markdown(), @r" + Here \_this\_ and \_\_\_that\_\_ should be escaped + Here *this* and **that** should be untouched + Here `this` and ``that`` should be untouched + + Here `_this_` and ``__that__`` should be untouched + Here `_this_` ``__that__`` should be untouched + `_this_too_should_be_untouched_` + + Here `_this_```__that__`` should be untouched but this\_is\_escaped + Here ``_this_```__that__` should be untouched but this\_is\_escaped + + Here `_this_ and _that_ should be escaped (but isn't) + Here \_this\_ and \_that\_` should be escaped + `Here _this_ and _that_ should be escaped (but isn't) + Here \_this\_ and \_that\_ should be escaped` + + Here ```_is_``__a__`_balanced_``_mess_``` + Here ```_is_`````__a__``\_random\_````_mess__```` + ```_is_`````__a__``\_random\_````_mess__```` + "); + } + + // A literal block where the `::` is flush with the paragraph + // and should become `:` + #[test] + fn literal_colon() { + let docstring = r#" + Check out this great example code:: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r#" + Check out this great example code:: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#); + + assert_snapshot!(docstring.render_markdown(), @r#" + Check out this great example code: + ```python + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + ``` + You love to see it. + "#); + } + + // A literal block where the `::` with the paragraph + // and should be erased + #[test] + fn literal_space() { + let docstring = r#" + Check out this great example code :: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r#" + Check out this great example code :: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#); + + assert_snapshot!(docstring.render_markdown(), @r#" + Check out this great example code : + ```python + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + ``` + You love to see it. + "#); + } + + // A literal block where the `::` is floating + // and the whole line should be deleted + #[test] + fn literal_own_line() { + let docstring = r#" + Check out this great example code + :: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r#" + Check out this great example code + :: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + You love to see it. + "#); + + assert_snapshot!(docstring.render_markdown(), @r#" + Check out this great example code +      + ```python + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + + ``` + You love to see it. + "#); + } + + // A literal block where the blank lines are missing + // and I have no idea what Should happen but let's record what Does + #[test] + fn literal_squeezed() { + let docstring = r#" + Check out this great example code:: + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + You love to see it. + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r#" + Check out this great example code:: + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + You love to see it. + "#); + + assert_snapshot!(docstring.render_markdown(), @r#" + Check out this great example code: + ```python + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + ``` + You love to see it. + "#); + } + + // A literal block where the docstring just ends + // and we should tidy up + #[test] + fn literal_flush() { + let docstring = r#" + Check out this great example code:: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done")"#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r#" + Check out this great example code:: + + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + "#); + + assert_snapshot!(docstring.render_markdown(), @r#" + Check out this great example code: + ```python + x_y = "hello" + + if len(x_y) > 4: + print(x_y) + else: + print("too short :(") + + print("done") + ``` + "#); + } + + // A nice doctest that is surrounded by prose + #[test] + fn doctest_simple() { + let docstring = r#" + This is a function description + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + This is a function description + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "); + + assert_snapshot!(docstring.render_markdown(), @r" + This is a function description + + ```python + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + ``` + As you can see it did the thing! + "); + } + + // A nice doctest that is surrounded by prose with an indent + #[test] + fn doctest_simple_indent() { + let docstring = r#" + This is a function description + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + This is a function description + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "); + + assert_snapshot!(docstring.render_markdown(), @r" + This is a function description + + ```python + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + ``` + As you can see it did the thing! + "); + } + + // A doctest that has nothing around it + #[test] + fn doctest_flush() { + let docstring = r#">>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing"#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + "); + + assert_snapshot!(docstring.render_markdown(), @r" + ```python + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + ``` + "); + } + + // A doctest embedded in a literal block (it's just a literal block) + #[test] + fn literal_doctest() { + let docstring = r#" + This is a function description:: + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + This is a function description:: + + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + As you can see it did the thing! + "); + + assert_snapshot!(docstring.render_markdown(), @r" + This is a function description: + ```python + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + + ``` + As you can see it did the thing! + "); + } + + #[test] + fn doctest_indent_flush() { + let docstring = r#" + And so you can see that + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing"#; + + let docstring = Docstring::new(docstring.to_owned()); + + assert_snapshot!(docstring.render_plaintext(), @r" + And so you can see that + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + "); + + assert_snapshot!(docstring.render_markdown(), @r" + And so you can see that + ```python + >>> thing.do_thing() + wow it did the thing + >>> thing.do_other_thing() + it sure did the thing + ``` + "); + } + #[test] fn test_google_style_parameter_documentation() { let docstring = r#" @@ -530,19 +1248,16 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): The first parameter description - param2 (int): The second parameter description - This is a continuation of param2 description. - param3: A parameter without type annotation - - Returns: - str: The return value description - - ``` + This is a function description. + + Args: +     param1 (str): The first parameter description +     param2 (int): The second parameter description +         This is a continuation of param2 description. +     param3: A parameter without type annotation + + Returns: +     str: The return value description "); } @@ -604,24 +1319,77 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. + This is a function description. + + Parameters + ---------- + param1 : str +     The first parameter description + param2 : int +     The second parameter description +     This is a continuation of param2 description. + param3 +     A parameter without type annotation + + Returns + ------- + str +     The return value description + "); + } - Parameters - ---------- - param1 : str - The first parameter description - param2 : int - The second parameter description - This is a continuation of param2 description. - param3 - A parameter without type annotation + #[test] + fn test_pep257_style_parameter_documentation() { + let docstring = r#"Insert an entry into the list of warnings filters (at the front). - Returns - ------- - str - The return value description + 'param1' -- The first parameter description + 'param2' -- The second parameter description + This is a continuation of param2 description. + 'param3' -- A parameter without type annotation + >>> print repr(foo.__doc__) + '\n This is the second line of the docstring.\n ' + >>> foo.__doc__.splitlines() + ['', ' This is the second line of the docstring.', ' '] + >>> trim(foo.__doc__) + 'This is the second line of the docstring.' + "#; + + let docstring = Docstring::new(docstring.to_owned()); + let param_docs = docstring.parameter_documentation(); + assert!(param_docs.is_empty()); + + assert_snapshot!(docstring.render_plaintext(), @r" + Insert an entry into the list of warnings filters (at the front). + + 'param1' -- The first parameter description + 'param2' -- The second parameter description + This is a continuation of param2 description. + 'param3' -- A parameter without type annotation + + >>> print repr(foo.__doc__) + '\n This is the second line of the docstring.\n ' + >>> foo.__doc__.splitlines() + ['', ' This is the second line of the docstring.', ' '] + >>> trim(foo.__doc__) + 'This is the second line of the docstring.' + "); + + assert_snapshot!(docstring.render_markdown(), @r" + Insert an entry into the list of warnings filters (at the front). + + 'param1' -- The first parameter description + 'param2' -- The second parameter description +             This is a continuation of param2 description. + 'param3' -- A parameter without type annotation + + ```python + >>> print repr(foo.__doc__) + '\n This is the second line of the docstring.\n ' + >>> foo.__doc__.splitlines() + ['', ' This is the second line of the docstring.', ' '] + >>> trim(foo.__doc__) + 'This is the second line of the docstring.' ``` "); } @@ -638,12 +1406,7 @@ mod tests { assert_snapshot!(docstring.render_plaintext(), @"This is a simple function description without parameter documentation."); - assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a simple function description without parameter documentation. - - ``` - "); + assert_snapshot!(docstring.render_markdown(), @"This is a simple function description without parameter documentation."); } #[test] @@ -692,19 +1455,16 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): Google-style parameter - param2 (int): Another Google-style parameter - - Parameters - ---------- - param3 : bool - NumPy-style parameter - - ``` + This is a function description. + + Args: +     param1 (str): Google-style parameter +     param2 (int): Another Google-style parameter + + Parameters + ---------- + param3 : bool +     NumPy-style parameter "); } @@ -750,17 +1510,14 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - :param str param1: The first parameter description - :param int param2: The second parameter description - This is a continuation of param2 description. - :param param3: A parameter without type annotation - :returns: The return value description + This is a function description. + + :param str param1: The first parameter description + :param int param2: The second parameter description +     This is a continuation of param2 description. + :param param3: A parameter without type annotation + :returns: The return value description :rtype: str - - ``` "); } @@ -818,21 +1575,18 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): Google-style parameter - - :param int param2: reST-style parameter - :param param3: Another reST-style parameter - - Parameters - ---------- - param4 : bool - NumPy-style parameter - - ``` + This is a function description. + + Args: +     param1 (str): Google-style parameter + + :param int param2: reST-style parameter + :param param3: Another reST-style parameter + + Parameters + ---------- + param4 : bool +     NumPy-style parameter "); } @@ -894,25 +1648,22 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - Parameters - ---------- - param1 : str - The first parameter description - param2 : int - The second parameter description - This is a continuation of param2 description. - param3 - A parameter without type annotation - - Returns - ------- - str - The return value description - - ``` + This is a function description. + + Parameters + ---------- + param1 : str +     The first parameter description + param2 : int +     The second parameter description +     This is a continuation of param2 description. + param3 +     A parameter without type annotation + + Returns + ------- + str +     The return value description "); } @@ -965,20 +1716,17 @@ mod tests { "); assert_snapshot!(docstring.render_markdown(), @r" - ```text - This is a function description. - - Parameters - ---------- - param1 : str - The first parameter description - param2 : int - The second parameter description - This is a continuation of param2 description. - param3 - A parameter without type annotation - - ``` + This is a function description. + + Parameters + ---------- + param1 : str +         The first parameter description + param2 : int +         The second parameter description +         This is a continuation of param2 description. + param3 +         A parameter without type annotation "); } @@ -1028,14 +1776,11 @@ mod tests { "); assert_snapshot!(docstring_windows.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): The first parameter - param2 (int): The second parameter - - ``` + This is a function description. + + Args: +     param1 (str): The first parameter +     param2 (int): The second parameter "); assert_snapshot!(docstring_mac.render_plaintext(), @r" @@ -1047,14 +1792,11 @@ mod tests { "); assert_snapshot!(docstring_mac.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): The first parameter - param2 (int): The second parameter - - ``` + This is a function description. + + Args: +     param1 (str): The first parameter +     param2 (int): The second parameter "); assert_snapshot!(docstring_unix.render_plaintext(), @r" @@ -1066,14 +1808,11 @@ mod tests { "); assert_snapshot!(docstring_unix.render_markdown(), @r" - ```text - This is a function description. - - Args: - param1 (str): The first parameter - param2 (int): The second parameter - - ``` + This is a function description. + + Args: +     param1 (str): The first parameter +     param2 (int): The second parameter "); } } diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index f1de7316bd..1b8716f18e 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -245,14 +245,11 @@ mod tests { ) -> Unknown ``` --- - ```text - This is such a great func!! - - Args: - a: first for a reason - b: coming for `a`'s title - - ``` + This is such a great func!! + + Args: +     a: first for a reason +     b: coming for `a`'s title --------------------------------------------- info[hover]: Hovered content is --> main.py:11:1 @@ -303,14 +300,11 @@ mod tests { ) -> Unknown ``` --- - ```text - This is such a great func!! - - Args: - a: first for a reason - b: coming for `a`'s title - - ``` + This is such a great func!! + + Args: +     a: first for a reason +     b: coming for `a`'s title --------------------------------------------- info[hover]: Hovered content is --> main.py:2:5 @@ -369,14 +363,11 @@ mod tests { ``` --- - ```text - This is such a great class!! - - Don't you know? - + This is such a great class!! + +     Don't you know? + Everyone loves my class!! - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:24:1 @@ -434,14 +425,11 @@ mod tests { ``` --- - ```text - This is such a great class!! - - Don't you know? - + This is such a great class!! + +     Don't you know? + Everyone loves my class!! - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:2:7 @@ -497,10 +485,7 @@ mod tests { ``` --- - ```text initializes MyClass (perfectly) - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:24:5 @@ -556,10 +541,7 @@ mod tests { ``` --- - ```text initializes MyClass (perfectly) - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:11 @@ -618,14 +600,11 @@ mod tests { ``` --- - ```text - This is such a great class!! - - Don't you know? - + This is such a great class!! + +     Don't you know? + Everyone loves my class!! - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:23:5 @@ -692,14 +671,11 @@ mod tests { ) -> Unknown ``` --- - ```text - This is such a great func!! - - Args: - a: first for a reason - b: coming for `a`'s title - - ``` + This is such a great func!! + + Args: +     a: first for a reason +     b: coming for `a`'s title --------------------------------------------- info[hover]: Hovered content is --> main.py:25:3 @@ -973,10 +949,7 @@ def ab(a: str): ... (a: int) -> Unknown ``` --- - ```text the int overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1036,10 +1009,7 @@ def ab(a: str): (a: str) -> Unknown ``` --- - ```text the int overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1105,10 +1075,7 @@ def ab(a: int): ) -> Unknown ``` --- - ```text the two arg overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1168,10 +1135,7 @@ def ab(a: int): (a: int) -> Unknown ``` --- - ```text the two arg overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1243,10 +1207,7 @@ def ab(a: int, *, c: int): ) -> Unknown ``` --- - ```text keywordless overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1318,10 +1279,7 @@ def ab(a: int, *, c: int): ) -> Unknown ``` --- - ```text keywordless overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1386,10 +1344,7 @@ def ab(a: int, *, c: int): ) -> Unknown ``` --- - ```text The first overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:19:1 @@ -1441,10 +1396,7 @@ def ab(a: int, *, c: int): (a: str) -> Unknown ``` --- - ```text The first overload - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:19:1 @@ -1494,12 +1446,9 @@ def ab(a: int, *, c: int): ``` --- - ```text - The cool lib_py module! - + The cool lib/_py module! + Wow this module rocks. - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:4:1 @@ -1544,12 +1493,9 @@ def ab(a: int, *, c: int): Wow this module rocks. --------------------------------------------- - ```text - The cool lib_py module! - + The cool lib/_py module! + Wow this module rocks. - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:2:8 @@ -2499,10 +2445,7 @@ def ab(a: int, *, c: int): bound method int.__add__(value: int, /) -> int ``` --- - ```text Return self+value. - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:2:12 @@ -2618,10 +2561,7 @@ def ab(a: int, *, c: int): int | float ``` --- - ```text Convert a string or number to a floating-point number, if possible. - - ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:2:4