1use std::path::PathBuf;
8
9use lsp_types::{TextDocumentContentChangeEvent, Url};
10
11use crate::position::{LineMap, PositionEncoding, position_to_byte};
12
13#[derive(Debug, Clone)]
15pub struct DocState {
16 pub uri: Url,
17 pub path: PathBuf,
18 pub text: String,
19 pub version: i32,
20 pub line_map: LineMap,
21 pub open: bool,
23}
24
25impl DocState {
26 pub fn new(uri: Url, text: String, version: i32, open: bool) -> Self {
27 let path = uri
28 .to_file_path()
29 .unwrap_or_else(|_| PathBuf::from(uri.path()));
30 let line_map = LineMap::new(&text);
31 Self {
32 uri,
33 path,
34 text,
35 version,
36 line_map,
37 open,
38 }
39 }
40
41 pub fn apply_change(
43 &mut self,
44 change: TextDocumentContentChangeEvent,
45 encoding: PositionEncoding,
46 ) -> bool {
47 match change.range {
48 Some(range) => {
49 let start =
50 position_to_byte(&self.line_map, &self.text, range.start, encoding) as usize;
51 let end =
52 position_to_byte(&self.line_map, &self.text, range.end, encoding) as usize;
53 if start > end || end > self.text.len() {
54 return false;
55 }
56 self.text.replace_range(start..end, &change.text);
57 }
58 None => {
59 self.text = change.text;
61 }
62 }
63 self.line_map = LineMap::new(&self.text);
64 true
65 }
66
67 pub fn set_text(&mut self, text: String, version: i32) {
68 self.text = text;
69 self.version = version;
70 self.line_map = LineMap::new(&self.text);
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use lsp_types::{Position, Range};
78
79 fn doc(text: &str) -> DocState {
80 DocState::new(
81 Url::parse("file:///tmp/test.gruel").unwrap(),
82 text.to_string(),
83 1,
84 true,
85 )
86 }
87
88 #[test]
89 fn incremental_replace() {
90 let mut d = doc("fn main() -> i32 { 0 }");
91 let change = TextDocumentContentChangeEvent {
92 range: Some(Range {
93 start: Position {
94 line: 0,
95 character: 19,
96 },
97 end: Position {
98 line: 0,
99 character: 20,
100 },
101 }),
102 range_length: None,
103 text: "42".to_string(),
104 };
105 assert!(d.apply_change(change, PositionEncoding::Utf8));
106 assert_eq!(d.text, "fn main() -> i32 { 42 }");
107 }
108
109 #[test]
110 fn incremental_insert_then_lines_recompute() {
111 let mut d = doc("a\nb\nc");
112 let change = TextDocumentContentChangeEvent {
113 range: Some(Range {
114 start: Position {
115 line: 1,
116 character: 1,
117 },
118 end: Position {
119 line: 1,
120 character: 1,
121 },
122 }),
123 range_length: None,
124 text: "\nINSERTED".to_string(),
125 };
126 assert!(d.apply_change(change, PositionEncoding::Utf8));
127 assert_eq!(d.text, "a\nb\nINSERTED\nc");
128 assert_eq!(d.line_map.line_count(), 4);
130 }
131
132 #[test]
133 fn full_replace() {
134 let mut d = doc("foo");
135 let change = TextDocumentContentChangeEvent {
136 range: None,
137 range_length: None,
138 text: "totally new".to_string(),
139 };
140 assert!(d.apply_change(change, PositionEncoding::Utf8));
141 assert_eq!(d.text, "totally new");
142 }
143}