Skip to content

Commit 7eb7c7b

Browse files
committed
Performance with Immutable charsequence
Signed-off-by: azerr <azerr@redhat.com>
1 parent 5ec1767 commit 7eb7c7b

File tree

52 files changed

+1375
-678
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1375
-678
lines changed

org.eclipse.lemminx/src/main/java/com/thaiopensource/relaxng/pattern/CMRelaxNGDocument.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,6 @@ DOMNode findNodeAt(Locator locator) {
317317
private static String getTextContent(DOMElement element) {
318318
int start = element.getStartTagCloseOffset() + 1;
319319
int end = element.getEndTagOpenOffset();
320-
return element.getOwnerDocument().getText().substring(start, end);
320+
return element.getOwnerDocument().getText().subSequence(start, end).toString();
321321
}
322322
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/CodeActionFactory.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.eclipse.lsp4j.Range;
2929
import org.eclipse.lsp4j.ResourceOperation;
3030
import org.eclipse.lsp4j.TextDocumentEdit;
31-
import org.eclipse.lsp4j.TextDocumentItem;
3231
import org.eclipse.lsp4j.TextEdit;
3332
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
3433
import org.eclipse.lsp4j.WorkspaceEdit;
@@ -49,7 +48,7 @@ public class CodeActionFactory {
4948
* @param diagnostic
5049
* @return
5150
*/
52-
public static CodeAction remove(String title, Range range, TextDocumentItem document, Diagnostic diagnostic) {
51+
public static CodeAction remove(String title, Range range, TextDocument document, Diagnostic diagnostic) {
5352
return replace(title, range, "", document, diagnostic);
5453
}
5554

@@ -64,7 +63,7 @@ public static CodeAction remove(String title, Range range, TextDocumentItem docu
6463
*
6564
* @return the CodeAction to insert a new content at the end of the given range.
6665
*/
67-
public static CodeAction insert(String title, Position position, String insertText, TextDocumentItem document,
66+
public static CodeAction insert(String title, Position position, String insertText, TextDocument document,
6867
Diagnostic diagnostic) {
6968
CodeAction insertContentAction = new CodeAction(title);
7069
insertContentAction.setKind(CodeActionKind.QuickFix);
@@ -84,7 +83,7 @@ public static CodeAction insert(String title, Position position, String insertTe
8483
*
8584
* @return the text edit to insert a new content at the end of the given range.
8685
*/
87-
public static TextDocumentEdit insertEdit(String insertText, Position position, TextDocumentItem document) {
86+
public static TextDocumentEdit insertEdit(String insertText, Position position, TextDocument document) {
8887
TextEdit edit = insertEdit(insertText, position);
8988
return insertEdits(document, Collections.singletonList(edit));
9089
}
@@ -93,19 +92,19 @@ public static TextEdit insertEdit(String insertText, Position position) {
9392
return new TextEdit(new Range(position, position), insertText);
9493
}
9594

96-
public static TextDocumentEdit insertEdits(TextDocumentItem document, List<TextEdit> edits) {
95+
public static TextDocumentEdit insertEdits(TextDocument document, List<TextEdit> edits) {
9796
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
9897
document.getUri(), document.getVersion());
9998
return new TextDocumentEdit(versionedTextDocumentIdentifier, edits);
10099
}
101100

102-
public static CodeAction replace(String title, Range range, String replaceText, TextDocumentItem document,
101+
public static CodeAction replace(String title, Range range, String replaceText, TextDocument document,
103102
Diagnostic diagnostic) {
104103
TextEdit replace = new TextEdit(range, replaceText);
105104
return replace(title, Collections.singletonList(replace), document, diagnostic);
106105
}
107106

108-
public static CodeAction replace(String title, List<TextEdit> replace, TextDocumentItem document,
107+
public static CodeAction replace(String title, List<TextEdit> replace, TextDocument document,
109108
Diagnostic diagnostic) {
110109

111110
CodeAction insertContentAction = new CodeAction(title);
@@ -128,7 +127,7 @@ public static CodeAction replace(String title, List<TextEdit> replace, TextDocum
128127
* @param document
129128
* @return the workspace edit of a given replacement text and range.
130129
*/
131-
public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range range, TextDocumentItem document) {
130+
public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range range, TextDocument document) {
132131
TextEdit replace = new TextEdit(range, replaceText);
133132
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
134133
document.getUri(), document.getVersion());
@@ -137,8 +136,8 @@ public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range ra
137136
return new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));
138137
}
139138

140-
public static CodeAction replaceAt(String title, String replaceText, TextDocumentItem document,
141-
Diagnostic diagnostic, Collection<Range> ranges) {
139+
public static CodeAction replaceAt(String title, String replaceText, TextDocument document, Diagnostic diagnostic,
140+
Collection<Range> ranges) {
142141
CodeAction insertContentAction = new CodeAction(title);
143142
insertContentAction.setKind(CodeActionKind.QuickFix);
144143
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ILineTracker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,14 @@ public interface ILineTracker {
135135
* @param text the substitution text
136136
* @exception BadLocationException if specified range is unknown to this tracker
137137
*/
138-
void replace(int offset, int length, String text) throws BadLocationException;
138+
void replace(int offset, int length, CharSequence text) throws BadLocationException;
139139

140140
/**
141141
* Sets the tracked text to the specified text.
142142
*
143143
* @param text the new tracked text
144144
*/
145-
void set(String text);
145+
void set(CharSequence text);
146146

147147
Position getPositionAt(int position) throws BadLocationException;
148148

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ListLineTracker.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ public final String getLineDelimiter(int line) throws BadLocationException {
343343
* @param offset the offset in the given text
344344
* @return the information of the first found delimiter or <code>null</code>
345345
*/
346-
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
346+
protected DelimiterInfo nextDelimiterInfo(CharSequence text, int offset) {
347347
char ch;
348348
int length = text.length();
349349
for (int i = offset; i < length; i++) {
@@ -389,7 +389,7 @@ protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
389389
* @param offset the offset of all newly created lines
390390
* @return the number of newly created lines
391391
*/
392-
private int createLines(String text, int insertPosition, int offset) {
392+
private int createLines(CharSequence text, int insertPosition, int offset) {
393393

394394
int count = 0;
395395
int start = 0;
@@ -427,12 +427,12 @@ private int createLines(String text, int insertPosition, int offset) {
427427
}
428428

429429
@Override
430-
public final void replace(int position, int length, String text) throws BadLocationException {
430+
public final void replace(int position, int length, CharSequence text) throws BadLocationException {
431431
throw new UnsupportedOperationException();
432432
}
433433

434434
@Override
435-
public final void set(String text) {
435+
public final void set(CharSequence text) {
436436
fLines.clear();
437437
if (text != null) {
438438
fTextLength = text.length();

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ModelTextDocument.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ private synchronized T getSynchronizedModel() {
9797
return model;
9898
}
9999

100-
@Override
100+
/*@Override
101101
public void setText(String text) {
102102
super.setText(text);
103103
// text changed, cancel the completable future which load the model
104104
cancelModel();
105-
}
105+
}*/
106106

107107
@Override
108108
public void setVersion(int version) {

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java

Lines changed: 142 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,50 @@
1717
import java.util.regex.Matcher;
1818
import 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;
2023
import org.eclipse.lsp4j.Position;
2124
import org.eclipse.lsp4j.Range;
2225
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
2326
import 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

Comments
 (0)