Skip to content

Commit

Permalink
Provide basic experimental formatter which supports invalid XML
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#1195

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Jun 18, 2022
1 parent 82abfce commit 099e2a7
Show file tree
Hide file tree
Showing 40 changed files with 7,009 additions and 1,096 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,6 @@ public XMLLanguageService getXMLLanguageService() {
return xmlLanguageService;
}

public SharedSettings getSettings() {
return xmlTextDocumentService.getSharedSettings();
}

public ScheduledFuture<?> schedule(Runnable command, int delay, TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
Expand All @@ -292,7 +288,7 @@ public long getParentProcessId() {
public CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionParams params) {
return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
return getXMLLanguageService().doAutoClose(xmlDocument, params.getPosition(),
getSettings().getCompletionSettings(), cancelChecker);
getSharedSettings().getCompletionSettings(), cancelChecker);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,25 +312,17 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume

@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
return computeAsync((cancelChecker) -> {
TextDocument document = getDocument(params.getTextDocument());
if (document == null) {
return null;
}
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions());
return getXMLLanguageService().format(document, null, settings);
return getXMLLanguageService().format(xmlDocument, null, settings);
});
}

@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
return computeAsync((cancelChecker) -> {
TextDocument document = getDocument(params.getTextDocument());
if (document == null) {
return null;
}
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions());
return getXMLLanguageService().format(document, params.getRange(), settings);
return getXMLLanguageService().format(xmlDocument, params.getRange(), settings);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public String lineText(int lineNumber) throws BadLocationException {
return text.substring(line.offset, line.offset + line.length);
}

public int lineOffsetAt(int position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformationOfOffset(position);
return line.offset;
}

public String lineDelimiter(int lineNumber) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
String lineDelimiter = lineTracker.getLineDelimiter(lineNumber);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,14 @@ public int getStart() {
@Override
public int getEnd() {
if (nodeAttrValue != null) {
// <foo attr="value"| >
return nodeAttrValue.getEnd();
}
if (hasDelimiter()) {
// <foo attr=| >
return delimiter + 1;
}
// <foo attr| >
return nodeAttrName.getEnd();
}

Expand Down Expand Up @@ -452,4 +455,8 @@ public boolean equals(Object obj) {
return true;
}

public int getDelimiterOffset() {
return delimiter;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,11 @@ public boolean isOrphanEndTagOf(String tagName) {
}

/**
* Returns the offset at which the given unclosed start tag should be closed with an angle bracket
* Returns the offset at which the given unclosed start tag should be closed
* with an angle bracket
*
* @returns the offset at which the given unclosed start tag should be closed with an angle bracket
* @returns the offset at which the given unclosed start tag should be closed
* with an angle bracket
*/
public int getUnclosedStartTagCloseOffset() {
String documentText = getOwnerDocument().getText();
Expand Down Expand Up @@ -554,7 +556,7 @@ public boolean isEmpty() {
for (DOMNode child : getChildren()) {
if (child.isText()) {
DOMText text = (DOMText) child;
if (!text.isWhitespace()) {
if (!text.isElementContentWhitespace()) {
return false;
}
} else {
Expand All @@ -564,4 +566,11 @@ public boolean isEmpty() {
return true;
}

public int getOffsetAfterStartTag() {
if (hasTagName()) {
return getStartTagOpenOffset() + 1;
}
return getStartTagOpenOffset() + getTagName().length() + 1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.eclipse.lemminx.dom;

import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;

/**
Expand Down Expand Up @@ -61,7 +62,8 @@ public String getWholeText() {
*/
@Override
public boolean isElementContentWhitespace() {
throw new UnsupportedOperationException();
String text = getOwnerDocument().getOwnerDocument().getText();
return StringUtils.isWhitespace(text, getStart(), getEnd());
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeLensParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCompletionParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelDocumentLinkParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelFormatterParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelHoverParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelSymbolsProviderParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelTypeDefinitionParticipant;
Expand Down Expand Up @@ -85,6 +86,8 @@ public class ContentModelPlugin implements IXMLExtension {

private DocumentTelemetryParticipant documentTelemetryParticipant;

private ContentModelFormatterParticipant formatterParticipant;

public ContentModelPlugin() {
completionParticipant = new ContentModelCompletionParticipant();
hoverParticipant = new ContentModelHoverParticipant();
Expand Down Expand Up @@ -207,7 +210,9 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
documentTelemetryParticipant = new DocumentTelemetryParticipant(registry.getTelemetryManager(),
contentModelManager);
registry.registerDocumentLifecycleParticipant(documentTelemetryParticipant);

formatterParticipant = new ContentModelFormatterParticipant(contentModelManager);
registry.registerFormatterParticipant(formatterParticipant);

// Register custom commands to re-validate XML files
IXMLCommandService commandService = registry.getCommandService();
if (commandService != null) {
Expand Down Expand Up @@ -236,7 +241,8 @@ public void stop(XMLExtensionsRegistry registry) {
registry.unregisterSymbolsProviderParticipant(symbolsProviderParticipant);
registry.unregisterCodeLensParticipant(codeLensParticipant);
registry.unregisterDocumentLifecycleParticipant(documentTelemetryParticipant);

registry.unregisterFormatterParticipant(formatterParticipant);

// Un-register custom commands to re-validate XML files
IXMLCommandService commandService = registry.getCommandService();
if (commandService != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,22 @@ default String getName(String prefix) {
*/
String getDocumentURI();

/**
* Returns true if the element is a string type (ex : xs:string) and false
* otherwise.
*
* @return true if the element is a string type (ex : xs:string) and false
* otherwise.
*/
boolean isStringType();

/**
* Returns true if the element can contains text and element both and false
* otherwise.
*
* @return true if the element can contains text and element both and false
* otherwise.
*/
boolean isMixedContent();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*******************************************************************************
* 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.extensions.contentmodel.participants;

import java.util.Collection;

import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant;
import org.eclipse.lemminx.services.format.FormatElementCategory;
import org.eclipse.lemminx.services.format.XMLFormattingConstraints;
import org.eclipse.lemminx.settings.SharedSettings;

/**
* Formatter participant which uses XSD/DTD grammar information to know the
* {@link FormatElementCategory} of a given element.
*
* <p>
*
* This participant is enabled when 'xml.format.grammarAwareFormatting' setting
* is set to true.
*
* </p>
*
* @author Angelo ZERR
*
*/
public class ContentModelFormatterParticipant implements IFormatterParticipant {

private final ContentModelManager contentModelManager;

public ContentModelFormatterParticipant(ContentModelManager contentModelManager) {
this.contentModelManager = contentModelManager;
}

@Override
public FormatElementCategory getFormatElementCategory(DOMElement element,
XMLFormattingConstraints parentConstraints, SharedSettings sharedSettings) {
boolean enabled = sharedSettings.getFormattingSettings().isGrammarAwareFormatting();
if (!enabled) {
return null;
}

Collection<CMDocument> cmDocuments = contentModelManager.findCMDocument(element);
for (CMDocument cmDocument : cmDocuments) {
CMElementDeclaration cmElement = cmDocument.findCMElement(element);
if (cmElement != null) {
if (cmElement.isStringType()) {
return FormatElementCategory.PreserveSpace;
}
if (cmElement.isMixedContent()) {
return FormatElementCategory.MixedContent;
}
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,14 @@ public int getIndex() {
public String getDocumentURI() {
return document.getURI();
}

@Override
public boolean isStringType() {
return false;
}

@Override
public boolean isMixedContent() {
return super.type == XMLElementDecl.TYPE_MIXED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,11 @@ public Collection<CMElementDeclaration> getPossibleElements(DOMElement parentEle
/**
* Returns the possible elements declaration if the given declaration is an
* xs:any and null otherwise.
*
*
* @param declaration the element, wildcard declaration.
* @return the possible elements declaration if the given declaration is an
* xs:any and null otherwise.
*
*
*/
private Collection<CMElementDeclaration> getXSAnyElements(Object declaration) {
short processContents = getXSAnyProcessContents(declaration);
Expand All @@ -230,7 +230,7 @@ private Collection<CMElementDeclaration> getXSAnyElements(Object declaration) {
/**
* Returns the value of the xs:any/@processContents if the given element is a
* xs:any and {@link #PC_UNKWOWN} otherwise.
*
*
* @param declaration the element, wildcard declaration.
* @return the value of the xs:any/@processContents if the given element is a
* xs:any and {@link #PC_UNKWOWN} otherwise.
Expand All @@ -248,7 +248,7 @@ private static short getXSAnyProcessContents(Object declaration) {
/**
* Returns list of element (QName) of child elements of the given parent element
* upon the given offset
*
*
* @param parentElement the parent element
* @param offset the offset where child element must be belong to
* @return list of element (QName) of child elements of the given parent element
Expand Down Expand Up @@ -355,7 +355,7 @@ public String getDocumentation(ISharedSettingsRequest request) {
/**
* Returns list of xs:annotation from the element declaration or type
* declaration.
*
*
* @return list of xs:annotation from the element declaration or type
* declaration.
*/
Expand Down Expand Up @@ -490,4 +490,31 @@ public String getDocumentURI() {
SchemaGrammar schemaGrammar = document.getOwnerSchemaGrammar(elementDeclaration);
return CMXSDDocument.getSchemaURI(schemaGrammar);
}

@Override
public boolean isStringType() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null) {
XSSimpleTypeDefinition simpleDefinition = null;
if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
simpleDefinition = (XSSimpleTypeDefinition) typeDefinition;
} else if (typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
simpleDefinition = ((XSComplexTypeDefinition) typeDefinition).getSimpleType();
}
if (simpleDefinition != null) {
return "string".equals(simpleDefinition.getName());
}
}
return false;
}

@Override
public boolean isMixedContent() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
XSComplexTypeDefinition complexTypeDefinition = (XSComplexTypeDefinition) typeDefinition;
return complexTypeDefinition.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ public enum XSISchemaLocationSplit {

onElement, onPair, none;

private static final String XSI_SCHEMA_LOCATION_SPLIT = "xsiSchemaLocationSplit";

public static XSISchemaLocationSplit getSplit(XMLFormattingOptions formattingSettings) {
return getSplit(formattingSettings.getString(XSI_SCHEMA_LOCATION_SPLIT));
return getSplit(formattingSettings.getXsiSchemaLocationSplit());
}

public static void setSplit(XSISchemaLocationSplit split, XMLFormattingOptions formattingSettings) {
formattingSettings.putString(XSI_SCHEMA_LOCATION_SPLIT, split.name());
formattingSettings.setXsiSchemaLocationSplit(split.name());
}

public static XSISchemaLocationSplit getSplit(String value) {
Expand Down
Loading

0 comments on commit 099e2a7

Please sign in to comment.