aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/syntax_highlighting/format.rs
blob: 3ab01295ab2c8a1da44e9757fd624220a4595a1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Syntax highlighting for format macro strings.
use syntax::{
    ast::{self, FormatSpecifier, HasFormatSpecifier},
    AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
};

use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange};

#[derive(Default)]
pub(super) struct FormatStringHighlighter {
    format_string: Option<SyntaxElement>,
}

impl FormatStringHighlighter {
    pub(super) fn reset(&mut self) {
        self.format_string = None;
    }

    pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) {
        // Check if macro takes a format string and remember it for highlighting later.
        // The macros that accept a format string expand to a compiler builtin macros
        // `format_args` and `format_args_nl`.
        if let Some(name) = parent
            .parent()
            .and_then(ast::MacroCall::cast)
            .and_then(|mc| mc.path())
            .and_then(|p| p.segment())
            .and_then(|s| s.name_ref())
        {
            match name.text().as_str() {
                "format_args" | "format_args_nl" => {
                    self.format_string = parent
                        .children_with_tokens()
                        .filter(|t| t.kind() != SyntaxKind::WHITESPACE)
                        .nth(1)
                        .filter(|e| {
                            ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind())
                        })
                }
                _ => {}
            }
        }
    }
    pub(super) fn highlight_format_string(
        &self,
        range_stack: &mut HighlightedRangeStack,
        string: &impl HasFormatSpecifier,
        range: TextRange,
    ) {
        if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) {
            range_stack.push();
            string.lex_format_specifier(|piece_range, kind| {
                if let Some(highlight) = highlight_format_specifier(kind) {
                    range_stack.add(HighlightedRange {
                        range: piece_range + range.start(),
                        highlight: highlight.into(),
                        binding_hash: None,
                    });
                }
            });
            range_stack.pop();
        }
    }
}

fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
    Some(match kind {
        FormatSpecifier::Open
        | FormatSpecifier::Close
        | FormatSpecifier::Colon
        | FormatSpecifier::Fill
        | FormatSpecifier::Align
        | FormatSpecifier::Sign
        | FormatSpecifier::NumberSign
        | FormatSpecifier::DollarSign
        | FormatSpecifier::Dot
        | FormatSpecifier::Asterisk
        | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier,
        FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
        FormatSpecifier::Identifier => HighlightTag::Local,
    })
}