Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve attribute line breaks #772

Merged
merged 1 commit into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.eclipse.lemminx.dom.DTDDeclNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.settings.XMLFormattingOptions;
import org.eclipse.lemminx.settings.XMLFormattingOptions.EmptyElements;
import org.eclipse.lemminx.utils.XMLBuilder;
import org.eclipse.lsp4j.Position;
Expand Down Expand Up @@ -438,16 +439,7 @@ private void formatElement(DOMElement element) throws BadLocationException {
// generate start element
xmlBuilder.startElement(tag, false);
if (element.hasAttributes()) {
// generate attributes
List<DOMAttr> attributes = element.getAttributeNodes();
if (hasSingleAttributeInFullDoc(element)) {
DOMAttr singleAttribute = attributes.get(0);
xmlBuilder.addSingleAttribute(singleAttribute.getName(), singleAttribute.getOriginalValue());
} else {
for (DOMAttr attr : attributes) {
xmlBuilder.addAttribute(attr, this.indentLevel);
}
}
formatAttributes(element);
}

EmptyElements emptyElements = getEmptyElements(element);
Expand All @@ -461,11 +453,11 @@ private void formatElement(DOMElement element) throws BadLocationException {
break;
case collapse:
// collapse empty element: <example></example> -> <example />
this.xmlBuilder.selfCloseElement();
formatElementStartTagSelfCloseBracket(element);
break;
default:
if (element.isStartTagClosed()) {
xmlBuilder.closeStartElement();
formatElementStartTagCloseBracket(element);
}
boolean hasElements = false;
if (element.hasChildNodes()) {
Expand All @@ -491,15 +483,97 @@ private void formatElement(DOMElement element) throws BadLocationException {
if (element.hasEndTag() && element.getEndTagOpenOffset() <= this.endOffset) {
this.xmlBuilder.endElement(tag, element.isEndTagClosed());
} else {
this.xmlBuilder.selfCloseElement();
formatElementStartTagSelfCloseBracket(element);
}
} else if (element.isSelfClosed()) {
this.xmlBuilder.selfCloseElement();
formatElementStartTagSelfCloseBracket(element);
}
}
}
}

/**
* Formats the start tag's closing bracket (>) according to
* {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}
*
* {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}:
* If true, must add a newline + indent before the closing bracket if the last attribute of the element
* and the closing bracket are in different lines.
*
* @param element
* @throws BadLocationException
*/
private void formatElementStartTagCloseBracket(DOMElement element) throws BadLocationException {
if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks()
&& element.hasAttributes()
&& !isSameLine(getLastAttribute(element).getEnd(), element.getStartTagCloseOffset())) {
xmlBuilder.linefeed();
}
xmlBuilder.closeStartElement();
}

/**
* Formats the self-closing tag (/>) according to
* {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}
*
* {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}:
* If true, must add a newline + indent before the self-closing tag if the last attribute of the element
* and the closing bracket are in different lines.
*
* @param element
* @throws BadLocationException
*/
private void formatElementStartTagSelfCloseBracket(DOMElement element) throws BadLocationException {
if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks()
&& element.hasAttributes()) {
int elementEndOffset = element.getEnd();
if (element.isStartTagClosed()) {
elementEndOffset = element.getStartTagCloseOffset();
}
if (!isSameLine(getLastAttribute(element).getEnd(), elementEndOffset)) {
this.xmlBuilder.linefeed();
this.xmlBuilder.indent(this.indentLevel);
}
}

this.xmlBuilder.selfCloseElement();
}

private void formatAttributes(DOMElement element) throws BadLocationException {
List<DOMAttr> attributes = element.getAttributeNodes();
boolean isSingleElement = hasSingleAttributeInFullDoc(element);
DOMNode prev = element;
for (DOMAttr attr : attributes) {
if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks()
&& !isSameLine(prev.getStart(), attr.getNodeAttrName().getStart())) {
xmlBuilder.linefeed();
xmlBuilder.indent(this.indentLevel + 1);
xmlBuilder.addSingleAttribute(attr, false, false);
} else if (isSingleElement){
xmlBuilder.addSingleAttribute(attr);
} else {
xmlBuilder.addAttribute(attr, this.indentLevel);
}
prev = attr.getNodeAttrName();
}
}

private boolean isSameLine(int first, int second) throws BadLocationException {
return getLineNumber(first) == getLineNumber(second);
}

private int getLineNumber(int offset) throws BadLocationException {
return this.textDocument.positionAt(offset).getLine();
}

private DOMAttr getLastAttribute(DOMElement element) {
if (!element.hasAttributes()) {
return null;
}
List<DOMAttr> attributes = element.getAttributeNodes();
return attributes.get(attributes.size() - 1);
}

