| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.codeInsight.lookup.impl; |
| |
| import com.intellij.codeInsight.AutoPopupController; |
| import com.intellij.codeInsight.CodeInsightUtilBase; |
| import com.intellij.codeInsight.completion.CodeCompletionFeatures; |
| import com.intellij.codeInsight.completion.CompletionPhase; |
| import com.intellij.codeInsight.completion.CompletionProgressIndicator; |
| import com.intellij.codeInsight.completion.PrefixMatcher; |
| import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; |
| import com.intellij.codeInsight.editorActions.AutoHardWrapHandler; |
| import com.intellij.codeInsight.editorActions.TypedHandler; |
| import com.intellij.codeInsight.lookup.CharFilter; |
| import com.intellij.codeInsight.lookup.LookupElement; |
| import com.intellij.codeInsight.lookup.LookupManager; |
| import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction; |
| import com.intellij.codeInsight.template.impl.TemplateSettings; |
| import com.intellij.codeInsight.template.impl.editorActions.TypedActionHandlerBase; |
| import com.intellij.featureStatistics.FeatureUsageTracker; |
| import com.intellij.ide.DataManager; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.EditorModificationUtil; |
| import com.intellij.openapi.editor.actionSystem.TypedActionHandler; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.util.PsiUtilBase; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Arrays; |
| |
| public class LookupTypedHandler extends TypedActionHandlerBase { |
| |
| public LookupTypedHandler(@Nullable TypedActionHandler originalHandler) { |
| super(originalHandler); |
| } |
| |
| @Override |
| public void execute(@NotNull Editor originalEditor, char charTyped, @NotNull DataContext dataContext) { |
| final Project project = CommonDataKeys.PROJECT.getData(dataContext); |
| PsiFile file; |
| |
| if (project == null |
| || (file = PsiUtilBase.getPsiFileInEditor(originalEditor, project)) == null |
| || !CodeInsightUtilBase.prepareEditorForWrite(originalEditor) |
| || !FileDocumentManager.getInstance().requestWriting(originalEditor.getDocument(), project)) { |
| if (myOriginalHandler != null){ |
| myOriginalHandler.execute(originalEditor, charTyped, dataContext); |
| } |
| return; |
| } |
| |
| CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase(); |
| if (oldPhase instanceof CompletionPhase.CommittingDocuments && ((CompletionPhase.CommittingDocuments)oldPhase).isRestartingCompletion()) { |
| assert oldPhase.indicator != null; |
| oldPhase.indicator.scheduleRestart(); |
| } |
| |
| Editor editor = TypedHandler.injectedEditorIfCharTypedIsSignificant(charTyped, originalEditor, file); |
| if (editor != originalEditor) { |
| file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); |
| } |
| |
| if (originalEditor.isInsertMode() && beforeCharTyped(charTyped, project, originalEditor, editor, file)) { |
| return; |
| } |
| |
| if (myOriginalHandler != null) { |
| myOriginalHandler.execute(originalEditor, charTyped, dataContext); |
| } |
| } |
| |
| private static boolean beforeCharTyped(final char charTyped, |
| Project project, |
| final Editor originalEditor, |
| final Editor editor, |
| PsiFile file) { |
| final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(originalEditor); |
| if (lookup == null){ |
| return false; |
| } |
| |
| if (charTyped == ' ' && ChooseItemAction.hasTemplatePrefix(lookup, TemplateSettings.SPACE_CHAR)) { |
| return false; |
| } |
| |
| final CharFilter.Result result = getLookupAction(charTyped, lookup); |
| if (lookup.isLookupDisposed()) { |
| return false; |
| } |
| |
| if (result == CharFilter.Result.ADD_TO_PREFIX) { |
| Document document = editor.getDocument(); |
| long modificationStamp = document.getModificationStamp(); |
| |
| if (!lookup.performGuardedChange(new Runnable() { |
| @Override |
| public void run() { |
| EditorModificationUtil.typeInStringAtCaretHonorMultipleCarets(originalEditor, String.valueOf(charTyped), true); |
| } |
| })) { |
| return true; |
| } |
| lookup.appendPrefix(charTyped); |
| if (lookup.isStartCompletionWhenNothingMatches() && lookup.getItems().isEmpty()) { |
| final CompletionProgressIndicator completion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); |
| if (completion != null) { |
| completion.scheduleRestart(); |
| } else { |
| AutoPopupController.getInstance(editor.getProject()).scheduleAutoPopup(editor); |
| } |
| } |
| |
| AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, DataManager.getInstance().getDataContext(editor.getContentComponent()), modificationStamp); |
| |
| final CompletionProgressIndicator completion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); |
| if (completion != null) { |
| completion.prefixUpdated(); |
| } |
| return true; |
| } |
| |
| if (result == CharFilter.Result.SELECT_ITEM_AND_FINISH_LOOKUP && lookup.isFocused()) { |
| LookupElement item = lookup.getCurrentItem(); |
| if (item != null) { |
| if (completeTillTypedCharOccurrence(charTyped, lookup, item)) { |
| return true; |
| } |
| |
| FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_DOT_ETC); |
| lookup.finishLookup(charTyped); |
| return true; |
| } |
| } |
| |
| lookup.hide(); |
| TypedHandler.autoPopupCompletion(editor, charTyped, project, file); |
| return false; |
| } |
| |
| private static boolean completeTillTypedCharOccurrence(char charTyped, LookupImpl lookup, LookupElement item) { |
| PrefixMatcher matcher = lookup.itemMatcher(item); |
| final String oldPrefix = matcher.getPrefix() + lookup.getAdditionalPrefix(); |
| PrefixMatcher expanded = matcher.cloneWithPrefix(oldPrefix + charTyped); |
| if (expanded.prefixMatches(item)) { |
| for (String s : item.getAllLookupStrings()) { |
| if (matcher.prefixMatches(s)) { |
| int i = -1; |
| while (true) { |
| i = s.indexOf(charTyped, i + 1); |
| if (i < 0) break; |
| final String newPrefix = s.substring(0, i + 1); |
| if (expanded.prefixMatches(newPrefix)) { |
| lookup.replacePrefix(oldPrefix, newPrefix); |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| static CharFilter.Result getLookupAction(final char charTyped, final LookupImpl lookup) { |
| final CharFilter.Result filtersDecision = getFiltersDecision(charTyped, lookup); |
| |
| final LookupElement currentItem = lookup.getCurrentItem(); |
| if (currentItem != null && charTyped != ' ') { |
| String postfix = lookup.getAdditionalPrefix() + charTyped; |
| final PrefixMatcher matcher = lookup.itemMatcher(currentItem); |
| for (String lookupString : currentItem.getAllLookupStrings()) { |
| if (lookupString.startsWith(matcher.getPrefix() + postfix)) { |
| return CharFilter.Result.ADD_TO_PREFIX; |
| } |
| } |
| } |
| |
| if (filtersDecision != null) { |
| return filtersDecision; |
| } |
| throw new AssertionError("Typed char not handler by char filter: c=" + charTyped + |
| "; prefix=" + currentItem + |
| "; filters=" + Arrays.toString(getFilters())); |
| } |
| |
| @Nullable |
| private static CharFilter.Result getFiltersDecision(char charTyped, LookupImpl lookup) { |
| LookupElement item = lookup.getCurrentItem(); |
| int prefixLength = item == null ? lookup.getAdditionalPrefix().length(): lookup.itemPattern(item).length(); |
| |
| for (final CharFilter extension : getFilters()) { |
| final CharFilter.Result result = extension.acceptChar(charTyped, prefixLength, lookup); |
| if (result != null) { |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| private static CharFilter[] getFilters() { |
| return Extensions.getExtensions(CharFilter.EP_NAME); |
| } |
| } |