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
;
45 import java
.util
.ArrayList
;
46 import java
.util
.Collection
;
47 import java
.util
.List
;
51 public class SpellCheckingInspection
extends LocalInspectionTool
{
53 public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME
= "SpellCheckingInspection";
57 public String
getGroupDisplayName() {
58 return SpellCheckerBundle
.message("spelling");
63 public String
getDisplayName() {
64 return SpellCheckerBundle
.message("spellchecking.inspection.name");
69 public String
getShortName() {
70 return SPELL_CHECKING_INSPECTION_TOOL_NAME
;
73 public boolean isEnabledByDefault() {
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
);
105 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, final boolean isOnTheFly
) {
106 return new PsiElementVisitor() {
109 public void visitElement(final PsiElement element
) {
111 final ASTNode node
= element
.getNode();
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
) {
127 else if (parserDefinition
.getCommentTokens().contains(elementType
)) {
128 if (!processComments
) {
132 else if (!processCode
) {
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) {
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) {
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) {
178 if (manager
.hasProblem(word
)) {
179 List
<LocalQuickFix
> fixes
= new ArrayList
<LocalQuickFix
>();
181 if (!token
.isUseRename()) {
182 fixes
.add(new ChangeTo(textRange
, word
, token
.getElement().getProject()));
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
);
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
);
234 private static boolean isKeyword(NamesValidator
[] validators
, PsiElement element
, String word
) {
235 if (validators
== null) {
238 for (NamesValidator validator
: validators
) {
239 if (validator
.isKeyword(word
, element
.getProject())) {
246 @SuppressWarnings({"PublicField"})
247 public boolean processCode
= true;
248 public boolean processLiterals
= true;
249 public boolean processComments
= true;
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());
268 verticalBox.add(linkToSettings);*/
269 final JPanel panel
= new JPanel(new BorderLayout());
270 panel
.add(verticalBox
, BorderLayout
.NORTH
);