Skip to content

Commit

Permalink
Wait a bit after change before sending diagnostics
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#1162

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed May 19, 2022
1 parent e77594e commit 97d3a2e
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;
Expand All @@ -36,6 +34,7 @@
import org.eclipse.lemminx.client.LimitFeature;
import org.eclipse.lemminx.commons.ModelTextDocument;
import org.eclipse.lemminx.commons.ModelTextDocuments;
import org.eclipse.lemminx.commons.ModelValidatorDelayer;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.commons.TextDocuments;
import org.eclipse.lemminx.dom.DOMDocument;
Expand Down Expand Up @@ -114,6 +113,8 @@ public class XMLTextDocumentService implements TextDocumentService {

private final XMLLanguageServer xmlLanguageServer;
private final TextDocuments<ModelTextDocument<DOMDocument>> documents;
private final ModelValidatorDelayer<DOMDocument> xmlValidatorDelayer;

private SharedSettings sharedSettings;
private LimitExceededWarner limitExceededWarner;

Expand Down Expand Up @@ -165,7 +166,6 @@ public void triggerValidationIfNeeded() {
}
}

final ScheduledExecutorService delayer = Executors.newScheduledThreadPool(2);
private boolean codeActionLiteralSupport;
private boolean hierarchicalDocumentSymbolSupport;
private boolean definitionLinkSupport;
Expand All @@ -179,6 +179,9 @@ public XMLTextDocumentService(XMLLanguageServer xmlLanguageServer) {
});
this.sharedSettings = new SharedSettings();
this.limitExceededWarner = null;
this.xmlValidatorDelayer = new ModelValidatorDelayer<DOMDocument>((document) -> {
validate(document, Collections.emptyMap());
});
}

public void updateClientCapabilities(ClientCapabilities capabilities,
Expand Down Expand Up @@ -376,6 +379,8 @@ public void didClose(DidCloseTextDocumentParams params) {
DOMDocument xmlDocument = getNowDOMDocument(uri);
// Remove the document from the cache
documents.onDidCloseTextDocument(params);
// Remove the validation from the delayer
xmlValidatorDelayer.cleanPendingValidation(uri);
// Publish empty errors from the document
xmlLanguageServer.getLanguageClient()
.publishDiagnostics(new PublishDiagnosticsParams(uri, Collections.emptyList()));
Expand Down Expand Up @@ -568,7 +573,7 @@ private void triggerValidationFor(Collection<ModelTextDocument<DOMDocument>> doc
xmlLanguageServer.schedule(() -> {
documents.forEach(document -> {
try {
validate(document.getModel().getNow(null));
validate(document.getModel().getNow(null), false);
} catch (CancellationException e) {
// Ignore the error and continue to validate other documents
}
Expand All @@ -587,7 +592,11 @@ private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy
((ModelTextDocument<DOMDocument>) document).getModel()//
.thenAcceptAsync(xmlDocument -> {
// Validate the DOM document
validate(xmlDocument);
// When validation is triggered by a didChange, we process the validation with
// delay to avoid
// reporting to many 'textDocument/publishDiagnostics' notifications on client
// side.
validate(xmlDocument, triggeredBy == TriggeredBy.didChange);
// Manage didOpen, didChange document lifecycle participants
switch (triggeredBy) {
case didOpen:
Expand Down Expand Up @@ -624,8 +633,12 @@ private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy
* @throws CancellationException when the DOM document content changed and
* diagnostics must be stopped.
*/
void validate(DOMDocument xmlDocument) throws CancellationException {
validate(xmlDocument, Collections.emptyMap());
void validate(DOMDocument xmlDocument, boolean withDelay) throws CancellationException {
if (withDelay) {
xmlValidatorDelayer.validateWithDelay(xmlDocument.getDocumentURI(), xmlDocument);
} else {
validate(xmlDocument, Collections.emptyMap());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
* Validate a given model document with delay.
*
* @author Angelo ZERR
*
* @param <T>
*/
public class ModelValidatorDelayer<T> {

private static final long DEFAULT_VALIDATION_DELAY_MS = 200;

private final ScheduledExecutorService executorService;

private final Consumer<T> validator;

private final Map<String, Future<?>> pendingValidationRequests;

private final long validationDelayMs;

public ModelValidatorDelayer(Consumer<T> validator) {
this(Executors.newScheduledThreadPool(2), validator, DEFAULT_VALIDATION_DELAY_MS);
}

public ModelValidatorDelayer(ScheduledExecutorService executorService, Consumer<T> validator,
long validationDelayMs) {
this.executorService = executorService;
this.validator = validator;
this.pendingValidationRequests = new HashMap<>();
this.validationDelayMs = validationDelayMs;
}

/**
* Validate the given model <code>document</code> identified by the given
* <code>uri</code> with a delay.
*
* @param uri the document URI.
* @param document the document model to validate.
*/
public void validateWithDelay(String uri, T document) {
cleanPendingValidation(uri);
Future<?> request = executorService.schedule(() -> {
synchronized (pendingValidationRequests) {
pendingValidationRequests.remove(uri);
}
validator.accept(document);
}, validationDelayMs, TimeUnit.MILLISECONDS);
synchronized (pendingValidationRequests) {
pendingValidationRequests.put(uri, request);
}
}

public void cleanPendingValidation(String uri) {
synchronized (pendingValidationRequests) {
Future<?> request = pendingValidationRequests.get(uri);
if (request != null) {
request.cancel(true);
pendingValidationRequests.remove(uri);
}
}
}
}

0 comments on commit 97d3a2e

Please sign in to comment.