increparse_lsp/document.rs
1//! One open editor file: text, session, version, and position encoding.
2
3use increparse::{CancelToken, Edit, Engine, Executor, RunReport, Session, Span};
4use lsp_types::{Location, Position, Range, TextDocumentContentChangeEvent, Uri};
5
6use crate::encoding::PositionEncoding;
7use crate::line_index::LineIndex;
8
9/// A single open document, bridging LSP change events and an increparse
10/// [`Session`].
11///
12/// `apply_changes` is the whole workflow: hand it the `didChange` payload and
13/// an engine; it splices the text, translates every change into a byte-range
14/// [`Edit`] (so the parse tree reuses everything the edit did not touch),
15/// and runs the engine synchronously. How you thread that call — inline in
16/// your server loop, or on a background thread with a [`CancelToken`] — is
17/// your framework's business.
18///
19/// # Examples
20///
21/// ```
22/// use increparse::{CancelToken, Engine, Outcome, Pass, Schedule, SerialExecutor, Span};
23/// use increparse_lsp::{Document, PositionEncoding};
24/// use lsp_types::{TextDocumentContentChangeEvent, Uri};
25///
26/// #[derive(Clone, Debug, PartialEq, Eq)]
27/// enum Ctx { File }
28///
29/// struct Accept;
30/// impl Pass for Accept {
31/// type Ctx = Ctx;
32/// fn parse(&self, _source: &str, _span: Span, _ctx: &Ctx) -> Outcome<Ctx> {
33/// Outcome::Done
34/// }
35/// }
36///
37/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38/// let mut schedule = Schedule::new();
39/// schedule.push(Accept);
40/// let engine = Engine::new(schedule);
41///
42/// let uri: Uri = "file:///w.txt".parse()?;
43/// let mut doc = Document::open(uri, 1, "".into(), "abc".into(), PositionEncoding::Utf16, Ctx::File);
44///
45/// // A client insert at (0, 1): "abc" -> "aXbc".
46/// let change = TextDocumentContentChangeEvent {
47/// range: Some(lsp_types::Range {
48/// start: lsp_types::Position { line: 0, character: 1 },
49/// end: lsp_types::Position { line: 0, character: 1 },
50/// }),
51/// range_length: None,
52/// text: "X".into(),
53/// };
54/// let report = doc.apply_changes(
55/// &engine, 2, &[change], &SerialExecutor, &CancelToken::new(),
56/// );
57///
58/// assert!(report.reached_fixpoint);
59/// assert_eq!(doc.version(), 2);
60/// assert_eq!(doc.text(), "aXbc");
61/// assert_eq!(doc.revision(), 1);
62/// # Ok(())
63/// # }
64/// ```
65pub struct Document<C> {
66 uri: Uri,
67 language_id: String,
68 text: String,
69 session: Session<C>,
70 version: i32,
71 encoding: PositionEncoding,
72 index: LineIndex,
73}
74
75impl<C: Clone + PartialEq + Send + 'static> Document<C> {
76 /// Opens a document at `version` with the full initial `text`.
77 ///
78 /// The tree root covers the whole text and carries `root_ctx`.
79 /// `language_id` is the client's language id for the document (the
80 /// `languageId` field of `didOpen`); it is informational — servers
81 /// that dispatch per language read it back with
82 /// [`language_id`](Self::language_id). Pass `""` when there is none.
83 pub fn open(
84 uri: Uri,
85 version: i32,
86 language_id: String,
87 text: String,
88 encoding: PositionEncoding,
89 root_ctx: C,
90 ) -> Self {
91 let index = LineIndex::new(&text);
92 let session = Session::new(0, Span::new(0, text.len(), 0), root_ctx);
93 Self {
94 uri,
95 language_id,
96 text,
97 session,
98 version,
99 encoding,
100 index,
101 }
102 }
103
104 /// The document's URI.
105 pub fn uri(&self) -> &Uri {
106 &self.uri
107 }
108
109 /// The client's language id for this document (`""` when opened
110 /// without one).
111 pub fn language_id(&self) -> &str {
112 &self.language_id
113 }
114
115 /// The current text.
116 pub fn text(&self) -> &str {
117 &self.text
118 }
119
120 /// The client's document version.
121 pub fn version(&self) -> i32 {
122 self.version
123 }
124
125 /// The negotiated position encoding.
126 pub fn encoding(&self) -> PositionEncoding {
127 self.encoding
128 }
129
130 /// The line index of the current text.
131 pub fn line_index(&self) -> &LineIndex {
132 &self.index
133 }
134
135 /// The parse session (tree, revisions, edits).
136 pub fn session(&self) -> &Session<C> {
137 &self.session
138 }
139
140 /// Mutable access to the session, for custom workflows.
141 pub fn session_mut(&mut self) -> &mut Session<C> {
142 &mut self.session
143 }
144
145 /// Current source revision of the parse tree (bumped once per applied
146 /// change event).
147 pub fn revision(&self) -> u64 {
148 self.session.revision()
149 }
150
151 /// Converts a tree span to an LSP location in this document — the
152 /// convenient shape for go-to-definition answers.
153 pub fn location(&self, span: Span) -> Location {
154 Location {
155 uri: self.uri.clone(),
156 range: self.range(span),
157 }
158 }
159
160 /// Converts a tree span to an LSP range in the document's encoding.
161 pub fn range(&self, span: Span) -> Range {
162 Range {
163 start: self.index.position(&self.text, span.start, self.encoding),
164 end: self.index.position(&self.text, span.end, self.encoding),
165 }
166 }
167
168 /// Converts an LSP position to a byte offset (clamped like
169 /// [`LineIndex::offset`]).
170 pub fn offset(&self, position: Position) -> usize {
171 self.index
172 .offset(&self.text, position, self.encoding)
173 .unwrap_or(self.text.len())
174 }
175
176 /// Applies a `didChange` batch and runs the engine once.
177 ///
178 /// Events are applied in order, each interpreted against the text left
179 /// by the previous one, per the LSP spec. A full-text event (`range ==
180 /// None`) replaces the whole document; ranged events splice their text
181 /// into the given range. Positions past the end of a line or document
182 /// are clamped, matching common client behavior while typing.
183 ///
184 /// `version` becomes the document's new version and is returned by
185 /// [`version`](Self::version); the parse tree sees one revision bump per
186 /// applied event and a single run at the end.
187 pub fn apply_changes<E>(
188 &mut self,
189 engine: &Engine<C>,
190 version: i32,
191 changes: &[TextDocumentContentChangeEvent],
192 exec: &E,
193 cancel: &CancelToken,
194 ) -> RunReport
195 where
196 E: Executor,
197 {
198 for change in changes {
199 match change.range {
200 None => {
201 let edit = Edit::replace(0, self.text.len(), change.text.len());
202 self.text = change.text.clone();
203 self.session.edit(edit);
204 }
205 Some(range) => {
206 let start = self.offset(range.start);
207 let end = self.offset(range.end).max(start);
208 let edit = Edit::replace(start, end, start + change.text.len());
209 self.text.replace_range(start..end, &change.text);
210 self.session.edit(edit);
211 }
212 }
213 self.index = LineIndex::new(&self.text);
214 }
215 self.version = version;
216 self.session.run(engine, &self.text, exec, cancel)
217 }
218}