makefile_lossless/
text.rs1pub fn variable_at_offset(text: &str, offset: usize) -> Option<&str> {
18 let bytes = text.as_bytes();
19 let mut start = None;
20 let mut i = offset;
21 while i >= 2 {
22 i -= 1;
23 if i > 0 && (bytes[i] == b'(' || bytes[i] == b'{') && bytes[i - 1] == b'$' {
24 start = Some(i + 1);
25 break;
26 }
27 if bytes[i] == b')' || bytes[i] == b'}' || bytes[i] == b'\n' {
28 return None;
29 }
30 }
31 let start = start?;
32 let rest = &text[start..];
33 let end = rest.find([')', '}'])?;
34 let var_name = &rest[..end];
35 if offset >= start && offset <= start + end {
36 Some(var_name)
37 } else {
38 None
39 }
40}
41
42pub fn word_at_offset(text: &str, offset: usize) -> Option<&str> {
55 if offset > text.len() {
56 return None;
57 }
58 let bytes = text.as_bytes();
59 let is_ident = |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b'.' || b == b'-';
60 if offset < text.len() && !is_ident(bytes[offset]) {
61 return None;
62 }
63 let start = (0..offset)
64 .rev()
65 .take_while(|&i| is_ident(bytes[i]))
66 .last()
67 .unwrap_or(offset);
68 let end = (offset..text.len())
69 .take_while(|&i| is_ident(bytes[i]))
70 .last()
71 .map(|i| i + 1)
72 .unwrap_or(offset);
73 if start == end {
74 return None;
75 }
76 Some(&text[start..end])
77}
78
79pub fn is_in_prerequisites(text: &str, offset: usize) -> bool {
91 let line_start = text[..offset].rfind('\n').map(|i| i + 1).unwrap_or(0);
92 let line = &text[line_start..];
93 if line.starts_with('\t') {
95 return false;
96 }
97 let col = offset - line_start;
98 line[..col].contains(':')
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_variable_at_offset_parens() {
108 assert_eq!(variable_at_offset("$(FOO)", 2), Some("FOO"));
109 assert_eq!(variable_at_offset("$(FOO)", 4), Some("FOO"));
110 }
111
112 #[test]
113 fn test_variable_at_offset_braces() {
114 assert_eq!(variable_at_offset("${BAR}", 2), Some("BAR"));
115 }
116
117 #[test]
118 fn test_variable_at_offset_none() {
119 assert_eq!(variable_at_offset("plain text", 3), None);
120 }
121
122 #[test]
123 fn test_variable_at_offset_nested_context() {
124 let text = "\t$(CC) main.c";
125 assert_eq!(variable_at_offset(text, 3), Some("CC"));
126 }
127
128 #[test]
129 fn test_word_at_offset_basic() {
130 assert_eq!(word_at_offset("hello world", 0), Some("hello"));
131 assert_eq!(word_at_offset("hello world", 3), Some("hello"));
132 assert_eq!(word_at_offset("hello world", 5), None);
133 assert_eq!(word_at_offset("hello world", 6), Some("world"));
134 }
135
136 #[test]
137 fn test_word_at_offset_special_chars() {
138 assert_eq!(word_at_offset("foo-bar.o", 0), Some("foo-bar.o"));
139 assert_eq!(word_at_offset("FOO_BAR", 3), Some("FOO_BAR"));
140 }
141
142 #[test]
143 fn test_is_in_prerequisites() {
144 let text = "all: build test\n\techo ok\n";
145 assert!(!is_in_prerequisites(text, 0)); assert!(is_in_prerequisites(text, 5)); assert!(!is_in_prerequisites(text, 17)); }
149
150 #[test]
151 fn test_is_in_prerequisites_no_colon() {
152 let text = "VAR = value\n";
153 assert!(!is_in_prerequisites(text, 6));
154 }
155}