Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/accept by line new #97

Merged
merged 7 commits into from
Oct 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 52 additions & 22 deletions src/main/java/com/zhongan/devpilot/completions/CompletionUtils.java
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.zhongan.devpilot.completions.general.SuggestionTrigger;
import com.zhongan.devpilot.completions.prediction.DevPilotCompletion;
@@ -45,42 +46,71 @@ private static String getCursorSuffix(@NotNull Document document, int cursorPosi

@Nullable
public static DevPilotCompletion createDevpilotCompletion(
@NotNull Document document,
int offset,
String oldPrefix,
ResultEntry result,
int index,
SuggestionTrigger suggestionTrigger) {
Editor editor,
@NotNull Document document,
int offset,
String oldPrefix,
ResultEntry result,
int index,
SuggestionTrigger suggestionTrigger) {
String cursorPrefix = CompletionUtils.getCursorPrefix(document, offset);
String cursorSuffix = CompletionUtils.getCursorSuffix(document, offset);
if (cursorPrefix == null || cursorSuffix == null) {
return null;
}

return new DevPilotCompletion(
result.id,
oldPrefix,
result.newPrefix,
result.oldSuffix,
result.newSuffix,
index,
cursorPrefix,
cursorSuffix,
result.completionMetadata,
suggestionTrigger);
editor,
result.id,
oldPrefix,
result.newPrefix,
result.oldSuffix,
result.newSuffix,
index,
cursorPrefix,
cursorSuffix,
result.completionMetadata,
suggestionTrigger);
}

@Nullable
public static DevPilotCompletion createSimpleDevpilotCompletion(
Editor editor,
int offset,
String oldPrefix,
String newPrefix,
String id,
@NotNull Document document) {
String cursorPrefix = CompletionUtils.getCursorPrefix(document, offset);
String cursorSuffix = CompletionUtils.getCursorSuffix(document, offset);
if (cursorPrefix == null || cursorSuffix == null) {
return null;
}
return new DevPilotCompletion(
editor,
id,
oldPrefix,
newPrefix,
"",
"",
0,
cursorPrefix,
cursorSuffix,
null,
null);
}

public static int completionLimit(
CompletionParameters parameters, CompletionResultSet result, boolean isLocked) {
CompletionParameters parameters, CompletionResultSet result, boolean isLocked) {
return completionLimit(
parameters.getEditor().getDocument(),
result.getPrefixMatcher().getPrefix(),
parameters.getOffset(),
isLocked);
parameters.getEditor().getDocument(),
result.getPrefixMatcher().getPrefix(),
parameters.getOffset(),
isLocked);
}

