shuck_server/edit/
text_document.rs1use lsp_types::TextDocumentContentChangeEvent;
2use shuck_indexer::LineIndex;
3
4use crate::PositionEncoding;
5
6use super::RangeExt;
7
8pub(crate) type DocumentVersion = i32;
9
10#[derive(Debug, Clone)]
11pub struct TextDocument {
12 contents: String,
13 index: LineIndex,
14 version: DocumentVersion,
15 language_id: Option<LanguageId>,
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub enum LanguageId {
20 Bash,
21 ShellScript,
22 Sh,
23 Zsh,
24 Ksh,
25 Other,
26}
27
28impl From<&str> for LanguageId {
29 fn from(language_id: &str) -> Self {
30 match language_id {
31 "bash" => Self::Bash,
32 "shellscript" => Self::ShellScript,
33 "sh" => Self::Sh,
34 "zsh" => Self::Zsh,
35 "ksh" => Self::Ksh,
36 _ => Self::Other,
37 }
38 }
39}
40
41impl TextDocument {
42 pub fn new(contents: String, version: DocumentVersion) -> Self {
43 let index = LineIndex::new(&contents);
44 Self {
45 contents,
46 index,
47 version,
48 language_id: None,
49 }
50 }
51
52 #[must_use]
53 pub fn with_language_id(mut self, language_id: &str) -> Self {
54 self.language_id = Some(LanguageId::from(language_id));
55 self
56 }
57
58 pub fn contents(&self) -> &str {
59 &self.contents
60 }
61
62 pub fn index(&self) -> &LineIndex {
63 &self.index
64 }
65
66 pub fn version(&self) -> DocumentVersion {
67 self.version
68 }
69
70 pub fn language_id(&self) -> Option<LanguageId> {
71 self.language_id
72 }
73
74 pub fn apply_changes(
75 &mut self,
76 changes: Vec<TextDocumentContentChangeEvent>,
77 new_version: DocumentVersion,
78 encoding: PositionEncoding,
79 ) {
80 if let [
81 TextDocumentContentChangeEvent {
82 range: None, text, ..
83 },
84 ] = changes.as_slice()
85 {
86 self.contents.clone_from(text);
87 self.index = LineIndex::new(&self.contents);
88 self.version = new_version;
89 return;
90 }
91
92 let mut new_contents = self.contents.clone();
93 let mut active_index = self.index.clone();
94
95 for change in changes {
96 if let Some(range) = change.range {
97 let range = range.to_text_range(&new_contents, &active_index, encoding);
98 new_contents.replace_range(
99 usize::from(range.start())..usize::from(range.end()),
100 &change.text,
101 );
102 } else {
103 new_contents = change.text;
104 }
105 active_index = LineIndex::new(&new_contents);
106 }
107
108 self.contents = new_contents;
109 self.index = active_index;
110 self.version = new_version;
111 }
112
113 pub fn update_version(&mut self, new_version: DocumentVersion) {
114 debug_assert!(new_version >= self.version);
115 self.version = new_version;
116 }
117}