Skip to main content

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}