use super::*;
use crate::syntax::SyntaxToken;
pub fn compute_selection_ranges(text: &str, positions: &[Position]) -> 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).min(text.len()) as u32);
selection_range_at(&root, &line_index, offset)
})
.collect()
}
fn selection_range_at(
root: &SyntaxNode,
line_index: &LineIndex,
offset: TextSize,
) -> 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),
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)
}
}
}
}