mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Extract formatting methods to FormattingService, cleanup CoreCodeStyleUtil
GitOrigin-RevId: 1397168cdd1f07d11adff84d0e7c44914d493755
This commit is contained in:
committed by
intellij-monorepo-bot
parent
69edaae5f9
commit
7e16cc9b20
@@ -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);
|
||||
}
|
||||
+43
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-1
@@ -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
-22
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+20
-145
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+126
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user