Open stephanj opened 7 months ago
This would be a really nice addition! I find myself often opening up VSCode besides IDEA just to get auto completion in code.
I tried a Copilot replacement for VSCode(https://github.com/ex3ndr/llama-coder) that uses Ollama and it seemed to work well. Perhaps their solution could serve as an inspiration 😄
Claude Sonnet 3.5 suggestion:
This kind of feature involves integrating with IntelliJ's code completion system and potentially using an AI model to generate suggestions. Here's a high-level approach to implement this feature:
First, you'll need to create a custom completion contributor that integrates with IntelliJ's completion system:
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
public class DevoxxGenieCompletionContributor extends CompletionContributor {
public DevoxxGenieCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet result) {
// Generate and add completions here
String suggestion = generateCodeSuggestion(parameters);
if (suggestion != null) {
result.addElement(LookupElementBuilder.create(suggestion));
}
}
}
);
}
private String generateCodeSuggestion(CompletionParameters parameters) {
// Implement your code suggestion logic here
// This could involve calling an AI model or using other heuristics
return null; // Replace with actual suggestion
}
}
Register your completion contributor in your plugin.xml
:
<extensions defaultExtensionNs="com.intellij">
<completion.contributor language="JAVA"
implementationClass="com.devoxx.genie.DevoxxGenieCompletionContributor"/>
</extensions>
The core of your autocompletion feature will be in the generateCodeSuggestion
method. This is where you'll need to analyze the current context and generate appropriate suggestions. Here's a basic example:
private String generateCodeSuggestion(CompletionParameters parameters) {
PsiElement element = parameters.getPosition();
PsiFile file = element.getContainingFile();
// Get the text up to the current caret position
Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(file);
if (document == null) return null;
int offset = parameters.getOffset();
String textUpToCaret = document.getText(new TextRange(0, offset));
// Here you would typically send this context to an AI model or use other logic
// to generate a suitable completion
// For this example, let's just suggest a simple method
return "public void exampleMethod() {\n // TODO: Implement method\n}";
}
For more advanced suggestions like those provided by GitHub Copilot, you'd need to integrate with an AI model. This could be done by sending the current code context to an API and receiving suggestions. Here's a conceptual example:
import com.devoxx.genie.service.AIModelService;
private String generateCodeSuggestion(CompletionParameters parameters) {
// ... (previous code to get context)
AIModelService aiService = AIModelService.getInstance(parameters.getProject());
return aiService.generateSuggestion(textUpToCaret);
}
For a more responsive experience, you might want to stream suggestions as they're generated:
public class DevoxxGenieCompletionContributor extends CompletionContributor {
@Override
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
CompletionResultSet resultSet = result.withPrefixMatcher(new PlainPrefixMatcher(""));
CompletionResultSet finalResultSet = resultSet;
ApplicationManager.getApplication().executeOnPooledThread(() -> {
String partialSuggestion = "";
while (!resultSet.isStopped()) {
// Generate next part of the suggestion
String nextPart = generateNextPartOfSuggestion();
if (nextPart == null) break;
partialSuggestion += nextPart;
finalResultSet.addElement(LookupElementBuilder.create(partialSuggestion));
}
});
}
}
Ensure your suggestions respect the user's code style settings:
private String formatSuggestion(String suggestion, Project project) {
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
PsiElementFactory factory = PsiElementFactory.getInstance(project);
PsiJavaFile file = (PsiJavaFile) factory.createFileFromText("dummy.java", JavaFileType.INSTANCE, suggestion);
codeStyleManager.reformat(file);
return file.getText();
}
This approach provides a foundation for implementing Copilot-like code autocompletion in your DevoxxGenie plugin. Remember that advanced AI-powered suggestions would require integration with a sophisticated language model, which is a significant undertaking in itself. You might start with simpler heuristic-based suggestions and gradually enhance the capability as your plugin evolves.
Rough Prototype 🤩 👇🏼
package com.devoxx.genie.service.completion;
import com.devoxx.genie.chatmodel.ollama.OllamaChatModelFactory;
import com.devoxx.genie.model.ChatModel;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.Response;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CodeCompletionService {
private final ChatModel llamaChatModel;
private final List<ChatMessage> messages = new ArrayList<>();
public CodeCompletionService() {
llamaChatModel = new ChatModel();
llamaChatModel.setModelName("llama3.1:latest");
llamaChatModel.setTemperature(0.7);
llamaChatModel.setTopP(0.9);
llamaChatModel.setMaxRetries(3);
messages.add(new SystemMessage("You're an auto complete coding genius! " +
"Only return the code completion so we can insert it where the developer stopped typing." +
"Do not return any other messages. Make sure the code completion is valid code."));
}
public void completeCode(Project project, Editor editor) {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
String suggestion = ApplicationManager.getApplication().runReadAction(
(Computable<String>) () -> generateCodeSuggestion(editor)
);
if (suggestion != null) {
ApplicationManager.getApplication().invokeLater(() -> {
insertSuggestion(project, editor, suggestion);
});
}
});
}
private String generateCodeSuggestion(@NotNull Editor editor) {
Document document = editor.getDocument();
int offset = editor.getCaretModel().getOffset();
String textUpToCaret = document.getText().substring(0, offset);
OllamaChatModelFactory ollamaChatModelFactory = new OllamaChatModelFactory();
ChatMessage userMessage = new UserMessage("Complete this code. Don't repeat the full code, only return the rest of the code. :" + textUpToCaret);
messages.add(userMessage);
ChatLanguageModel chatModel = ollamaChatModelFactory.createChatModel(llamaChatModel);
Response<AiMessage> generate = chatModel.generate(messages);
System.out.println(">>Original code: " + textUpToCaret);
System.out.println(">>Code suggestion: " + generate.content().text());
return generate.content().text();
}
private void insertSuggestion(Project project, Editor editor, String suggestion) {
WriteCommandAction.runWriteCommandAction(project, "AI Code Completion", null, () -> {
int caretOffset = editor.getCaretModel().getOffset();
editor.getDocument().insertString(caretOffset, suggestion);
editor.getCaretModel().moveToOffset(caretOffset + suggestion.length());
});
}
}
package com.devoxx.genie.action;
import com.devoxx.genie.service.completion.CodeCompletionService;
import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
public class CodeCompleteAction extends DumbAwareAction {
private final CodeCompletionService codeCompletionService;
public CodeCompleteAction() {
super("AI Code Complete");
this.codeCompletionService = new CodeCompletionService();
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project != null) {
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor != null) {
if (editor.getCaretModel().getVisualPosition().column == 0) {
AnAction defaultTabAction = ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_TAB);
defaultTabAction.actionPerformed(e);
return;
}
if (DevoxxGenieStateService.getInstance().getAutoCompleteCode()) {
codeCompletionService.completeCode(project, editor);
} else {
// If AI completion is disabled, perform the default TAB action
AnAction defaultTabAction = ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_TAB);
if (defaultTabAction != null) {
defaultTabAction.actionPerformed(e);
}
}
}
}
}
@Override
public void update(@NotNull AnActionEvent e) {
Project project = e.getProject();
Editor editor = e.getData(CommonDataKeys.EDITOR);
e.getPresentation().setEnabledAndVisible(project != null && editor != null);
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.BGT;
}
@Override
public boolean isDumbAware() {
return true;
}
}
Plugin config
<action id="com.devoxx.genie.action.CodeCompleteAction"
class="com.devoxx.genie.action.CodeCompleteAction"
text="AI Code Complete"
description="Complete code using AI">
<keyboard-shortcut first-keystroke="TAB" keymap="$default"/>
<add-to-group group-id="EditorActions" anchor="last"/>
</action>
I would like to ask, is the above prototype example still unable to achieve better results? I don't see anything like that in the repository yet. @stephanj
The prototype serves as a basic implementation to help you get started. The goal is to achieve functionality akin to CoPilot, where code completion is seamlessly inserted at the correct location, with only the suggested code block being displayed.
An experimental AcceptAutocompleteAction (pressing tab key) is available and can be activated be enabling the plugin config file
However the related prompt does not really return useable auto complete code 😂
Suggestions welcome how we can accomplish this.