use super::*;
use crate::syntax::SyntaxToken;
pub fn compute_selection_ranges(
text: &str,
positions: &[Position],
encoding: PositionEncoding,
) -> Vec<SelectionRange> {
let root = parse(text).cst;
let line_index = LineIndex::new(text);
positions
.iter()
.map(|&position| {
let offset = TextSize::new(
line_index
.position_to_byte(position, encoding)
.min(text.len()) as u32,
);
selection_range_at(&root, &line_index, offset, encoding)
})
.collect()
}
fn selection_range_at(
root: &SyntaxNode,
line_index: &LineIndex,
offset: TextSize,
encoding: PositionEncoding,
) -> SelectionRange {
let mut ranges: Vec<TextRange> = Vec::new();
if let Some(token) = token_at(root, offset) {
if !matches!(token.kind(), SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE) {
ranges.push(token.text_range());
}
for ancestor in token.parent_ancestors() {
let range = ancestor.text_range();
if ranges.last() != Some(&range) {
ranges.push(range);
}
}
}
if ranges.is_empty() {
ranges.push(root.text_range());
}
let mut selection: Option<SelectionRange> = None;
for range in ranges.iter().rev() {
selection = Some(SelectionRange {
range: text_range_to_lsp_range(line_index, *range, encoding),
parent: selection.map(Box::new),
});
}
selection.expect("ranges is non-empty")
}
fn token_at(root: &SyntaxNode, offset: TextSize) -> Option<SyntaxToken> {
let is_trivia = |kind| matches!(kind, SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE);
match root.token_at_offset(offset) {
TokenAtOffset::None => None,
TokenAtOffset::Single(token) => Some(token),
TokenAtOffset::Between(left, right) => {
if !is_trivia(right.kind()) {
Some(right)
} else if !is_trivia(left.kind()) {
Some(left)
} else {
Some(right)
}
}
}
}