/*
* Copyright 2000-2016 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.hints;
import com.intellij.codeHighlighting.EditorBoundHighlightingPass;
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory;
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hints.filtering.Matcher;
import com.intellij.codeInsight.hints.filtering.MatcherConstructor;
import com.intellij.codeInsight.hints.settings.Diff;
import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings;
import com.intellij.lang.Language;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.editor.ex.util.CaretVisualPositionKeeper;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SyntaxTraverser;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import consulo.annotations.RequiredReadAction;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
public class ParameterHintsPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
private static final Key<Boolean> REPEATED_PASS = Key.create("RepeatedParameterHintsPass");
public ParameterHintsPassFactory(Project project, TextEditorHighlightingPassRegistrar registrar) {
super(project);
registrar.registerTextEditorHighlightingPass(this, null, null, false, -1);
}
@Nullable
@Override
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
if (editor.isOneLineMode()) return null;
return new ParameterHintsPass(file, editor);
}
private static class ParameterHintsPass extends EditorBoundHighlightingPass {
private final Map<Integer, String> myAnnotations = new HashMap<>();
private ParameterHintsPass(@NotNull PsiFile file, @NotNull Editor editor) {
super(editor, file, true);
}
@RequiredReadAction
@Override
public void doCollectInformation(@NotNull ProgressIndicator progress) {
assert myDocument != null;
myAnnotations.clear();
if (!isEnabled()) return;
Language language = myFile.getLanguage();
InlayParameterHintsProvider provider = InlayParameterHintsProvider.EP.forLanguage(language);
if (provider == null) return;
Set<String> blackList = getBlackList(language);
Language dependentLanguage = provider.getBlackListDependencyLanguage();
if (dependentLanguage != null) {
blackList.addAll(getBlackList(dependentLanguage));
}
List<Matcher> matchers = blackList
.stream()
.map(MatcherConstructor::createMatcher)
.filter(Objects::nonNull)
.collect(Collectors.toList());
SyntaxTraverser.psiTraverser(myFile).forEach(element -> process(element, provider, matchers));
}
private static Set<String> getBlackList(Language language) {
InlayParameterHintsProvider provider = InlayParameterHintsProvider.EP.forLanguage(language);
if (provider != null) {
ParameterNameHintsSettings settings = ParameterNameHintsSettings.getInstance();
Diff diff = settings.getBlackListDiff(language);
return diff.applyOn(provider.getDefaultBlackList());
}
return ContainerUtil.newHashOrEmptySet(ContainerUtil.emptyIterable());
}
private static boolean isEnabled() {
return EditorSettingsExternalizable.getInstance().isShowParameterNameHints();
}
private static boolean isMatchedByAny(MethodInfo info, List<Matcher> matchers) {
return matchers.stream().anyMatch((e) -> e.isMatching(info.getFullyQualifiedName(), info.getParamNames()));
}
private void process(PsiElement element, InlayParameterHintsProvider provider, List<Matcher> blackListMatchers) {
List<InlayInfo> hints = provider.getParameterHints(element);
if (hints.isEmpty()) return;
MethodInfo info = provider.getMethodInfo(element);
if (info == null || !isMatchedByAny(info, blackListMatchers)) {
hints.forEach((h) -> myAnnotations.put(h.getOffset(), h.getText()));
}
}
@Override
public void doApplyInformationToEditor() {
assert myDocument != null;
boolean firstTime = myEditor.getUserData(REPEATED_PASS) == null;
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
Set<String> removedHints = new HashSet<>();
TIntObjectHashMap<Caret> caretMap = new TIntObjectHashMap<>();
CaretVisualPositionKeeper keeper = new CaretVisualPositionKeeper(myEditor);
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
caretMap.put(caret.getOffset(), caret);
}
for (Inlay inlay : myEditor.getInlayModel().getInlineElementsInRange(0, myDocument.getTextLength())) {
if (!presentationManager.isParameterHint(inlay)) continue;
int offset = inlay.getOffset();
String newText = myAnnotations.remove(offset);
if (delayRemoval(inlay, caretMap)) continue;
String oldText = presentationManager.getHintText(inlay);
if (!Objects.equals(newText, oldText)) {
if (newText == null) {
removedHints.add(oldText);
presentationManager.deleteHint(myEditor, inlay);
}
else {
presentationManager.replaceHint(myEditor, inlay, newText);
}
}
}
for (Map.Entry<Integer, String> e : myAnnotations.entrySet()) {
int offset = e.getKey();
String text = e.getValue();
presentationManager.addHint(myEditor, offset, text, !firstTime && !removedHints.contains(text));
}
keeper.restoreOriginalLocation();
myEditor.putUserData(REPEATED_PASS, Boolean.TRUE);
}
private boolean delayRemoval(Inlay inlay, TIntObjectHashMap<Caret> caretMap) {
int offset = inlay.getOffset();
Caret caret = caretMap.get(offset);
if (caret == null) return false;
char afterCaret = myEditor.getDocument().getImmutableCharSequence().charAt(offset);
if (afterCaret != ',' && afterCaret != ')') return false;
VisualPosition afterInlayPosition = myEditor.offsetToVisualPosition(offset, true, false);
// check whether caret is to the right of inlay
if (!caret.getVisualPosition().equals(afterInlayPosition)) return false;
return true;
}
}
}