1717import java .util .regex .Matcher ;
1818import java .util .regex .Pattern ;
1919
20+ import org .eclipse .lemminx .commons .text .CompositeCharSequence ;
21+ import org .eclipse .lemminx .commons .text .ImmutableCharSequence ;
22+ import org .eclipse .lemminx .commons .text .ImmutableCharSequenceImpl ;
2023import org .eclipse .lsp4j .Position ;
2124import org .eclipse .lsp4j .Range ;
2225import org .eclipse .lsp4j .TextDocumentContentChangeEvent ;
2326import org .eclipse .lsp4j .TextDocumentItem ;
27+ import org .eclipse .lsp4j .jsonrpc .util .Preconditions ;
28+ import org .eclipse .lsp4j .jsonrpc .validation .NonNull ;
2429
2530/**
2631 * Text document extends LSP4j {@link TextDocumentItem} to provide methods to
2732 * retrieve position.
2833 *
2934 */
30- public class TextDocument extends TextDocumentItem {
35+ public class TextDocument {
3136
3237 private static final Logger LOGGER = Logger .getLogger (TextDocument .class .getName ());
3338
39+ /**
40+ * The text document's uri.
41+ */
42+ @ NonNull
43+ private String uri ;
44+
45+ /**
46+ * The text document's language identifier
47+ */
48+ @ NonNull
49+ private String languageId ;
50+
51+ /**
52+ * The version number of this document (it will strictly increase after each
53+ * change, including undo/redo).
54+ */
55+ private int version ;
56+
57+ /**
58+ * The content of the opened text document.
59+ */
60+ // CharSequence-based text storage for memory-efficient incremental updates
61+ @ NonNull
62+ private ImmutableCharSequence text ;
63+
3464 private final Object lock = new Object ();
3565
3666 private static String DEFAULT_DELIMTER = System .lineSeparator ();
@@ -41,13 +71,13 @@ public class TextDocument extends TextDocumentItem {
4171
4272 public TextDocument (TextDocumentItem document ) {
4373 this (document .getText (), document .getUri ());
44- super .setVersion (document .getVersion ());
45- super .setLanguageId (document .getLanguageId ());
74+ this .setVersion (document .getVersion ());
75+ this .setLanguageId (document .getLanguageId ());
4676 }
4777
4878 public TextDocument (String text , String uri ) {
49- super .setUri (uri );
50- super . setText (text );
79+ this .setUri (uri );
80+ this . text = ImmutableCharSequenceImpl . fromString (text );
5181 }
5282
5383 public void setIncremental (boolean incremental ) {
@@ -74,8 +104,8 @@ public int offsetAt(Position position) throws BadLocationException {
74104 public String lineText (int lineNumber ) throws BadLocationException {
75105 ILineTracker lineTracker = getLineTracker ();
76106 Line line = lineTracker .getLineInformation (lineNumber );
77- String text = super . getText ();
78- return text .substring (line .offset , line .offset + line .length );
107+ CharSequence text = getText ();
108+ return text .subSequence (line .offset , line .offset + line .length ). toString ( );
79109 }
80110
81111 public int lineOffsetAt (int position ) throws BadLocationException {
@@ -115,8 +145,8 @@ public Range getWordRangeAt(int textOffset, Pattern wordDefinition) {
115145 Position pos = positionAt (textOffset );
116146 ILineTracker lineTracker = getLineTracker ();
117147 Line line = lineTracker .getLineInformation (pos .getLine ());
118- String text = super . getText ();
119- String lineText = text .substring (line .offset , textOffset );
148+ CharSequence text = getText ();
149+ String lineText = text .subSequence (line .offset , textOffset ). toString ( );
120150 int position = lineText .length ();
121151 Matcher m = wordDefinition .matcher (lineText );
122152 int currentPosition = 0 ;
@@ -149,7 +179,7 @@ private synchronized ILineTracker createLineTracker() {
149179 return lineTracker ;
150180 }
151181 ILineTracker lineTracker = isIncremental () ? new TreeLineTracker (new ListLineTracker ()) : new ListLineTracker ();
152- lineTracker .set (super . getText ());
182+ lineTracker .set (getText ());
153183 return lineTracker ;
154184 }
155185
@@ -168,31 +198,48 @@ public void update(List<TextDocumentContentChangeEvent> changes) {
168198 try {
169199 long start = System .currentTimeMillis ();
170200 synchronized (lock ) {
171- // Initialize buffer and line tracker from the current text document
172- StringBuilder buffer = new StringBuilder ( getText () );
201+ // Get current text as CharSequence (no copy)
202+ CharSequence currentText = getText ();
173203
174204 // Loop for each changes and update the buffer
175205 for (int i = 0 ; i < changes .size (); i ++) {
176206
177207 TextDocumentContentChangeEvent changeEvent = changes .get (i );
178208 Range range = changeEvent .getRange ();
179- int length = 0 ;
180209
181210 if (range != null ) {
182211 Integer rangeLength = changeEvent .getRangeLength ();
183- length = rangeLength != null ? rangeLength .intValue () : offsetAt (range .getEnd ()) - offsetAt (range .getStart ());
212+ int startOffset = offsetAt (range .getStart ());
213+ int length ;
214+
215+ if (rangeLength != null ) {
216+ // Use rangeLength if provided (preferred)
217+ length = rangeLength .intValue ();
218+ } else {
219+ // Calculate length from range.end
220+ int endOffset = offsetAt (range .getEnd ());
221+ length = endOffset - startOffset ;
222+ }
223+
224+ String text = changeEvent .getText ();
225+
226+ // Use CompositeCharSequence for zero-copy update
227+ // This avoids copying the entire document text
228+ ImmutableCharSequence newText = CompositeCharSequence .replaceRange (currentText , startOffset ,
229+ startOffset + length , ImmutableCharSequenceImpl .fromString (text ));
230+
231+ lineTracker .replace (startOffset , length , text );
232+ setText (newText );
233+
234+ // IMPORTANT: Update currentText for next iteration
235+ currentText = newText ;
184236 } else {
185- // range is optional and if not given, the whole file content is replaced
186- length = buffer .length ();
187- range = new Range (positionAt (0 ), positionAt (length ));
237+ // Full replacement
238+ setText (changeEvent .getText ());
239+ lineTracker .set (changeEvent .getText ());
240+ currentText = getText ();
188241 }
189- String text = changeEvent .getText ();
190- int startOffset = offsetAt (range .getStart ());
191- buffer .replace (startOffset , startOffset + length , text );
192- lineTracker .replace (startOffset , length , text );
193242 }
194- // Update the new text content from the updated buffer
195- setText (buffer .toString ());
196243 }
197244 LOGGER .fine ("Text document content updated in " + (System .currentTimeMillis () - start ) + "ms" );
198245 } catch (BadLocationException e ) {
@@ -209,4 +256,76 @@ public void update(List<TextDocumentContentChangeEvent> changes) {
209256 }
210257 }
211258 }
259+
260+ /**
261+ * Set the text content from a String. Converts to ImmutableCharSequence
262+ * internally.
263+ *
264+ * @param text the new text content
265+ */
266+ private void setText (String text ) {
267+ this .text = ImmutableCharSequenceImpl .fromString (text );
268+ }
269+
270+ private void setText (ImmutableCharSequence text ) {
271+ this .text = text ;
272+ }
273+
274+ public int getTextLength () {
275+ return getText ().length ();
276+ }
277+
278+ /**
279+ * The text document's uri.
280+ */
281+ @ NonNull
282+ public String getUri () {
283+ return this .uri ;
284+ }
285+
286+ /**
287+ * The text document's uri.
288+ */
289+ public void setUri (@ NonNull final String uri ) {
290+ this .uri = Preconditions .checkNotNull (uri , "uri" );
291+ }
292+
293+ /**
294+ * The text document's language identifier
295+ */
296+ @ NonNull
297+ public String getLanguageId () {
298+ return this .languageId ;
299+ }
300+
301+ /**
302+ * The text document's language identifier
303+ */
304+ public void setLanguageId (@ NonNull final String languageId ) {
305+ this .languageId = Preconditions .checkNotNull (languageId , "languageId" );
306+ }
307+
308+ /**
309+ * The version number of this document (it will strictly increase after each
310+ * change, including undo/redo).
311+ */
312+ public int getVersion () {
313+ return this .version ;
314+ }
315+
316+ /**
317+ * The version number of this document (it will strictly increase after each
318+ * change, including undo/redo).
319+ */
320+ public void setVersion (final int version ) {
321+ this .version = version ;
322+ }
323+
324+ /**
325+ * The content of the opened text document.
326+ */
327+ public CharSequence getText () {
328+ return text ;
329+ }
330+
212331}
0 commit comments