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.
17 package com
.intellij
.codeInsight
.editorActions
;
19 import com
.intellij
.codeInsight
.AutoPopupController
;
20 import com
.intellij
.codeInsight
.CodeInsightSettings
;
21 import com
.intellij
.codeInsight
.highlighting
.BraceMatcher
;
22 import com
.intellij
.codeInsight
.highlighting
.BraceMatchingUtil
;
23 import com
.intellij
.codeInsight
.highlighting
.NontrivialBraceMatcher
;
24 import com
.intellij
.injected
.editor
.DocumentWindow
;
25 import com
.intellij
.lang
.ASTNode
;
26 import com
.intellij
.lang
.Language
;
27 import com
.intellij
.lang
.LanguageParserDefinitions
;
28 import com
.intellij
.lang
.ParserDefinition
;
29 import com
.intellij
.lexer
.Lexer
;
30 import com
.intellij
.openapi
.actionSystem
.DataContext
;
31 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
32 import com
.intellij
.openapi
.application
.ApplicationManager
;
33 import com
.intellij
.openapi
.diagnostic
.Logger
;
34 import com
.intellij
.openapi
.editor
.Document
;
35 import com
.intellij
.openapi
.editor
.Editor
;
36 import com
.intellij
.openapi
.editor
.EditorModificationUtil
;
37 import com
.intellij
.openapi
.editor
.ScrollType
;
38 import com
.intellij
.openapi
.editor
.actionSystem
.TypedActionHandler
;
39 import com
.intellij
.openapi
.editor
.ex
.EditorEx
;
40 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
41 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterIterator
;
42 import com
.intellij
.openapi
.extensions
.Extensions
;
43 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
44 import com
.intellij
.openapi
.fileTypes
.FileType
;
45 import com
.intellij
.openapi
.project
.Project
;
46 import com
.intellij
.openapi
.vfs
.VirtualFile
;
47 import com
.intellij
.psi
.PsiDocumentManager
;
48 import com
.intellij
.psi
.PsiElement
;
49 import com
.intellij
.psi
.PsiFile
;
50 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
51 import com
.intellij
.psi
.impl
.source
.tree
.injected
.InjectedLanguageUtil
;
52 import com
.intellij
.psi
.templateLanguages
.TemplateLanguage
;
53 import com
.intellij
.psi
.tree
.IElementType
;
54 import com
.intellij
.psi
.tree
.TokenSet
;
55 import com
.intellij
.psi
.util
.PsiUtilBase
;
56 import com
.intellij
.util
.IncorrectOperationException
;
57 import com
.intellij
.util
.text
.CharArrayUtil
;
58 import org
.jetbrains
.annotations
.NotNull
;
59 import org
.jetbrains
.annotations
.Nullable
;
61 import java
.util
.HashMap
;
64 public class TypedHandler
implements TypedActionHandler
{
65 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.editorActions.TypedHandler");
67 private final TypedActionHandler myOriginalHandler
;
69 private static final Map
<FileType
,QuoteHandler
> quoteHandlers
= new HashMap
<FileType
, QuoteHandler
>();
71 private static final Map
<Class
<?
extends Language
>, QuoteHandler
> ourBaseLanguageQuoteHandlers
= new HashMap
<Class
<?
extends Language
>, QuoteHandler
>();
73 public TypedHandler(TypedActionHandler originalHandler
){
74 myOriginalHandler
= originalHandler
;
78 public static QuoteHandler
getQuoteHandler(@NotNull PsiFile file
) {
79 QuoteHandler quoteHandler
= getQuoteHandlerForType(file
.getFileType());
80 if (quoteHandler
== null) {
81 final Language baseLanguage
= file
.getViewProvider().getBaseLanguage();
82 for (Map
.Entry
<Class
<?
extends Language
>, QuoteHandler
> entry
: ourBaseLanguageQuoteHandlers
.entrySet()) {
83 if (entry
.getKey().isInstance(baseLanguage
)) {
84 return entry
.getValue();
91 public static void registerBaseLanguageQuoteHandler(Class
<?
extends Language
> languageClass
, QuoteHandler quoteHandler
) {
92 ourBaseLanguageQuoteHandlers
.put(languageClass
, quoteHandler
);
95 public static QuoteHandler
getQuoteHandlerForType(final FileType fileType
) {
96 if (!quoteHandlers
.containsKey(fileType
)) {
97 QuoteHandler handler
= null;
98 final QuoteHandlerEP
[] handlerEPs
= Extensions
.getExtensions(QuoteHandlerEP
.EP_NAME
);
99 for(QuoteHandlerEP ep
: handlerEPs
) {
100 if (ep
.fileType
.equals(fileType
.getName())) {
101 handler
= ep
.getHandler();
105 quoteHandlers
.put(fileType
, handler
);
107 return quoteHandlers
.get(fileType
);
110 public static void registerQuoteHandler(FileType fileType
, QuoteHandler quoteHandler
) {
111 quoteHandlers
.put(fileType
, quoteHandler
);
114 public void execute(@NotNull Editor editor
, char charTyped
, @NotNull DataContext dataContext
) {
115 Project project
= PlatformDataKeys
.PROJECT
.getData(dataContext
);
116 if (project
== null || editor
.isColumnMode()){
117 if (myOriginalHandler
!= null){
118 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
123 PsiFile file
= PsiUtilBase
.getPsiFileInEditor(editor
, project
);
126 if (myOriginalHandler
!= null){
127 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
132 if (editor
.isViewer()) return;
134 if (!FileDocumentManager
.getInstance().requestWriting(editor
.getDocument(), project
)) {
138 Editor injectedEditor
= injectedEditorIfCharTypedIsSignificant(charTyped
, editor
, file
);
139 if (injectedEditor
!= editor
) {
140 file
= PsiDocumentManager
.getInstance(project
).getPsiFile(injectedEditor
.getDocument());
141 editor
= injectedEditor
;
144 final TypedHandlerDelegate
[] delegates
= Extensions
.getExtensions(TypedHandlerDelegate
.EP_NAME
);
145 AutoPopupController autoPopupController
= AutoPopupController
.getInstance(project
);
147 boolean handled
= false;
148 for(TypedHandlerDelegate delegate
: delegates
) {
149 final TypedHandlerDelegate
.Result result
= delegate
.checkAutoPopup(charTyped
, project
, editor
, file
);
150 handled
= result
== TypedHandlerDelegate
.Result
.STOP
;
151 if (result
!= TypedHandlerDelegate
.Result
.CONTINUE
) break;
155 if (charTyped
== '.') {
156 autoPopupController
.autoPopupMemberLookup(editor
, null);
159 if (charTyped
== '(' && !isInsideStringLiteral(editor
, file
)) {
160 autoPopupController
.autoPopupParameterInfo(editor
, null);
164 if (!editor
.isInsertMode()){
165 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
169 if (editor
.getSelectionModel().hasSelection()){
170 EditorModificationUtil
.deleteSelectedText(editor
);
173 final VirtualFile virtualFile
= file
.getVirtualFile();
174 FileType fileType
= virtualFile
== null ? file
.getFileType() : virtualFile
.getFileType();
176 for(TypedHandlerDelegate delegate
: delegates
) {
177 final TypedHandlerDelegate
.Result result
= delegate
.beforeCharTyped(charTyped
, project
, editor
, file
, fileType
);
178 if (result
== TypedHandlerDelegate
.Result
.STOP
) {
181 if (result
== TypedHandlerDelegate
.Result
.DEFAULT
) {
186 if (')' == charTyped
|| ']' == charTyped
|| '}' == charTyped
){
187 if (handleRParen(editor
, fileType
, charTyped
)) return;
189 else if ('"' == charTyped
|| '\'' == charTyped
){
190 if (handleQuote(editor
, charTyped
, dataContext
, file
)) return;
193 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
195 if (('(' == charTyped
|| '[' == charTyped
|| '{' == charTyped
) && CodeInsightSettings
.getInstance().AUTOINSERT_PAIR_BRACKET
) {
196 handleAfterLParen(editor
, fileType
, charTyped
);
198 else if ('}' == charTyped
) {
199 indentClosingBrace(project
, editor
);
202 for(TypedHandlerDelegate delegate
: delegates
) {
203 final TypedHandlerDelegate
.Result result
= delegate
.charTyped(charTyped
, project
, editor
, file
);
204 if (result
== TypedHandlerDelegate
.Result
.STOP
) {
207 if (result
== TypedHandlerDelegate
.Result
.DEFAULT
) {
211 if ('{' == charTyped
) {
212 indentOpenedBrace(project
, editor
);
216 private static boolean isInsideStringLiteral(final Editor editor
, final PsiFile file
) {
217 int offset
= editor
.getCaretModel().getOffset();
218 PsiElement element
= file
.findElementAt(offset
);
219 if (element
== null) return false;
220 final ParserDefinition definition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(element
.getLanguage());
221 if (definition
!= null) {
222 final TokenSet stringLiteralElements
= definition
.getStringLiteralElements();
223 final ASTNode node
= element
.getNode();
224 if (node
== null) return false;
225 final IElementType elementType
= node
.getElementType();
226 if (stringLiteralElements
.contains(elementType
)) {
229 PsiElement parent
= element
.getParent();
230 if (parent
!= null && stringLiteralElements
.contains(parent
.getNode().getElementType())) {
237 static Editor
injectedEditorIfCharTypedIsSignificant(final char charTyped
, Editor editor
, PsiFile oldFile
) {
238 boolean significant
= charTyped
== '"' ||
251 int offset
= editor
.getCaretModel().getOffset();
252 // even for uncommitted document try to retrieve injected fragment that has been there recently
253 // we are assuming here that when user is (even furiously) typing, injected language would not change
254 // and thus we can use its lexer to insert closing braces etc
255 for (DocumentWindow documentWindow
: InjectedLanguageUtil
.getCachedInjectedDocuments(oldFile
)) {
256 if (documentWindow
.isValid() && documentWindow
.containsRange(offset
, offset
)) {
257 PsiFile injectedFile
= PsiDocumentManager
.getInstance(oldFile
.getProject()).getPsiFile(documentWindow
);
258 return InjectedLanguageUtil
.getInjectedEditorForInjectedFile(editor
, injectedFile
);
265 private static void handleAfterLParen(Editor editor
, FileType fileType
, char lparenChar
){
266 int offset
= editor
.getCaretModel().getOffset();
267 HighlighterIterator iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(offset
);
268 boolean atEndOfDocument
= offset
== editor
.getDocument().getTextLength();
270 if (!atEndOfDocument
) iterator
.retreat();
271 if (iterator
.atEnd()) return;
272 BraceMatcher braceMatcher
= BraceMatchingUtil
.getBraceMatcher(fileType
, iterator
);
273 if (iterator
.atEnd()) return;
274 IElementType braceTokenType
= iterator
.getTokenType();
275 final CharSequence fileText
= editor
.getDocument().getCharsSequence();
276 if (!braceMatcher
.isLBraceToken(iterator
, fileText
, fileType
)) return;
278 if (!iterator
.atEnd()) {
281 IElementType tokenType
= iterator
.atEnd() ?
null : iterator
.getTokenType();
282 if (!BraceMatchingUtil
.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType
, tokenType
, fileType
)) {
286 if (!iterator
.atEnd() && BraceMatchingUtil
.isLBraceToken(iterator
, fileText
, fileType
)) {
293 int lparenOffset
= BraceMatchingUtil
.findLeftmostLParen(iterator
, braceTokenType
, fileText
,fileType
);
294 if (lparenOffset
< 0) lparenOffset
= 0;
296 iterator
= ((EditorEx
)editor
).getHighlighter().createIterator(lparenOffset
);
297 boolean matched
= BraceMatchingUtil
.matchBrace(fileText
, fileType
, iterator
, true);
301 if (lparenChar
== '(') {
304 else if (lparenChar
== '[') {
307 else if (lparenChar
== '<') {
310 else if (lparenChar
== '{') {
314 LOG
.error("Unknown char "+lparenChar
);
317 editor
.getDocument().insertString(offset
, text
);
321 public static boolean handleRParen(Editor editor
, FileType fileType
, char charTyped
) {
322 if (!CodeInsightSettings
.getInstance().AUTOINSERT_PAIR_BRACKET
) return false;
324 int offset
= editor
.getCaretModel().getOffset();
326 if (offset
== editor
.getDocument().getTextLength()) return false;
328 HighlighterIterator iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(offset
);
329 if (iterator
.atEnd()) return false;
331 Language language
= iterator
.getTokenType().getLanguage();
332 final ParserDefinition definition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(language
);
333 if (definition
!= null && !(language
instanceof TemplateLanguage
)) {
334 final Lexer lexer
= definition
.createLexer(editor
.getProject());
335 lexer
.start(Character
.toString(charTyped
));
336 final IElementType tokenType
= lexer
.getTokenType();
337 if (tokenType
!= iterator
.getTokenType()) {
342 BraceMatcher braceMatcher
= BraceMatchingUtil
.getBraceMatcher(fileType
, iterator
);
343 CharSequence text
= editor
.getDocument().getCharsSequence();
344 if (!braceMatcher
.isRBraceToken(iterator
, text
, fileType
)) {
348 IElementType tokenType
= iterator
.getTokenType();
352 IElementType lparenTokenType
= braceMatcher
.getOppositeBraceTokenType(tokenType
);
353 int lparenthOffset
= BraceMatchingUtil
.findLeftmostLParen(
360 if (lparenthOffset
< 0) {
361 if (braceMatcher
instanceof NontrivialBraceMatcher
) {
362 for(IElementType t
:((NontrivialBraceMatcher
)braceMatcher
).getOppositeBraceTokenTypes(tokenType
)) {
363 if (t
== lparenTokenType
) continue;
364 lparenthOffset
= BraceMatchingUtil
.findLeftmostLParen(
369 if (lparenthOffset
>= 0) break;
372 if (lparenthOffset
< 0) return false;
375 iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(lparenthOffset
);
376 boolean matched
= BraceMatchingUtil
.matchBrace(text
, fileType
, iterator
, true);
378 if (!matched
) return false;
380 editor
.getCaretModel().moveToOffset(offset
+ 1);
381 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
385 private boolean handleQuote(Editor editor
, char quote
, DataContext dataContext
, PsiFile file
) {
386 if (!CodeInsightSettings
.getInstance().AUTOINSERT_PAIR_QUOTE
) return false;
387 final QuoteHandler quoteHandler
= getQuoteHandler(file
);
388 if (quoteHandler
== null) return false;
390 int offset
= editor
.getCaretModel().getOffset();
392 CharSequence chars
= editor
.getDocument().getCharsSequence();
393 int length
= editor
.getDocument().getTextLength();
394 if (isTypingEscapeQuote(editor
, quoteHandler
, offset
)) return false;
396 if (offset
< length
&& chars
.charAt(offset
) == quote
){
397 if (isClosingQuote(editor
, quoteHandler
, offset
)){
398 editor
.getCaretModel().moveToOffset(offset
+ 1);
399 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
404 HighlighterIterator iterator
= ((EditorEx
)editor
).getHighlighter().createIterator(offset
);
406 if (!iterator
.atEnd()){
407 IElementType tokenType
= iterator
.getTokenType();
408 if (quoteHandler
instanceof JavaLikeQuoteHandler
) {
410 if (!((JavaLikeQuoteHandler
)quoteHandler
).isAppropriateElementTypeForLiteral(tokenType
)) return false;
412 catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs
) {
418 myOriginalHandler
.execute(editor
, quote
, dataContext
);
419 offset
= editor
.getCaretModel().getOffset();
421 if (isOpeningQuote(editor
, quoteHandler
, offset
- 1) &&
422 hasNonClosedLiterals(editor
, quoteHandler
, offset
- 1)) {
423 editor
.getDocument().insertString(offset
, String
.valueOf(quote
));
429 private static boolean isClosingQuote(Editor editor
, QuoteHandler quoteHandler
, int offset
) {
430 HighlighterIterator iterator
= ((EditorEx
)editor
).getHighlighter().createIterator(offset
);
431 if (iterator
.atEnd()){
432 LOG
.assertTrue(false);
436 return quoteHandler
.isClosingQuote(iterator
,offset
);
439 private static boolean isOpeningQuote(Editor editor
, QuoteHandler quoteHandler
, int offset
) {
440 HighlighterIterator iterator
= ((EditorEx
)editor
).getHighlighter().createIterator(offset
);
441 if (iterator
.atEnd()){
442 LOG
.assertTrue(false);
446 return quoteHandler
.isOpeningQuote(iterator
, offset
);
449 private static boolean hasNonClosedLiterals(Editor editor
, QuoteHandler quoteHandler
, int offset
) {
450 HighlighterIterator iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(offset
);
451 if (iterator
.atEnd()) {
452 LOG
.assertTrue(false);
456 return quoteHandler
.hasNonClosedLiteral(editor
, iterator
, offset
);
459 private static boolean isTypingEscapeQuote(Editor editor
, QuoteHandler quoteHandler
, int offset
){
460 if (offset
== 0) return false;
461 CharSequence chars
= editor
.getDocument().getCharsSequence();
462 int offset1
= CharArrayUtil
.shiftBackward(chars
, offset
- 1, "\\");
463 int slashCount
= offset
- 1 - offset1
;
464 return slashCount
% 2 != 0 && isInsideLiteral(editor
, quoteHandler
, offset
);
467 private static boolean isInsideLiteral(Editor editor
, QuoteHandler quoteHandler
, int offset
){
468 if (offset
== 0) return false;
470 HighlighterIterator iterator
= ((EditorEx
)editor
).getHighlighter().createIterator(offset
- 1);
471 if (iterator
.atEnd()){
472 LOG
.assertTrue(false);
476 return quoteHandler
.isInsideLiteral(iterator
);
479 private static void indentClosingBrace(@NotNull Project project
, @NotNull Editor editor
){
480 indentBrace(project
, editor
, '}');
483 static void indentOpenedBrace(@NotNull Project project
, @NotNull Editor editor
){
484 indentBrace(project
, editor
, '{');
487 private static void indentBrace(@NotNull final Project project
, @NotNull final Editor editor
, final char braceChar
) {
488 final int offset
= editor
.getCaretModel().getOffset() - 1;
489 final Document document
= editor
.getDocument();
490 CharSequence chars
= document
.getCharsSequence();
491 if (offset
< 0 || chars
.charAt(offset
) != braceChar
) return;
493 int spaceStart
= CharArrayUtil
.shiftBackward(chars
, offset
- 1, " \t");
494 if (spaceStart
< 0 || chars
.charAt(spaceStart
) == '\n' || chars
.charAt(spaceStart
) == '\r'){
495 PsiDocumentManager documentManager
= PsiDocumentManager
.getInstance(project
);
496 documentManager
.commitDocument(document
);
498 final PsiFile file
= documentManager
.getPsiFile(document
);
499 if (file
== null || !file
.isWritable()) return;
500 PsiElement element
= file
.findElementAt(offset
);
501 if (element
== null) return;
503 EditorHighlighter highlighter
= ((EditorEx
)editor
).getHighlighter();
504 HighlighterIterator iterator
= highlighter
.createIterator(offset
);
506 BraceMatcher braceMatcher
= BraceMatchingUtil
.getBraceMatcher(file
.getFileType(), iterator
);
507 if (element
.getNode() != null && braceMatcher
.isStructuralBrace(iterator
, chars
, file
.getFileType())) {
508 final Runnable action
= new Runnable() {
511 int newOffset
= CodeStyleManager
.getInstance(project
).adjustLineIndent(file
, offset
);
512 editor
.getCaretModel().moveToOffset(newOffset
+ 1);
513 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
514 editor
.getSelectionModel().removeSelection();
516 catch(IncorrectOperationException e
){
521 ApplicationManager
.getApplication().runWriteAction(action
);