mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[mod-commands] IJPL-214795 (part) simplest proof-of-concept: complete a few keywords for Java
Under a registry key ide.completion.modcommand GitOrigin-RevId: e0d6a25a811089aa0b72c2269ae450afc81b4d46
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1f1f4fa579
commit
d6cffffa81
@@ -1249,6 +1249,8 @@
|
||||
|
||||
<extendWordSelectionHandler implementation="com.intellij.codeInsight.editorActions.wordSelection.VarargsSelectioner"/>
|
||||
|
||||
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.KeywordCompletionItemProvider"/>
|
||||
|
||||
<completion.skip implementation="com.intellij.codeInsight.completion.AbstractExpectedTypeSkipper" id="skipAbstract"/>
|
||||
<completion.skip implementation="com.intellij.codeInsight.completion.DeprecatedSkipper" id="skipDeprecated"/>
|
||||
<statistician key="completion" implementationClass="com.intellij.codeInsight.completion.JavaCompletionStatistician"/>
|
||||
|
||||
@@ -55,69 +55,69 @@ public final class JavaTailTypes {
|
||||
}
|
||||
};
|
||||
|
||||
public static final TailType SYNCHRONIZED_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType SYNCHRONIZED_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_SYNCHRONIZED_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_SYNCHRONIZED_PARENTHESES;
|
||||
}
|
||||
};
|
||||
public static final TailType CATCH_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType CATCH_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_CATCH_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_CATCH_PARENTHESES;
|
||||
}
|
||||
};
|
||||
public static final TailType SWITCH_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType SWITCH_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_SWITCH_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_SWITCH_PARENTHESES;
|
||||
}
|
||||
};
|
||||
public static final TailType WHILE_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType WHILE_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_WHILE_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_WHILE_PARENTHESES;
|
||||
}
|
||||
};
|
||||
public static final TailType FOR_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType FOR_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_FOR_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_FOR_PARENTHESES;
|
||||
}
|
||||
};
|
||||
public static final TailType IF_LPARENTH = new ParenthesesTailType() {
|
||||
public static final ModNavigatorTailType IF_LPARENTH = new ParenthesesTailType() {
|
||||
@Override
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceBeforeParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_BEFORE_IF_PARENTHESES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final Editor editor, final int tailOffset) {
|
||||
protected boolean isSpaceWithinParentheses(final CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return styleSettings.SPACE_WITHIN_IF_PARENTHESES;
|
||||
}
|
||||
};
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion.modcompletion;
|
||||
|
||||
import com.intellij.codeInsight.ModNavigatorTailType;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcompletion.CompletionItemPresentation;
|
||||
import com.intellij.modcompletion.PsiUpdateCompletionItem;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.text.MarkupText;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A completion item for a Java keyword.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
final class KeywordCompletionItem extends PsiUpdateCompletionItem {
|
||||
private final @NlsSafe String myKeyword;
|
||||
private final ModNavigatorTailType myTail;
|
||||
|
||||
KeywordCompletionItem(@NlsSafe String keyword, ModNavigatorTailType tail) {
|
||||
myKeyword = keyword;
|
||||
myTail = tail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mainLookupString() {
|
||||
return myKeyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeywordInfo contextObject() {
|
||||
return new KeywordInfo(myKeyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemPresentation presentation() {
|
||||
return new CompletionItemPresentation(MarkupText.plainText(myKeyword).highlightAll(MarkupText.Kind.STRONG));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ActionContext actionContext, InsertionContext insertionContext, ModPsiUpdater updater) {
|
||||
myTail.processTail(actionContext.project(), updater, actionContext.offset());
|
||||
}
|
||||
|
||||
public record KeywordInfo(String keyword) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myKeyword;
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion.modcompletion;
|
||||
|
||||
import com.intellij.codeInsight.JavaTailTypes;
|
||||
import com.intellij.codeInsight.ModNavigatorTailType;
|
||||
import com.intellij.codeInsight.TailTypes;
|
||||
import com.intellij.java.syntax.parser.JavaKeywords;
|
||||
import com.intellij.modcompletion.CompletionItem;
|
||||
import com.intellij.modcompletion.CompletionItemProvider;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.filters.FilterPositionUtil;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiAnnotation;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
|
||||
import static com.intellij.patterns.StandardPatterns.string;
|
||||
|
||||
/**
|
||||
* A provider for Java keywords completion.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
final class KeywordCompletionItemProvider implements CompletionItemProvider {
|
||||
@Override
|
||||
public void provideItems(CompletionContext context, Consumer<CompletionItem> sink) {
|
||||
PsiElement element = context.element();
|
||||
if (!context.isSmart()) {
|
||||
if (canAddKeywords(element)) {
|
||||
if (isStatementPosition(element)) {
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.SWITCH, JavaTailTypes.SWITCH_LPARENTH));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.WHILE, JavaTailTypes.WHILE_LPARENTH));
|
||||
//sink.accept(new KeywordCompletionItem(JavaKeywords.DO, JavaTailTypes.DO_LBRACE));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.FOR, JavaTailTypes.FOR_LPARENTH));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.IF, JavaTailTypes.IF_LPARENTH));
|
||||
//sink.accept(new KeywordCompletionItem(JavaKeywords.TRY, JavaTailTypes.TRY_LBRACE));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.SYNCHRONIZED, JavaTailTypes.SYNCHRONIZED_LPARENTH));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.THROW, (ModNavigatorTailType)TailTypes.insertSpaceType()));
|
||||
sink.accept(new KeywordCompletionItem(JavaKeywords.NEW, (ModNavigatorTailType)TailTypes.insertSpaceType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canAddKeywords(PsiElement position) {
|
||||
if (PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (psiElement().afterLeaf("::").accepts(position)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isStatementPosition(PsiElement position) {
|
||||
if (psiElement()
|
||||
.withSuperParent(2, PsiConditionalExpression.class)
|
||||
.andNot(psiElement().insideStarting(psiElement(PsiConditionalExpression.class)))
|
||||
.accepts(position)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isEndOfBlock(position) &&
|
||||
PsiTreeUtil.getParentOfType(position, PsiCodeBlock.class, true, PsiMember.class) != null) {
|
||||
return !isForLoopMachinery(position);
|
||||
}
|
||||
|
||||
if (psiElement().withParents(PsiReferenceExpression.class, PsiExpressionStatement.class, PsiIfStatement.class).andNot(
|
||||
psiElement().afterLeaf(".")).accepts(position)) {
|
||||
PsiElement stmt = position.getParent().getParent();
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)stmt.getParent();
|
||||
return ifStatement.getElseBranch() == stmt || ifStatement.getThenBranch() == stmt;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isEndOfBlock(PsiElement element) {
|
||||
PsiElement prev = prevSignificantLeaf(element);
|
||||
if (prev == null) {
|
||||
PsiFile file = element.getContainingFile();
|
||||
return !(file instanceof PsiCodeFragment) || isStatementCodeFragment(file);
|
||||
}
|
||||
|
||||
if (psiElement().inside(psiAnnotation()).accepts(prev)) return false;
|
||||
|
||||
if (prev instanceof OuterLanguageElement) return true;
|
||||
if (psiElement().withText(string().oneOf("{", "}", ";", ":", "else")).accepts(prev)) return true;
|
||||
if (prev.textMatches(")")) {
|
||||
PsiElement parent = prev.getParent();
|
||||
if (parent instanceof PsiParameterList) {
|
||||
return PsiTreeUtil.getParentOfType(PsiTreeUtil.prevVisibleLeaf(element), PsiDocComment.class) != null;
|
||||
}
|
||||
|
||||
return !(parent instanceof PsiExpressionList || parent instanceof PsiTypeCastExpression
|
||||
|| parent instanceof PsiRecordHeader);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement prevSignificantLeaf(PsiElement position) {
|
||||
return FilterPositionUtil.searchNonSpaceNonCommentBack(position);
|
||||
}
|
||||
|
||||
private static boolean isStatementCodeFragment(PsiFile file) {
|
||||
return file instanceof JavaCodeFragment &&
|
||||
!(file instanceof PsiExpressionCodeFragment ||
|
||||
file instanceof PsiJavaCodeReferenceCodeFragment ||
|
||||
file instanceof PsiTypeCodeFragment);
|
||||
}
|
||||
|
||||
private static boolean isForLoopMachinery(PsiElement position) {
|
||||
PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class);
|
||||
if (statement == null) return false;
|
||||
|
||||
return statement instanceof PsiForStatement ||
|
||||
statement.getParent() instanceof PsiForStatement && statement != ((PsiForStatement)statement.getParent()).getBody();
|
||||
}
|
||||
}
|
||||
+23
-11
@@ -3,23 +3,35 @@
|
||||
package com.intellij.codeInsight.completion.simple;
|
||||
|
||||
import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.codeInsight.TailType;
|
||||
import com.intellij.codeInsight.editorActions.TabOutScopesTracker;
|
||||
import com.intellij.codeInsight.ModNavigatorTailType;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ModNavigator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class ParenthesesTailType extends TailType {
|
||||
public abstract class ParenthesesTailType extends ModNavigatorTailType {
|
||||
|
||||
protected abstract boolean isSpaceBeforeParentheses(CommonCodeStyleSettings styleSettings, Editor editor, final int tailOffset);
|
||||
protected boolean isSpaceBeforeParentheses(CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract boolean isSpaceWithinParentheses(CommonCodeStyleSettings styleSettings, Editor editor, final int tailOffset);
|
||||
protected boolean isSpaceWithinParentheses(CommonCodeStyleSettings styleSettings, final int tailOffset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, int tailOffset) {
|
||||
CommonCodeStyleSettings styleSettings = CodeStyle.getLocalLanguageSettings(editor, tailOffset);
|
||||
if (isSpaceBeforeParentheses(styleSettings, editor, tailOffset)) {
|
||||
public int processTail(@NotNull Project project, @NotNull ModNavigator editor, int tailOffset) {
|
||||
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
|
||||
assert psiFile != null;
|
||||
Language language = PsiUtilCore.getLanguageAtOffset(psiFile, tailOffset);
|
||||
CommonCodeStyleSettings styleSettings = CodeStyle.getLanguageSettings(psiFile, language);
|
||||
if (isSpaceBeforeParentheses(styleSettings, tailOffset)) {
|
||||
tailOffset = insertChar(editor, tailOffset, ' ');
|
||||
}
|
||||
Document document = editor.getDocument();
|
||||
@@ -28,7 +40,7 @@ public abstract class ParenthesesTailType extends TailType {
|
||||
}
|
||||
|
||||
tailOffset = insertChar(editor, tailOffset, '(');
|
||||
if (isSpaceWithinParentheses(styleSettings, editor, tailOffset)) {
|
||||
if (isSpaceWithinParentheses(styleSettings, tailOffset)) {
|
||||
tailOffset = insertChar(editor, tailOffset, ' ');
|
||||
tailOffset = insertChar(editor, tailOffset, ' ');
|
||||
tailOffset = insertChar(editor, tailOffset, ')');
|
||||
@@ -36,7 +48,7 @@ public abstract class ParenthesesTailType extends TailType {
|
||||
} else {
|
||||
tailOffset = insertChar(editor, tailOffset, ')');
|
||||
moveCaret(editor, tailOffset, -1);
|
||||
TabOutScopesTracker.getInstance().registerEmptyScopeAtCaret(editor);
|
||||
editor.registerTabOut(TextRange.from(editor.getCaretOffset(), 0), editor.getCaretOffset() + 1);
|
||||
}
|
||||
return tailOffset;
|
||||
}
|
||||
|
||||
@@ -96,13 +96,11 @@ com.intellij.codeHighlighting.HighlightDisplayLevelColoredIcon
|
||||
- a:getColor():java.awt.Color
|
||||
- a:getIcon():javax.swing.Icon
|
||||
c:com.intellij.codeInsight.CharTailType
|
||||
- com.intellij.codeInsight.TailType
|
||||
- <init>(C):V
|
||||
- <init>(C,Z):V
|
||||
- equals(java.lang.Object):Z
|
||||
- hashCode():I
|
||||
- isApplicable(com.intellij.codeInsight.completion.InsertionContext):Z
|
||||
- processTail(com.intellij.openapi.editor.Editor,I):I
|
||||
a:com.intellij.codeInsight.TailType
|
||||
- sf:CASE_COLON:com.intellij.codeInsight.TailType
|
||||
- sf:DOT:com.intellij.codeInsight.TailType
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
c:com.intellij.codeInsight.CharTailType
|
||||
- com.intellij.codeInsight.ModNavigatorTailType
|
||||
- processTail(com.intellij.openapi.project.Project,com.intellij.openapi.editor.ModNavigator,I):I
|
||||
a:com.intellij.codeInsight.ModNavigatorTailType
|
||||
- com.intellij.codeInsight.TailType
|
||||
- <init>():V
|
||||
- processTail(com.intellij.openapi.editor.Editor,I):I
|
||||
- a:processTail(com.intellij.openapi.project.Project,com.intellij.openapi.editor.ModNavigator,I):I
|
||||
a:com.intellij.codeInsight.TailType
|
||||
- s:insertChar(com.intellij.openapi.editor.ModNavigator,I,C):I
|
||||
- s:insertChar(com.intellij.openapi.editor.ModNavigator,I,C,Z):I
|
||||
- ps:moveCaret(com.intellij.openapi.editor.ModNavigator,I,I):I
|
||||
com.intellij.codeInsight.completion.BatchConsumer
|
||||
- com.intellij.util.Consumer
|
||||
- endBatch():V
|
||||
|
||||
@@ -255,3 +255,4 @@ modcommand.executor.cannot.rename.file=Cannot rename ''{0}'' into ''{1}'': {2}
|
||||
modcommand.executor.unable.to.find.the.new.file=Unable to find the new file {0}
|
||||
modcommand.executor.modification.of.guarded.region=Action tries to modify guarded region
|
||||
preview.binary.content=(binary content)
|
||||
complete=Complete
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ModNavigator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -10,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* Use {@link TailTypes#charType(char)} factory method instead of constructor to avoid possible deadlock
|
||||
* until deprecated static fields are not removed from {@link TailType},
|
||||
*/
|
||||
public class CharTailType extends TailType {
|
||||
public class CharTailType extends ModNavigatorTailType {
|
||||
private final char myChar;
|
||||
private final boolean myOverwrite;
|
||||
|
||||
@@ -29,8 +30,8 @@ public class CharTailType extends TailType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, final int tailOffset) {
|
||||
return insertChar(editor, tailOffset, myChar, myOverwrite);
|
||||
public int processTail(@NotNull Project project, @NotNull ModNavigator navigator, int tailOffset) {
|
||||
return insertChar(navigator, tailOffset, myChar, myOverwrite);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ModNavigator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A base class for tail types that use {@link ModNavigator} instead of {@link Editor} to insert the tail.
|
||||
*/
|
||||
public abstract class ModNavigatorTailType extends TailType {
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, int tailOffset) {
|
||||
return processTail(Objects.requireNonNull(editor.getProject()), editor.asPsiNavigator(), tailOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param project current project
|
||||
* @param navigator {@link ModNavigator} to use
|
||||
* @param tailOffset tail offset
|
||||
* @return new tail offset
|
||||
*/
|
||||
public abstract int processTail(@NotNull Project project, @NotNull ModNavigator navigator, int tailOffset);
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.openapi.editor.CaretModel;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ModNavigator;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
@@ -39,7 +39,15 @@ public abstract class TailType {
|
||||
return insertChar(editor, tailOffset, c, true);
|
||||
}
|
||||
|
||||
public static int insertChar(@NotNull ModNavigator editor, int tailOffset, char c) {
|
||||
return insertChar(editor, tailOffset, c, true);
|
||||
}
|
||||
|
||||
public static int insertChar(@NotNull Editor editor, int tailOffset, char c, boolean overwrite) {
|
||||
return insertChar(editor.asPsiNavigator(), tailOffset, c, overwrite);
|
||||
}
|
||||
|
||||
public static int insertChar(@NotNull ModNavigator editor, int tailOffset, char c, boolean overwrite) {
|
||||
Document document = editor.getDocument();
|
||||
int textLength = document.getTextLength();
|
||||
CharSequence chars = document.getCharsSequence();
|
||||
@@ -54,9 +62,12 @@ public abstract class TailType {
|
||||
}
|
||||
|
||||
protected static int moveCaret(@NotNull Editor editor, int tailOffset, int delta) {
|
||||
CaretModel model = editor.getCaretModel();
|
||||
if (model.getOffset() == tailOffset) {
|
||||
model.moveToOffset(tailOffset + delta);
|
||||
return moveCaret(editor.asPsiNavigator(), tailOffset, delta);
|
||||
}
|
||||
|
||||
protected static int moveCaret(@NotNull ModNavigator editor, int tailOffset, int delta) {
|
||||
if (editor.getCaretOffset() == tailOffset) {
|
||||
editor.moveCaretTo(tailOffset + delta);
|
||||
}
|
||||
return tailOffset + delta;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,20 @@ package com.intellij.codeInsight;
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ModNavigator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class TailTypes {
|
||||
private TailTypes() { }
|
||||
|
||||
private static final TailType UNKNOWN = new TailType() {
|
||||
private static final ModNavigatorTailType UNKNOWN = new ModNavigatorTailType() {
|
||||
@Override
|
||||
public int processTail(@NotNull Project project, @NotNull ModNavigator navigator, int tailOffset) {
|
||||
return tailOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, final int tailOffset) {
|
||||
return tailOffset;
|
||||
@@ -22,7 +29,12 @@ public final class TailTypes {
|
||||
}
|
||||
};
|
||||
|
||||
private static final TailType NONE = new TailType() {
|
||||
private static final ModNavigatorTailType NONE = new ModNavigatorTailType() {
|
||||
@Override
|
||||
public int processTail(@NotNull Project project, @NotNull ModNavigator navigator, int tailOffset) {
|
||||
return tailOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, final int tailOffset) {
|
||||
return tailOffset;
|
||||
@@ -34,13 +46,13 @@ public final class TailTypes {
|
||||
}
|
||||
};
|
||||
|
||||
private static final TailType SEMICOLON = new CharTailType(';');
|
||||
private static final ModNavigatorTailType SEMICOLON = new CharTailType(';');
|
||||
|
||||
private static final TailType SPACE = new CharTailType(' ');
|
||||
private static final ModNavigatorTailType SPACE = new CharTailType(' ');
|
||||
|
||||
private static final TailType INSERT_SPACE = new CharTailType(' ', false);
|
||||
private static final ModNavigatorTailType INSERT_SPACE = new CharTailType(' ', false);
|
||||
|
||||
private static final TailType HUMBLE_SPACE_BEFORE_WORD = new CharTailType(' ', false) {
|
||||
private static final ModNavigatorTailType HUMBLE_SPACE_BEFORE_WORD = new CharTailType(' ', false) {
|
||||
@Override
|
||||
public boolean isApplicable(@NotNull InsertionContext context) {
|
||||
CharSequence text = context.getDocument().getCharsSequence();
|
||||
@@ -60,15 +72,15 @@ public final class TailTypes {
|
||||
}
|
||||
};
|
||||
|
||||
private static final TailType DOT = new CharTailType('.');
|
||||
private static final ModNavigatorTailType DOT = new CharTailType('.');
|
||||
|
||||
private static final TailType CASE_COLON = new CharTailType(':');
|
||||
private static final ModNavigatorTailType CASE_COLON = new CharTailType(':');
|
||||
|
||||
private static final TailType EQUALS = new CharTailType('=');
|
||||
private static final ModNavigatorTailType EQUALS = new CharTailType('=');
|
||||
|
||||
private static final TailType COND_EXPR_COLON = new TailType() {
|
||||
private static final ModNavigatorTailType COND_EXPR_COLON = new ModNavigatorTailType() {
|
||||
@Override
|
||||
public int processTail(final @NotNull Editor editor, final int tailOffset) {
|
||||
public int processTail(@NotNull Project project, @NotNull ModNavigator editor, int tailOffset) {
|
||||
Document document = editor.getDocument();
|
||||
int textLength = document.getTextLength();
|
||||
CharSequence chars = document.getCharsSequence();
|
||||
|
||||
+8
-1
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.LanguageExtension;
|
||||
import com.intellij.lang.LanguageExtensionWithAny;
|
||||
import com.intellij.modcompletion.CompletionItemProvider;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
@@ -14,6 +15,7 @@ import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.PossiblyDumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
@@ -232,7 +234,12 @@ public abstract class CompletionContributor implements PossiblyDumbAware {
|
||||
}
|
||||
|
||||
public static @NotNull List<CompletionContributor> forLanguage(@NotNull Language language) {
|
||||
return INSTANCE.forKey(language);
|
||||
List<CompletionContributor> contributors = INSTANCE.forKey(language);
|
||||
if (Registry.is("ide.completion.modcommand")) {
|
||||
contributors =
|
||||
ContainerUtil.concat(ContainerUtil.map(CompletionItemProvider.forLanguage(language), CompletionItemContributor::new), contributors);
|
||||
}
|
||||
return contributors;
|
||||
}
|
||||
|
||||
public static @NotNull List<CompletionContributor> forLanguageHonorDumbness(@NotNull Language language, @NotNull Project project) {
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcompletion.CompletionItemProvider;
|
||||
import com.intellij.openapi.diagnostic.ReportingClassSubstitutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link CompletionItemProvider} to be used as a {@link CompletionContributor}.
|
||||
*/
|
||||
final class CompletionItemContributor extends CompletionContributor implements ReportingClassSubstitutor {
|
||||
private final CompletionItemProvider myProvider;
|
||||
|
||||
@SuppressWarnings("NonDefaultConstructor")
|
||||
CompletionItemContributor(CompletionItemProvider provider) {
|
||||
myProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
|
||||
ActionContext actionContext = ActionContext.from(parameters.getEditor(), parameters.getOriginalFile())
|
||||
.withElement(parameters.getPosition());
|
||||
CompletionItemProvider.CompletionContext context = new CompletionItemProvider.CompletionContext(
|
||||
actionContext, result.getPrefixMatcher().getPrefix(), parameters.getInvocationCount(), parameters.getCompletionType());
|
||||
myProvider.provideItems(context, item -> {
|
||||
result.addElement(new CompletionItemLookupElement(item));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Class<?> getSubstitutedClass() {
|
||||
return myProvider.getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Adapter for " + myProvider;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.analysis.AnalysisBundle;
|
||||
import com.intellij.codeInsight.lookup.Lookup;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommandExecutor;
|
||||
import com.intellij.modcompletion.CompletionItem;
|
||||
import com.intellij.modcompletion.CompletionItemPresentation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link CompletionItem} that adapts it to {@link LookupElement}.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
@ApiStatus.Internal
|
||||
public final class CompletionItemLookupElement extends LookupElement {
|
||||
private final CompletionItem item;
|
||||
|
||||
CompletionItemLookupElement(CompletionItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public CompletionItem item() {
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupString() {
|
||||
return item.mainLookupString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable Set<String> getAllLookupStrings() {
|
||||
Set<String> strings = item.additionalLookupStrings();
|
||||
return strings.isEmpty() ? Set.of(item.mainLookupString()) :
|
||||
Stream.concat(Stream.of(item.mainLookupString()), strings.stream()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return item.contextObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderElement(LookupElementPresentation presentation) {
|
||||
CompletionItemPresentation itemPresentation = item.presentation();
|
||||
// TODO: apply styles when possible
|
||||
presentation.setItemText(itemPresentation.mainText().toText());
|
||||
presentation.setTailText(" (MC)");
|
||||
presentation.setTypeText(itemPresentation.detailText().toText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresCommittedDocuments() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInsert(InsertionContext context) {
|
||||
CompletionItem.InsertionContext insertionContext = new CompletionItem.InsertionContext(
|
||||
context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR ?
|
||||
CompletionItem.InsertionMode.OVERWRITE : CompletionItem.InsertionMode.INSERT,
|
||||
context.getCompletionChar());
|
||||
ActionContext actionContext = ActionContext.from(context.getEditor(), context.getFile());
|
||||
ModCommandExecutor.executeInteractively(
|
||||
actionContext,
|
||||
AnalysisBundle.message("complete"), context.getEditor(),
|
||||
() -> item.perform(actionContext, insertionContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Adapter for " + item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcompletion;
|
||||
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An item in the code completion list
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface CompletionItem {
|
||||
/**
|
||||
* @return the string searched for
|
||||
*/
|
||||
String mainLookupString();
|
||||
|
||||
/**
|
||||
* @return set of additional lookup strings, if necessary
|
||||
*/
|
||||
default Set<String> additionalLookupStrings() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a context object which could be used for item weighing
|
||||
*/
|
||||
Object contextObject();
|
||||
|
||||
/**
|
||||
* @return a presentation of the completion item
|
||||
*/
|
||||
CompletionItemPresentation presentation();
|
||||
|
||||
/**
|
||||
* @param actionContext action context where the completion is performed
|
||||
* @param insertionContext an insertion context, which describes how exactly the user invoked the completion
|
||||
* @return the command to perform the completion (e.g., insert the lookup string)
|
||||
*/
|
||||
ModCommand perform(ActionContext actionContext, InsertionContext insertionContext);
|
||||
|
||||
/**
|
||||
* Context for the item insertion
|
||||
*
|
||||
* @param mode whether to insert or overwrite the existing text
|
||||
* @param insertionCharacter the character used to finish the completion
|
||||
*/
|
||||
record InsertionContext(InsertionMode mode, char insertionCharacter) {
|
||||
|
||||
}
|
||||
|
||||
enum InsertionMode {
|
||||
/**
|
||||
* Insert mode: we should insert the text to the caret position
|
||||
*/
|
||||
INSERT,
|
||||
|
||||
/**
|
||||
* Overwrite mode: we should overwrite the existing text at the caret position
|
||||
* The exact behavior (e.g. overwrite to the end of current word) is up to the specific completion item.
|
||||
*/
|
||||
OVERWRITE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcompletion;
|
||||
|
||||
import com.intellij.openapi.util.text.MarkupText;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Visual representation of {@link CompletionItem}.
|
||||
*
|
||||
* @param mainText main text describing the completion item (usually the same text that will be inserted, with optional suffix information)
|
||||
* @param detailText optional text describing the completion item in more detail (like method type, etc.)
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public record CompletionItemPresentation(
|
||||
MarkupText mainText,
|
||||
MarkupText detailText
|
||||
) {
|
||||
/**
|
||||
* Creates a presentation with the given main text and an empty detail text.
|
||||
*
|
||||
* @param mainText main text to use
|
||||
*/
|
||||
public CompletionItemPresentation(MarkupText mainText) {
|
||||
this(mainText, MarkupText.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mainText new main text
|
||||
* @return a new presentation with the given main text and the same detail text
|
||||
*/
|
||||
public CompletionItemPresentation withMainText(MarkupText mainText) {
|
||||
return new CompletionItemPresentation(mainText, detailText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailText new detail text
|
||||
* @return a new presentation with the same main text and the given detail text
|
||||
*/
|
||||
public CompletionItemPresentation withDetailText(MarkupText detailText) {
|
||||
return new CompletionItemPresentation(mainText, detailText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcompletion;
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionType;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.LanguageExtension;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A language-specific provider for {@link CompletionItem} completion options
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface CompletionItemProvider {
|
||||
LanguageExtension<CompletionItemProvider> EP_NAME = new LanguageExtension<>("com.intellij.modcompletion.completionItemProvider");
|
||||
|
||||
/**
|
||||
* Provide completion items for given context
|
||||
*
|
||||
* @param context context to use
|
||||
* @param sink a consumer to pass completion items to
|
||||
*/
|
||||
void provideItems(CompletionContext context, Consumer<CompletionItem> sink);
|
||||
|
||||
/**
|
||||
* @param language language to get providers for
|
||||
* @return language-specific completion providers
|
||||
*/
|
||||
static List<CompletionItemProvider> forLanguage(Language language) {
|
||||
return EP_NAME.forKey(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion context
|
||||
*
|
||||
* @param context an action context to use
|
||||
* @param prefix current completion prefix
|
||||
* @param invocationCount invocation count (0 = auto-popup)
|
||||
* @param type completion type
|
||||
*/
|
||||
record CompletionContext(ActionContext context, String prefix, int invocationCount, CompletionType type) {
|
||||
/**
|
||||
* @return a context PSI element
|
||||
*/
|
||||
public PsiElement element() {
|
||||
return Objects.requireNonNull(context.element());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the smart completion is invoked (Ctrl+Shift+Space)
|
||||
*/
|
||||
public boolean isSmart() {
|
||||
return type == CompletionType.SMART;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the basic completion is invoked (Ctrl+Space)
|
||||
*/
|
||||
public boolean isBasic() {
|
||||
return type == CompletionType.BASIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcompletion;
|
||||
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A {@link CompletionItem} that performs an update via {@link ModCommand#psiUpdate} API. The overwrite mode is handled automatically.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public abstract class PsiUpdateCompletionItem implements CompletionItem {
|
||||
@Override
|
||||
public ModCommand perform(ActionContext actionContext, InsertionContext insertionContext) {
|
||||
return ModCommand.psiUpdate(actionContext, updater -> {
|
||||
if (insertionContext.mode() == InsertionMode.OVERWRITE) {
|
||||
updater.getDocument().deleteString(
|
||||
actionContext.offset(), calculateEndOffsetForOverwrite(updater.getDocument(), actionContext.offset()));
|
||||
}
|
||||
update(actionContext, insertionContext, updater);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param document the document to use
|
||||
* @param startFrom a caret position
|
||||
* @return the position of the identifier end. The default implementation uses Java identifier rules.
|
||||
* Override this method to use different rules for ID boundary.
|
||||
*/
|
||||
protected int calculateEndOffsetForOverwrite(Document document, int startFrom) {
|
||||
final CharSequence text = document.getCharsSequence();
|
||||
int idEnd = startFrom;
|
||||
while (idEnd < text.length() && Character.isJavaIdentifierPart(text.charAt(idEnd))) {
|
||||
idEnd++;
|
||||
}
|
||||
return idEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs PSI/document update of the file copy to generate a final {@link ModCommand}.
|
||||
*
|
||||
* @param actionContext context of the action
|
||||
* @param insertionContext context of the insertion (like which character was used to finish the completion)
|
||||
* @param updater an updater to use
|
||||
*/
|
||||
public abstract void update(ActionContext actionContext, InsertionContext insertionContext, ModPsiUpdater updater);
|
||||
}
|
||||
@@ -249,6 +249,7 @@ f:com.intellij.openapi.components.ServicesKt
|
||||
- a:getCaretOffset():I
|
||||
- a:getDocument():com.intellij.openapi.editor.Document
|
||||
- a:moveCaretTo(I):V
|
||||
- registerTabOut(com.intellij.openapi.util.TextRange,I):V
|
||||
- a:select(com.intellij.openapi.util.TextRange):V
|
||||
f:com.intellij.openapi.editor.markup.AttributesFlyweight
|
||||
- *s:create(java.awt.Color,java.awt.Color,I,java.awt.Color,com.intellij.openapi.editor.markup.EffectType,java.util.Map,java.awt.Color):com.intellij.openapi.editor.markup.AttributesFlyweight
|
||||
|
||||
@@ -35,4 +35,17 @@ public interface ModNavigator {
|
||||
* @return the document being edited
|
||||
*/
|
||||
@NotNull Document getDocument();
|
||||
|
||||
/**
|
||||
* Registers a tab out scope, so pressing tab inside the scope moves the caret to the specified offset
|
||||
* instead of adding a tab character.
|
||||
*
|
||||
* May do nothing if tab out is not supported by the implementation.
|
||||
*
|
||||
* @param range scope range
|
||||
* @param tabOutOffset target offset for tab-out
|
||||
*/
|
||||
default void registerTabOut(@NotNull TextRange range, int tabOutOffset) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.analysis.AnalysisBundle;
|
||||
import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.CodeInsightSettings;
|
||||
import com.intellij.codeInsight.completion.CompletionAssertions.WatchingInsertionContext;
|
||||
@@ -14,6 +15,9 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl;
|
||||
import com.intellij.featureStatistics.FeatureUsageTracker;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommandExecutor;
|
||||
import com.intellij.modcompletion.CompletionItem;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
|
||||
import com.intellij.openapi.application.*;
|
||||
@@ -714,6 +718,11 @@ public class CodeCompletionHandlerBase {
|
||||
|
||||
WatchingInsertionContext context =
|
||||
CompletionUtil.createInsertionContext(lookupItems, item, completionChar, editor, psiFile, caretOffset, idEndOffset, offsetMap);
|
||||
if (item instanceof CompletionItemLookupElement wrapper) {
|
||||
insertItem(completionChar, editor, psiFile, wrapper);
|
||||
return context;
|
||||
}
|
||||
|
||||
int initialStartOffset = Math.max(0, caretOffset - item.getLookupString().length());
|
||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
@@ -746,6 +755,21 @@ public class CodeCompletionHandlerBase {
|
||||
return context;
|
||||
}
|
||||
|
||||
private static void insertItem(char completionChar,
|
||||
@NotNull Editor editor,
|
||||
@NotNull PsiFile psiFile,
|
||||
CompletionItemLookupElement wrapper) {
|
||||
CompletionItem.InsertionContext insertionContext = new CompletionItem.InsertionContext(
|
||||
completionChar == Lookup.REPLACE_SELECT_CHAR ?
|
||||
CompletionItem.InsertionMode.OVERWRITE : CompletionItem.InsertionMode.INSERT,
|
||||
completionChar);
|
||||
ActionContext actionContext = ActionContext.from(editor, psiFile);
|
||||
ModCommandExecutor.executeInteractively(
|
||||
actionContext,
|
||||
AnalysisBundle.message("complete"), editor,
|
||||
() -> wrapper.item().perform(actionContext, insertionContext));
|
||||
}
|
||||
|
||||
private static @NotNull WatchingInsertionContext callHandleInsert(@NotNull CompletionProcessEx indicator,
|
||||
@NotNull LookupElement item,
|
||||
@NotNull List<LookupElement> items,
|
||||
|
||||
@@ -556,6 +556,7 @@ com.intellij.openapi.editor.ex.EditorEx
|
||||
- a:addFocusListener(com.intellij.openapi.editor.ex.FocusChangeListener,com.intellij.openapi.Disposable):V
|
||||
- a:addPropertyChangeListener(java.beans.PropertyChangeListener):V
|
||||
- a:addPropertyChangeListener(java.beans.PropertyChangeListener,com.intellij.openapi.Disposable):V
|
||||
- asPsiNavigator():com.intellij.openapi.editor.ModNavigator
|
||||
- a:createBoundColorSchemeDelegate(com.intellij.openapi.editor.colors.EditorColorsScheme):com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
- a:getBackgroundColor():java.awt.Color
|
||||
- a:getContentSize():java.awt.Dimension
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.openapi.editor.ex;
|
||||
|
||||
import com.intellij.codeInsight.editorActions.TabOutScopesTracker;
|
||||
import com.intellij.ide.CopyProvider;
|
||||
import com.intellij.ide.CutProvider;
|
||||
import com.intellij.ide.DeleteProvider;
|
||||
@@ -374,4 +375,34 @@ public interface EditorEx extends Editor {
|
||||
default int getStickyLinesPanelHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default @NotNull ModNavigator asPsiNavigator() {
|
||||
return new ModNavigator() {
|
||||
@Override
|
||||
public @NotNull Document getDocument() {
|
||||
return EditorEx.this.getDocument();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select(@NotNull TextRange range) {
|
||||
getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveCaretTo(int offset) {
|
||||
getCaretModel().moveToOffset(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCaretOffset() {
|
||||
return getCaretModel().getOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTabOut(@NotNull TextRange range, int tabOutOffset) {
|
||||
TabOutScopesTracker.getInstance().registerScopeRange(EditorEx.this, range.getStartOffset(), range.getEndOffset(), tabOutOffset);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<extensionPoint qualifiedName="com.intellij.mlCompletionFeaturesCollector" dynamic="true" beanClass="com.intellij.lang.LanguageExtensionPoint">
|
||||
<with attribute="implementationClass" implements="com.intellij.codeInsight.inline.completion.features.InlineCompletionFeaturesCollector"/>
|
||||
</extensionPoint>
|
||||
<extensionPoint qualifiedName="com.intellij.modcompletion.completionItemProvider"
|
||||
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
|
||||
<with attribute="implementationClass" implements="com.intellij.modcompletion.CompletionItemProvider"/>
|
||||
</extensionPoint>
|
||||
|
||||
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command.factory"
|
||||
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
|
||||
@@ -79,4 +83,8 @@
|
||||
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.command.commands.IntentionCommandOffsetProvider"/>
|
||||
</extensionPoint>
|
||||
</extensionPoints>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<registryKey defaultValue="false" description="ModCompletion contributors (see com.intellij.modcompletion.CompletionItemProvider)"
|
||||
key="ide.completion.modcommand"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
Reference in New Issue
Block a user