IDEA-24645
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / editorActions / TypedHandler.java
blobd6c4880508bbee1b8e19e3b5cb1fc15d010dc344
1 /*
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;
62 import java.util.Map;
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;
77 @Nullable
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();
88 return quoteHandler;
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();
102 break;
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);
120 return;
123 PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project);
125 if (file == null){
126 if (myOriginalHandler != null){
127 myOriginalHandler.execute(editor, charTyped, dataContext);
129 return;
132 if (editor.isViewer()) return;
134 if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
135 return;
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;
154 if (!handled) {
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);
166 return;
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) {
179 return;
181 if (result == TypedHandlerDelegate.Result.DEFAULT) {
182 break;
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) {
205 return;
207 if (result == TypedHandlerDelegate.Result.DEFAULT) {
208 break;
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)) {
227 return true;
229 PsiElement parent = element.getParent();
230 if (parent != null && stringLiteralElements.contains(parent.getNode().getElementType())) {
231 return true;
234 return false;
237 static Editor injectedEditorIfCharTypedIsSignificant(final char charTyped, Editor editor, PsiFile oldFile) {
238 boolean significant = charTyped == '"' ||
239 charTyped == '\'' ||
240 charTyped == '[' ||
241 charTyped == '(' ||
242 charTyped == ']' ||
243 charTyped == ')' ||
244 charTyped == '{' ||
245 charTyped == '}' ||
246 charTyped == '.';
247 if (!significant) {
248 return editor;
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);
262 return editor;
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()) {
279 iterator.advance();
281 IElementType tokenType = iterator.atEnd() ? null : iterator.getTokenType();
282 if (!BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType, tokenType, fileType)) {
283 return;
286 if (!iterator.atEnd() && BraceMatchingUtil.isLBraceToken(iterator, fileText, fileType)) {
287 return;
290 iterator.retreat();
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);
299 if (!matched) {
300 String text;
301 if (lparenChar == '(') {
302 text = ")";
304 else if (lparenChar == '[') {
305 text = "]";
307 else if (lparenChar == '<') {
308 text = ">";
310 else if (lparenChar == '{') {
311 text = "}";
313 else {
314 LOG.error("Unknown char "+lparenChar);
315 return;
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()) {
338 return false;
342 BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator);
343 CharSequence text = editor.getDocument().getCharsSequence();
344 if (!braceMatcher.isRBraceToken(iterator, text, fileType)) {
345 return false;
348 IElementType tokenType = iterator.getTokenType();
350 iterator.retreat();
352 IElementType lparenTokenType = braceMatcher.getOppositeBraceTokenType(tokenType);
353 int lparenthOffset = BraceMatchingUtil.findLeftmostLParen(
354 iterator,
355 lparenTokenType,
356 text,
357 fileType
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(
365 iterator,
366 t, text,
367 fileType
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);
382 return true;
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);
400 return true;
404 HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
406 if (!iterator.atEnd()){
407 IElementType tokenType = iterator.getTokenType();
408 if (quoteHandler instanceof JavaLikeQuoteHandler) {
409 try {
410 if (!((JavaLikeQuoteHandler)quoteHandler).isAppropriateElementTypeForLiteral(tokenType)) return false;
412 catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs) {
413 // ignore
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));
426 return true;
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);
433 return 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);
443 return 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);
453 return 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);
473 return 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() {
509 public void run(){
510 try{
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){
517 LOG.error(e);
521 ApplicationManager.getApplication().runWriteAction(action);