perl_lsp_text_utils/
lib.rs1#![warn(missing_docs)]
2pub struct TextEditHelpers<'a> {
6 source: &'a str,
7 lines: &'a [String],
8}
9
10impl<'a> TextEditHelpers<'a> {
11 #[must_use]
13 pub fn new(source: &'a str, lines: &'a [String]) -> Self {
14 Self { source, lines }
15 }
16
17 #[must_use]
19 pub fn lines(&self) -> &'a [String] {
20 self.lines
21 }
22
23 #[must_use]
36 pub fn find_statement_start(&self, pos: usize) -> usize {
37 let after_semi = self
38 .source
39 .char_indices()
40 .take_while(|(idx, _)| *idx < pos)
41 .filter(|(_, ch)| *ch == ';')
42 .map(|(idx, _)| idx + 1)
43 .last()
44 .unwrap_or(0);
45 if self.source.as_bytes().get(after_semi) == Some(&b'\n') {
48 after_semi + 1
49 } else {
50 after_semi
51 }
52 }
53
54 #[must_use]
56 pub fn find_subroutine_insert_position(&self, current_pos: usize) -> usize {
57 let search_end = current_pos.min(self.source.len());
58 self.source[..search_end].rfind("sub ").unwrap_or(self.source.len())
59 }
60
61 #[must_use]
63 pub fn find_pragma_insert_position(&self) -> usize {
64 if self.source.starts_with("#!")
65 && let Some(pos) = self.source.find('\n')
66 {
67 return pos + 1;
68 }
69 0
70 }
71
72 #[must_use]
74 pub fn find_import_insert_position(&self) -> usize {
75 let mut pos = self.find_pragma_insert_position();
76
77 for line in self.lines {
78 if line.starts_with("use ") || line.starts_with("require ") {
79 pos = self.source.find(line).unwrap_or(0) + line.len() + 1;
80 } else if !line.is_empty() && !line.starts_with('#') {
81 break;
82 }
83 }
84
85 pos
86 }
87
88 #[must_use]
90 pub fn get_indent_at(&self, pos: usize) -> String {
91 let safe_pos = pos.min(self.source.len());
92 let line_start = self.source[..safe_pos].rfind('\n').map_or(0, |p| p + 1);
93
94 self.source[line_start..].chars().take_while(|ch| *ch == ' ' || *ch == '\t').collect()
95 }
96
97 #[must_use]
99 pub fn truncate_expr(&self, expr: &str, max_len: usize) -> String {
100 if expr.chars().count() <= max_len {
101 return expr.to_string();
102 }
103
104 if max_len <= 3 {
105 return "...".to_string();
106 }
107
108 format!("{}...", expr.chars().take(max_len - 3).collect::<String>())
109 }
110
111 #[must_use]
113 pub fn has_non_ascii_content(&self) -> bool {
114 !self.source.is_ascii()
115 }
116}