Skip to content

Commit

Permalink
feat(eclipse): support partially accept inline completion. (#3269)
Browse files Browse the repository at this point in the history
  • Loading branch information
icycodes authored Oct 14, 2024
1 parent 2f97888 commit dac6145
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 16 deletions.
4 changes: 2 additions & 2 deletions clients/eclipse/feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<feature
id="com.tabbyml.features.tabby4eclipse"
label="Tabby"
version="0.0.2.25"
version="0.0.2.26"
provider-name="com.tabbyml">

<description url="http://www.example.com/description">
Expand All @@ -19,6 +19,6 @@

<plugin
id="com.tabbyml.tabby4eclipse"
version="0.0.2.25"/>
version="0.0.2.26"/>

</feature>
2 changes: 1 addition & 1 deletion clients/eclipse/plugin/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tabby Plugin for Eclipse
Bundle-SymbolicName: com.tabbyml.tabby4eclipse;singleton:=true
Bundle-Version: 0.0.2.25
Bundle-Version: 0.0.2.26
Bundle-Activator: com.tabbyml.tabby4eclipse.Activator
Bundle-Vendor: com.tabbyml
Require-Bundle: org.eclipse.ui,
Expand Down
47 changes: 43 additions & 4 deletions clients/eclipse/plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@
name="Accept Inline Completion"
id="com.tabbyml.tabby4eclipse.commands.inlineCompletion.accept">
</command>
<command
categoryId="com.tabbyml.tabby4eclipse.commands.inlineCompletion"
name="Accept Next Line of Inline Completion"
id="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextLine">
</command>
<command
categoryId="com.tabbyml.tabby4eclipse.commands.inlineCompletion"
name="Accept Next Word of Inline Completion"
id="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextWord">
</command>
<command
categoryId="com.tabbyml.tabby4eclipse.commands.inlineCompletion"
name="Dismiss Inline Completion"
Expand Down Expand Up @@ -203,6 +213,14 @@
class="com.tabbyml.tabby4eclipse.commands.inlineCompletion.Accept"
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.accept">
</handler>
<handler
class="com.tabbyml.tabby4eclipse.commands.inlineCompletion.AcceptNextLine"
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextLine">
</handler>
<handler
class="com.tabbyml.tabby4eclipse.commands.inlineCompletion.AcceptNextWord"
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextWord">
</handler>
<handler
class="com.tabbyml.tabby4eclipse.commands.inlineCompletion.Dismiss"
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.dismiss">
Expand Down Expand Up @@ -241,6 +259,15 @@
</handler>
</extension>

<extension point="org.eclipse.ui.contexts">
<context
id="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
name="Inline Completion Visible"
description="Tabby inline completion is visible."
parentId="org.eclipse.ui.textEditorScope">
</context>
</extension>

<extension point="org.eclipse.ui.bindings">
<key
commandId="com.tabbyml.tabby4eclipse.commands.chat.toggleChatView"
Expand All @@ -256,25 +283,37 @@
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.previous"
contextId="org.eclipse.ui.textEditorScope"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M3+[">
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.next"
contextId="org.eclipse.ui.textEditorScope"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M3+]">
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.accept"
contextId="org.eclipse.ui.textEditorScope"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="TAB">
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextLine"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+TAB">
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.acceptNextWord"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+ARROW_RIGHT">
</key>
<key
commandId="com.tabbyml.tabby4eclipse.commands.inlineCompletion.dismiss"
contextId="org.eclipse.ui.textEditorScope"
contextId="com.tabbyml.tabby4eclipse.inlineCompletionVisible"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="ESC">
</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.eclipse.core.commands.ExecutionException;

import com.tabbyml.tabby4eclipse.Logger;
import com.tabbyml.tabby4eclipse.inlineCompletion.IInlineCompletionService.AcceptType;
import com.tabbyml.tabby4eclipse.inlineCompletion.InlineCompletionService;

public class Accept extends AbstractHandler {
Expand All @@ -13,7 +14,7 @@ public class Accept extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
logger.debug("Accept the current inline completion.");
InlineCompletionService.getInstance().accept();
InlineCompletionService.getInstance().accept(AcceptType.FULL_COMPLETION);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tabbyml.tabby4eclipse.commands.inlineCompletion;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;

import com.tabbyml.tabby4eclipse.Logger;
import com.tabbyml.tabby4eclipse.inlineCompletion.IInlineCompletionService.AcceptType;
import com.tabbyml.tabby4eclipse.inlineCompletion.InlineCompletionService;

public class AcceptNextLine extends AbstractHandler {
private Logger logger = new Logger("Commands.InlineCompletion.AcceptNextLine");

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
logger.debug("Accept next line of the current inline completion.");
InlineCompletionService.getInstance().accept(AcceptType.NEXT_LINE);
return null;
}

@Override
public boolean isEnabled() {
return InlineCompletionService.getInstance().isCompletionItemVisible();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tabbyml.tabby4eclipse.commands.inlineCompletion;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;

import com.tabbyml.tabby4eclipse.Logger;
import com.tabbyml.tabby4eclipse.inlineCompletion.IInlineCompletionService.AcceptType;
import com.tabbyml.tabby4eclipse.inlineCompletion.InlineCompletionService;

public class AcceptNextWord extends AbstractHandler {
private Logger logger = new Logger("Commands.InlineCompletion.AcceptNextLine");

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
logger.debug("Accept next word of the current inline completion.");
InlineCompletionService.getInstance().accept(AcceptType.NEXT_WORD);
return null;
}

@Override
public boolean isEnabled() {
return InlineCompletionService.getInstance().isCompletionItemVisible();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.texteditor.ITextEditor;

import com.tabbyml.tabby4eclipse.chat.ChatMessage.FileContext;
Expand Down Expand Up @@ -67,6 +68,10 @@ public static Display getDisplay(ITextEditor textEditor) {
return getStyledTextWidget(textEditor).getDisplay();
}

public static IContextService getContextService(ITextEditor textEditor) {
return textEditor.getSite().getService(IContextService.class);
}

public static void asyncExec(Runnable runnable) {
PlatformUI.getWorkbench().getDisplay().asyncExec(runnable);
}
Expand Down Expand Up @@ -127,15 +132,15 @@ public static int getCurrentOffsetInDocument(ITextEditor textEditor) throws Ille
throw new IllegalStateException("Failed to get current offset in document.");
});
}

public static String getSelectedText() {
ITextEditor editor = getActiveTextEditor();
if (editor != null) {
return getSelectedText(editor);
}
return null;
}

public static String getSelectedText(ITextEditor textEditor) {
ISelection selection = textEditor.getSelectionProvider().getSelection();
if (selection instanceof ITextSelection textSelection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ public interface IInlineCompletionService {
*/
public void previous();

enum AcceptType {
FULL_COMPLETION, NEXT_LINE, NEXT_WORD,
}

/**
* Accept the current completion item ghost text.
*
* @param type the type of completion to accept.
*/
public void accept();
public void accept(AcceptType type);

/**
* Dismiss the current completion item ghost text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
Expand All @@ -12,6 +14,8 @@
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.texteditor.ITextEditor;

import com.tabbyml.tabby4eclipse.Logger;
Expand All @@ -34,9 +38,12 @@ private static class LazyHolder {
private static final IInlineCompletionService INSTANCE = new InlineCompletionService();
}

private static final String INLINE_COMPLETION_VISIBLE_CONTEXT_ID = "com.tabbyml.tabby4eclipse.inlineCompletionVisible";

private Logger logger = new Logger("InlineCompletionService");
private InlineCompletionRenderer renderer = new InlineCompletionRenderer();
private InlineCompletionContext current;
private IContextActivation inlineCompletionVisibleContext;

@Override
public boolean isCompletionItemVisible() {
Expand Down Expand Up @@ -76,6 +83,7 @@ public void trigger(boolean isManualTrigger) {
logger.info("Provide inline completion for TextEditor " + textEditor.getTitle() + " at offset " + offset
+ " with modification stamp " + modificationStamp);
renderer.hide();
deactivateInlineCompletionVisibleContext(textEditor);
if (current != null) {
if (current.job != null && !current.job.isDone()) {
logger.info("Cancel the current job due to new request.");
Expand Down Expand Up @@ -105,6 +113,7 @@ public void trigger(boolean isManualTrigger) {
InlineCompletionList list = request.convertInlineCompletionList(completionList);
current.response = new InlineCompletionContext.Response(list);
renderer.show(textViewer, current.response.getActiveCompletionItem());
activateInlineCompletionVisibleContext(textEditor);
EventParams eventParams = buildTelemetryEventParams(EventParams.Type.VIEW);
postTelemetryEvent(eventParams);
} catch (BadLocationException e) {
Expand Down Expand Up @@ -191,31 +200,55 @@ private int calcCycleIndex(int index, int listSize, int step) {
}

@Override
public void accept() {
public void accept(AcceptType acceptType) {
ITextEditor textEditor = EditorUtils.getActiveTextEditor();
logger.info("Accept inline completion in TextEditor " + textEditor.toString());
if (current == null || current.request == null || current.response == null) {
return;
}
int offset = current.request.offset;
InlineCompletionItem item = current.response.getActiveCompletionItem();
EventParams eventParams = buildTelemetryEventParams(EventParams.Type.SELECT);
EventParams eventParams = buildTelemetryEventParams(EventParams.Type.SELECT,
acceptType == AcceptType.FULL_COMPLETION ? null : "line");

renderer.hide();
deactivateInlineCompletionVisibleContext(textEditor);
current = null;

int prefixReplaceLength = item.getReplaceRange().getPrefixLength();
int suffixReplaceLength = item.getReplaceRange().getSuffixLength();
String text = item.getInsertText().substring(prefixReplaceLength);
if (text.isEmpty()) {
String textToInsert;

if (acceptType == AcceptType.NEXT_WORD) {
Pattern pattern = Pattern.compile("\\w+|\\W+");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
textToInsert = matcher.group();
} else {
textToInsert = text;
}
} else if (acceptType == AcceptType.NEXT_LINE) {
List<String> lines = List.of(text.split("\n"));
String line = lines.get(0);
if (text.isEmpty() && lines.size() > 1) {
line += "\n";
line += lines.get(1);
}
textToInsert = line;
} else {
textToInsert = text;
}

if (textToInsert.isEmpty()) {
return;
}

IDocument document = EditorUtils.getDocument(textEditor);
EditorUtils.syncExec(textEditor, () -> {
try {
document.replace(offset, suffixReplaceLength, text);
ITextSelection selection = new TextSelection(offset + text.length(), 0);
document.replace(offset, suffixReplaceLength, textToInsert);
ITextSelection selection = new TextSelection(offset + textToInsert.length(), 0);
textEditor.getSelectionProvider().setSelection(selection);
postTelemetryEvent(eventParams);
} catch (BadLocationException e) {
Expand All @@ -230,6 +263,8 @@ public void dismiss() {
logger.info("Dismiss inline completion.");
EventParams eventParams = buildTelemetryEventParams(EventParams.Type.DISMISS);
renderer.hide();
ITextEditor textEditor = EditorUtils.getActiveTextEditor();
deactivateInlineCompletionVisibleContext(textEditor);
postTelemetryEvent(eventParams);
}
if (current != null) {
Expand Down Expand Up @@ -269,6 +304,22 @@ private void postTelemetryEvent(EventParams params) {
}
}

private void activateInlineCompletionVisibleContext(ITextEditor editor) {
IContextService contextService = EditorUtils.getContextService(editor);
if (contextService != null) {
logger.debug("Activating inline completion visible context.");
inlineCompletionVisibleContext = contextService.activateContext(INLINE_COMPLETION_VISIBLE_CONTEXT_ID);
}
}

private void deactivateInlineCompletionVisibleContext(ITextEditor editor) {
IContextService contextService = EditorUtils.getContextService(editor);
if (contextService != null && inlineCompletionVisibleContext != null) {
logger.debug("Deactivating inline completion visible context.");
contextService.deactivateContext(inlineCompletionVisibleContext);
}
}

private class InlineCompletionContext {
private static class Request {
private Logger logger = new Logger("InlineCompletionContext.Request");
Expand Down

0 comments on commit dac6145

Please sign in to comment.