pass ASTNode at caret to WhiteSpaceFormattingStrategy.adjustWhiteSpaceIfNecessary(); use that to correctly insert \ when a line break is inserted via the document formatting model (PY-10319)

This commit is contained in:
Dmitry Jemerov
2013-11-12 13:39:04 +01:00
parent ade3e46cf8
commit fdfb56f0f6
19 changed files with 147 additions and 41 deletions
@@ -15,9 +15,11 @@
*/
package com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a model of the document containing the formatted text, as seen by the
@@ -73,15 +75,18 @@ public interface FormattingDocumentModel {
* <p/>
* Current method adjusts given white space text if necessary.
*
*
*
* @param whiteSpaceText white space text to use by default
* @param startOffset start offset of the document text that is intended to be replaced by the given white space text (inclusive)
* @param endOffset end offset of the document text that is intended to be replaced by the given white space text (exclusive)
* @param nodeAfter the AST node following the block, if known
* @param changedViaPsi flag that identifies whether formatter introduces changes via PSI tree or directly via the document
* @return white space to use for replacing document symbols at <code>[startOffset; endOffset)</code> region
*/
@NotNull
CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText, int startOffset, int endOffset,
boolean changedViaPsi);
@Nullable ASTNode nodeAfter, boolean changedViaPsi);
///**
// * Allows to answer if given symbol is treated by the current model as white space symbol during formatting.
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed 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 com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public interface FormattingModelEx extends FormattingModel {
/**
* Replaces the contents of the specified text range in the document with the specified text
* string consisting of whitespace characters. If necessary, other characters may be inserted
* in addition to the passed whitespace (for example, \ characters for breaking lines in
* languages like Python).
*
* @param textRange the text range to replace with whitespace.
* @param nodeAfter the AST node following the whitespace, if known
* @param whiteSpace the whitespace to replace with.
* @return new white space text range
*/
TextRange replaceWhiteSpace(TextRange textRange, @Nullable ASTNode nodeAfter, String whiteSpace);
}
@@ -16,6 +16,7 @@
package com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
@@ -38,7 +39,7 @@ public abstract class AbstractBlockWrapper {
Indent.Type.NORMAL, Indent.Type.CONTINUATION, Indent.Type.CONTINUATION_WITHOUT_FIRST
));
protected WhiteSpace myWhiteSpace;
protected WhiteSpace myWhiteSpaceBefore;
protected CompositeBlockWrapper myParent;
protected int myStart;
protected int myEnd;
@@ -53,12 +54,13 @@ public abstract class AbstractBlockWrapper {
private IndentImpl myIndent = null;
private AlignmentImpl myAlignment;
private WrapImpl myWrap;
private final ASTNode myNode;
public AbstractBlockWrapper(final Block block,
final WhiteSpace whiteSpace,
final WhiteSpace whiteSpaceBefore,
final CompositeBlockWrapper parent,
final TextRange textRange) {
myWhiteSpace = whiteSpace;
myWhiteSpaceBefore = whiteSpaceBefore;
myParent = parent;
myStart = textRange.getStartOffset();
myEnd = textRange.getEndOffset();
@@ -67,6 +69,7 @@ public abstract class AbstractBlockWrapper {
myAlignment = (AlignmentImpl)block.getAlignment();
myWrap = (WrapImpl)block.getWrap();
myLanguage = deriveLanguage(block);
myNode = block instanceof ASTBlock ? ((ASTBlock) block).getNode() : null;
}
@Nullable
@@ -76,9 +79,24 @@ public abstract class AbstractBlockWrapper {
}
return null;
}
/**
* Returns the whitespace preceding the block.
*
* @return the whitespace preceding the block
*/
public WhiteSpace getWhiteSpace() {
return myWhiteSpace;
return myWhiteSpaceBefore;
}
/**
* Returns the AST node corresponding to the block, if known.
*
* @return the AST node or null
*/
@Nullable
public ASTNode getNode() {
return myNode;
}
/**
@@ -529,7 +547,7 @@ public abstract class AbstractBlockWrapper {
myIndent = null;
myIndentFromParent = null;
myParent = null;
myWhiteSpace = null;
myWhiteSpaceBefore = null;
}
@Override
@@ -30,15 +30,15 @@ public class CompositeBlockWrapper extends AbstractBlockWrapper{
* {@link Block#getTextRange() text range associated with the given block}.
*
* @param block block to wrap
* @param whiteSpace white space before the block
* @param whiteSpaceBefore white space before the block
* @param parent wrapped block parent
*/
public CompositeBlockWrapper(final Block block, final WhiteSpace whiteSpace, @Nullable final CompositeBlockWrapper parent) {
super(block, whiteSpace, parent, block.getTextRange());
public CompositeBlockWrapper(final Block block, final WhiteSpace whiteSpaceBefore, @Nullable final CompositeBlockWrapper parent) {
super(block, whiteSpaceBefore, parent, block.getTextRange());
}
public CompositeBlockWrapper(final Block block, final WhiteSpace whiteSpace, final CompositeBlockWrapper parent, TextRange textRange) {
super(block, whiteSpace, parent, textRange);
public CompositeBlockWrapper(final Block block, final WhiteSpace whiteSpaceBefore, final CompositeBlockWrapper parent, TextRange textRange) {
super(block, whiteSpaceBefore, parent, textRange);
}
public List<AbstractBlockWrapper> getChildren() {
@@ -15,13 +15,14 @@
*/
package com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
/**
* @author yole
*/
public class DelegatingFormattingModel implements FormattingModel {
public class DelegatingFormattingModel implements FormattingModelEx {
private final FormattingModel myBaseModel;
private final Block myRootBlock;
@@ -47,6 +48,14 @@ public class DelegatingFormattingModel implements FormattingModel {
return myBaseModel.replaceWhiteSpace(textRange, whiteSpace);
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, ASTNode nodeAfter, String whiteSpace) {
if (myBaseModel instanceof FormattingModelEx) {
return ((FormattingModelEx) myBaseModel).replaceWhiteSpace(textRange, nodeAfter, whiteSpace);
}
return myBaseModel.replaceWhiteSpace(textRange, whiteSpace);
}
@Override
public TextRange shiftIndentInsideRange(TextRange range, int indent) {
return myBaseModel.shiftIndentInsideRange(range, indent);
@@ -338,7 +338,7 @@ class FormatProcessor {
WhiteSpace whiteSpace = block.getWhiteSpace();
CharSequence newWs = documentModel.adjustWhiteSpaceIfNecessary(
whiteSpace.generateWhiteSpace(getIndentOptionsToUse(block, indentOption)), whiteSpace.getStartOffset(),
whiteSpace.getEndOffset(), false
whiteSpace.getEndOffset(), block.getNode(), false
);
if (changes.size() > 10000) {
CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
@@ -386,7 +386,9 @@ class FormatProcessor {
final TextRange textRange = whiteSpace.getTextRange();
final TextRange wsRange = shiftRange(textRange, shift);
final String newWhiteSpace = _newWhiteSpace.toString();
TextRange newWhiteSpaceRange = model.replaceWhiteSpace(wsRange, newWhiteSpace);
TextRange newWhiteSpaceRange = model instanceof FormattingModelEx
? ((FormattingModelEx) model).replaceWhiteSpace(wsRange, block.getNode(), newWhiteSpace)
: model.replaceWhiteSpace(textRange, newWhiteSpace);
shift += newWhiteSpaceRange.getLength() - textRange.getLength();
@@ -16,6 +16,7 @@
package com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
@@ -378,7 +379,8 @@ public class FormatterImpl extends FormatterEx
}
WhiteSpace whiteSpace = blockAfterOffset != null ? blockAfterOffset.getWhiteSpace() : processor.getLastWhiteSpace();
return adjustLineIndent(offset, documentModel, processor, indentOptions, model, whiteSpace);
return adjustLineIndent(offset, documentModel, processor, indentOptions, model, whiteSpace,
blockAfterOffset != null ? blockAfterOffset.getNode() : null);
}
finally {
enableFormatting();
@@ -439,7 +441,8 @@ public class FormatterImpl extends FormatterEx
final FormatProcessor processor,
final CommonCodeStyleSettings.IndentOptions indentOptions,
final FormattingModel model,
final WhiteSpace whiteSpace)
final WhiteSpace whiteSpace,
ASTNode nodeAfter)
{
boolean wsContainsCaret = whiteSpace.getStartOffset() <= offset && offset < whiteSpace.getEndOffset();
@@ -450,7 +453,12 @@ public class FormatterImpl extends FormatterEx
final String newWS = whiteSpace.generateWhiteSpace(indentOptions, lineStartOffset, indent).toString();
if (!whiteSpace.equalsToString(newWS)) {
try {
model.replaceWhiteSpace(whiteSpace.getTextRange(), newWS);
if (model instanceof FormattingModelEx) {
((FormattingModelEx) model).replaceWhiteSpace(whiteSpace.getTextRange(), nodeAfter, newWS);
}
else {
model.replaceWhiteSpace(whiteSpace.getTextRange(), newWS);
}
}
finally {
model.commitChanges();
@@ -43,7 +43,7 @@ public abstract class AbstractWhiteSpaceFormattingStrategy implements WhiteSpace
public CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText,
@NotNull CharSequence text,
int startOffset,
int endOffset, CodeStyleSettings codeStyleSettings)
int endOffset, CodeStyleSettings codeStyleSettings, ASTNode nodeAfter)
{
// Does nothing
return whiteSpaceText;
@@ -85,7 +85,7 @@ public abstract class AbstractWhiteSpaceFormattingStrategy implements WhiteSpace
}
}
return adjustWhiteSpaceIfNecessary(whiteSpaceText, buffer, 0, endOffset - startOffset, codeStyleSettings);
return adjustWhiteSpaceIfNecessary(whiteSpaceText, buffer, 0, endOffset - startOffset, codeStyleSettings, null);
}
@Nullable
@@ -85,11 +85,11 @@ public class CompositeWhiteSpaceFormattingStrategy implements WhiteSpaceFormatti
public CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText,
@NotNull CharSequence text,
int startOffset,
int endOffset, CodeStyleSettings codeStyleSettings)
int endOffset, CodeStyleSettings codeStyleSettings, ASTNode nodeAfter)
{
CharSequence result = whiteSpaceText;
for (WhiteSpaceFormattingStrategy strategy : myStrategies) {
result = strategy.adjustWhiteSpaceIfNecessary(result, text, startOffset, endOffset, codeStyleSettings);
result = strategy.adjustWhiteSpaceIfNecessary(result, text, startOffset, endOffset, codeStyleSettings, nodeAfter);
}
return result;
}
@@ -18,7 +18,8 @@ package com.intellij.psi.formatter;
import com.intellij.formatting.Block;
import com.intellij.formatting.FormattingDocumentModel;
import com.intellij.formatting.FormattingModel;
import com.intellij.formatting.FormattingModelEx;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
@@ -36,7 +37,7 @@ import org.jetbrains.annotations.Nullable;
/**
* @author lesya
*/
public class DocumentBasedFormattingModel implements FormattingModel {
public class DocumentBasedFormattingModel implements FormattingModelEx {
private final Block myRootBlock;
private final FormattingDocumentModel myDocumentModel;
@NotNull private final Document myDocument;
@@ -86,8 +87,14 @@ public class DocumentBasedFormattingModel implements FormattingModel {
return myDocumentModel;
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, String whiteSpace) {
return replaceWhiteSpace(textRange, null, whiteSpace);
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, ASTNode nodeAfter, String whiteSpace) {
boolean removesStartMarker;
String marker;
@@ -121,7 +128,7 @@ public class DocumentBasedFormattingModel implements FormattingModel {
}
CharSequence whiteSpaceToUse = getDocumentModel().adjustWhiteSpaceIfNecessary(
whiteSpace, textRange.getStartOffset(), textRange.getEndOffset(), false
whiteSpace, textRange.getStartOffset(), textRange.getEndOffset(), nodeAfter, false
);
myDocument.replaceString(textRange.getStartOffset(),
@@ -17,6 +17,7 @@
package com.intellij.psi.formatter;
import com.intellij.formatting.FormattingDocumentModel;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@@ -149,11 +150,11 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel {
@NotNull
@Override
public CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText, int startOffset, int endOffset,
boolean changedViaPsi)
ASTNode nodeAfter, boolean changedViaPsi)
{
if (!changedViaPsi) {
return myWhiteSpaceStrategy.adjustWhiteSpaceIfNecessary(whiteSpaceText, myDocument.getCharsSequence(), startOffset, endOffset,
mySettings);
mySettings, nodeAfter);
}
final PsiElement element = myFile.findElementAt(startOffset);
@@ -18,21 +18,21 @@ package com.intellij.psi.formatter;
import com.intellij.formatting.Block;
import com.intellij.formatting.FormattingDocumentModel;
import com.intellij.formatting.FormattingModel;
import com.intellij.formatting.FormattingModelEx;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PsiBasedFormattingModel implements FormattingModel {
public class PsiBasedFormattingModel implements FormattingModelEx {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.PsiBasedFormattingModel");
@@ -50,12 +50,19 @@ public class PsiBasedFormattingModel implements FormattingModel {
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, String whiteSpace) {
return replaceWhiteSpace(textRange, null, whiteSpace);
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, ASTNode nodeAfter, String whiteSpace) {
String whiteSpaceToUse
= myDocumentModel.adjustWhiteSpaceIfNecessary(whiteSpace, textRange.getStartOffset(), textRange.getEndOffset(), true).toString();
= myDocumentModel.adjustWhiteSpaceIfNecessary(whiteSpace, textRange.getStartOffset(), textRange.getEndOffset(), nodeAfter, true).toString();
final String wsReplaced = replaceWithPSI(textRange, whiteSpaceToUse);
if (wsReplaced != null){
return new TextRange(textRange.getStartOffset(), textRange.getStartOffset() + wsReplaced.length());
} else {
@@ -71,20 +71,22 @@ public interface WhiteSpaceFormattingStrategy {
* document symbols sub-sequence if necessary.
*
*
*
* @param whiteSpaceText white space text to use by default for replacing sub-sequence of the given text
* @param text target text which region is to be replaced by the given white space symbols
* @param startOffset start offset to use with the given text (inclusive)
* @param endOffset end offset to use with the given text (exclusive)
* @param codeStyleSettings the code style settings
* @param nodeAfter the AST node following the whitespace, if known
* @return symbols to use for replacing <code>[startOffset; endOffset)</code> sub-sequence of the given text
*/
@NotNull
CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText, @NotNull CharSequence text, int startOffset,
int endOffset, CodeStyleSettings codeStyleSettings);
int endOffset, CodeStyleSettings codeStyleSettings, ASTNode nodeAfter);
/**
* PSI-based version of {@link #adjustWhiteSpaceIfNecessary(CharSequence, CharSequence, int, int, com.intellij.psi.codeStyle.CodeStyleSettings)}.
* PSI-based version of {@link #adjustWhiteSpaceIfNecessary(CharSequence, CharSequence, int, int, com.intellij.psi.codeStyle.CodeStyleSettings, com.intellij.lang.ASTNode)}.
* <p/>
* There is a possible case that particular changes are performed to PSI tree and it's not yet synchronized with the underlying
* document. Hence, we can't directly work with document char sequence but need to traverse PSI tree instead. I.e. we start with
@@ -49,7 +49,7 @@ public class WhiteSpaceFormattingStrategyAdapter implements WhiteSpaceFormatting
public CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText,
@NotNull CharSequence text,
int startOffset,
int endOffset, CodeStyleSettings codeStyleSettings) {
int endOffset, CodeStyleSettings codeStyleSettings, ASTNode nodeAfter) {
return whiteSpaceText;
}
@@ -1,5 +1,6 @@
package com.intellij.formatting;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.impl.DocumentImpl;
@@ -106,7 +107,7 @@ public class TestFormattingModel implements FormattingModel, FormattingDocumentM
@NotNull
@Override
public CharSequence adjustWhiteSpaceIfNecessary(@NotNull CharSequence whiteSpaceText, int startOffset, int endOffset,
boolean changedViaPsi) {
ASTNode nodeAfter, boolean changedViaPsi) {
return whiteSpaceText;
}
@@ -69,6 +69,7 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
PyFunction.class,
PySliceExpression.class,
PySubscriptionExpression.class,
PyGeneratorExpression.class
};
@Override
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.formatter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
@@ -61,6 +62,7 @@ public class PyWhiteSpaceFormattingStrategy extends StaticSymbolWhiteSpaceDefini
* @param startOffset start offset to use with the given text (inclusive)
* @param endOffset end offset to use with the given text (exclusive)
* @param codeStyleSettings the code style settings
* @param nodeAfter
* @return symbols to use for replacing <code>[startOffset; endOffset)</code> sub-sequence of the given text
*/
@NotNull
@@ -69,11 +71,16 @@ public class PyWhiteSpaceFormattingStrategy extends StaticSymbolWhiteSpaceDefini
@NotNull CharSequence text,
int startOffset,
int endOffset,
CodeStyleSettings codeStyleSettings)
CodeStyleSettings codeStyleSettings, ASTNode nodeAfter)
{
// The general idea is that '\' symbol before line feed should be preserved.
TIntIntHashMap initialBackSlashes = countBackSlashes(text, startOffset, endOffset);
if (initialBackSlashes.isEmpty()) {
if (nodeAfter != null && whiteSpaceText.length() > 0 && whiteSpaceText.charAt(0) == '\n' &&
PythonEnterHandler.needInsertBackslash(nodeAfter, false)) {
PyCodeStyleSettings settings = codeStyleSettings.getCustomSettings(PyCodeStyleSettings.class);
return (settings.SPACE_BEFORE_BACKSLASH ? " \\" : "\\") + whiteSpaceText;
}
return whiteSpaceText;
}
@@ -1,2 +1,2 @@
id = 1 if looooooooooooooooooooooooong_vaaaaaaaaaaaaaaaar == 'loooooooooooooooong_vaaaaaaaaaaaaaaaaaaaaaaaaaalue' else list(
'foo')[0]
id = 1 if looooooooooooooooooooooooong_vaaaaaaaaaaaaaaaar == 'loooooooooooooooong_vaaaaaaaaaaaaaaaaaaaaaaaaaalue' else \
list('foo')[0]
@@ -337,7 +337,7 @@ public class PyFormatterTest extends PyTestCase {
doTest();
}
public void _testWrapBeforeElse() { // PY-10319
public void testWrapBeforeElse() { // PY-10319
doTest(true);
}