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