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";
55 private static final AcceptWordAsCorrect BATCH_ACCEPT_FIX
= new AcceptWordAsCorrect();
59 public String
getGroupDisplayName() {
60 return SpellCheckerBundle
.message("spelling");
65 public String
getDisplayName() {
66 return SpellCheckerBundle
.message("spellchecking.inspection.name");
71 public String
getShortName() {
72 return SPELL_CHECKING_INSPECTION_TOOL_NAME
;
75 public boolean isEnabledByDefault() {
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
);
107 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, final boolean isOnTheFly
) {
108 return new PsiElementVisitor() {
111 public void visitElement(final PsiElement element
) {
113 final ASTNode node
= element
.getNode();
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
) {
129 else if (parserDefinition
.getCommentTokens().contains(elementType
)) {
130 if (!processComments
) {
134 else if (!processCode
) {
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) {
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) {
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) {
180 if (manager
.hasProblem(word
)) {
181 List
<SpellCheckerQuickFix
> fixes
= new ArrayList
<SpellCheckerQuickFix
>();
183 if (!token
.isUseRename()) {
184 fixes
.add(new ChangeTo());
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(),
216 for (SpellCheckerQuickFix fix
: fixes
) {
217 fix
.setDescriptor(problemDescriptor
);
220 return problemDescriptor
;
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
);
242 private static boolean isKeyword(NamesValidator
[] validators
, PsiElement element
, String word
) {
243 if (validators
== null) {
246 for (NamesValidator validator
: validators
) {
247 if (validator
.isKeyword(word
, element
.getProject())) {
254 @SuppressWarnings({"PublicField"})
255 public boolean processCode
= true;
256 public boolean processLiterals
= true;
257 public boolean processComments
= true;
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());
276 verticalBox.add(linkToSettings);*/
277 final JPanel panel
= new JPanel(new BorderLayout());
278 panel
.add(verticalBox
, BorderLayout
.NORTH
);