RUBY-16568 Spell check does not work outside of HTML tags
[idea/community.git] / spellchecker / src / com / intellij / spellchecker / tokenizer / SpellcheckingStrategy.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.spellchecker.tokenizer;
17
18 import com.intellij.codeInspection.SuppressionUtil;
19 import com.intellij.openapi.extensions.ExtensionPointName;
20 import com.intellij.openapi.fileTypes.FileType;
21 import com.intellij.openapi.fileTypes.impl.CustomSyntaxTableFileType;
22 import com.intellij.openapi.util.TextRange;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.psi.*;
25 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
26 import com.intellij.psi.xml.XmlAttributeValue;
27 import com.intellij.psi.xml.XmlText;
28 import com.intellij.psi.xml.XmlToken;
29 import com.intellij.psi.xml.XmlTokenType;
30 import com.intellij.spellchecker.inspections.PlainTextSplitter;
31 import com.intellij.spellchecker.inspections.TextSplitter;
32 import com.intellij.spellchecker.quickfixes.AcceptWordAsCorrect;
33 import com.intellij.spellchecker.quickfixes.ChangeTo;
34 import com.intellij.spellchecker.quickfixes.RenameTo;
35 import com.intellij.spellchecker.quickfixes.SpellCheckerQuickFix;
36 import org.jetbrains.annotations.NotNull;
37
38 public class SpellcheckingStrategy {
39   protected final Tokenizer<PsiComment> myCommentTokenizer = new CommentTokenizer();
40   protected final Tokenizer<XmlAttributeValue> myXmlAttributeTokenizer = new XmlAttributeValueTokenizer();
41   protected final Tokenizer<XmlText> myXmlTextTokenizer = new XmlTextTokenizer();
42
43   public static final ExtensionPointName<SpellcheckingStrategy> EP_NAME = ExtensionPointName.create("com.intellij.spellchecker.support");
44   public static final Tokenizer EMPTY_TOKENIZER = new Tokenizer() {
45     @Override
46     public void tokenize(@NotNull PsiElement element, TokenConsumer consumer) {
47     }
48   };
49
50   public static final Tokenizer<PsiElement> TEXT_TOKENIZER = new TokenizerBase<PsiElement>(PlainTextSplitter.getInstance());
51
52   private static final SpellCheckerQuickFix[] BATCH_FIXES = new SpellCheckerQuickFix[]{new AcceptWordAsCorrect()};
53
54   @NotNull
55   public Tokenizer getTokenizer(PsiElement element) {
56     if (element instanceof PsiLanguageInjectionHost && InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)element)) {
57       return EMPTY_TOKENIZER;
58     }
59     if (element instanceof PsiNameIdentifierOwner) return new PsiIdentifierOwnerTokenizer();
60     if (element instanceof PsiComment) {
61       if (SuppressionUtil.isSuppressionComment(element)) {
62         return EMPTY_TOKENIZER;
63       }
64       return myCommentTokenizer;
65     }
66     if (element instanceof XmlAttributeValue) return myXmlAttributeTokenizer;
67     if (element instanceof XmlText) return myXmlTextTokenizer;
68     if (element instanceof PsiPlainText) {
69       PsiFile file = element.getContainingFile();
70       FileType fileType = file == null ? null : file.getFileType();
71       if (fileType instanceof CustomSyntaxTableFileType) {
72         return new CustomFileTypeTokenizer(((CustomSyntaxTableFileType)fileType).getSyntaxTable());
73       }
74       return TEXT_TOKENIZER;
75     }
76     if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) {
77       return TEXT_TOKENIZER;
78     }
79     return EMPTY_TOKENIZER;
80   }
81
82   public SpellCheckerQuickFix[] getRegularFixes(PsiElement element,
83                                                 int offset,
84                                                 @NotNull TextRange textRange,
85                                                 boolean useRename,
86                                                 String wordWithTypo) {
87     return getDefaultRegularFixes(useRename, wordWithTypo);
88   }
89
90   public static SpellCheckerQuickFix[] getDefaultRegularFixes(boolean useRename, String wordWithTypo) {
91     return new SpellCheckerQuickFix[]{
92       useRename ? new RenameTo(wordWithTypo) : new ChangeTo(wordWithTypo),
93       new AcceptWordAsCorrect(wordWithTypo)
94     };
95   }
96
97   public static SpellCheckerQuickFix[] getDefaultBatchFixes() {
98     return BATCH_FIXES;
99   }
100
101   protected static class XmlAttributeValueTokenizer extends Tokenizer<XmlAttributeValue> {
102     public void tokenize(@NotNull final XmlAttributeValue element, final TokenConsumer consumer) {
103       if (element instanceof PsiLanguageInjectionHost && InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)element)) return;
104
105       final String valueTextTrimmed = element.getValue().trim();
106       // do not inspect colors like #00aaFF
107       if (valueTextTrimmed.startsWith("#") && valueTextTrimmed.length() <= 7 && isHexString(valueTextTrimmed.substring(1))) {
108         return;
109       }
110
111       consumer.consumeToken(element, TextSplitter.getInstance());
112     }
113
114     private static boolean isHexString(final String s) {
115       for (int i = 0; i < s.length(); i++) {
116         if (!StringUtil.isHexDigit(s.charAt(i))) {
117           return false;
118         }
119       }
120       return true;
121     }
122   }
123
124   public boolean isMyContext(@NotNull PsiElement element) {
125     return true;
126   }
127 }