Skip to main content

shuck_server/edit/
text_document.rs

1use 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}