bytebraise_syntax/syntax/ast/
quoted_value.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3use std::ops::Range;
4
5use cow_utils::CowUtils;
6use derive_builder::Builder;
7use itertools::Itertools;
8use nested_intervals::IntervalSet;
9use rowan::{TextRange, TextSize};
10
11use crate::syntax::ast::AstToken;
12use crate::syntax::ast::tokens::{DoubleQuotedValue, SingleQuotedValue};
13use crate::syntax::make;
14use crate::syntax::syntax_kind::SyntaxKind;
15use crate::syntax::syntax_node::SyntaxToken;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum QuotedValue {
19    SingleQuoted(SingleQuotedValue),
20    DoubleQuoted(DoubleQuotedValue),
21}
22
23impl AstToken for QuotedValue {
24    fn can_cast(token: SyntaxKind) -> bool
25    where
26        Self: Sized,
27    {
28        match token {
29            SyntaxKind::DoubleQuotedValue | SyntaxKind::SingleQuotedValue => true,
30            _ => false,
31        }
32    }
33
34    fn cast(syntax: SyntaxToken) -> Option<Self>
35    where
36        Self: Sized,
37    {
38        let ret = match syntax.kind() {
39            SyntaxKind::DoubleQuotedValue => {
40                QuotedValue::DoubleQuoted(DoubleQuotedValue { syntax })
41            }
42            SyntaxKind::SingleQuotedValue => {
43                QuotedValue::SingleQuoted(SingleQuotedValue { syntax })
44            }
45            _ => return None,
46        };
47
48        Some(ret)
49    }
50
51    fn syntax(&self) -> &SyntaxToken {
52        match self {
53            QuotedValue::DoubleQuoted(it) => &it.syntax,
54            QuotedValue::SingleQuoted(it) => &it.syntax,
55        }
56    }
57}
58
59impl QuotedValue {
60    pub fn text_range_between_quotes(&self) -> TextRange {
61        let range = self.syntax().text_range();
62        let quote_size = TextSize::of("\"");
63        let (start, end) = (range.start() + quote_size, range.end() - quote_size);
64        TextRange::new(start, end)
65    }
66
67    pub fn raw_value(&self) -> &str {
68        &self.text()[self.text_range_between_quotes() - self.syntax().text_range().start()]
69    }
70
71    pub fn lines(&self) -> impl Iterator<Item = &str> {
72        self.line_ranges().into_iter().map(move |range| {
73            let start_offset = self.syntax().text_range().start();
74            let absolute_range = range - start_offset;
75            &self.text()[absolute_range]
76        })
77    }
78
79    pub fn sorted_lines(&self) -> Self {
80        let vals = self
81            .lines()
82            .filter_map(|v| match v.trim() {
83                "" => None,
84                v => Some(v.to_owned()),
85            })
86            .sorted()
87            .collect::<Vec<_>>();
88        make::quoted_value_from_slice(&vals)
89    }
90
91    /// Value text (between the quotes) with escaped newlines removed
92    pub fn value(&self) -> Cow<str> {
93        // Handle escaped newlines
94        self.raw_value().cow_replace("\\\n", "")
95    }
96
97    ///
98    pub fn line_ranges(&self) -> Vec<TextRange> {
99        // Absolute range of the text between the quotes
100        let range = self.text_range_between_quotes();
101
102        // Absolute position of the opening quote value
103        let start_offset = self.syntax().text_range().start();
104
105        // Relative range of the text between the quotes, i.e. 1..
106        let relative_range =
107            TextRange::new(range.start() - start_offset, range.end() - start_offset);
108        assert_eq!(u32::from(relative_range.start()), 1);
109        assert_eq!(
110            u32::from(relative_range.end()),
111            self.text().len() as u32 - 1
112        );
113
114        let escaped_newline_size = TextSize::of("\\\n");
115
116        // Produce relative (to the text between quotes) ranges of each escaped newline
117        let intervals = self
118            .raw_value()
119            .match_indices("\\\n")
120            .map(|m| {
121                // Add 1 because `match_indices` produces indices starting at 0, but the string
122                // we are searching is at an offset of 1.
123                let start = m.0 as u32 + 1;
124                start..start + u32::from(escaped_newline_size)
125            })
126            .collect::<Vec<Range<u32>>>();
127
128        let interval = IntervalSet::new(intervals.as_slice()).unwrap();
129
130        // Invert the set to produce a set of relative ranges of the unescaped content, then
131        // transform them to be absolute ranges
132        interval
133            .invert(
134                u32::from(relative_range.start()),
135                u32::from(relative_range.end()),
136            )
137            .iter()
138            .map(|(range, _)| {
139                TextRange::new(
140                    TextSize::from(range.start) + start_offset,
141                    TextSize::from(range.end) + start_offset,
142                )
143            })
144            .collect::<Vec<_>>()
145    }
146}
147
148#[derive(Debug, Builder, Clone)]
149#[builder(setter(into))]
150pub struct QuotedValueFormat {
151    packing: SubValuePacking,
152    closing_quote_on_own_line: bool,
153    alignment: SubValueAlignment,
154}
155
156impl Default for QuotedValueFormat {
157    fn default() -> Self {
158        Self {
159            alignment: SubValueAlignment::default(),
160            closing_quote_on_own_line: true,
161            packing: SubValuePacking::default(),
162        }
163    }
164}
165
166/// Controls how to layout multiple space-separated values.
167#[derive(Debug, Default, Clone)]
168pub enum SubValuePacking {
169    Off,
170    #[default]
171    OnePerLine,
172    Wrapped {
173        line_width: usize,
174    },
175}
176
177#[derive(Debug, Clone)]
178pub enum SubValueAlignment {
179    WithOperator,
180    WithFirstSubValue,
181    Indented { indent: usize },
182}
183
184impl Default for SubValueAlignment {
185    fn default() -> Self {
186        SubValueAlignment::Indented { indent: 4 }
187    }
188}
189
190pub struct QuotedValueBuilder {
191    sub_values: Vec<String>,
192}
193
194fn ff() {
195    let _b = QuotedValueFormatBuilder::default()
196        .alignment(SubValueAlignment::WithFirstSubValue)
197        .build();
198}