css_variable_lsp/
text_utils.rs1use crate::types::{offset_to_position, position_to_offset, CssVariable};
2use ls_types::{Position, Range, TextDocumentContentChangeEvent};
3
4pub fn clamp_to_char_boundary(text: &str, mut idx: usize) -> usize {
5 if idx > text.len() {
6 idx = text.len();
7 }
8 while idx > 0 && !text.is_char_boundary(idx) {
9 idx -= 1;
10 }
11 idx
12}
13
14pub fn is_word_char(c: char) -> bool {
15 c.is_ascii_alphanumeric() || c == '-' || c == '_'
16}
17
18pub fn is_word_byte(b: u8) -> bool {
19 is_word_char(b as char)
20}
21
22pub fn range_contains_position(range: &Range, position: Position) -> bool {
23 range.start <= position && position <= range.end
24}
25
26pub fn range_contains(outer: &Range, inner: &Range) -> bool {
28 outer.start <= inner.start && inner.end <= outer.end
29}
30
31pub fn apply_change_to_text(text: &mut String, change: &TextDocumentContentChangeEvent) {
32 if let Some(range) = change.range {
33 let start = position_to_offset(text, range.start);
34 let end = position_to_offset(text, range.end);
35 if let (Some(start), Some(end)) = (start, end) {
36 if start <= end && end <= text.len() {
37 text.replace_range(start..end, &change.text);
38 return;
39 }
40 }
41 }
42 *text = change.text.clone();
43}
44
45pub fn find_value_range_in_definition(text: &str, def: &CssVariable) -> Option<Range> {
46 let start = position_to_offset(text, def.range.start)?;
47 let end = position_to_offset(text, def.range.end)?;
48 if start >= end || end > text.len() {
49 return None;
50 }
51 let def_text = &text[start..end];
52 let colon_index = def_text.find(':')?;
53 let after_colon = &def_text[colon_index + 1..];
54 let value_trim = def.value.trim();
55 let value_index = after_colon.find(value_trim)?;
56
57 let absolute_start = start + colon_index + 1 + value_index;
58 let absolute_end = absolute_start + value_trim.len();
59
60 Some(Range::new(
61 offset_to_position(text, absolute_start),
62 offset_to_position(text, absolute_end),
63 ))
64}