• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.googlejavaformat.intellij;
18 
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableSet;
21 import com.google.common.collect.Range;
22 import com.google.googlejavaformat.java.Formatter;
23 import com.google.googlejavaformat.java.FormatterException;
24 import com.google.googlejavaformat.java.JavaFormatterOptions;
25 import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
26 import com.intellij.formatting.service.AsyncDocumentFormattingService;
27 import com.intellij.formatting.service.AsyncFormattingRequest;
28 import com.intellij.ide.highlighter.JavaFileType;
29 import com.intellij.lang.ImportOptimizer;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.TextRange;
32 import com.intellij.psi.PsiFile;
33 import java.util.Collection;
34 import java.util.List;
35 import java.util.Set;
36 import org.jetbrains.annotations.NotNull;
37 
38 /** Uses {@code google-java-format} to reformat code. */
39 public class GoogleJavaFormatFormattingService extends AsyncDocumentFormattingService {
40 
41   public static final ImmutableSet<ImportOptimizer> IMPORT_OPTIMIZERS =
42       ImmutableSet.of(new GoogleJavaFormatImportOptimizer());
43 
44   @Override
createFormattingTask(AsyncFormattingRequest request)45   protected FormattingTask createFormattingTask(AsyncFormattingRequest request) {
46     Project project = request.getContext().getProject();
47 
48     if (!JreConfigurationChecker.checkJreConfiguration(project)) {
49       return null;
50     }
51 
52     Style style = GoogleJavaFormatSettings.getInstance(project).getStyle();
53     Formatter formatter = createFormatter(style, request.canChangeWhitespaceOnly());
54     return new GoogleJavaFormatFormattingTask(formatter, request);
55   }
56 
57   @Override
getNotificationGroupId()58   protected String getNotificationGroupId() {
59     return Notifications.PARSING_ERROR_NOTIFICATION_GROUP;
60   }
61 
62   @Override
getName()63   protected String getName() {
64     return "google-java-format";
65   }
66 
createFormatter(Style style, boolean canChangeWhiteSpaceOnly)67   private static Formatter createFormatter(Style style, boolean canChangeWhiteSpaceOnly) {
68     JavaFormatterOptions.Builder optBuilder = JavaFormatterOptions.builder().style(style);
69     if (canChangeWhiteSpaceOnly) {
70       optBuilder.formatJavadoc(false).reorderModifiers(false);
71     }
72     return new Formatter(optBuilder.build());
73   }
74 
75   @Override
getFeatures()76   public @NotNull Set<Feature> getFeatures() {
77     return Set.of(Feature.FORMAT_FRAGMENTS, Feature.OPTIMIZE_IMPORTS);
78   }
79 
80   @Override
canFormat(@otNull PsiFile file)81   public boolean canFormat(@NotNull PsiFile file) {
82     return JavaFileType.INSTANCE.equals(file.getFileType())
83         && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled();
84   }
85 
86   @Override
getImportOptimizers(@otNull PsiFile file)87   public @NotNull Set<ImportOptimizer> getImportOptimizers(@NotNull PsiFile file) {
88     return IMPORT_OPTIMIZERS;
89   }
90 
91   private static final class GoogleJavaFormatFormattingTask implements FormattingTask {
92     private final Formatter formatter;
93     private final AsyncFormattingRequest request;
94 
GoogleJavaFormatFormattingTask(Formatter formatter, AsyncFormattingRequest request)95     private GoogleJavaFormatFormattingTask(Formatter formatter, AsyncFormattingRequest request) {
96       this.formatter = formatter;
97       this.request = request;
98     }
99 
100     @Override
run()101     public void run() {
102       try {
103         String formattedText = formatter.formatSource(request.getDocumentText(), toRanges(request));
104         request.onTextReady(formattedText);
105       } catch (FormatterException e) {
106         request.onError(
107             Notifications.PARSING_ERROR_TITLE,
108             Notifications.parsingErrorMessage(request.getContext().getContainingFile().getName()));
109       }
110     }
111 
toRanges(AsyncFormattingRequest request)112     private static Collection<Range<Integer>> toRanges(AsyncFormattingRequest request) {
113       if (isWholeFile(request)) {
114         // The IDE sometimes passes invalid ranges when the file is unsaved before invoking the
115         // formatter. So this is a workaround for that issue.
116         return ImmutableList.of(Range.closedOpen(0, request.getDocumentText().length()));
117       }
118       return request.getFormattingRanges().stream()
119           .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset()))
120           .collect(ImmutableList.toImmutableList());
121     }
122 
isWholeFile(AsyncFormattingRequest request)123     private static boolean isWholeFile(AsyncFormattingRequest request) {
124       List<TextRange> ranges = request.getFormattingRanges();
125       return ranges.size() == 1
126           && ranges.get(0).getStartOffset() == 0
127           // using greater than or equal because ranges are sometimes passed inaccurately
128           && ranges.get(0).getEndOffset() >= request.getDocumentText().length();
129     }
130 
131     @Override
isRunUnderProgress()132     public boolean isRunUnderProgress() {
133       return true;
134     }
135 
136     @Override
cancel()137     public boolean cancel() {
138       return false;
139     }
140   }
141 }
142