Spellchecker should use #ref anchor to save memory
[fedora-idea.git] / plugins / spellchecker / src / com / intellij / spellchecker / inspections / SpellCheckingInspection.java
blobf1b01613c94ba7600f56ac4ceaafb869cbcc5344
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.tokenizer.SpellcheckingStrategy;
34 import com.intellij.spellchecker.tokenizer.Token;
35 import com.intellij.spellchecker.tokenizer.Tokenizer;
36 import com.intellij.spellchecker.util.SpellCheckerBundle;
37 import com.intellij.util.containers.hash.HashMap;
38 import org.jetbrains.annotations.Nls;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import javax.swing.*;
44 import java.awt.*;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.Map;
51 public class SpellCheckingInspection extends LocalInspectionTool {
53 public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME = "SpellCheckingInspection";
55 @Nls
56 @NotNull
57 public String getGroupDisplayName() {
58 return SpellCheckerBundle.message("spelling");
61 @Nls
62 @NotNull
63 public String getDisplayName() {
64 return SpellCheckerBundle.message("spellchecking.inspection.name");
67 @NonNls
68 @NotNull
69 public String getShortName() {
70 return SPELL_CHECKING_INSPECTION_TOOL_NAME;
73 public boolean isEnabledByDefault() {
74 return true;
77 @NotNull
78 public HighlightDisplayLevel getDefaultLevel() {
79 return SpellCheckerManager.getHighlightDisplayLevel();
82 private static final Map<Language, SpellcheckingStrategy> factories = new HashMap<Language, SpellcheckingStrategy>();
84 private static void ensureFactoriesAreLoaded() {
85 synchronized (factories) {
86 if (!factories.isEmpty()) return;
87 final SpellcheckingStrategy[] spellcheckingStrategies = Extensions.getExtensions(SpellcheckingStrategy.EP_NAME);
88 if (spellcheckingStrategies != null) {
89 for (SpellcheckingStrategy spellcheckingStrategy : spellcheckingStrategies) {
90 final Language language = spellcheckingStrategy.getLanguage();
91 if (language != Language.ANY) {
92 factories.put(language, spellcheckingStrategy);
100 private static SpellcheckingStrategy getFactoryByLanguage(@NotNull Language lang) {
101 return factories.containsKey(lang) ? factories.get(lang) : factories.get(PlainTextLanguage.INSTANCE);
104 @NotNull
105 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
106 return new PsiElementVisitor() {
108 @Override
109 public void visitElement(final PsiElement element) {
111 final ASTNode node = element.getNode();
112 if (node == null) {
113 return;
115 // Extract parser definition from element
116 final Language language = element.getLanguage();
117 final IElementType elementType = node.getElementType();
118 final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
120 // Handle selected options
121 if (parserDefinition != null) {
122 if (parserDefinition.getStringLiteralElements().contains(elementType)) {
123 if (!processLiterals) {
124 return;
127 else if (parserDefinition.getCommentTokens().contains(elementType)) {
128 if (!processComments) {
129 return;
132 else if (!processCode) {
133 return;
137 ensureFactoriesAreLoaded();
139 final SpellcheckingStrategy factoryByLanguage = getFactoryByLanguage(language);
140 final Tokenizer tokenizer = factoryByLanguage.getTokenizer(element);
141 @SuppressWarnings({"unchecked"})
142 final Token[] tokens = tokenizer.tokenize(element);
143 if (tokens == null) {
144 return;
146 for (Token token : tokens) {
147 inspect(token, holder, isOnTheFly, getNamesValidators());
153 private static void inspect(Token token, ProblemsHolder holder, boolean isOnTheFly, NamesValidator... validators) {
154 List<CheckArea> areaList = TextSplitter.splitText(token.getText());
155 if (areaList == null) {
156 return;
158 for (CheckArea area : areaList) {
159 boolean ignored = area.isIgnored();
160 boolean keyword = isKeyword(validators, token.getElement(), area.getWord());
161 if (!ignored && !keyword) {
162 inspect(area, token, holder, isOnTheFly);
168 private static void inspect(@NotNull CheckArea area, @NotNull Token token, @NotNull ProblemsHolder holder, boolean isOnTheFly) {
169 SpellCheckerManager manager = SpellCheckerManager.getInstance(token.getElement().getProject());
171 final TextRange textRange = area.getTextRange();
172 final String word = area.getWord();
174 if (textRange == null || word == null) {
175 return;
178 if (manager.hasProblem(word)) {
179 List<LocalQuickFix> fixes = new ArrayList<LocalQuickFix>();
180 if (isOnTheFly) {
181 if (!token.isUseRename()) {
182 fixes.add(new ChangeTo(textRange, word, token.getElement().getProject()));
184 else {
185 fixes.add(new RenameTo(textRange, word, token.getElement().getProject()));
189 final AcceptWordAsCorrect acceptWordAsCorrect = new AcceptWordAsCorrect();
190 fixes.add(acceptWordAsCorrect);
192 final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes);
193 holder.registerProblem(problemDescriptor);
195 acceptWordAsCorrect.setDescriptor(problemDescriptor);
200 private static ProblemDescriptor createProblemDescriptor(Token token,
201 ProblemsHolder holder,
202 TextRange textRange, Collection<LocalQuickFix> fixes) {
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("word.0.1.is.misspelled", token.getElement().getLanguage());
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 final LocalQuickFix[] quickFixes = fixes.size() > 0 ? fixes.toArray(new LocalQuickFix[fixes.size()]) : null;
211 return holder.getManager()
212 .createProblemDescriptor(token.getElement(), highlightRange, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, quickFixes);
215 @Nullable
216 public static NamesValidator[] getNamesValidators() {
217 final Object[] extensions = Extensions.getExtensions("com.intellij.lang.namesValidator");
218 NamesValidator[] validators = null;
219 if (extensions != null) {
220 List<NamesValidator> validatorList = new ArrayList<NamesValidator>();
221 for (Object extension : extensions) {
222 if (extension instanceof LanguageExtensionPoint && ((LanguageExtensionPoint)extension).getInstance() instanceof NamesValidator) {
223 validatorList.add((NamesValidator)((LanguageExtensionPoint)extension).getInstance());
226 if (validatorList.size() > 0) {
227 validators = new NamesValidator[validatorList.size()];
228 validatorList.toArray(validators);
231 return validators;
234 private static boolean isKeyword(NamesValidator[] validators, PsiElement element, String word) {
235 if (validators == null) {
236 return false;
238 for (NamesValidator validator : validators) {
239 if (validator.isKeyword(word, element.getProject())) {
240 return true;
243 return false;
246 @SuppressWarnings({"PublicField"})
247 public boolean processCode = true;
248 public boolean processLiterals = true;
249 public boolean processComments = true;
251 @Override
252 public JComponent createOptionsPanel() {
253 final Box verticalBox = Box.createVerticalBox();
254 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.code"), this, "processCode"));
255 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.literals"), this, "processLiterals"));
256 verticalBox.add(new SingleCheckboxOptionsPanel(SpellCheckerBundle.message("process.comments"), this, "processComments"));
257 /*HyperlinkLabel linkToSettings = new HyperlinkLabel(SpellCheckerBundle.message("link.to.settings"));
258 linkToSettings.addHyperlinkListener(new HyperlinkListener() {
259 public void hyperlinkUpdate(final HyperlinkEvent e) {
260 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
261 final OptionsEditor optionsEditor = OptionsEditor.KEY.getData(DataManager.getInstance().getDataContext());
262 // ??project?
268 verticalBox.add(linkToSettings);*/
269 final JPanel panel = new JPanel(new BorderLayout());
270 panel.add(verticalBox, BorderLayout.NORTH);
271 return panel;