Skip to content

Commit

Permalink
Feature/accept by line new (#97)
Browse files Browse the repository at this point in the history
* Accept by line.

* Accept by line.

* Accept by line.

* Completion by line accept display fix.

* Completion by line accept display fix.

* Ctr + z undo fix.

* Ignore multi-caret model completion
  • Loading branch information
maozhen520 authored Oct 30, 2024
1 parent 7d7d360 commit 1faaf2d
Show file tree
Hide file tree
Showing 15 changed files with 547 additions and 83 deletions.
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
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Up @@ -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();

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

return createCompletions(
editor,
completionsResponse,
editor.getDocument(),
offset,
Expand Down Expand Up @@ -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,
Expand All @@ -257,6 +260,7 @@ private List<DevPilotCompletion> createCompletions(
.mapToObj(
index ->
CompletionUtils.createDevpilotCompletion(
editor,
document,
offset,
completions.oldPrefix,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 1faaf2d

Please sign in to comment.