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 <[email protected]>
  • Loading branch information
angelozerr committed Apr 26, 2022
1 parent 6113791 commit ade4919
Show file tree
Hide file tree
Showing 32 changed files with 6,083 additions and 989 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,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 @@ -286,7 +282,7 @@ public long getParentProcessId() {
public CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionParams params) {
return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
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 @@ -79,6 +79,7 @@
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.LinkedEditingRangeParams;
Expand Down Expand Up @@ -242,7 +243,7 @@ public CompletableFuture<Hover> hover(HoverParams params) {
* @return the indentation settings (`xml.format.tabSize` and
* `xml.format.insertSpaces`) for the document with the given URI
*/
private CompletableFuture<XMLFormattingOptions> getIndentationSettings(@NonNull String uri) {
private CompletableFuture<FormattingOptions> getIndentationSettings(@NonNull String uri) {
ConfigurationItem insertSpaces = new ConfigurationItem();
insertSpaces.setScopeUri(uri);
insertSpaces.setSection("xml.format.insertSpaces");
Expand All @@ -254,7 +255,7 @@ private CompletableFuture<XMLFormattingOptions> getIndentationSettings(@NonNull
return xmlLanguageServer.getLanguageClient().configuration(new ConfigurationParams(Arrays.asList( //
insertSpaces, tabSize //
))).thenApply(indentationSettings -> {
XMLFormattingOptions newOptions = new XMLFormattingOptions();
FormattingOptions newOptions = new FormattingOptions();
if (indentationSettings.get(0) != null) {
newOptions.setInsertSpaces(((JsonPrimitive) indentationSettings.get(0)).getAsBoolean());
}
Expand Down Expand Up @@ -323,27 +324,17 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume

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

Expand Down Expand Up @@ -473,7 +464,7 @@ public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams param
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
String uri = params.getTextDocument().getUri();
return getIndentationSettings(uri) //
.handle((XMLFormattingOptions indentationSettings, Throwable err) -> {
.handle((FormattingOptions indentationSettings, Throwable err) -> {
if (indentationSettings != null) {
sharedSettings.getFormattingSettings().merge(indentationSettings);
}
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 @@ -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,72 @@
/*******************************************************************************
* 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 static final String GRAMMAR_AWARE_FORMATTING = "grammarAwareFormatting";

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 ade4919

Please sign in to comment.