Skip to content

Commit

Permalink
Merge pull request apache#3314 from matthiasblaesing/css_formatting2
Browse files Browse the repository at this point in the history
Exclude less (@{}) and scss (#{}) string interpolation from formatting
  • Loading branch information
matthiasblaesing authored Nov 13, 2021
2 parents 5eb4848 + 94fa014 commit a92befa
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 54 deletions.
4 changes: 3 additions & 1 deletion ide/css.editor/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.

auxiliary.org-netbeans-modules-css-prep.less_2e_configured=true
auxiliary.org-netbeans-modules-css-prep.sass_2e_configured=true
release.external/css21-spec.zip=docs/css21-spec.zip
release.external/css3-spec.zip=docs/css3-spec.zip

Expand All @@ -33,4 +35,4 @@ test.config.stableBTD.excludes=\
**/properties/parser/PropertyValueTest.class,\
**/CssBracketCompleterTest.class

test-unit-sys-prop.netbeans.dirs=${netbeans.dest.dir}/${nb.cluster.ide.dir}
test-unit-sys-prop.netbeans.dirs=${netbeans.dest.dir}/${nb.cluster.ide.dir}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import javax.swing.text.BadLocationException;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
Expand Down Expand Up @@ -94,37 +95,76 @@ private void doFormat() throws BadLocationException {
.get(context.document())
.embeddedTokenSequences(reg.getStartOffset(), false);
TokenSequence<?> ts = tslist.get(tslist.size() - 1);
int blockLevel = determineBlocklevel(ts);

LinkedList<IndentType> blockLevel = new LinkedList<>();

// Initialize blockLevel with the indentions created by the
// outside context
ts.moveStart();
Token lastToken = null;
if (ts.moveNext()) {
List<TokenSequence<?>> tokenSequences = TokenHierarchy
.get(context.document())
.tokenSequenceList(ts.languagePath(), 0, ts.offset());
OUTER:
for (TokenSequence tsX : tokenSequences) {
tsX.moveStart();
while (tsX.moveNext()) {
if (tsX.offset() >= ts.offset()) {
break OUTER;
}
if (tsX.token().id() == CssTokenId.LBRACE) {
if (isStringInterpolation(lastToken)) {
// The sequence "@{" and "#{" lead in a strong interpolation
// in LESS (former) and SCSS (latter).
blockLevel.addLast(IndentType.NONE);
} else {
blockLevel.addLast(IndentType.BLOCK);
}
} else if (tsX.token().id() == CssTokenId.RBRACE) {
blockLevel.pollLast();
}
lastToken = tsX.token();
}
}
}

ts.moveStart();
while(ts.moveNext()) {
if(ts.token().id() == CssTokenId.LBRACE) {
blockLevel++;
// Ensure, that there is a newline after an block opening
// brace "{"
if(LexerUtils.followsToken(ts, CssTokenId.NL, false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
while(ts.moveNext()) {
if (ts.token().id() != CssTokenId.WS && ts.token().id() != CssTokenId.COMMENT) {
newlinesMissing.add(ts.offset());
ts.movePrevious();
break;
if(! isStringInterpolation(lastToken)) {
blockLevel.add(IndentType.BLOCK);
// Ensure, that there is a newline after an block opening
// brace "{"
if(LexerUtils.followsToken(ts, CssTokenId.NL, false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
while(ts.moveNext()) {
if (ts.token().id() != CssTokenId.WS && ts.token().id() != CssTokenId.COMMENT) {
newlinesMissing.add(ts.offset());
ts.movePrevious();
break;
}
}
}
} else {
blockLevel.add(IndentType.NONE);
}
} else if (ts.token().id() == CssTokenId.RBRACE) {
blockLevel--;
// Ensure, that there is a newline before an block closing
// brace "}"
if (LexerUtils.followsToken(ts, CssTokenId.NL, true, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
newlinesMissing.add(ts.offset());
}
// Ensure, that there is a newline after an block closing
// brace "}"
if (LexerUtils.followsToken(ts, CssTokenId.NL, false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
while(ts.moveNext()) {
if (ts.token().id() != CssTokenId.WS && ts.token().id() != CssTokenId.COMMENT) {
newlinesMissing.add(ts.offset());
ts.movePrevious();
break;
IndentType it = blockLevel.removeLast();
if (it == IndentType.BLOCK) {
// Ensure, that there is a newline before an block closing
// brace "}"
if (LexerUtils.followsToken(ts, CssTokenId.NL, true, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
newlinesMissing.add(ts.offset());
}
// Ensure, that there is a newline after an block closing
// brace "}"
if (LexerUtils.followsToken(ts, CssTokenId.NL, false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
while (ts.moveNext()) {
if (ts.token().id() != CssTokenId.WS && ts.token().id() != CssTokenId.COMMENT) {
newlinesMissing.add(ts.offset());
ts.movePrevious();
break;
}
}
}
}
Expand All @@ -133,7 +173,7 @@ private void doFormat() throws BadLocationException {
// (between property definitions). This should only be done
// in regular CSS definitions (rules), but not in inline
// style definitions
if (blockLevel > 0 && LexerUtils.followsToken(ts, asList(CssTokenId.NL, CssTokenId.RBRACE), false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
if (!blockLevel.isEmpty() && LexerUtils.followsToken(ts, asList(CssTokenId.NL, CssTokenId.RBRACE), false, true, CssTokenId.WS, CssTokenId.COMMENT) == null) {
while (ts.moveNext()) {
if (ts.token().id() != CssTokenId.WS && ts.token().id() != CssTokenId.COMMENT) {
newlinesMissing.add(ts.offset());
Expand All @@ -143,6 +183,7 @@ private void doFormat() throws BadLocationException {
}
}
}
lastToken = ts.token();
}
}
// Remove newline insertions if
Expand All @@ -162,6 +203,10 @@ private void doFormat() throws BadLocationException {
}
}

private static boolean isStringInterpolation(Token lastToken) {
return lastToken != null && (lastToken.id() == CssTokenId.AT_SIGN || lastToken.id() == CssTokenId.HASH_SYMBOL);
}

private boolean isPositionInFormatRegions(int pos) {
for(Region r: context.indentRegions()) {
if(r.getStartOffset() <= pos && r.getEndOffset() > pos) {
Expand All @@ -170,31 +215,6 @@ private boolean isPositionInFormatRegions(int pos) {
}
return false;
}

private int determineBlocklevel(TokenSequence<?> ts) {
int blockLevel = 0;
ts.moveStart();
if (!ts.moveNext()) {
return 0;
}
List<TokenSequence<?>> tokenSequences = TokenHierarchy
.get(context.document())
.tokenSequenceList(ts.languagePath(), 0, ts.offset());
OUTER: for(TokenSequence tsX: tokenSequences) {
tsX.moveStart();
while(tsX.moveNext()) {
if(tsX.offset() >= ts.offset()) {
break OUTER;
}
if(tsX.token().id() == CssTokenId.LBRACE) {
blockLevel++;
} else if (tsX.token().id() == CssTokenId.RBRACE) {
blockLevel--;
}
}
}
return blockLevel;
}

@Override
public ExtraLock indentLock() {
Expand All @@ -206,4 +226,8 @@ public Lookup getLookup() {
return lookup;
}

private static enum IndentType {
NONE,
BLOCK
}
}
1 change: 1 addition & 0 deletions ide/css.editor/test/unit/data/testfiles/case005.less
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.@{my-selector} { font-weight: bold; line-height: 40px; margin: 0 auto; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.@{my-selector} {
font-weight: bold;
line-height: 40px;
margin: 0 auto;
}
6 changes: 6 additions & 0 deletions ide/css.editor/test/unit/data/testfiles/case006.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@mixin corner-icon($name, $top-or-bottom, $left-or-right) {
.icon-#{$name} {
background-image: url("/icons/#{$name}.svg"); position: absolute;
#{$top-or-bottom}: 0; #{$left-or-right}: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@mixin corner-icon($name, $top-or-bottom, $left-or-right) {
.icon-#{$name} {
background-image: url("/icons/#{$name}.svg");
position: absolute;
#{$top-or-bottom}: 0;
#{$left-or-right}: 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public void testFormattingNetBeansCSS() throws Exception {
public void testPartitialFormatting() throws Exception {
IndentPrefs preferences = new IndentPrefs(4, 4);
String file = "testfiles/partialformatting.css";

FileObject fo = getTestFile(file);
assertNotNull(fo);
BaseDocument doc = getDocument(fo);
Expand All @@ -201,14 +201,14 @@ public void testPartitialFormatting() throws Exception {
assertDescriptionMatches(file, after, false, ".formatted");

DataObject.find(fo).getLookup().lookup(Closable.class).close();

doc = getDocument(fo);
setupDocumentIndentation(doc, preferences);
format(doc, formatter, 857, 904, false);
after = doc.getText(0, doc.getLength());
assertDescriptionMatches(file, after, false, ".formatted2");
}

public void testIndentation() throws Exception {
// property indentation:
insertNewline("a{^background: red;\n }\n", "a{\n ^background: red;\n }\n", null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.css.editor.indent;

import org.netbeans.modules.css.editor.test.*;
import org.netbeans.modules.csl.api.Formatter;
import org.netbeans.modules.csl.api.test.CslTestBase;
import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
import org.netbeans.modules.css.editor.csl.CssLanguage;


public class LessIndenterTest extends CslTestBase {

private static final String PROP_MIME_TYPE = "mimeType"; //NOI18N

public LessIndenterTest(String name) {
super(name);
}

@Override
protected DefaultLanguageConfig getPreferredLanguage() {
return new CssLanguage();
}

@Override
protected String getPreferredMimeType() {
return "text/less";
}

@Override
public Formatter getFormatter(IndentPrefs preferences) {
return null;
}

public void testFormattingCase5() throws Exception {
reformatFileContents("testfiles/case005.less", new IndentPrefs(4, 4));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.css.editor.indent;

import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.csl.api.Formatter;
import org.netbeans.modules.csl.api.test.CslTestBase;
import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
import org.netbeans.modules.css.editor.csl.CssLanguage;
import org.netbeans.modules.css.lib.TestUtil;
import org.netbeans.modules.parsing.api.Source;
import org.openide.filesystems.FileObject;


public class ScssIndenterTest extends CslTestBase {

private static final String PROP_MIME_TYPE = "mimeType"; //NOI18N

public ScssIndenterTest(String name) {
super(name);
}

@Override
protected DefaultLanguageConfig getPreferredLanguage() {
return new CssLanguage();
}

@Override
protected String getPreferredMimeType() {
return "text/scss";
}

@Override
public Formatter getFormatter(IndentPrefs preferences) {
return null;
}

public void testFormattingCase6() throws Exception {
reformatFileContents("testfiles/case006.scss", new IndentPrefs(4, 4));
}

}

0 comments on commit a92befa

Please sign in to comment.