Skip to content

Commit 402d24a

Browse files
committed
Experimental incremental DOM parser
Signed-off-by: azerr <azerr@redhat.com>
1 parent 82bb65c commit 402d24a

14 files changed

Lines changed: 1181 additions & 226 deletions

org.eclipse.lemminx/pom.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,14 @@
227227
<groupId>xerces</groupId>
228228
<artifactId>xercesImpl</artifactId>
229229
<version>2.12.2</version>
230+
<exclusions>
231+
<exclusion>
232+
<groupId>xml-apis</groupId>
233+
<artifactId>xml-apis</artifactId>
234+
</exclusion>
235+
</exclusions>
230236
</dependency>
231-
<dependency>
232-
<groupId>xml-apis</groupId>
233-
<artifactId>xml-apis</artifactId>
234-
<version>1.4.01</version>
235-
</dependency>
237+
236238
<dependency>
237239
<groupId>com.kotcrab.remark</groupId>
238240
<artifactId>remark</artifactId>

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ private synchronized void updateSettings(Object initOptions, boolean initLogs) {
232232
xmlTextDocumentService.getValidationSettings().merge(validationSettings);
233233

234234
}
235+
xmlTextDocumentService.setIncrementalParsing(xmlClientSettings.isIncrementalParsing());
235236
// Update XML language service extensions
236237
xmlTextDocumentService.updateSettings(initSettings);
237238
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.eclipse.lemminx.commons.TextDocument;
3939
import org.eclipse.lemminx.dom.DOMDocument;
4040
import org.eclipse.lemminx.dom.DOMParser;
41+
import org.eclipse.lemminx.dom.IncrementalDOMParser;
4142
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings;
4243
import org.eclipse.lemminx.services.DocumentSymbolsResult;
4344
import org.eclipse.lemminx.services.SymbolInformationResult;
@@ -127,7 +128,7 @@ public class XMLTextDocumentService implements TextDocumentService {
127128

128129
private SharedSettings sharedSettings;
129130
private LimitExceededWarner limitExceededWarner;
130-
131+
131132
/**
132133
* Enumeration for Validation triggered by.
133134
*
@@ -199,7 +200,12 @@ public XMLTextDocumentService(XMLLanguageServer xmlLanguageServer) {
199200
DOMParser parser = DOMParser.getInstance();
200201
this.documents = new ModelTextDocuments<DOMDocument>((document, cancelChecker) -> {
201202
return parser.parse(document, getXMLLanguageService().getResolverExtensionManager(), true, cancelChecker);
202-
});
203+
}, //
204+
(document, changes) -> {
205+
IncrementalDOMParser p = IncrementalDOMParser.getInstance();
206+
p.parseIncremental(document, changes);
207+
208+
});
203209
this.sharedSettings = new SharedSettings();
204210
this.limitExceededWarner = null;
205211
this.xmlValidatorDelayer = new ModelValidatorDelayer<DOMDocument>((document) -> {
@@ -217,6 +223,10 @@ public XMLTextDocumentService(XMLLanguageServer xmlLanguageServer) {
217223
});
218224
}
219225

226+
public void setIncrementalParsing(boolean incrementalParsing) {
227+
this.documents.setIncrementalModel(incrementalParsing);
228+
}
229+
220230
public void updateClientCapabilities(ClientCapabilities capabilities,
221231
ExtendedClientCapabilities extendedClientCapabilities) {
222232
if (capabilities != null) {
@@ -719,8 +729,7 @@ void validate(DOMDocument xmlDocument, Map<String, Object> validationArgs) throw
719729
cancelChecker.checkCanceled();
720730
getXMLLanguageService().publishDiagnostics(xmlDocument,
721731
params -> xmlLanguageServer.getLanguageClient().publishDiagnostics(params),
722-
(doc) -> triggerValidationFor(doc, TriggeredBy.Other),
723-
sharedSettings.getValidationSettings(),
732+
(doc) -> triggerValidationFor(doc, TriggeredBy.Other), sharedSettings.getValidationSettings(),
724733
validationArgs, cancelChecker);
725734
}
726735

@@ -827,4 +836,5 @@ public LimitExceededWarner getLimitExceededWarner() {
827836
}
828837
return this.limitExceededWarner;
829838
}
839+
830840
}

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
*******************************************************************************/
1212
package org.eclipse.lemminx.commons;
1313

14+
import java.util.List;
1415
import java.util.concurrent.CancellationException;
16+
import java.util.function.BiConsumer;
1517
import java.util.function.BiFunction;
1618
import java.util.logging.Logger;
1719

20+
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
1821
import org.eclipse.lsp4j.TextDocumentItem;
1922
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
2023

@@ -33,14 +36,21 @@ public class ModelTextDocument<T> extends TextDocument {
3336

3437
private T model;
3538

36-
public ModelTextDocument(TextDocumentItem document, BiFunction<TextDocument, CancelChecker, T> parse) {
39+
private final BiConsumer<T, List<TextDocumentContentChangeEvent>> updateModel;
40+
41+
private boolean incrementalModel;
42+
43+
public ModelTextDocument(TextDocumentItem document, BiFunction<TextDocument, CancelChecker, T> parse,
44+
BiConsumer<T, List<TextDocumentContentChangeEvent>> updateModel) {
3745
super(document);
3846
this.parse = parse;
47+
this.updateModel = updateModel;
3948
}
4049

4150
public ModelTextDocument(String text, String uri, BiFunction<TextDocument, CancelChecker, T> parse) {
4251
super(text, uri);
4352
this.parse = parse;
53+
this.updateModel = null;
4454
}
4555

4656
/**
@@ -83,7 +93,8 @@ private synchronized T getSynchronizedModel() {
8393
LOGGER.fine("Start parsing of model with version '" + version);
8494
// Stop of parse process can be done when completable future is canceled or when
8595
// version of document changes
86-
CancelChecker cancelChecker = new TextDocumentVersionChecker(this, version);
96+
CancelChecker cancelChecker = isIncrementalModel() ? ModelTextDocuments.NO_CANCELLABLE
97+
: new TextDocumentVersionChecker(this, version);
8798
// parse the model
8899
model = parse.apply(this, cancelChecker);
89100
} catch (CancellationException e) {
@@ -101,14 +112,30 @@ private synchronized T getSynchronizedModel() {
101112
public void setText(String text) {
102113
super.setText(text);
103114
// text changed, cancel the completable future which load the model
104-
cancelModel();
115+
if (!isIncrementalModel()) {
116+
cancelModel();
117+
}
105118
}
106119

107120
@Override
108121
public void setVersion(int version) {
109122
super.setVersion(version);
110123
// version changed, mark the model as dirty
111-
cancelModel();
124+
if (!isIncrementalModel()) {
125+
cancelModel();
126+
}
127+
}
128+
129+
@Override
130+
public void update(List<TextDocumentContentChangeEvent> changes) {
131+
super.update(changes);
132+
if (isIncrementalModel() && model != null) {
133+
updateModel(model, changes);
134+
}
135+
}
136+
137+
private void updateModel(T model, List<TextDocumentContentChangeEvent> changes) {
138+
updateModel.accept(model, changes);
112139
}
113140

114141
/**
@@ -118,4 +145,12 @@ private void cancelModel() {
118145
model = null;
119146
}
120147

148+
public boolean isIncrementalModel() {
149+
return incrementalModel;
150+
}
151+
152+
public void setIncrementalModel(boolean incrementalModel) {
153+
this.incrementalModel = incrementalModel;
154+
}
155+
121156
}

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
*******************************************************************************/
1212
package org.eclipse.lemminx.commons;
1313

14+
import java.util.List;
1415
import java.util.concurrent.CompletableFuture;
16+
import java.util.function.BiConsumer;
1517
import java.util.function.BiFunction;
1618
import java.util.function.Function;
1719

20+
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
1821
import org.eclipse.lsp4j.TextDocumentIdentifier;
1922
import org.eclipse.lsp4j.TextDocumentItem;
2023
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
@@ -30,16 +33,26 @@
3033
*/
3134
public class ModelTextDocuments<T> extends TextDocuments<ModelTextDocument<T>> {
3235

36+
final static CancelChecker NO_CANCELLABLE = () -> {
37+
};
38+
3339
private final BiFunction<TextDocument, CancelChecker, T> parse;
3440

35-
public ModelTextDocuments(BiFunction<TextDocument, CancelChecker, T> parse) {
41+
private final BiConsumer<T, List<TextDocumentContentChangeEvent>> updateModel;
42+
43+
private boolean incrementalModel;
44+
45+
public ModelTextDocuments(BiFunction<TextDocument, CancelChecker, T> parse,
46+
BiConsumer<T, List<TextDocumentContentChangeEvent>> updateModel) {
3647
this.parse = parse;
48+
this.updateModel = updateModel;
3749
}
3850

3951
@Override
4052
public ModelTextDocument<T> createDocument(TextDocumentItem document) {
41-
ModelTextDocument<T> doc = new ModelTextDocument<T>(document, parse);
53+
ModelTextDocument<T> doc = new ModelTextDocument<T>(document, parse, updateModel);
4254
doc.setIncremental(isIncremental());
55+
doc.setIncrementalModel(isIncrementalModel());
4356
return doc;
4457
}
4558

@@ -115,7 +128,7 @@ public <R> CompletableFuture<R> computeModelAsync(TextDocumentIdentifier documen
115128
return null;
116129
}
117130
// Apply the function code by using the parsed model.
118-
return code.apply(model, cancelChecker);
131+
return code.apply(model, isIncrementalModel() ? NO_CANCELLABLE : cancelChecker);
119132
});
120133
}
121134

@@ -150,4 +163,12 @@ private static <R> CompletableFuture<R> computeAsyncCompose(Function<CancelCheck
150163
start.complete(new FutureCancelChecker(result));
151164
return result;
152165
}
166+
167+
public boolean isIncrementalModel() {
168+
return incrementalModel;
169+
}
170+
171+
public void setIncrementalModel(boolean incrementalModel) {
172+
this.incrementalModel = incrementalModel;
173+
}
153174
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ public abstract class DOMNode implements Node, DOMRange {
6868
int start; // |<root> </root>
6969
int end; // <root> </root>|
7070

71+
/**
72+
* Index of this node in parent's children list. -1 if not set or if this is a
73+
* root node.
74+
*/
75+
int indexInParent = -1;
76+
7177
DOMNode parent;
7278

7379
private static final NodeList EMPTY_CHILDREN = new NodeList() {
@@ -497,7 +503,39 @@ public void addChild(DOMNode child) {
497503
if (children == null) {
498504
children = new XMLNodeList<>();
499505
}
500-
getChildren().add(child);
506+
// Set the index
507+
child.indexInParent = children.size();
508+
children.add(child);
509+
}
510+
511+
/**
512+
* Replace a child node at the given index and update indices
513+
*/
514+
protected void replaceChildAt(int index, DOMNode newChild) {
515+
if (children == null || index < 0 || index >= children.size()) {
516+
return;
517+
}
518+
519+
// Set new child's parent and index
520+
newChild.parent = this;
521+
newChild.indexInParent = index;
522+
523+
// Replace in list
524+
children.set(index, newChild);
525+
}
526+
527+
/**
528+
* Clear all children and invalidate their indices
529+
*/
530+
protected void clearChildren() {
531+
if (children != null) {
532+
// Invalidate indices of removed children
533+
for (DOMNode child : children) {
534+
child.indexInParent = -1;
535+
child.parent = null;
536+
}
537+
children.clear();
538+
}
501539
}
502540

503541
/**
@@ -727,11 +765,11 @@ public String getNamespaceURI() {
727765
@Override
728766
public DOMNode getNextSibling() {
729767
DOMNode parentNode = getParentNode();
730-
if (parentNode == null) {
768+
if (parentNode == null || indexInParent < 0) {
731769
return null;
732770
}
733771
List<DOMNode> children = parentNode.getChildren();
734-
int nextIndex = children.indexOf(this) + 1;
772+
int nextIndex = indexInParent + 1;
735773
return nextIndex < children.size() ? children.get(nextIndex) : null;
736774
}
737775

@@ -751,15 +789,14 @@ public String getPrefix() {
751789
* @see org.w3c.dom.Node#getPreviousSibling()
752790
*/
753791
@Override
754-
public DOMNode getPreviousSibling() {
755-
DOMNode parentNode = getParentNode();
756-
if (parentNode == null) {
757-
return null;
758-
}
759-
List<DOMNode> children = parentNode.getChildren();
760-
int previousIndex = children.indexOf(this) - 1;
761-
return previousIndex >= 0 ? children.get(previousIndex) : null;
762-
}
792+
public DOMNode getPreviousSibling() {
793+
DOMNode parentNode = getParentNode();
794+
if (parentNode == null || indexInParent < 0) {
795+
return null;
796+
}
797+
int previousIndex = indexInParent - 1;
798+
return previousIndex >= 0 ? parentNode.getChildren().get(previousIndex) : null;
799+
}
763800

764801
public DOMNode getPreviousNonTextSibling() {
765802
DOMNode prev = getPreviousSibling();
@@ -776,8 +813,7 @@ public DOMNode getPreviousNonTextSibling() {
776813
* The following sample sample with tagName=foo will returns the <\foo> orphan
777814
* end element:
778815
* <p>
779-
* |
780-
* <\foo>
816+
* | <\foo>
781817
* </p>
782818
*
783819
* @param offset the offset.
@@ -797,8 +833,7 @@ public DOMElement getOrphanEndElement(int offset, String tagName) {
797833
* The following sample sample with tagName=bar will returns the <\foo> orphan
798834
* end element:
799835
* <p>
800-
* |
801-
* <\foo>
836+
* | <\foo>
802837
* </p>
803838
*
804839
* @param offset the offset.

0 commit comments

Comments
 (0)