/**
* Return the option to use to generate empty elements.
*
Expand Down Expand Up @@ -619,6 +693,14 @@ private static boolean formatDTD(DOMDocumentType doctype, int level, int end, XM
return true;
}

/**
* Returns true if the provided element has one attribute
* in the fullDomDocument (not the rangeDomDocument)
*
* @param element
* @return true if the provided element has one attribute
* in the fullDomDocument (not the rangeDomDocument)
*/
private boolean hasSingleAttributeInFullDoc(DOMElement element) {
DOMElement fullElement = getFullDocElemFromRangeElem(element);
return fullElement.getAttributeNodes().size() == 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class XMLFormattingOptions extends FormattingOptions {
public static final int DEFAULT_PRESERVER_NEW_LINES = 2;
public static final int DEFAULT_TAB_SIZE = 2;
public static final EnforceQuoteStyle DEFAULT_ENFORCE_QUOTE_STYLE = EnforceQuoteStyle.ignore;
public static final boolean DEFAULT_PRESERVE_ATTR_LINE_BREAKS = false;

// All possible keys
private static final String SPLIT_ATTRIBUTES = "splitAttributes";
Expand All @@ -38,6 +39,7 @@ public class XMLFormattingOptions extends FormattingOptions {
private static final String PRESERVED_NEWLINES = "preservedNewlines";
private static final String TRIM_FINAL_NEWLINES = "trimFinalNewlines";
private static final String ENFORCE_QUOTE_STYLE = "enforceQuoteStyle";
private static final String PRESERVE_ATTR_LINE_BREAKS = "preserveAttributeLineBreaks";

enum Quotations {
doubleQuotes, singleQuotes
Expand Down Expand Up @@ -311,6 +313,32 @@ public EnforceQuoteStyle getEnforceQuoteStyle() {
return enforceStyle == null ? DEFAULT_ENFORCE_QUOTE_STYLE : enforceStyle;
}

/**
* Sets the value of preserveAttrLineBreaks
*/
public void setPreserveAttrLineBreaks(final boolean preserveAttrLineBreaks) {
this.putBoolean(XMLFormattingOptions.PRESERVE_ATTR_LINE_BREAKS, Boolean.valueOf(preserveAttrLineBreaks));
}

/**
* Returns the value of preserveAttrLineBreaks
*
* @return the value of preserveAttrLineBreaks
*/
public boolean isPreserveAttrLineBreaks() {
if (this.isSplitAttributes()) {
// splitAttributes overrides preserveAttrLineBreaks
return false;
}

final Boolean value = this.getBoolean(XMLFormattingOptions.PRESERVE_ATTR_LINE_BREAKS);
if ((value != null)) {
return (value).booleanValue();
} else {
return XMLFormattingOptions.DEFAULT_PRESERVE_ATTR_LINE_BREAKS;
}
}

public XMLFormattingOptions merge(FormattingOptions formattingOptions) {
formattingOptions.entrySet().stream().forEach(entry -> {
this.put(entry.getKey(), entry.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,28 @@ public XMLBuilder closeStartElement() {
}

public XMLBuilder selfCloseElement() {
if (sharedSettings.getFormattingSettings().isSpaceBeforeEmptyCloseTag()) {
if (sharedSettings.getFormattingSettings().isSpaceBeforeEmptyCloseTag()
&& !isLastLineEmptyOrWhitespace()) {
appendSpace();
}
xml.append("/>");
return this;
}

public XMLBuilder addSingleAttribute(String name, String value) {
return addSingleAttribute(name, value, false);
public XMLBuilder addSingleAttribute(DOMAttr attr) {
return addSingleAttribute(attr, false, true);
}


public XMLBuilder addSingleAttribute(DOMAttr attr, boolean surroundWithQuotes, boolean prependSpace) {
return addSingleAttribute(attr.getName(), attr.getOriginalValue(), surroundWithQuotes, prependSpace);
}

public XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes) {
return addSingleAttribute(name, value, surroundWithQuotes, true);
}


/**
* Used when only one attribute is being added to a node.
*
Expand All @@ -113,8 +124,10 @@ public XMLBuilder addSingleAttribute(String name, String value) {
* @param surroundWithQuotes true if quotes should be added around originalValue
* @return this XML Builder
*/
public XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes) {
appendSpace();
public XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes, boolean prependSpace) {
if (prependSpace) {
appendSpace();
}
addAttributeContents(name, true, value, surroundWithQuotes);
return this;
}
Expand Down Expand Up @@ -159,7 +172,7 @@ public XMLBuilder addAttribute(DOMAttr attr, int level) {
return addAttribute(attr, level, false);
}

public XMLBuilder addAttribute(DOMAttr attr, int level, boolean surroundWithQuotes) {
private XMLBuilder addAttribute(DOMAttr attr, int level, boolean surroundWithQuotes) {
if (isSplitAttributes()) {
linefeed();
indent(level + splitAttributesIndent);
Expand Down
Loading