|
19 | 19 | import static java.util.Comparator.comparing; |
20 | 20 |
|
21 | 21 | import com.google.common.collect.ImmutableList; |
22 | | -import com.google.common.collect.Range; |
23 | 22 | import com.google.googlejavaformat.java.Formatter; |
24 | | -import com.google.googlejavaformat.java.FormatterException; |
25 | | -import com.google.googlejavaformat.java.Replacement; |
| 23 | +import com.google.googlejavaformat.java.JavaFormatterOptions; |
| 24 | +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; |
26 | 25 | import com.intellij.openapi.application.ApplicationManager; |
27 | 26 | import com.intellij.openapi.command.WriteCommandAction; |
28 | 27 | import com.intellij.openapi.editor.Document; |
29 | 28 | import com.intellij.openapi.fileTypes.StdFileTypes; |
30 | 29 | import com.intellij.openapi.util.TextRange; |
31 | 30 | import com.intellij.psi.PsiDocumentManager; |
| 31 | +import com.intellij.psi.PsiElement; |
32 | 32 | import com.intellij.psi.PsiFile; |
33 | 33 | import com.intellij.psi.codeStyle.CodeStyleManager; |
34 | 34 | import com.intellij.psi.impl.CheckUtil; |
35 | 35 | import com.intellij.util.IncorrectOperationException; |
36 | 36 | import java.util.Collection; |
37 | | -import java.util.List; |
38 | | -import java.util.Optional; |
39 | | -import java.util.stream.Collectors; |
| 37 | +import java.util.Map; |
| 38 | +import java.util.Map.Entry; |
| 39 | +import java.util.TreeMap; |
40 | 40 | import org.jetbrains.annotations.NotNull; |
41 | 41 |
|
42 | 42 | /** |
43 | 43 | * A {@link CodeStyleManager} implementation which formats .java files with google-java-format. |
44 | 44 | * Formatting of all other types of files is delegated to IJ's default implementation. |
45 | | - * |
46 | | - * @author bcsf@google.com (Brian Chang) |
47 | 45 | */ |
48 | | -public abstract class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator { |
| 46 | +class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator { |
49 | 47 |
|
50 | 48 | public GoogleJavaFormatCodeStyleManager(@NotNull CodeStyleManager original) { |
51 | 49 | super(original); |
52 | 50 | } |
53 | 51 |
|
54 | 52 | @Override |
55 | | - public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) |
| 53 | + public void reformatText(PsiFile file, int startOffset, int endOffset) |
56 | 54 | throws IncorrectOperationException { |
57 | | - Optional<Formatter> formatter = getFormatterForFile(file); |
58 | | - if (formatter.isPresent() && StdFileTypes.JAVA.equals(file.getFileType())) { |
59 | | - formatInternal( |
60 | | - formatter.get(), file, ImmutableList.of(Range.closedOpen(startOffset, endOffset))); |
| 55 | + if (overrideFormatterForFile(file)) { |
| 56 | + formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset))); |
61 | 57 | } else { |
62 | 58 | super.reformatText(file, startOffset, endOffset); |
63 | 59 | } |
64 | 60 | } |
65 | 61 |
|
66 | 62 | @Override |
67 | | - public void reformatText(@NotNull PsiFile file, @NotNull Collection<TextRange> ranges) |
| 63 | + public void reformatText(PsiFile file, Collection<TextRange> ranges) |
68 | 64 | throws IncorrectOperationException { |
69 | | - Optional<Formatter> formatter = getFormatterForFile(file); |
70 | | - if (formatter.isPresent() && StdFileTypes.JAVA.equals(file.getFileType())) { |
71 | | - formatInternal(formatter.get(), file, convertToRanges(ranges)); |
| 65 | + if (overrideFormatterForFile(file)) { |
| 66 | + formatInternal(file, ranges); |
72 | 67 | } else { |
73 | 68 | super.reformatText(file, ranges); |
74 | 69 | } |
75 | 70 | } |
76 | 71 |
|
77 | 72 | @Override |
78 | | - public void reformatTextWithContext(@NotNull PsiFile file, @NotNull Collection<TextRange> ranges) |
79 | | - throws IncorrectOperationException { |
80 | | - Optional<Formatter> formatter = getFormatterForFile(file); |
81 | | - if (formatter.isPresent() && StdFileTypes.JAVA.equals(file.getFileType())) { |
82 | | - formatInternal(formatter.get(), file, convertToRanges(ranges)); |
| 73 | + public void reformatTextWithContext(PsiFile file, Collection<TextRange> ranges) { |
| 74 | + if (overrideFormatterForFile(file)) { |
| 75 | + formatInternal(file, ranges); |
83 | 76 | } else { |
84 | 77 | super.reformatTextWithContext(file, ranges); |
85 | 78 | } |
86 | 79 | } |
87 | 80 |
|
88 | | - /** |
89 | | - * Get the {@link Formatter} to be used with the given file, or absent to use the built-in |
90 | | - * IntelliJ formatter. |
91 | | - */ |
92 | | - protected abstract Optional<Formatter> getFormatterForFile(PsiFile file); |
| 81 | + @Override |
| 82 | + public PsiElement reformatRange( |
| 83 | + PsiElement element, int startOffset, int endOffset, boolean canChangeWhiteSpacesOnly) { |
| 84 | + // Only handle elements that are PsiFile for now -- otherwise we need to search for some |
| 85 | + // element within the file at new locations given the original startOffset and endOffsets |
| 86 | + // to serve as the return value. |
| 87 | + PsiFile file = element instanceof PsiFile ? (PsiFile) element : null; |
| 88 | + if (file != null && canChangeWhiteSpacesOnly && overrideFormatterForFile(file)) { |
| 89 | + formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset))); |
| 90 | + return file; |
| 91 | + } else { |
| 92 | + return super.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly); |
| 93 | + } |
| 94 | + } |
93 | 95 |
|
94 | | - private void formatInternal(Formatter formatter, PsiFile file, List<Range<Integer>> ranges) |
95 | | - throws IncorrectOperationException { |
| 96 | + /** Return whether or not this formatter can handle formatting the given file. */ |
| 97 | + private boolean overrideFormatterForFile(PsiFile file) { |
| 98 | + return StdFileTypes.JAVA.equals(file.getFileType()) |
| 99 | + && GoogleJavaFormatSettings.getInstance(getProject()).isEnabled(); |
| 100 | + } |
| 101 | + |
| 102 | + private void formatInternal(PsiFile file, Collection<TextRange> ranges) { |
96 | 103 | ApplicationManager.getApplication().assertWriteAccessAllowed(); |
97 | | - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| 104 | + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject()); |
| 105 | + documentManager.commitAllDocuments(); |
98 | 106 | CheckUtil.checkWritable(file); |
99 | 107 |
|
100 | | - Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file); |
101 | | - if (document != null) { |
102 | | - try { |
103 | | - List<Replacement> replacements = |
104 | | - formatter |
105 | | - .getFormatReplacements(document.getText(), ranges) |
106 | | - .stream() |
107 | | - .sorted( |
108 | | - comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed()) |
109 | | - .collect(Collectors.toList()); |
110 | | - performReplacements(document, replacements); |
111 | | - } catch (FormatterException e) { |
112 | | - // Do not format on errors |
113 | | - } |
| 108 | + Document document = documentManager.getDocument(file); |
| 109 | + |
| 110 | + if (document == null) { |
| 111 | + return; |
114 | 112 | } |
| 113 | + // If there are postponed PSI changes (e.g., during a refactoring), just abort. |
| 114 | + // If we apply them now, then the incoming text ranges may no longer be valid. |
| 115 | + if (documentManager.isDocumentBlockedByPsi(document)) { |
| 116 | + return; |
| 117 | + } |
| 118 | + |
| 119 | + format(document, ranges); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Format the ranges of the given document. |
| 124 | + * |
| 125 | + * <p>Overriding methods will need to modify the document with the result of the external |
| 126 | + * formatter (usually using {@link #performReplacements(Document, Map)}. |
| 127 | + */ |
| 128 | + private void format(Document document, Collection<TextRange> ranges) { |
| 129 | + Style style = GoogleJavaFormatSettings.getInstance(getProject()).getStyle(); |
| 130 | + Formatter formatter = new Formatter(JavaFormatterOptions.builder().style(style).build()); |
| 131 | + performReplacements( |
| 132 | + document, FormatterUtil.getReplacements(formatter, document.getText(), ranges)); |
115 | 133 | } |
116 | 134 |
|
117 | 135 | private void performReplacements( |
118 | | - final Document document, final List<Replacement> reverseSortedReplacements) { |
| 136 | + final Document document, final Map<TextRange, String> replacements) { |
| 137 | + |
| 138 | + if (replacements.isEmpty()) { |
| 139 | + return; |
| 140 | + } |
| 141 | + |
| 142 | + TreeMap<TextRange, String> sorted = new TreeMap<>(comparing(TextRange::getStartOffset)); |
| 143 | + sorted.putAll(replacements); |
119 | 144 | WriteCommandAction.runWriteCommandAction( |
120 | 145 | getProject(), |
121 | 146 | () -> { |
122 | | - for (Replacement replacement : reverseSortedReplacements) { |
123 | | - Range<Integer> range = replacement.getReplaceRange(); |
| 147 | + for (Entry<TextRange, String> entry : sorted.descendingMap().entrySet()) { |
124 | 148 | document.replaceString( |
125 | | - range.lowerEndpoint(), range.upperEndpoint(), replacement.getReplacementString()); |
| 149 | + entry.getKey().getStartOffset(), entry.getKey().getEndOffset(), entry.getValue()); |
126 | 150 | } |
127 | 151 | PsiDocumentManager.getInstance(getProject()).commitDocument(document); |
128 | 152 | }); |
129 | 153 | } |
130 | | - |
131 | | - private static List<Range<Integer>> convertToRanges(Collection<TextRange> textRanges) { |
132 | | - ImmutableList.Builder<Range<Integer>> ranges = ImmutableList.builder(); |
133 | | - for (TextRange textRange : textRanges) { |
134 | | - ranges.add(Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset())); |
135 | | - } |
136 | | - return ranges.build(); |
137 | | - } |
138 | 154 | } |
0 commit comments