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