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