public static int completionLimit(
@NotNull Document document, @NotNull String prefix, int offset, boolean isLocked) {
@NotNull Document document, @NotNull String prefix, int offset, boolean isLocked) {
if (isLocked) {
return 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.zhongan.devpilot.completions.inline;

import com.intellij.codeInsight.hint.HintManagerImpl.ActionToIgnore;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;

public class AcceptDevPilotInlineCompletionByLineAction extends EditorAction implements ActionToIgnore, InlineCompletionAction {

public static final String ACTION_ID = "AcceptDevPilotInlineCompletionByLineAction";

public AcceptDevPilotInlineCompletionByLineAction() {
super(new AcceptInlineCompletionHandler());
}

private static class AcceptInlineCompletionHandler extends EditorWriteActionHandler {

@Override
public void executeWriteAction(Editor editor, Caret caret, DataContext dataContext) {
CompletionPreview.getInstance(editor).applyPreviewByLine(caret != null ? caret : editor.getCaretModel().getCurrentCaret());
}

@Override
protected boolean isEnabledForCaret(Editor editor, Caret caret, DataContext dataContext) {
return CompletionPreview.getInstance(editor) != null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package com.zhongan.devpilot.completions.inline;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
@@ -18,18 +26,24 @@
import com.zhongan.devpilot.completions.inline.render.DevPilotInlay;
import com.zhongan.devpilot.completions.prediction.DevPilotCompletion;
import com.zhongan.devpilot.treesitter.TreeSitterParser;
import com.zhongan.devpilot.util.DevPilotMessageBundle;
import com.zhongan.devpilot.util.TelemetryUtils;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static com.zhongan.devpilot.completions.CompletionUtils.createSimpleDevpilotCompletion;
import static com.zhongan.devpilot.completions.inline.CompletionPreviewUtils.shouldRemoveSuffix;

public class CompletionPreview implements Disposable {
private static final Key<CompletionPreview> INLINE_COMPLETION_PREVIEW =
Key.create("INLINE_COMPLETION_PREVIEW");
Key.create("INLINE_COMPLETION_PREVIEW");

public final Editor editor;

@@ -44,7 +58,7 @@ public class CompletionPreview implements Disposable {
private InlineCaretListener inlineCaretListener;

private CompletionPreview(
@NotNull Editor editor, List<DevPilotCompletion> completions, int offset) {
@NotNull Editor editor, List<DevPilotCompletion> completions, int offset) {
this.editor = editor;
this.completions = completions;
this.offset = offset;
@@ -108,8 +122,8 @@ private DevPilotCompletion createPreview() {
DevPilotCompletion completion = completions.get(currentIndex);

if (!(editor instanceof EditorImpl)
|| editor.getSelectionModel().hasSelection()
|| InplaceRefactoring.getActiveInplaceRenamer(editor) != null) {
|| editor.getSelectionModel().hasSelection()
|| InplaceRefactoring.getActiveInplaceRenamer(editor) != null) {
return null;
}

@@ -181,10 +195,112 @@ private void applyPreviewInternal(@NotNull Integer cursorOffset, Project project
getAutoImportHandler(editor, fileAfterCompletion, startOffset, endOffset).invoke();
});

TelemetryUtils.completionAccept(completion.id, file);
TelemetryUtils.completionAccept(completion.id, file, completion.getUnacceptedLines());
}

public void applyPreviewByLine(@Nullable Caret caret) {
if (caret == null) {
return;
}

Project project = editor.getProject();

if (project == null) {
return;
}

PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());

if (file == null) {
return;
}

try {
applyPreviewInternalByLine(project, file);
} catch (Throwable e) {
Logger.getInstance(getClass()).warn("Failed in the processes of accepting completion by line", e);
} finally {
Disposer.dispose(this);
}
}

private void applyPreviewInternalByLine(Project project, PsiFile file) {
DevPilotCompletion completion = completions.get(currentIndex);
Document document = editor.getDocument();
String line = completion.getNextUnacceptLineState().getLine();
LogicalPosition currentPos = editor.getCaretModel().getLogicalPosition();
int insertionOffset = editor.logicalPositionToOffset(currentPos);
if (StringUtils.isEmpty(line)) {
completion.acceptLine(insertionOffset);
line = completion.getNextUnacceptLineState().getLine();
}
if (completion.getLineStateItems().getIndex() > 0) {
insertionOffset += "\n".length(); // can't remove
}
completion.acceptLine(insertionOffset + line.length());
document.insertString(insertionOffset, line + "\n"); // offset don't change
editor.getCaretModel().moveToOffset(insertionOffset + line.length());
Objects.requireNonNull(CompletionPreview.getInstance(editor)).continuePreview();
PsiFile fileAfterCompletion = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
int startOffset = insertionOffset - completion.oldPrefix.length();
int endOffset = insertionOffset + line.length();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
getAutoImportHandler(editor, fileAfterCompletion, startOffset, endOffset).invoke();
});
TelemetryUtils.completionAccept(completion.id, file, line);
}

public void continuePreview() {
DevPilotCompletion completion = completions.get(currentIndex);
if (!(editor instanceof EditorImpl)
|| editor.getSelectionModel().hasSelection()
|| InplaceRefactoring.getActiveInplaceRenamer(editor) != null) {
return;
}

try {
editor.getDocument().startGuardedBlockChecking();
DevPilotCompletion simpleDevpilotCompletion = createSimpleDevpilotCompletion(editor, editor.getCaretModel().getOffset(),
"",
completion.getLineStateItems().getUnacceptedLines(),
UUID.randomUUID().toString(), editor.getDocument());
CompletionPreview.clear(editor);
CompletionPreview.createInstance(editor, Collections.singletonList(simpleDevpilotCompletion), editor.getCaretModel().getOffset());
} finally {
editor.getDocument().stopGuardedBlockChecking();
}
}

public boolean isByLineAcceptCaretChange(CaretEvent caretEvent) {
int newOffset = editor.logicalPositionToOffset(caretEvent.getNewPosition());
DevPilotCompletion completion = completions.get(currentIndex);
int currentCompletionPosition = completion.getCurrentCompletionPosition();
return newOffset == currentCompletionPosition;
}

public boolean isByLineAcceptDocumentChange(DocumentEvent documentEvent) {
int previousOffset = documentEvent.getOffset();
int newOffset = previousOffset + documentEvent.getNewLength();
if (newOffset < 0 || previousOffset >= newOffset) return false; // previousOffset == newOffset ctr + z
String addedText = editor.getDocument().getText(new TextRange(previousOffset, newOffset));

DevPilotCompletion completion = completions.get(currentIndex);
String completionCode = completion.getCurrentCompletionCode();
return StringUtils.equals(addedText, completionCode);
}

private static AutoImportHandler getAutoImportHandler(Editor editor, PsiFile file, int startOffset, int endOffset) {
return new AutoImportHandler(startOffset, endOffset, editor, file);
}

public static String byLineAcceptHintText() {
String acceptShortcut = getByLineAcceptShortcutText();
return String.format("%s %s", acceptShortcut, DevPilotMessageBundle.get("completion.apply.partial.tooltips"));
}

private static String getByLineAcceptShortcutText() {
return StringUtil.defaultIfEmpty(
KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction(AcceptDevPilotInlineCompletionByLineAction.ACTION_ID)),
"Missing shortcut key");
}
}
Original file line number Diff line number Diff line change
@@ -53,10 +53,15 @@ public void documentChangedNonBulk(@NotNull DocumentEvent event) {
}
Document document = event.getDocument();
Editor editor = getActiveEditor(document);
if (editor == null || !EditorUtils.isMainEditor(editor)) {
if (editor == null || !EditorUtils.isMainEditor(editor) || editor.getCaretModel().getCaretCount() > 1) {
return;
}
DevPilotCompletion lastShownCompletion = CompletionPreview.getCurrentCompletion(editor);
CompletionPreview completionPreview = CompletionPreview.getInstance(editor);

if (completionPreview != null && completionPreview.isByLineAcceptDocumentChange(event)) {
return;
}
CompletionPreview.clear(editor);
int offset = event.getOffset() + event.getNewLength();

Original file line number Diff line number Diff line change
@@ -190,6 +190,7 @@ private List<DevPilotCompletion> retrieveInlineCompletion(
}

return createCompletions(
editor,
completionsResponse,
editor.getDocument(),
offset,
@@ -249,6 +250,8 @@ private void afterCompletionShown(DevPilotCompletion completion, Editor editor)
}

private List<DevPilotCompletion> createCompletions(
@NotNull Editor editor,
@NotNull
AutocompleteResponse completions,
@NotNull Document document,
int offset,
@@ -257,6 +260,7 @@ private List<DevPilotCompletion> createCompletions(
.mapToObj(
index ->
CompletionUtils.createDevpilotCompletion(
editor,
document,
offset,
completions.oldPrefix,
Original file line number Diff line number Diff line change
@@ -24,13 +24,15 @@ public void caretPositionChanged(@NotNull CaretEvent event) {
return;
}

if (completionPreview.isByLineAcceptCaretChange(event)) {
return;
}
Disposer.dispose(completionPreview);
InlineCompletionCache.clear(event.getEditor());
}

private boolean isSingleOffsetChange(CaretEvent event) {
return event.getOldPosition().line == event.getNewPosition().line
&& event.getOldPosition().column + 1 == event.getNewPosition().column;
return event.getOldPosition().line == event.getNewPosition().line;
}

@Override
Loading