Spellchecker fix string duplication
[fedora-idea.git] / plugins / spellchecker / src / com / intellij / spellchecker / inspections / SpellCheckingInspection.java
blob1a5badd5b1eb58555d0b2eec798e5ee7a2c3f20c
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";
56 @Nls
57 @NotNull
58 public String getGroupDisplayName() {
59 return SpellCheckerBundle.message("spelling");
62 @Nls
63 @NotNull
64 public String getDisplayName() {
65 return SpellCheckerBundle.message("spellchecking.inspection.name");
68 @NonNls
69 @NotNull
70 public String getShortName() {
71 return SPELL_CHECKING_INSPECTION_TOOL_NAME;
74 public boolean isEnabledByDefault() {
75 return true;
78 @NotNull
79 public HighlightDisplayLevel getDefaultLevel() {
80 return SpellCheckerManager.getHighlightDisplayLevel();
83 private static final Map<Language, SpellcheckingStrategy> factories = new HashMap<Language, SpellcheckingStrategy>();
85 private static void ensureFactoriesAreLoaded() {
86 synchronized (factories) {
87 if (!factories.isEmpty()) return;
88 final SpellcheckingStrategy[] spellcheckingStrategies = Extensions.getExtensions(SpellcheckingStrategy.EP_NAME);
89 if (spellcheckingStrategies != null) {
90 for (SpellcheckingStrategy spellcheckingStrategy : spellcheckingStrategies) {
91 final Language language = spellcheckingStrategy.getLanguage();
92 if (language != Language.ANY) {
93 factories.put(language, spellcheckingStrategy);
101 private static SpellcheckingStrategy getFactoryByLanguage(@NotNull Language lang) {
102 return factories.containsKey(lang) ? factories.get(lang) : factories.get(PlainTextLanguage.INSTANCE);
105 @NotNull
106 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
107 return new PsiElementVisitor() {
109 @Override
110 public void visitElement(final PsiElement element) {
112 final ASTNode node = element.getNode();
113 if (node == null) {
114 return;
116 // Extract parser definition from element
117 final Language language = element.getLanguage();
118 final IElementType elementType = node.getElementType();
119 final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
121 // Handle selected options
122 if (parserDefinition != null) {
123 if (parserDefinition.getStringLiteralElements().contains(elementType)) {
124 if (!processLiterals) {
125 return;
128 else if (parserDefinition.getCommentTokens().contains(elementType)) {
129 if (!processComments) {
130 return;
133 else if (!processCode) {
134 return;
138 ensureFactoriesAreLoaded();
140 final SpellcheckingStrategy factoryByLanguage = getFactoryByLanguage(language);
141 final Tokenizer tokenizer = factoryByLanguage.getTokenizer(element);
142 @SuppressWarnings({"unchecked"})
143 final Token[] tokens = tokenizer.tokenize(element);
144 if (tokens == null) {
145 return;
147 for (Token token : tokens) {
148 inspect(token, holder, isOnTheFly, getNamesValidators());
154 private static void inspect(Token token, ProblemsHolder holder, boolean isOnTheFly, NamesValidator... validators) {
155 List<CheckArea> areaList = TextSplitter.splitText(token.getText());
156 if (areaList == null) {
157 return;
159 for (CheckArea area : areaList) {
160 boolean ignored = area.isIgnored();
161 boolean keyword = isKeyword(validators, token.getElement(), area.getWord());
162 if (!ignored && !keyword) {
163 inspect(area, token, holder, isOnTheFly);
169 private static void inspect(@NotNull CheckArea area, @NotNull Token token, @NotNull ProblemsHolder holder, boolean isOnTheFly) {
170 SpellCheckerManager manager = SpellCheckerManager.getInstance(token.getElement().getProject());
172 final TextRange textRange = area.getTextRange();
173 final String word = area.getWord();
175 if (textRange == null || word == null) {
176 return;
179 if (manager.hasProblem(word)) {
180 List<SpellCheckerQuickFix> fixes = new ArrayList<SpellCheckerQuickFix>();
181 if (isOnTheFly) {
182 if (!token.isUseRename()) {
183 fixes.add(new ChangeTo());
185 else {
186 fixes.add(new RenameTo());
190 final AcceptWordAsCorrect acceptWordAsCorrect = new AcceptWordAsCorrect();
191 fixes.add(acceptWordAsCorrect);
193 final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes);
194 holder.registerProblem(problemDescriptor);
199 private static ProblemDescriptor createProblemDescriptor(Token token,
200 ProblemsHolder holder,
201 TextRange textRange, Collection<SpellCheckerQuickFix> fixes) {
202 //TODO: these descriptions eat LOTS of HEAP on batch run - need either to make them constant or evaluate template dynamically
203 // ( add something like #text substitution)
204 final String defaultDescription = SpellCheckerBundle.message("word.0.1.is.misspelled");
205 final String tokenDescription = token.getDescription();
206 final String description = tokenDescription == null ? defaultDescription : tokenDescription;
207 final TextRange highlightRange = TextRange.from(token.getOffset() + textRange.getStartOffset(), textRange.getLength());
208 final LocalQuickFix[] quickFixes = fixes.size() > 0 ? fixes.toArray(new LocalQuickFix[fixes.size()]) : null;
210 final ProblemDescriptor problemDescriptor = holder.getManager()
211 .createProblemDescriptor(token.getElement(), highlightRange, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, quickFixes);
212 for (SpellCheckerQuickFix fix : fixes) {
213 fix.setDescriptor(problemDescriptor);
215 return problemDescriptor;
218 @Nullable
219 public static NamesValidator[] getNamesValidators() {
220 final Object[] extensions = Extensions.getExtensions("com.intellij.lang.namesValidator");
221 NamesValidator[] validators = null;
222 if (extensions != null) {
223 List<NamesValidator> validatorList = new ArrayList<NamesValidator>();
224 for (Object extension : extensions) {
225 if (extension instanceof LanguageExtensionPoint && ((LanguageExtensionPoint)extension).getInstance() instanceof NamesValidator) {
226 validatorList.add((NamesValidator)((LanguageExtensionPoint)extension).getInstance());
229 if (validatorList.size() > 0) {
230 validators = new NamesValidator[validatorList.size()];
231 validatorList.toArray(validators);
234 return validators;
237 private static boolean isKeyword(NamesValidator[] validators, PsiElement element, String word) {
238 if (validators == null) {
239 return false;
241 for (NamesValidator validator : validators) {
242 if (validator.isKeyword(word, element.getProject())) {
243 return true;
246 return false;
249 @SuppressWarnings({"PublicField"})
250 public boolean processCode = true;
251 public boolean processLiterals = true;
252 public boolean processComments = true;
254 @Override
255 public JComponent createOptionsPanel() {
256 final Box verticalBox = Box.createVerticalBox();
257 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.code"), this, "processCode"));
258 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.literals"), this, "processLiterals"));
259 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.comments"), this, "processComments"));
260 /*HyperlinkLabel linkToSettings = new HyperlinkLabel(SpellCheckerBundle.message("link.to.settings"));
261 linkToSettings.addHyperlinkListener(new HyperlinkListener() {
262 public void hyperlinkUpdate(final HyperlinkEvent e) {
263 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
264 final OptionsEditor optionsEditor = OptionsEditor.KEY.getData(DataManager.getInstance().getDataContext());
265 // ??project?
271 verticalBox.add(linkToSettings);*/
272 final JPanel panel = new JPanel(new BorderLayout());
273 panel.add(verticalBox, BorderLayout.NORTH);
274 return panel;