Extract formatting methods to FormattingService, cleanup CoreCodeStyleUtil

GitOrigin-RevId: 1397168cdd1f07d11adff84d0e7c44914d493755
This commit is contained in:
Rustam Vishnyakov
2021-03-23 10:38:54 +00:00
committed by intellij-monorepo-bot
parent 69edaae5f9
commit 7e16cc9b20
9 changed files with 250 additions and 206 deletions
@@ -0,0 +1,18 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.formatting;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface FormattingRangesInfo {
boolean isWhitespaceReadOnly(@NotNull TextRange range);
boolean isReadOnly(@NotNull TextRange range);
boolean isOnInsertedLine(int offset);
@NotNull
List<TextRange> getTextRanges();
}
@@ -1,7 +1,10 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.formatting.service;
import com.intellij.formatting.FormattingRangesInfo;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -11,4 +14,10 @@ public interface FormattingService {
ExtensionPointName<FormattingService> EP_NAME = ExtensionPointName.create("com.intellij.formattingService");
boolean canFormat(@NotNull PsiFile file);
PsiElement formatElement(@NotNull PsiElement element, boolean canChangeWhiteSpaceOnly);
PsiElement formatElement(@NotNull PsiElement element, @NotNull TextRange range, boolean canChangeWhiteSpaceOnly);
void formatRanges(@NotNull PsiFile file, FormattingRangesInfo rangesInfo);
}
@@ -61,12 +61,12 @@ public class FormatTextRanges implements FormattingRangesInfo {
@Override
public boolean isWhitespaceReadOnly(final @NotNull TextRange range) {
return myRanges.stream().allMatch(formatTextRange -> formatTextRange.isWhitespaceReadOnly(range));
return ContainerUtil.and(myRanges, formatTextRange -> formatTextRange.isWhitespaceReadOnly(range));
}
@Override
public boolean isReadOnly(@NotNull TextRange range) {
return myRanges.stream().allMatch(formatTextRange -> formatTextRange.isReadOnly(range));
return ContainerUtil.and(myRanges, formatTextRange -> formatTextRange.isReadOnly(range));
}
@Override
@@ -105,7 +105,8 @@ public class FormatTextRanges implements FormattingRangesInfo {
return myRanges.size() == 1 && file.getTextRange().equals(myRanges.get(0).getTextRange());
}
public List<TextRange> getTextRanges() {
@Override
public @NotNull List<TextRange> getTextRanges() {
List<TextRange> ranges = ContainerUtil.map(myRanges, FormatTextRange::getTextRange);
ranges.sort(Segment.BY_START_OFFSET_THEN_END_OFFSET);
return ranges;
@@ -1,27 +0,0 @@
/*
* Copyright 2000-2016 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.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
public interface FormattingRangesInfo {
boolean isWhitespaceReadOnly(@NotNull TextRange range);
boolean isReadOnly(@NotNull TextRange range);
boolean isOnInsertedLine(int offset);
}
@@ -3,16 +3,20 @@ package com.intellij.formatting.service;
import com.intellij.application.options.CodeStyle;
import com.intellij.formatting.FormatTextRanges;
import com.intellij.formatting.FormattingRangesInfo;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.impl.source.codeStyle.CodeFormatterFacade;
import com.intellij.util.SlowOperations;
import com.intellij.psi.impl.source.codeStyle.CoreCodeStyleUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Consumer;
public final class CoreFormattingService implements FormattingService {
private final static Logger LOG =Logger.getInstance(CoreFormattingService.class);
@@ -21,4 +25,39 @@ public final class CoreFormattingService implements FormattingService {
return true;
}
@Override
public PsiElement formatElement(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly) {
ASTNode treeElement = element.getNode();
PsiFile file = element.getContainingFile();
final PsiElement formatted =
new CodeFormatterFacade(getSettings(file), element.getLanguage(), canChangeWhiteSpacesOnly)
.processElement(treeElement).getPsi();
if (!canChangeWhiteSpacesOnly) {
return CoreCodeStyleUtil.postProcessElement(file, formatted);
}
return formatted;
}
@Override
public PsiElement formatElement(@NotNull PsiElement element,
@NotNull TextRange range,
boolean canChangeWhiteSpacesOnly) {
ASTNode treeElement = element.getNode();
PsiFile file = element.getContainingFile();
final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(file), element.getLanguage());
final PsiElement formatted = codeFormatter.processRange(treeElement, range.getStartOffset(), range.getEndOffset()).getPsi();
return canChangeWhiteSpacesOnly ? formatted : CoreCodeStyleUtil.postProcessElement(file, formatted);
}
@Override
public void formatRanges(@NotNull PsiFile file, FormattingRangesInfo rangesInfo) {
List<CoreCodeStyleUtil.RangeFormatInfo> infos = CoreCodeStyleUtil.getRangeFormatInfoList(file, rangesInfo);
final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(file), file.getLanguage());
codeFormatter.processText(file, (FormatTextRanges)rangesInfo, true);
CoreCodeStyleUtil.postProcessRanges(file, infos, range -> CoreCodeStyleUtil.postProcessText(file, range));
}
private static CodeStyleSettings getSettings(@NotNull PsiFile file) {
return CodeStyle.getSettings(file);
}
}
@@ -2,12 +2,17 @@
package com.intellij.formatting.service;
import com.intellij.formatting.FormatTextRanges;
import com.intellij.formatting.FormattingRangesInfo;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.ExternalFormatProcessor;
import com.intellij.psi.impl.source.codeStyle.CoreCodeStyleUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* To support legacy API
*/
@@ -18,7 +23,26 @@ public final class ExternalFormatProcessorAdapter implements FormattingService {
return ExternalFormatProcessor.useExternalFormatter(file);
}
@Override
public PsiElement formatElement(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly) {
return ExternalFormatProcessor.formatElement(element, element.getTextRange(), canChangeWhiteSpacesOnly);
}
@Override
public PsiElement formatElement(@NotNull PsiElement element,
@NotNull TextRange range,
boolean canChangeWhiteSpacesOnly) {
return ExternalFormatProcessor.formatElement(element, range, canChangeWhiteSpacesOnly);
}
@Override
public void formatRanges(@NotNull PsiFile file, FormattingRangesInfo rangesInfo) {
List<CoreCodeStyleUtil.RangeFormatInfo> infos = CoreCodeStyleUtil.getRangeFormatInfoList(file, rangesInfo);
CoreCodeStyleUtil.postProcessRanges(
file, infos, range -> ExternalFormatProcessor.formatRangeInFile(file, range, false, false));
}
public void formatCollectedRanges(@NotNull PsiFile file, @NotNull FormatTextRanges ranges) {
CoreCodeStyleUtil.formatRanges(file, ranges);
formatRanges(file, ranges);
}
}
@@ -5,6 +5,7 @@ import com.intellij.CodeStyleBundle;
import com.intellij.application.options.CodeStyle;
import com.intellij.application.options.codeStyle.cache.CodeStyleCachingService;
import com.intellij.formatting.*;
import com.intellij.formatting.service.FormattingServiceUtil;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.lang.*;
import com.intellij.lang.injection.InjectedLanguageManager;
@@ -71,23 +72,12 @@ public class CodeStyleManagerImpl extends CodeStyleManager implements Formatting
return element;
}
ASTNode treeElement = element.getNode();
final PsiFile file = element.getContainingFile();
if (file == null)
return element;
if (ExternalFormatProcessor.useExternalFormatter(file)) {
return ExternalFormatProcessor.formatElement(element, element.getTextRange(), canChangeWhiteSpacesOnly);
}
final PsiElement formatted =
new CodeFormatterFacade(getSettings(file), element.getLanguage(), canChangeWhiteSpacesOnly)
.processElement(treeElement).getPsi();
if (!canChangeWhiteSpacesOnly) {
return CoreCodeStyleUtil.postProcessElement(file, formatted);
}
return formatted;
return FormattingServiceUtil.findService(file).formatElement(element, canChangeWhiteSpacesOnly);
}
@Override
@@ -171,7 +161,7 @@ public class CodeStyleManagerImpl extends CodeStyleManager implements Formatting
removeEndingWhiteSpaceFromEachRange(file, ranges);
}
CoreCodeStyleUtil.formatRanges(file, ranges);
FormattingServiceUtil.findService(file).formatRanges(file, ranges);
if (caretKeeper != null) {
caretKeeper.restoreCaretPosition();
@@ -215,15 +205,8 @@ public class CodeStyleManagerImpl extends CodeStyleManager implements Formatting
return element;
}
ASTNode treeElement = element.getNode();
final PsiFile file = element.getContainingFile();
if (ExternalFormatProcessor.useExternalFormatter(file)) {
return ExternalFormatProcessor.formatElement(element, TextRange.create(startOffset, endOffset), canChangeWhiteSpacesOnly);
}
final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(file), element.getLanguage());
final PsiElement formatted = codeFormatter.processRange(treeElement, startOffset, endOffset).getPsi();
return canChangeWhiteSpacesOnly ? formatted : CoreCodeStyleUtil.postProcessElement(file, formatted);
return FormattingServiceUtil.findService(element.getContainingFile())
.formatElement(element,TextRange.create(startOffset, endOffset), canChangeWhiteSpacesOnly);
}
@@ -3,52 +3,41 @@ package com.intellij.psi.impl.source.codeStyle;
import com.intellij.application.options.CodeStyle;
import com.intellij.diagnostic.PluginException;
import com.intellij.formatting.FormatTextRanges;
import com.intellij.formatting.FormatterTagHandler;
import com.intellij.lang.ASTNode;
import com.intellij.formatting.FormattingRangesInfo;
import com.intellij.lang.CompositeLanguage;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageFormatting;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.ExternalFormatProcessor;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.CharTable;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@ApiStatus.Internal
public class CoreCodeStyleUtil {
private final static Logger LOG = Logger.getInstance(CoreCodeStyleUtil.class);
@NonNls private static final String DUMMY_IDENTIFIER = "xxx";
private static final ThreadLocal<ProcessingUnderProgressInfo> SEQUENTIAL_PROCESSING_ALLOWED
= ThreadLocal.withInitial(() -> new ProcessingUnderProgressInfo());
private CoreCodeStyleUtil() {
}
static PsiElement postProcessElement(@NotNull PsiFile file, @NotNull final PsiElement formatted) {
public static PsiElement postProcessElement(@NotNull PsiFile file, @NotNull final PsiElement formatted) {
PsiElement result = formatted;
CodeStyleSettings settingsForFile = CodeStyle.getSettings(file);
if (settingsForFile.FORMATTER_TAGS_ENABLED && formatted instanceof PsiFile) {
@@ -76,7 +65,8 @@ public class CoreCodeStyleUtil {
return result;
}
public static void formatRanges(@NotNull PsiFile file, @NotNull FormatTextRanges ranges) {
public static List<RangeFormatInfo> getRangeFormatInfoList(@NotNull PsiFile file, @NotNull FormattingRangesInfo ranges) {
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(file.getProject());
List<RangeFormatInfo> infos = new ArrayList<>();
@@ -98,31 +88,27 @@ public class CoreCodeStyleUtil {
formatToEnd
));
}
return infos;
}
if (!ExternalFormatProcessor.useExternalFormatter(file)) {
final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(file), file.getLanguage());
codeFormatter.processText(file, ranges, true);
}
for (RangeFormatInfo info : infos) {
public static void postProcessRanges(@NotNull PsiFile file,
@NotNull List<RangeFormatInfo> rangeFormatInfoList,
@NotNull Consumer<TextRange> postProcessFormatter) {
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(file.getProject());
for (RangeFormatInfo info : rangeFormatInfoList) {
final PsiElement startElement = info.startPointer == null ? null : info.startPointer.getElement();
final PsiElement endElement = info.endPointer == null ? null : info.endPointer.getElement();
if ((startElement != null || info.fromStart) && (endElement != null || info.toEnd)) {
TextRange currRange = new TextRange(info.fromStart ? 0 : startElement.getTextRange().getStartOffset(),
info.toEnd ? file.getTextLength() : endElement.getTextRange().getEndOffset());
if (ExternalFormatProcessor.useExternalFormatter(file)) {
ExternalFormatProcessor.formatRangeInFile(file, currRange, false, false);
}
else {
postProcessText(file, currRange);
}
postProcessFormatter.accept(currRange);
}
if (info.startPointer != null) smartPointerManager.removePointer(info.startPointer);
if (info.endPointer != null) smartPointerManager.removePointer(info.endPointer);
}
}
private static void postProcessText(@NotNull final PsiFile file, @NotNull final TextRange textRange) {
public static void postProcessText(@NotNull final PsiFile file, @NotNull final TextRange textRange) {
if (!getSettings(file).FORMATTER_TAGS_ENABLED) {
TextRange currentRange = textRange;
for (final PostFormatProcessor myPostFormatProcessor : PostFormatProcessor.EP_NAME.getExtensionList()) {
@@ -146,7 +132,7 @@ public class CoreCodeStyleUtil {
}
}
private static class RangeFormatInfo{
public static class RangeFormatInfo{
private final SmartPsiElementPointer<?> startPointer;
private final SmartPsiElementPointer<?> endPointer;
private final boolean fromStart;
@@ -165,7 +151,7 @@ public class CoreCodeStyleUtil {
}
@Nullable
static PsiElement findElementInTreeWithFormatterEnabled(final PsiFile file, final int offset) {
public static PsiElement findElementInTreeWithFormatterEnabled(final PsiFile file, final int offset) {
final PsiElement bottomost = file.findElementAt(offset);
if (bottomost != null && LanguageFormatting.INSTANCE.forContext(bottomost) != null){
return bottomost;
@@ -179,85 +165,7 @@ public class CoreCodeStyleUtil {
return bottomost;
}
/**
* Formatter trims line that contains white spaces symbols only, however, there is a possible case that we want
* to preserve them for particular line
* (e.g. for live template that defines line with whitespaces that contains $END$ marker: templateText $END$).
* <p/>
* Current approach is to do the following:
* <pre>
* <ol>
* <li>Insert dummy text at the end of the blank line which white space symbols should be preserved;</li>
* <li>Perform formatting;</li>
* <li>Remove dummy text;</li>
* </ol>
* </pre>
* <p/>
* This method inserts that dummy comment (fallback to identifier {@code xxx}, see {@link #createMarker(PsiFile, int)})
* if necessary.
* <p/>
* <b>Note:</b> it's expected that the whole white space region that contains given offset is processed in a way that all
* {@link RangeMarker range markers} registered for the given offset are expanded to the whole white space region.
* E.g. there is a possible case that particular range marker serves for defining formatting range, hence, its start/end offsets
* are updated correspondingly after current method call and whole white space region is reformatted.
*
* @param file target PSI file
* @param document target document
* @param offset offset that defines end boundary of the target line text fragment (start boundary is the first line's symbol)
* @return text range that points to the newly inserted dummy text if any; {@code null} otherwise
* @throws IncorrectOperationException if given file is read-only
*/
@Nullable
public static TextRange insertNewLineIndentMarker(@NotNull PsiFile file, @NotNull Document document, int offset) {
CharSequence text = document.getImmutableCharSequence();
if (offset <= 0 || offset >= text.length() || !isWhiteSpaceSymbol(text.charAt(offset))) {
return null;
}
if (!isWhiteSpaceSymbol(text.charAt(offset - 1))) {
return null; // no whitespaces before offset
}
int end = offset;
for (; end < text.length(); end++) {
if (text.charAt(end) == '\n') {
break; // line is empty till the end
}
if (!isWhiteSpaceSymbol(text.charAt(end))) {
return null;
}
}
String marker = createMarker(file, offset);
document.insertString(offset, marker);
return new TextRange(offset, offset + marker.length());
}
private static boolean isWhiteSpaceSymbol(char c) {
return c == ' ' || c == '\t' || c == '\n';
}
private static @NotNull String createMarker(@NotNull PsiFile file, int offset) {
Project project = file.getProject();
PsiElement injectedElement = InjectedLanguageManager.getInstance(project).findInjectedElementAt(file, offset);
Language language = injectedElement != null ? injectedElement.getLanguage() : PsiUtilCore.getLanguageAtOffset(file, offset);
setSequentialProcessingAllowed(false);
NewLineIndentMarkerProvider markerProvider = NewLineIndentMarkerProvider.EP.forLanguage(language);
String marker = markerProvider == null ? null : markerProvider.createMarker(file, offset);
if (marker != null) {
return marker;
}
PsiComment comment = null;
try {
comment = PsiParserFacade.SERVICE.getInstance(project).createLineOrBlockCommentFromText(language, "");
}
catch (Throwable ignored) {
}
String text = comment != null ? comment.getText() : null;
return text != null ? text : DUMMY_IDENTIFIER;
}
@ApiStatus.Internal
public static void setSequentialProcessingAllowed(boolean allowed) {
@@ -305,38 +213,5 @@ public class CoreCodeStyleUtil {
return CodeStyle.getSettings(file);
}
/**
* Allows to check if given offset points to white space element within the given PSI file and return that white space
* element in the case of positive answer.
*
* @param file target file
* @param offset offset that might point to white space element within the given PSI file
* @return target white space element for the given offset within the given file (if any); {@code null} otherwise
*/
@Nullable
public static PsiElement findWhiteSpaceNode(@NotNull PsiFile file, int offset) {
return doFindWhiteSpaceNode(file, offset).first;
}
@NotNull
private static Pair<PsiElement, CharTable> doFindWhiteSpaceNode(@NotNull PsiFile file, int offset) {
ASTNode astNode = SourceTreeToPsiMap.psiElementToTree(file);
if (!(astNode instanceof FileElement)) {
return new Pair<>(null, null);
}
PsiElement elementAt = InjectedLanguageManager.getInstance(file.getProject()).findInjectedElementAt(file, offset);
final CharTable charTable = ((FileElement)astNode).getCharTable();
if (elementAt == null) {
elementAt = findElementInTreeWithFormatterEnabled(file, offset);
}
if( elementAt == null) {
return new Pair<>(null, charTable);
}
ASTNode node = elementAt.getNode();
if (node == null || node.getElementType() != TokenType.WHITE_SPACE) {
return new Pair<>(null, charTable);
}
return Pair.create(elementAt, charTable);
}
}
@@ -7,8 +7,10 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.codeInsight.template.Expression;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.idea.ActionsBundle;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LangBundle;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.IdeActions;
@@ -19,16 +21,24 @@ import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.codeStyle.CoreCodeStyleUtil;
import com.intellij.psi.impl.source.codeStyle.NewLineIndentMarkerProvider;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
import com.intellij.util.CharTable;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,6 +46,8 @@ import java.util.ArrayList;
import java.util.List;
final class InteractiveTemplateStateProcessor implements TemplateStateProcessor {
@NonNls private static final String DUMMY_IDENTIFIER = "xxx";
private boolean myLookupShown;
@Override
@@ -52,12 +64,42 @@ final class InteractiveTemplateStateProcessor implements TemplateStateProcessor
@Override
public TextRange insertNewLineIndentMarker(PsiFile file, Document document, int offset) {
return CoreCodeStyleUtil.insertNewLineIndentMarker(file, document, offset);
return doInsertNewLineIndentMarker(file, document, offset);
}
/**
* Allows to check if given offset points to white space element within the given PSI file and return that white space
* element in the case of positive answer.
*
* @param file target file
* @param offset offset that might point to white space element within the given PSI file
* @return target white space element for the given offset within the given file (if any); {@code null} otherwise
*/
@Override
public PsiElement findWhiteSpaceNode(PsiFile file, int offset) {
return CoreCodeStyleUtil.findWhiteSpaceNode(file, offset);
return doFindWhiteSpaceNode(file, offset).first;
}
@NotNull
private static Pair<PsiElement, CharTable> doFindWhiteSpaceNode(@NotNull PsiFile file, int offset) {
ASTNode astNode = SourceTreeToPsiMap.psiElementToTree(file);
if (!(astNode instanceof FileElement)) {
return new Pair<>(null, null);
}
PsiElement elementAt = InjectedLanguageManager.getInstance(file.getProject()).findInjectedElementAt(file, offset);
final CharTable charTable = ((FileElement)astNode).getCharTable();
if (elementAt == null) {
elementAt = CoreCodeStyleUtil.findElementInTreeWithFormatterEnabled(file, offset);
}
if( elementAt == null) {
return new Pair<>(null, charTable);
}
ASTNode node = elementAt.getNode();
if (node == null || node.getElementType() != TokenType.WHITE_SPACE) {
return new Pair<>(null, charTable);
}
return Pair.create(elementAt, charTable);
}
@Override
@@ -164,6 +206,86 @@ final class InteractiveTemplateStateProcessor implements TemplateStateProcessor
return false;
}
/**
* Formatter trims line that contains white spaces symbols only, however, there is a possible case that we want
* to preserve them for particular line
* (e.g. for live template that defines line with whitespaces that contains $END$ marker: templateText $END$).
* <p/>
* Current approach is to do the following:
* <pre>
* <ol>
* <li>Insert dummy text at the end of the blank line which white space symbols should be preserved;</li>
* <li>Perform formatting;</li>
* <li>Remove dummy text;</li>
* </ol>
* </pre>
* <p/>
* This method inserts that dummy comment (fallback to identifier {@code xxx}, see {@link #createMarker(PsiFile, int)})
* if necessary.
* <p/>
* <b>Note:</b> it's expected that the whole white space region that contains given offset is processed in a way that all
* {@link RangeMarker range markers} registered for the given offset are expanded to the whole white space region.
* E.g. there is a possible case that particular range marker serves for defining formatting range, hence, its start/end offsets
* are updated correspondingly after current method call and whole white space region is reformatted.
*
* @param file target PSI file
* @param document target document
* @param offset offset that defines end boundary of the target line text fragment (start boundary is the first line's symbol)
* @return text range that points to the newly inserted dummy text if any; {@code null} otherwise
* @throws IncorrectOperationException if given file is read-only
*/
@Nullable
private static TextRange doInsertNewLineIndentMarker(@NotNull PsiFile file, @NotNull Document document, int offset) {
CharSequence text = document.getImmutableCharSequence();
if (offset <= 0 || offset >= text.length() || !isWhiteSpaceSymbol(text.charAt(offset))) {
return null;
}
if (!isWhiteSpaceSymbol(text.charAt(offset - 1))) {
return null; // no whitespaces before offset
}
int end = offset;
for (; end < text.length(); end++) {
if (text.charAt(end) == '\n') {
break; // line is empty till the end
}
if (!isWhiteSpaceSymbol(text.charAt(end))) {
return null;
}
}
String marker = createMarker(file, offset);
document.insertString(offset, marker);
return new TextRange(offset, offset + marker.length());
}
private static boolean isWhiteSpaceSymbol(char c) {
return c == ' ' || c == '\t' || c == '\n';
}
private static @NotNull String createMarker(@NotNull PsiFile file, int offset) {
Project project = file.getProject();
PsiElement injectedElement = InjectedLanguageManager.getInstance(project).findInjectedElementAt(file, offset);
Language language = injectedElement != null ? injectedElement.getLanguage() : PsiUtilCore.getLanguageAtOffset(file, offset);
CoreCodeStyleUtil.setSequentialProcessingAllowed(false);
NewLineIndentMarkerProvider markerProvider = NewLineIndentMarkerProvider.EP.forLanguage(language);
String marker = markerProvider == null ? null : markerProvider.createMarker(file, offset);
if (marker != null) {
return marker;
}
PsiComment comment = null;
try {
comment = PsiParserFacade.SERVICE.getInstance(project).createLineOrBlockCommentFromText(language, "");
}
catch (Throwable ignored) {
}
String text = comment != null ? comment.getText() : null;
return text != null ? text : DUMMY_IDENTIFIER;
}
private static final class MyBasicUndoableAction extends BasicUndoableAction implements Disposable {
private final Project myProject;
@Nullable