Skip to content

Commit

Permalink
Provide Document Lifecycle Participant for tracking didOpen, did*
Browse files Browse the repository at this point in the history
Fixes #603

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 30, 2021
1 parent d5569b9 commit 508db6d
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/LemMinX-Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ The [LemMinx Extensions API](https:/eclipse/lemminx/tree/master/org.
- Formatter with [IFormatterParticipant](https:/eclipse/lemminx/blob/master/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/format/IFormatterParticipant.java)
- Symbols with [ISymbolsProviderParticipant](https:/eclipse/lemminx/blob/master/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ISymbolsProviderParticipant.java)
- Monitoring workspace folders with [IWorkspaceServiceParticipant](https:/eclipse/lemminx/blob/master/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IWorkspaceServiceParticipant.java)
- Monitoring document lifecycle (didOpen, didChange, etc) with [IDocumentLifecycleParticipant](https:/eclipse/lemminx/blob/master/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IDocumentLifecycleParticipant.java)

## XML Language Server services available for extensions
XML Language Server extension may need to use standard Language Server features such as commands, documents and ability to manipulate documents. These are available to extensions indirectly via specialized service API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import com.google.gson.JsonPrimitive;

import org.eclipse.lemminx.client.ExtendedClientCapabilities;
import org.eclipse.lemminx.client.LimitExceededWarner;
import org.eclipse.lemminx.client.LimitFeature;
Expand Down Expand Up @@ -91,6 +91,7 @@
import org.eclipse.lsp4j.SelectionRangeParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.TypeDefinitionParams;
Expand All @@ -100,17 +101,31 @@
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.lsp4j.services.TextDocumentService;

import com.google.gson.JsonPrimitive;

/**
* XML text document service.
*
*/
public class XMLTextDocumentService implements TextDocumentService {

private static final Logger LOGGER = Logger.getLogger(XMLTextDocumentService.class.getName());

private final XMLLanguageServer xmlLanguageServer;
private final TextDocuments<ModelTextDocument<DOMDocument>> documents;
private SharedSettings sharedSettings;
private LimitExceededWarner limitExceededWarner;

/**
* Enumeration for Validation triggered by.
*
*/
private static enum TriggeredBy {
didOpen, //
didChange, //
Other;
}

/**
* Save context.
*/
Expand Down Expand Up @@ -341,7 +356,7 @@ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
@Override
public void didOpen(DidOpenTextDocumentParams params) {
TextDocument document = documents.onDidOpenTextDocument(params);
triggerValidationFor(document);
triggerValidationFor(document, TriggeredBy.didOpen);
}

/**
Expand All @@ -350,17 +365,39 @@ public void didOpen(DidOpenTextDocumentParams params) {
@Override
public void didChange(DidChangeTextDocumentParams params) {
TextDocument document = documents.onDidChangeTextDocument(params);
triggerValidationFor(document);
triggerValidationFor(document, TriggeredBy.didChange, params.getContentChanges());
}

@Override
public void didClose(DidCloseTextDocumentParams params) {
TextDocumentIdentifier identifier = params.getTextDocument();
String uri = identifier.getUri();
DOMDocument xmlDocument = getNowDOMDocument(uri);
// Remove the document from the cache
documents.onDidCloseTextDocument(params);
TextDocumentIdentifier document = params.getTextDocument();
String uri = document.getUri();
// Publish empty errors from the document
xmlLanguageServer.getLanguageClient()
.publishDiagnostics(new PublishDiagnosticsParams(uri, Collections.emptyList()));
getLimitExceededWarner().evictValue(uri);
// Manage didClose document lifecycle participants
if (xmlDocument != null) {
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didClose(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didClose for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
}
}

private DOMDocument getNowDOMDocument(String uri) {
TextDocument document = documents.get(uri);
if (document != null) {
return ((ModelTextDocument<DOMDocument>) document).getModel().getNow(null);
}
return null;
}

@Override
Expand Down Expand Up @@ -483,6 +520,19 @@ public void didSave(DidSaveTextDocumentParams params) {
// A document was saved, collect documents to revalidate
SaveContext context = new SaveContext(params.getTextDocument().getUri());
doSave(context);

// Manage didSave document lifecycle participants
final DOMDocument xmlDocument = getNowDOMDocument(params.getTextDocument().getUri());
if (xmlDocument != null) {
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didSave(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didSave for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
}
return null;
});
}
Expand Down Expand Up @@ -526,19 +576,52 @@ private void triggerValidationFor(Collection<ModelTextDocument<DOMDocument>> doc
}
}

private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy) {
triggerValidationFor(document, triggeredBy, null);
}

@SuppressWarnings("unchecked")
private void triggerValidationFor(TextDocument document) {
((ModelTextDocument<DOMDocument>) document).getModel().thenAcceptAsync(xmlDocument -> {
validate(xmlDocument);
});
private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy,
List<TextDocumentContentChangeEvent> changeEvents) {
((ModelTextDocument<DOMDocument>) document).getModel()//
.thenAcceptAsync(xmlDocument -> {
// Validate the DOM document
validate(xmlDocument);
// Manage didOpen, didChange document lifecycle participants
switch (triggeredBy) {
case didOpen:
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didOpen(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didOpen for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
break;
case didChange:
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didChange(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didChange for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
break;
default:
// Do nothing
}
});
}

void validate(DOMDocument xmlDocument) throws CancellationException {
CancelChecker cancelChecker = xmlDocument.getCancelChecker();
cancelChecker.checkCanceled();
getXMLLanguageService().publishDiagnostics(xmlDocument,
params -> xmlLanguageServer.getLanguageClient().publishDiagnostics(params),
(doc) -> triggerValidationFor(doc), sharedSettings.getValidationSettings(), cancelChecker);
(doc) -> triggerValidationFor(doc, TriggeredBy.Other), sharedSettings.getValidationSettings(),
cancelChecker);
}

private XMLLanguageService getXMLLanguageService() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2021 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.services.extensions;

import org.eclipse.lemminx.dom.DOMDocument;

/**
* Document LifecycleService participant API.
*
* @since 0.18.0
*/
public interface IDocumentLifecycleParticipant {

/**
* Handler called when an XML document is opened.
*
* @param document the DOM document
*/
void didOpen(DOMDocument document);

/**
* Handler called when an XML document is changed.
*
* @param document the DOM document
*/
void didChange(DOMDocument document);

/**
* Handler called when an XML document is saved.
*
* @param document the DOM document
*/
void didSave(DOMDocument document);

/**
* Handler called when an XML document is closed.
*
* @param document the DOM document
*/
void didClose(DOMDocument document);

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class XMLExtensionsRegistry implements IComponentProvider {
private final List<IFormatterParticipant> formatterParticipants;
private final List<ISymbolsProviderParticipant> symbolsProviderParticipants;
private final List<IWorkspaceServiceParticipant> workspaceServiceParticipants;
private final List<IDocumentLifecycleParticipant> documentLifecycleParticipants;
private IXMLDocumentProvider documentProvider;
private IXMLValidationService validationService;
private IXMLCommandService commandService;
Expand Down Expand Up @@ -89,6 +90,7 @@ public XMLExtensionsRegistry() {
formatterParticipants = new ArrayList<>();
symbolsProviderParticipants = new ArrayList<>();
workspaceServiceParticipants = new ArrayList<>();
documentLifecycleParticipants = new ArrayList<>();
resolverExtensionManager = new URIResolverExtensionManager();
components = new HashMap<>();
registerComponent(resolverExtensionManager);
Expand Down Expand Up @@ -201,6 +203,8 @@ public Collection<ISymbolsProviderParticipant> getSymbolsProviderParticipants()
}

/**
* Return the registered workspace service participants.
*
* @return the registered workspace service participants.
* @since 0.14.2
*/
Expand All @@ -209,6 +213,17 @@ public Collection<IWorkspaceServiceParticipant> getWorkspaceServiceParticipants(
return workspaceServiceParticipants;
}

/**
* Return the registered document lifecycle participants.
*
* @return the registered document lifecycle participants.
* @since 0.18.0
*/
public List<IDocumentLifecycleParticipant> getDocumentLifecycleParticipants() {
initializeIfNeeded();
return documentLifecycleParticipants;
}

public void initializeIfNeeded() {
if (initialized) {
return;
Expand Down Expand Up @@ -373,9 +388,10 @@ public void registerSymbolsProviderParticipant(ISymbolsProviderParticipant symbo
public void unregisterSymbolsProviderParticipant(ISymbolsProviderParticipant symbolsProviderParticipant) {
symbolsProviderParticipants.remove(symbolsProviderParticipant);
}

/**
* Register a new workspace service participant
*
* @param workspaceServiceParticipant the participant to register
* @since 0.14.2
*/
Expand All @@ -385,13 +401,34 @@ public void registerWorkspaceServiceParticipant(IWorkspaceServiceParticipant wor

/**
* Unregister a new workspace service participant.
*
* @param workspaceServiceParticipant the participant to unregister
* @since 0.14.2
*/
public void unregisterWorkspaceServiceParticipant(IWorkspaceServiceParticipant workspaceServiceParticipant) {
workspaceServiceParticipants.remove(workspaceServiceParticipant);
}

/**
* Register a new document lifecycle participant
*
* @param documentLifecycleParticipant the participant to register
* @since 0.18.0
*/
public void registerDocumentLifecycleParticipant(IDocumentLifecycleParticipant documentLifecycleParticipant) {
documentLifecycleParticipants.add(documentLifecycleParticipant);
}

/**
* Unregister a new document lifecycle participant.
*
* @param documentLifecycleParticipant the participant to unregister
* @since 0.18.0
*/
public void unregisterDocumentLifecycleParticipant(IDocumentLifecycleParticipant documentLifecycleParticipant) {
documentLifecycleParticipants.remove(documentLifecycleParticipant);
}

/**
* Returns the XML Document provider and null otherwise.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@

import org.eclipse.lemminx.customservice.ActionableNotification;
import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService.IDelegateCommandHandler;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;

/**
* Mock XML Language server which helps to track show messages, actionable
Expand Down Expand Up @@ -82,4 +87,41 @@ public TextDocumentIdentifier didOpen(String fileURI, String xml) {
}
return xmlIdentifier;
}

public TextDocumentIdentifier didChange(String fileURI, List<TextDocumentContentChangeEvent> contentChanges) {
TextDocumentIdentifier xmlIdentifier = new TextDocumentIdentifier(fileURI);
DidChangeTextDocumentParams params = new DidChangeTextDocumentParams(
new VersionedTextDocumentIdentifier(xmlIdentifier.getUri(), 1), contentChanges);
XMLTextDocumentService textDocumentService = (XMLTextDocumentService) super.getTextDocumentService();
textDocumentService.didChange(params);
try {
// Force the parse of DOM document
textDocumentService.getDocument(params.getTextDocument().getUri()).getModel().get();
} catch (Exception e) {
e.printStackTrace();
}
return xmlIdentifier;
}

public TextDocumentIdentifier didClose(String fileURI) {
TextDocumentIdentifier xmlIdentifier = new TextDocumentIdentifier(fileURI);
DidCloseTextDocumentParams params = new DidCloseTextDocumentParams(xmlIdentifier);
XMLTextDocumentService textDocumentService = (XMLTextDocumentService) super.getTextDocumentService();
textDocumentService.didClose(params);
return xmlIdentifier;
}

public TextDocumentIdentifier didSave(String fileURI) {
TextDocumentIdentifier xmlIdentifier = new TextDocumentIdentifier(fileURI);
DidSaveTextDocumentParams params = new DidSaveTextDocumentParams(xmlIdentifier);
XMLTextDocumentService textDocumentService = (XMLTextDocumentService) super.getTextDocumentService();
textDocumentService.didSave(params);
try {
// Force the parse of DOM document
textDocumentService.getDocument(params.getTextDocument().getUri()).getModel().get();
} catch (Exception e) {
e.printStackTrace();
}
return xmlIdentifier;
}
}
Loading

0 comments on commit 508db6d

Please sign in to comment.