Skip to content

Commit

Permalink
fix: Formatting is broken when using simple {}
Browse files Browse the repository at this point in the history
Fixes #1345

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 1, 2024
1 parent 7641f57 commit b945502
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,142 +26,140 @@
/**
* Template aware formatter which provides formatting for Qute syntax and delegates formatting
* for the templated language to that languages formatter
*
* <p>
* This class is a copy/paste from https://github.com/JetBrains/intellij-plugins/blob/master/Qute/src/com/dmarcotte/Qute/format/HbFormattingModelBuilder.java adapted for Qute.
*/
public class QuteHtmlFormattingModelBuilder extends TemplateLanguageFormattingModelBuilder {


@Override
public TemplateLanguageBlock createTemplateLanguageBlock(@NotNull ASTNode node,
@Nullable Wrap wrap,
@Nullable Alignment alignment,
@Nullable List<DataLanguageBlockWrapper> foreignChildren,
@NotNull CodeStyleSettings codeStyleSettings) {
final FormattingDocumentModelImpl documentModel = FormattingDocumentModelImpl.createOn(node.getPsi().getContainingFile());
HtmlPolicy policy = new HtmlPolicy(codeStyleSettings, documentModel);
return new QuteBlock(node, wrap, alignment, this, codeStyleSettings, foreignChildren, policy);
}

/**
* We have to override {@link TemplateLanguageFormattingModelBuilder#createModel}
* since after we delegate to some templated languages, those languages (xml/html for sure, potentially others)
* delegate right back to us to format the HbTokenTypes.OUTER_ELEMENT_TYPE token we tell them to ignore,
* causing a stack-overflowing loop of polite format-delegation.
*/
@Override
public @NotNull FormattingModel createModel(@NotNull FormattingContext formattingContext) {
final PsiFile file = formattingContext.getContainingFile();
Block rootBlock;

ASTNode node = formattingContext.getNode();

if (node.getElementType() == QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE) {
// If we're looking at a HbTokenTypes.OUTER_ELEMENT_TYPE element, then we've been invoked by our templated
// language. Make a dummy block to allow that formatter to continue
return new SimpleTemplateLanguageFormattingModelBuilder().createModel(formattingContext);
}
else {
rootBlock = getRootBlock(file, file.getViewProvider(), formattingContext.getCodeStyleSettings());
}
return new DocumentBasedFormattingModel(
rootBlock, formattingContext.getProject(), formattingContext.getCodeStyleSettings(), file.getFileType(), file);
}

/**
* Do format my model!
*
* @return false all the time to tell the {@link TemplateLanguageFormattingModelBuilder}
* to not-not format our model (i.e. yes please! Format away!)
*/
@Override
public boolean dontFormatMyModel() {
return false;
}

private static class QuteBlock extends TemplateLanguageBlock {

@NotNull
protected final HtmlPolicy myHtmlPolicy;


QuteBlock(@NotNull ASTNode node,
Wrap wrap,
Alignment alignment,
@NotNull TemplateLanguageBlockFactory blockFactory,
@NotNull CodeStyleSettings settings,
@Nullable List<DataLanguageBlockWrapper> foreignChildren,
@NotNull HtmlPolicy htmlPolicy) {
super(node, wrap, alignment, blockFactory, settings, foreignChildren);
myHtmlPolicy = htmlPolicy;
}

@Override
public Indent getIndent() {
// ignore whitespace
if (myNode.getText().trim().isEmpty()) {
return Indent.getNoneIndent();
}

// any element that is the direct descendant of a foreign block gets an indent
// (unless that foreign element has been configured to not indent its children)
DataLanguageBlockWrapper foreignParent = getForeignBlockParent(true);
if (foreignParent != null) {
if (foreignParent.getNode() instanceof XmlTag
&& !myHtmlPolicy.indentChildrenOf((XmlTag)foreignParent.getNode())) {
return Indent.getNoneIndent();
}
return Indent.getNormalIndent();
}

return Indent.getNoneIndent();
}

@Override
protected IElementType getTemplateTextElementType() {
// we ignore CONTENT tokens since they get formatted by the templated language
return QuteElementTypes.QUTE_TEXT;
public TemplateLanguageBlock createTemplateLanguageBlock(@NotNull ASTNode node,
@Nullable Wrap wrap,
@Nullable Alignment alignment,
@Nullable List<DataLanguageBlockWrapper> foreignChildren,
@NotNull CodeStyleSettings codeStyleSettings) {
final FormattingDocumentModelImpl documentModel = FormattingDocumentModelImpl.createOn(node.getPsi().getContainingFile());
HtmlPolicy policy = new HtmlPolicy(codeStyleSettings, documentModel);
return new QuteBlock(node, wrap, alignment, this, codeStyleSettings, foreignChildren, policy);
}

/**
* We have to override {@link TemplateLanguageFormattingModelBuilder#createModel}
* since after we delegate to some templated languages, those languages (xml/html for sure, potentially others)
* delegate right back to us to format the HbTokenTypes.OUTER_ELEMENT_TYPE token we tell them to ignore,
* causing a stack-overflowing loop of polite format-delegation.
*/
@Override
public boolean isRequiredRange(TextRange range) {
// seems our approach doesn't require us to insert any custom DataLanguageBlockFragmentWrapper blocks
return false;
public @NotNull FormattingModel createModel(@NotNull FormattingContext formattingContext) {
final PsiFile file = formattingContext.getContainingFile();
Block rootBlock;

ASTNode node = formattingContext.getNode();

if (node.getElementType() == QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE) {
// If we're looking at a QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE element, then we've been invoked by our templated
// language. Make a dummy block to allow that formatter to continue
return new SimpleTemplateLanguageFormattingModelBuilder().createModel(formattingContext);
} else {
rootBlock = getRootBlock(file, file.getViewProvider(), formattingContext.getCodeStyleSettings());
}
return new DocumentBasedFormattingModel(
rootBlock, formattingContext.getProject(), formattingContext.getCodeStyleSettings(), file.getFileType(), file);
}

/**
* <p/>
* This method handles indent and alignment on Enter.
* Do format my model!
*
* @return false all the time to tell the {@link TemplateLanguageFormattingModelBuilder}
* to not-not format our model (i.e. yes please! Format away!)
*/
@NotNull
@Override
public ChildAttributes getChildAttributes(int newChildIndex) {
return new ChildAttributes(Indent.getNoneIndent(), null);
public boolean dontFormatMyModel() {
return false;
}

private static class QuteBlock extends TemplateLanguageBlock {

/**
* Returns this block's first "real" foreign block parent if it exists, and null otherwise. (By "real" here, we mean that this method
* skips SyntheticBlock blocks inserted by the template formatter)
*
* @param immediate Pass true to only check for an immediate foreign parent, false to look up the hierarchy.
*/
private DataLanguageBlockWrapper getForeignBlockParent(boolean immediate) {
DataLanguageBlockWrapper foreignBlockParent = null;
BlockWithParent parent = getParent();

while (parent != null) {
if (parent instanceof DataLanguageBlockWrapper && !(((DataLanguageBlockWrapper)parent).getOriginal() instanceof SyntheticBlock)) {
foreignBlockParent = (DataLanguageBlockWrapper)parent;
break;
@NotNull
protected final HtmlPolicy myHtmlPolicy;


QuteBlock(@NotNull ASTNode node,
Wrap wrap,
Alignment alignment,
@NotNull TemplateLanguageBlockFactory blockFactory,
@NotNull CodeStyleSettings settings,
@Nullable List<DataLanguageBlockWrapper> foreignChildren,
@NotNull HtmlPolicy htmlPolicy) {
super(node, wrap, alignment, blockFactory, settings, foreignChildren);
myHtmlPolicy = htmlPolicy;
}
else if (immediate && parent instanceof QuteBlock) {
break;

@Override
public Indent getIndent() {
// ignore whitespace
if (myNode.getText().trim().isEmpty()) {
return Indent.getNoneIndent();
}

// any element that is the direct descendant of a foreign block gets an indent
// (unless that foreign element has been configured to not indent its children)
DataLanguageBlockWrapper foreignParent = getForeignBlockParent(true);
if (foreignParent != null) {
if (foreignParent.getNode() instanceof XmlTag
&& !myHtmlPolicy.indentChildrenOf((XmlTag) foreignParent.getNode())) {
return Indent.getNoneIndent();
}
return Indent.getNormalIndent();
}

return Indent.getNoneIndent();
}

@Override
protected IElementType getTemplateTextElementType() {
// we ignore CONTENT tokens since they get formatted by the templated language
return QuteElementTypes.QUTE_TEXT;
}
parent = parent.getParent();
}

return foreignBlockParent;
@Override
public boolean isRequiredRange(TextRange range) {
// seems our approach doesn't require us to insert any custom DataLanguageBlockFragmentWrapper blocks
return false;
}

/**
* <p/>
* This method handles indent and alignment on Enter.
*/
@NotNull
@Override
public ChildAttributes getChildAttributes(int newChildIndex) {
return new ChildAttributes(Indent.getNoneIndent(), null);
}


/**
* Returns this block's first "real" foreign block parent if it exists, and null otherwise. (By "real" here, we mean that this method
* skips SyntheticBlock blocks inserted by the template formatter)
*
* @param immediate Pass true to only check for an immediate foreign parent, false to look up the hierarchy.
*/
private DataLanguageBlockWrapper getForeignBlockParent(boolean immediate) {
DataLanguageBlockWrapper foreignBlockParent = null;
BlockWithParent parent = getParent();

while (parent != null) {
if (parent instanceof DataLanguageBlockWrapper && !(((DataLanguageBlockWrapper) parent).getOriginal() instanceof SyntheticBlock)) {
foreignBlockParent = (DataLanguageBlockWrapper) parent;
break;
} else if (immediate && parent instanceof QuteBlock) {
break;
}
parent = parent.getParent();
}

return foreignBlockParent;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ public class QuteLexerForStartTag extends AbstractQuteSubLexer {
private int myLastState;
private IElementType myLastTokenType;
private int myLastTokenEnd;
private ExpressionScanner expressionScanner;

private AbstractQuteSubLexer currentSubLexer;

public QuteLexerForStartTag(String text, TemplateScanner templateScanner, int startTagOpenOffset) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,28 @@ public void parseTemplate() {
}

while (!eof()) {
final IElementType tt = token();
if (tt == QUTE_START_EXPRESSION) {
parseExpression();
} else if (tt == QUTE_START_TAG_OPEN) {
parseStartSection();
} else if (isCommentToken(tt)) {
parseComment();
} else if (tt == QUTE_TEXT) {
parseText();
} else {
advance();
}
parseContent();
}

template.done(QUTE_CONTENT);
}

private void parseStartSection() {
private void parseContent() {
final IElementType tt = token();
if (tt == QUTE_START_EXPRESSION) {
parseExpression();
} else if (tt == QUTE_START_TAG_OPEN) {
parseSection();
} else if (isCommentToken(tt)) {
parseComment();
} else if (tt == QUTE_TEXT) {
parseText();
} else {
advance();
}
}

private void parseSection() {
final PsiBuilder.Marker startSection = mark();
advance();

Expand Down Expand Up @@ -91,17 +95,18 @@ private void parseStartSection() {
advance();
propertyPart.done(QUTE_EXPRESSION_PROPERTY_PART);
continue;
} else {
//final PsiBuilder.Marker error = mark();
} else if (tt == QUTE_END_TAG_CLOSE || tt == QUTE_END_TAG_SELF_CLOSE) {
advance();
// error.error("BAD comments!");
continue;
break;
} else {
parseContent();
}
break;
}
startSection.done(QUTE_START_SECTION);
}


private void parseText() {
final PsiBuilder.Marker text = mark();
advance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@ private static String convertStreamToString(InputStream is) {
}
}

private static final String JDT_SCHEME = "jdt";
private static final String CONTENTS_AUTHORITY = "jarentry";

// see
// https://github.com/microsoft/vscode-java-dependency/blob/27c306b770c23b1eba1f9a7c3e70d2793baced68/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ExtUtils.java#L39

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.redhat.devtools.intellij.qute.lang.psi;

import com.intellij.lang.ParserDefinition;
import com.intellij.mock.MockSmartPointerManager;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.psi.SmartPointerManager;
import com.intellij.testFramework.ParsingTestCase;
import com.redhat.devtools.intellij.qute.lang.QuteParserDefinition;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;

public class QuteParsingTestCase extends ParsingTestCase {

public QuteParsingTestCase() {
super("psi", "qute", new QuteParserDefinition());
}

@Override
protected void setUp() throws Exception {
super.setUp();
project.registerService(SmartPointerManager.class, new MockSmartPointerManager());
}

@Override
protected String getTestDataPath() {
return new File("testData/qute").getPath();
}

public void testHelloWorld() throws IOException { doCodeTest("<h1>Hello {http:param('name', 'Quarkus')}!</h1> "); }

public void testSimpleText() throws IOException { doCodeTest("foo"); }

public void testSectionWithHtml() throws IOException {
doCodeTest("""
<div>
<div>
{#if}
{#if}
{inject:flash.get("key")}
<form>
</form>
{/if}
{/if}
</div>
</div>
""");
}
}
Loading

0 comments on commit b945502

Please sign in to comment.