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
;
46 import java
.util
.ArrayList
;
47 import java
.util
.Collection
;
48 import java
.util
.List
;
52 public class SpellCheckingInspection
extends LocalInspectionTool
{
54 public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME
= "SpellCheckingInspection";
58 public String
getGroupDisplayName() {
59 return SpellCheckerBundle
.message("spelling");
64 public String
getDisplayName() {
65 return SpellCheckerBundle
.message("spellchecking.inspection.name");
70 public String
getShortName() {
71 return SPELL_CHECKING_INSPECTION_TOOL_NAME
;
74 public boolean isEnabledByDefault() {
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
);
106 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, final boolean isOnTheFly
) {
107 return new PsiElementVisitor() {
110 public void visitElement(final PsiElement element
) {
112 final ASTNode node
= element
.getNode();
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
) {
128 else if (parserDefinition
.getCommentTokens().contains(elementType
)) {
129 if (!processComments
) {
133 else if (!processCode
) {
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) {
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) {
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) {
179 if (manager
.hasProblem(word
)) {
180 List
<SpellCheckerQuickFix
> fixes
= new ArrayList
<SpellCheckerQuickFix
>();
182 if (!token
.isUseRename()) {
183 fixes
.add(new ChangeTo());
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
;
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
);
237 private static boolean isKeyword(NamesValidator
[] validators
, PsiElement element
, String word
) {
238 if (validators
== null) {
241 for (NamesValidator validator
: validators
) {
242 if (validator
.isKeyword(word
, element
.getProject())) {
249 @SuppressWarnings({"PublicField"})
250 public boolean processCode
= true;
251 public boolean processLiterals
= true;
252 public boolean processComments
= true;
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());
271 verticalBox.add(linkToSettings);*/
272 final JPanel panel
= new JPanel(new BorderLayout());
273 panel
.add(verticalBox
, BorderLayout
.NORTH
);