Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatten nested highlight ranges during DFS traversal #3826

Merged
merged 2 commits into from
Apr 8, 2020

Conversation

ltentrup
Copy link
Contributor

@ltentrup ltentrup commented Apr 2, 2020

Implements the flattening of nested highlights from #3447.

There is a caveat: I needed to add Clone to HighlightedRange to split highlight ranges and the nesting does not appear in the syntax highlighting test (it does appear in the accidental-quadratic test but there it is not checked against a ground-truth).

I have added a test case for the example mentioned in #3447.


let mut current_macro_call: Option<ast::MacroCall> = None;

// Walk all nodes, keeping track of whether we are inside a macro or not.
// If in macro, expand it first and highlight the expanded code.
for event in root.preorder_with_tokens() {
assert!(!res.is_empty());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have this assert! here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant is that the stack never becomes empty. This is asserted before and after the modification. I am fine with removing this assertion, I just usually err on the side of too many assertions 😉

@kjeremy
Copy link
Contributor

kjeremy commented Apr 2, 2020

Makes sense to me. I question the usefulness of assert!s but will defer that to @matklad.

One thing... how will this interact with the range LSP request? In theory I could see that splicing nested tokens.

@ltentrup
Copy link
Contributor Author

ltentrup commented Apr 2, 2020

This PR changes only the representation of the nesting and keeps the original logic untouched. Thus, the handling of range requests should be the same as before.

Comment on lines 106 to 107
assert!(!res.is_empty());
let current = res.last_mut().unwrap();
Copy link
Contributor

@Veetaha Veetaha Apr 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server will panic on either line of code, I'd propose to replace assert!() with res.last_mut().expect("reason of the assert");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I'll update the PR with this change

@ltentrup ltentrup force-pushed the flatten-highlights branch from 1d666b2 to bb45aca Compare April 3, 2020 07:46
@ltentrup
Copy link
Contributor Author

ltentrup commented Apr 3, 2020

I have updated the PR with the suggested changes and a new test case

@@ -119,19 +161,20 @@ pub(crate) fn highlight(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we add highlights below this line, I think we have an invariant that the highlight must not extend beyond the current node (otherwise, flattening might be broken). Let's add an assert for this.

}
}

res
assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also assert that ranges are sorted and disjoint

*
* The following code implements the flattening, for our example this results to:
* `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this, we should be able to remove the relevant logic from highlight_as_html function.

@@ -131,3 +131,19 @@ fn test_ranges() {

assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
}

#[test]
fn test_flattening() {
Copy link
Member

@matklad matklad Apr 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good test here would be something that touches highlight_injection logic:

fn fixture(ra_fixture: &str) {}

fn main() {
  fixture(r#"
trait Foo {
    fn foo() {
        println!("2 + 2 = {}", 4);
    }
}
"#)
}

This functionality is currently untested simply because naive flattening in highlight_as_html can't deal with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of the highlight_injection logic, this is only a special case for rust-analyzer development, right? However, this is a great test for the flattening logic, otherwise, Rust does not seem to contain many nestings other than the attributes from the issue.
The current highlight_as_html logic iterates over both, the parsed tokens and the highlight ranges. With the change of the function type to be natively flat, it would be better to only iterate over the highlight ranges and consume the text file range by range. Is there an easy way to do that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, this is only for rust-anayzer. And yes, you'd need to iteratete the text:

let text = parse.tree().syntax().to_string();
let mut curr = &text[..];
for range in ranges {
    curr = &curr[range.len..]
}

@matklad
Copy link
Member

matklad commented Apr 3, 2020

I wonder if it makes sense to change the return type of the function to be natively flat. Like this:

#[derive(Debug)]
pub struct HighlightedRange {
    pub len: TextUnit,
    pub highlight: Option<Highlight>,
    pub binding_hash: Option<u64>,
}

I would probably go for it

@matklad
Copy link
Member

matklad commented Apr 8, 2020

bors r+

Thanks!

@bors
Copy link
Contributor

bors bot commented Apr 8, 2020

@bors bors bot merged commit 8ea7c9c into rust-lang:master Apr 8, 2020
@kjeremy kjeremy mentioned this pull request Apr 8, 2020
bors bot added a commit that referenced this pull request Apr 24, 2020
3998: Make add_function generate functions in other modules via qualified path r=matklad a=TimoFreiberg

Additional feature for #3639 

- [x] Add tests for paths with more segments
- [x] Make generating the function in another file work
- [x] Add `pub` or `pub(crate)` to the generated function if it's generated in a different module
- [x] Make the assist jump to the edited file
- [x] Enable file support in the `check_assist` helper

4006: Syntax highlighting for format strings r=matklad a=ltentrup

I have an implementation for syntax highlighting for format string modifiers `{}`.
The first commit refactors the changes in #3826 into a separate struct.
The second commit implements the highlighting: first we check in a macro call whether the macro is a format macro from `std`. In this case, we remember the format string node. If we encounter this node during syntax highlighting, we check for the format modifiers `{}` using regular expressions.

There are a few places which I am not quite sure:
- Is the way I extract the macro names correct?
- Is the `HighlightTag::Attribute` suitable for highlighting the `{}`?

Let me know what you think, any feedback is welcome!

Co-authored-by: Timo Freiberg <[email protected]>
Co-authored-by: Leander Tentrup <[email protected]>
Co-authored-by: Leander Tentrup <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants