Avoid creating multiple fixes on batch runs
[fedora-idea.git] / plugins / spellchecker / src / com / intellij / spellchecker / inspections / SpellCheckingInspection.java
blob26ef6f4abb65470e0e0fef9144fff52aebe6fcb4
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.spellchecker.inspections;
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.codeInspection.*;
20 import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
21 import com.intellij.lang.*;
22 import com.intellij.lang.refactoring.NamesValidator;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.fileTypes.PlainTextLanguage;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.psi.PsiElement;
27 import com.intellij.psi.PsiElementVisitor;
28 import com.intellij.psi.tree.IElementType;
29 import com.intellij.spellchecker.SpellCheckerManager;
30 import com.intellij.spellchecker.quickfixes.AcceptWordAsCorrect;
31 import com.intellij.spellchecker.quickfixes.ChangeTo;
32 import com.intellij.spellchecker.quickfixes.RenameTo;
33 import com.intellij.spellchecker.quickfixes.SpellCheckerQuickFix;
34 import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy;
35 import com.intellij.spellchecker.tokenizer.Token;
36 import com.intellij.spellchecker.tokenizer.Tokenizer;
37 import com.intellij.spellchecker.util.SpellCheckerBundle;
38 import com.intellij.util.containers.hash.HashMap;
39 import org.jetbrains.annotations.Nls;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
44 import javax.swing.*;
45 import java.awt.*;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.Map;
52 public class SpellCheckingInspection extends LocalInspectionTool {
54 public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME = "SpellCheckingInspection";
55 private static final AcceptWordAsCorrect BATCH_ACCEPT_FIX = new AcceptWordAsCorrect();
57 @Nls
58 @NotNull
59 public String getGroupDisplayName() {
60 return SpellCheckerBundle.message("spelling");
63 @Nls
64 @NotNull
65 public String getDisplayName() {
66 return SpellCheckerBundle.message("spellchecking.inspection.name");
69 @NonNls
70 @NotNull
71 public String getShortName() {
72 return SPELL_CHECKING_INSPECTION_TOOL_NAME;
75 public boolean isEnabledByDefault() {
76 return true;
79 @NotNull
80 public HighlightDisplayLevel getDefaultLevel() {
81 return SpellCheckerManager.getHighlightDisplayLevel();
84 private static final Map<Language, SpellcheckingStrategy> factories = new HashMap<Language, SpellcheckingStrategy>();
86 private static void ensureFactoriesAreLoaded() {
87 synchronized (factories) {
88 if (!factories.isEmpty()) return;
89 final SpellcheckingStrategy[] spellcheckingStrategies = Extensions.getExtensions(SpellcheckingStrategy.EP_NAME);
90 if (spellcheckingStrategies != null) {
91 for (SpellcheckingStrategy spellcheckingStrategy : spellcheckingStrategies) {
92 final Language language = spellcheckingStrategy.getLanguage();
93 if (language != Language.ANY) {
94 factories.put(language, spellcheckingStrategy);
102 private static SpellcheckingStrategy getFactoryByLanguage(@NotNull Language lang) {
103 return factories.containsKey(lang) ? factories.get(lang) : factories.get(PlainTextLanguage.INSTANCE);
106 @NotNull
107 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
108 return new PsiElementVisitor() {
110 @Override
111 public void visitElement(final PsiElement element) {
113 final ASTNode node = element.getNode();
114 if (node == null) {
115 return;
117 // Extract parser definition from element
118 final Language language = element.getLanguage();
119 final IElementType elementType = node.getElementType();
120 final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
122 // Handle selected options
123 if (parserDefinition != null) {
124 if (parserDefinition.getStringLiteralElements().contains(elementType)) {
125 if (!processLiterals) {
126 return;
129 else if (parserDefinition.getCommentTokens().contains(elementType)) {
130 if (!processComments) {
131 return;
134 else if (!processCode) {
135 return;
139 ensureFactoriesAreLoaded();
141 final SpellcheckingStrategy factoryByLanguage = getFactoryByLanguage(language);
142 final Tokenizer tokenizer = factoryByLanguage.getTokenizer(element);
143 @SuppressWarnings({"unchecked"})
144 final Token[] tokens = tokenizer.tokenize(element);
145 if (tokens == null) {
146 return;
148 for (Token token : tokens) {
149 inspect(token, holder, isOnTheFly, getNamesValidators());
155 private static void inspect(Token token, ProblemsHolder holder, boolean isOnTheFly, NamesValidator... validators) {
156 List<CheckArea> areaList = TextSplitter.splitText(token.getText());
157 if (areaList == null) {
158 return;
160 for (CheckArea area : areaList) {
161 boolean ignored = area.isIgnored();
162 boolean keyword = isKeyword(validators, token.getElement(), area.getWord());
163 if (!ignored && !keyword) {
164 inspect(area, token, holder, isOnTheFly);
170 private static void inspect(@NotNull CheckArea area, @NotNull Token token, @NotNull ProblemsHolder holder, boolean isOnTheFly) {
171 SpellCheckerManager manager = SpellCheckerManager.getInstance(token.getElement().getProject());
173 final TextRange textRange = area.getTextRange();
174 final String word = area.getWord();
176 if (textRange == null || word == null) {
177 return;
180 if (manager.hasProblem(word)) {
181 List<SpellCheckerQuickFix> fixes = new ArrayList<SpellCheckerQuickFix>();
182 if (isOnTheFly) {
183 if (!token.isUseRename()) {
184 fixes.add(new ChangeTo());
186 else {
187 fixes.add(new RenameTo());
191 final AcceptWordAsCorrect acceptWordAsCorrect = isOnTheFly ? BATCH_ACCEPT_FIX : new AcceptWordAsCorrect();
192 fixes.add(acceptWordAsCorrect);
194 final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes, isOnTheFly);
195 holder.registerProblem(problemDescriptor);
200 private static ProblemDescriptor createProblemDescriptor(Token token,
201 ProblemsHolder holder,
202 TextRange textRange, Collection<SpellCheckerQuickFix> fixes, boolean onTheFly) {
203 //TODO: these descriptions eat LOTS of HEAP on batch run - need either to make them constant or evaluate template dynamically
204 // ( add something like #text substitution)
205 final String defaultDescription = SpellCheckerBundle.message("typo.in");
206 final String tokenDescription = token.getDescription();
207 final String description = tokenDescription == null ? defaultDescription : tokenDescription;
208 final TextRange highlightRange = TextRange.from(token.getOffset() + textRange.getStartOffset(), textRange.getLength());
209 assert highlightRange.getStartOffset()>=0 : token.getText();
210 final LocalQuickFix[] quickFixes = fixes.size() > 0 ? fixes.toArray(new LocalQuickFix[fixes.size()]) : null;
212 final ProblemDescriptor problemDescriptor = holder.getManager()
213 .createProblemDescriptor(token.getElement(), highlightRange, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, holder.isOnTheFly(),
214 quickFixes);
215 if(onTheFly) {
216 for (SpellCheckerQuickFix fix : fixes) {
217 fix.setDescriptor(problemDescriptor);
220 return problemDescriptor;
223 @Nullable
224 public static NamesValidator[] getNamesValidators() {
225 final Object[] extensions = Extensions.getExtensions("com.intellij.lang.namesValidator");
226 NamesValidator[] validators = null;
227 if (extensions != null) {
228 List<NamesValidator> validatorList = new ArrayList<NamesValidator>();
229 for (Object extension : extensions) {
230 if (extension instanceof LanguageExtensionPoint && ((LanguageExtensionPoint)extension).getInstance() instanceof NamesValidator) {
231 validatorList.add((NamesValidator)((LanguageExtensionPoint)extension).getInstance());
234 if (validatorList.size() > 0) {
235 validators = new NamesValidator[validatorList.size()];
236 validatorList.toArray(validators);
239 return validators;
242 private static boolean isKeyword(NamesValidator[] validators, PsiElement element, String word) {
243 if (validators == null) {
244 return false;
246 for (NamesValidator validator : validators) {
247 if (validator.isKeyword(word, element.getProject())) {
248 return true;
251 return false;
254 @SuppressWarnings({"PublicField"})
255 public boolean processCode = true;
256 public boolean processLiterals = true;
257 public boolean processComments = true;
259 @Override
260 public JComponent createOptionsPanel() {
261 final Box verticalBox = Box.createVerticalBox();
262 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.code"), this, "processCode"));
263 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.literals"), this, "processLiterals"));
264 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.comments"), this, "processComments"));
265 /*HyperlinkLabel linkToSettings = new HyperlinkLabel(SpellCheckerBundle.message("link.to.settings"));
266 linkToSettings.addHyperlinkListener(new HyperlinkListener() {
267 public void hyperlinkUpdate(final HyperlinkEvent e) {
268 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
269 final OptionsEditor optionsEditor = OptionsEditor.KEY.getData(DataManager.getInstance().getDataContext());
270 // ??project?
276 verticalBox.add(linkToSettings);*/
277 final JPanel panel = new JPanel(new BorderLayout());
278 panel.add(verticalBox, BorderLayout.NORTH);
279 return panel;