mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-20 01:02:02 +00:00
feat: add apply and diff actions for toolwindow code editor
This commit is contained in:
parent
6e9fa02201
commit
465ae704d6
15 changed files with 303 additions and 59 deletions
|
|
@ -10,21 +10,24 @@ public class CallParameters {
|
|||
private final ConversationType conversationType;
|
||||
private final Message message;
|
||||
private final boolean retry;
|
||||
private final String highlightedText;
|
||||
private @Nullable String imageMediaType;
|
||||
private byte[] imageData;
|
||||
|
||||
public CallParameters(Conversation conversation, Message message) {
|
||||
this(conversation, ConversationType.DEFAULT, message, false);
|
||||
this(conversation, ConversationType.DEFAULT, message, null, false);
|
||||
}
|
||||
|
||||
public CallParameters(
|
||||
Conversation conversation,
|
||||
ConversationType conversationType,
|
||||
Message message,
|
||||
@Nullable String highlightedText,
|
||||
boolean retry) {
|
||||
this.conversation = conversation;
|
||||
this.conversationType = conversationType;
|
||||
this.message = message;
|
||||
this.highlightedText = highlightedText;
|
||||
this.retry = retry;
|
||||
}
|
||||
|
||||
|
|
@ -59,4 +62,8 @@ public class CallParameters {
|
|||
public void setImageData(byte[] imageData) {
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
public @Nullable String getHighlightedText() {
|
||||
return highlightedText;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,13 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
}
|
||||
|
||||
public void sendMessage(Message message, ConversationType conversationType) {
|
||||
sendMessage(message, conversationType, null);
|
||||
}
|
||||
|
||||
public void sendMessage(
|
||||
Message message,
|
||||
ConversationType conversationType,
|
||||
@Nullable String highlightedText) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
var referencedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES);
|
||||
var chatToolWindowPanel = project.getService(ChatToolWindowContentManager.class)
|
||||
|
|
@ -139,7 +146,8 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
|
||||
var userMessagePanel = new UserMessagePanel(project, message, this);
|
||||
var attachedFilePath = CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH.get(project);
|
||||
var callParameters = getCallParameters(conversationType, message, attachedFilePath);
|
||||
var callParameters =
|
||||
getCallParameters(conversationType, message, highlightedText, attachedFilePath);
|
||||
if (callParameters.getImageData() != null) {
|
||||
message.setImageFilePath(attachedFilePath);
|
||||
chatToolWindowPanel.ifPresent(panel -> panel.clearNotifications(project));
|
||||
|
|
@ -149,7 +157,7 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
|
||||
messagePanel.add(userMessagePanel);
|
||||
|
||||
var responsePanel = createResponsePanel(message, conversationType);
|
||||
var responsePanel = createResponsePanel(callParameters, conversationType);
|
||||
messagePanel.add(responsePanel);
|
||||
call(callParameters, responsePanel);
|
||||
});
|
||||
|
|
@ -158,8 +166,10 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
private CallParameters getCallParameters(
|
||||
ConversationType conversationType,
|
||||
Message message,
|
||||
@Nullable String highlightedText,
|
||||
@Nullable String attachedFilePath) {
|
||||
var callParameters = new CallParameters(conversation, conversationType, message, false);
|
||||
var callParameters = new CallParameters(conversation, conversationType, message,
|
||||
highlightedText, false);
|
||||
if (attachedFilePath != null && !attachedFilePath.isEmpty()) {
|
||||
try {
|
||||
callParameters.setImageData(Files.readAllBytes(Path.of(attachedFilePath)));
|
||||
|
|
@ -171,12 +181,20 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
return callParameters;
|
||||
}
|
||||
|
||||
private ResponsePanel createResponsePanel(Message message, ConversationType conversationType) {
|
||||
private ResponsePanel createResponsePanel(
|
||||
CallParameters callParameters,
|
||||
ConversationType conversationType) {
|
||||
var message = callParameters.getMessage();
|
||||
return new ResponsePanel()
|
||||
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(
|
||||
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(),
|
||||
new ChatMessageResponseBody(
|
||||
project,
|
||||
callParameters.getHighlightedText(),
|
||||
true,
|
||||
false,
|
||||
message.isWebSearchIncluded(),
|
||||
message.getDocumentationDetails() != null, this));
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +215,8 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
if (responsePanel != null) {
|
||||
message.setResponse("");
|
||||
conversationService.saveMessage(conversation, message);
|
||||
call(new CallParameters(conversation, conversationType, message, true), responsePanel);
|
||||
call(new CallParameters(conversation, conversationType, message, null, true),
|
||||
responsePanel);
|
||||
}
|
||||
|
||||
totalTokensPanel.updateConversationTokens(conversation);
|
||||
|
|
@ -251,6 +270,7 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
private Unit handleSubmit(String text, List<AppliedActionInlay> appliedInlayActions) {
|
||||
var message = new Message(text);
|
||||
var editor = EditorUtil.getSelectedEditor(project);
|
||||
String highlightedText = null;
|
||||
if (editor != null) {
|
||||
var selectionModel = editor.getSelectionModel();
|
||||
var selectedText = selectionModel.getSelectedText();
|
||||
|
|
@ -258,6 +278,7 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
var fileExtension = FileUtil.getFileExtension(
|
||||
((EditorImpl) editor).getVirtualFile().getName());
|
||||
message = new Message(text + format("%n```%s%n%s%n```", fileExtension, selectedText));
|
||||
highlightedText = selectedText;
|
||||
selectionModel.removeSelection();
|
||||
}
|
||||
}
|
||||
|
|
@ -282,7 +303,7 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
CodeGPTKeys.ADDED_PERSONA.set(project, null);
|
||||
}
|
||||
|
||||
sendMessage(message, ConversationType.DEFAULT);
|
||||
sendMessage(message, ConversationType.DEFAULT, highlightedText);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ abstract class ToolWindowCompletionResponseEventListener implements
|
|||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
try {
|
||||
responsePanel.enableActions();
|
||||
responseContainer.enableActions();
|
||||
totalTokensPanel.updateUserPromptTokens(textArea.getText());
|
||||
totalTokensPanel.updateConversationTokens(callParameters.getConversation());
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import com.intellij.ui.components.ActionLink;
|
|||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.ReplaceCodeInMainEditorAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.CompareWithOriginalActionLink;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.DirectApplyActionLink;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.DiffAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.EditAction;
|
||||
|
|
@ -40,16 +42,19 @@ import java.awt.FlowLayout;
|
|||
import javax.swing.Box;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ResponseEditorPanel extends JPanel implements Disposable {
|
||||
|
||||
private final Editor editor;
|
||||
private final JPanel directLinksPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));
|
||||
|
||||
public ResponseEditorPanel(
|
||||
Project project,
|
||||
String code,
|
||||
String markdownLanguage,
|
||||
boolean readOnly,
|
||||
@Nullable String highlightedText,
|
||||
Disposable disposableParent) {
|
||||
super(new BorderLayout());
|
||||
setBorder(JBUI.Borders.empty(8, 0));
|
||||
|
|
@ -59,7 +64,6 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
|
|||
project,
|
||||
findLanguageExtensionMapping(markdownLanguage).getValue(),
|
||||
StringUtil.convertLineSeparators(code));
|
||||
|
||||
var group = new DefaultActionGroup();
|
||||
group.add(new ReplaceCodeInMainEditorAction());
|
||||
String originalGroupId = ((EditorEx) editor).getContextMenuGroupId();
|
||||
|
|
@ -76,9 +80,22 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
|
|||
findLanguageExtensionMapping(markdownLanguage).getValue());
|
||||
add(editor.getComponent(), BorderLayout.CENTER);
|
||||
|
||||
if (highlightedText != null && !highlightedText.isEmpty()) {
|
||||
directLinksPanel.setVisible(false);
|
||||
directLinksPanel.setBorder(JBUI.Borders.emptyTop(4));
|
||||
directLinksPanel.add(new CompareWithOriginalActionLink(project, editor, highlightedText));
|
||||
directLinksPanel.add(Box.createHorizontalStrut(8));
|
||||
directLinksPanel.add(new DirectApplyActionLink(project, editor, highlightedText));
|
||||
add(directLinksPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
Disposer.register(disposableParent, this);
|
||||
}
|
||||
|
||||
public void showEditorActions() {
|
||||
directLinksPanel.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
EditorFactory.getInstance().releaseEditor(editor);
|
||||
|
|
|
|||
|
|
@ -2,58 +2,40 @@ package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
|
|||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.intellij.diff.DiffContentFactory;
|
||||
import com.intellij.diff.DiffDialogHints;
|
||||
import com.intellij.diff.DiffManager;
|
||||
import com.intellij.diff.requests.SimpleDiffRequest;
|
||||
import com.intellij.diff.util.DiffUserDataKeys;
|
||||
import com.intellij.diff.util.DiffUtil;
|
||||
import com.intellij.diff.util.Side;
|
||||
import com.intellij.icons.AllIcons.Actions;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import ee.carlrobert.codegpt.util.EditorDiffUtil;
|
||||
import ee.carlrobert.codegpt.util.EditorUtil;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DiffAction extends AbstractAction {
|
||||
|
||||
private final EditorEx editor;
|
||||
private final EditorEx toolwindowEditor;
|
||||
private final Point locationOnScreen;
|
||||
|
||||
public DiffAction(@NotNull EditorEx editor, @NotNull Point locationOnScreen) {
|
||||
public DiffAction(EditorEx toolwindowEditor, @Nullable Point locationOnScreen) {
|
||||
super("Diff", Actions.DiffWithClipboard);
|
||||
this.editor = editor;
|
||||
this.toolwindowEditor = toolwindowEditor;
|
||||
this.locationOnScreen = locationOnScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
var project = requireNonNull(editor.getProject());
|
||||
var selectedTextEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
|
||||
if (!EditorUtil.hasSelection(selectedTextEditor)) {
|
||||
var project = requireNonNull(toolwindowEditor.getProject());
|
||||
var mainEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
|
||||
if (mainEditor != null && !EditorUtil.hasSelection(mainEditor) && locationOnScreen != null) {
|
||||
OverlayUtil.showSelectedEditorSelectionWarning(project, locationOnScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
var resultEditorFile = FileUtil.getEditorFile(selectedTextEditor);
|
||||
var diffContentFactory = DiffContentFactory.getInstance();
|
||||
var request = new SimpleDiffRequest(
|
||||
CodeGPTBundle.get("editor.diff.title"),
|
||||
diffContentFactory.create(project, FileUtil.getEditorFile(editor)),
|
||||
diffContentFactory.create(project, resultEditorFile),
|
||||
CodeGPTBundle.get("editor.diff.local.content.title"),
|
||||
resultEditorFile.getName());
|
||||
request.putUserData(
|
||||
DiffUserDataKeys.SCROLL_TO_LINE,
|
||||
Pair.create(Side.RIGHT, DiffUtil.getCaretPosition(selectedTextEditor).line));
|
||||
|
||||
DiffManager.getInstance().showDiff(project, request, DiffDialogHints.DEFAULT);
|
||||
EditorDiffUtil.showDiff(
|
||||
project,
|
||||
toolwindowEditor,
|
||||
mainEditor.getSelectionModel().getSelectedText());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import javax.swing.Icon;
|
|||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.SwingConstants;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ChatMessageResponseBody extends JPanel {
|
||||
|
||||
|
|
@ -58,24 +59,19 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
private final WebpageList webpageList = new WebpageList(webpageListModel);
|
||||
private final JPanel webDocProgressContainer = new JPanel();
|
||||
private final AsyncProcessIcon spinner = new AsyncProcessIcon("sign_in_spinner");
|
||||
private final @Nullable String highlightedText;
|
||||
private ResponseEditorPanel currentlyProcessedEditorPanel;
|
||||
private JTextPane currentlyProcessedTextPane;
|
||||
private JPanel webpageListPanel;
|
||||
private boolean responseReceived;
|
||||
|
||||
public ChatMessageResponseBody(Project project, Disposable parentDisposable) {
|
||||
this(project, false, parentDisposable);
|
||||
}
|
||||
|
||||
public ChatMessageResponseBody(
|
||||
Project project,
|
||||
boolean withGhostText,
|
||||
Disposable parentDisposable) {
|
||||
this(project, withGhostText, false, false, false, parentDisposable);
|
||||
this(project, null, false, false, false, false, parentDisposable);
|
||||
}
|
||||
|
||||
public ChatMessageResponseBody(
|
||||
Project project,
|
||||
@Nullable String highlightedText,
|
||||
boolean withGhostText,
|
||||
boolean readOnly,
|
||||
boolean webSearchIncluded,
|
||||
|
|
@ -83,6 +79,7 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
Disposable parentDisposable) {
|
||||
super(new BorderLayout());
|
||||
this.project = project;
|
||||
this.highlightedText = highlightedText;
|
||||
this.parentDisposable = parentDisposable;
|
||||
this.streamParser = new StreamParser();
|
||||
this.readOnly = readOnly;
|
||||
|
|
@ -107,6 +104,14 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
public void enableActions() {
|
||||
if (highlightedText != null
|
||||
&& !highlightedText.isEmpty()
|
||||
&& currentlyProcessedEditorPanel != null) {
|
||||
currentlyProcessedEditorPanel.showEditorActions();
|
||||
}
|
||||
}
|
||||
|
||||
public ChatMessageResponseBody withResponse(String response) {
|
||||
for (var message : MarkdownUtil.splitCodeBlocks(response)) {
|
||||
processResponse(message, message.startsWith("```"), false);
|
||||
|
|
@ -265,6 +270,11 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
}
|
||||
|
||||
private void prepareProcessingText(boolean caretVisible) {
|
||||
if (highlightedText != null && !highlightedText.isEmpty()
|
||||
&& currentlyProcessedEditorPanel != null) {
|
||||
currentlyProcessedEditorPanel.showEditorActions();
|
||||
}
|
||||
|
||||
currentlyProcessedEditorPanel = null;
|
||||
currentlyProcessedTextPane = createTextPane("", caretVisible);
|
||||
add(currentlyProcessedTextPane);
|
||||
|
|
@ -274,7 +284,8 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
hideCaret();
|
||||
currentlyProcessedTextPane = null;
|
||||
currentlyProcessedEditorPanel =
|
||||
new ResponseEditorPanel(project, code, markdownLanguage, readOnly, parentDisposable);
|
||||
new ResponseEditorPanel(project, code, markdownLanguage, readOnly, highlightedText,
|
||||
parentDisposable);
|
||||
add(currentlyProcessedEditorPanel);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ public class UserMessagePanel extends JPanel {
|
|||
Disposable parentDisposable) {
|
||||
return new ChatMessageResponseBody(
|
||||
project,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.util.EditorDiffUtil
|
||||
|
||||
class CompareWithOriginalActionLink(
|
||||
project: Project,
|
||||
toolwindowEditor: Editor,
|
||||
highlightedText: String,
|
||||
) : ToolwindowEditorActionLink(
|
||||
project,
|
||||
CodeGPTBundle.get("action.compareWithOriginal.title"),
|
||||
CompareWithOriginalAction(project, toolwindowEditor, highlightedText),
|
||||
highlightedText
|
||||
) {
|
||||
|
||||
class CompareWithOriginalAction(
|
||||
private val project: Project,
|
||||
private val toolwindowEditor: Editor,
|
||||
private val highlightedText: String
|
||||
) : AnAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
EditorDiffUtil.showDiff(project, toolwindowEditor, highlightedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ScrollType
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
|
||||
class DirectApplyActionLink(
|
||||
project: Project,
|
||||
toolwindowEditor: Editor,
|
||||
highlightedText: String,
|
||||
) : ToolwindowEditorActionLink(
|
||||
project,
|
||||
CodeGPTBundle.get("action.applyDirectly.title"),
|
||||
DirectApplyAction(project, toolwindowEditor, highlightedText),
|
||||
highlightedText
|
||||
) {
|
||||
|
||||
class DirectApplyAction(
|
||||
private val project: Project,
|
||||
private val toolwindowEditor: Editor,
|
||||
private val highlightedText: String
|
||||
) : AnAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val mainEditor = FileEditorManager.getInstance(project).selectedTextEditor
|
||||
?: throw IllegalStateException("No editor selected")
|
||||
val startIndex = mainEditor.document.text.indexOf(highlightedText)
|
||||
if (startIndex == -1) {
|
||||
return
|
||||
}
|
||||
|
||||
val endIndex = startIndex + highlightedText.length
|
||||
val replacement = toolwindowEditor.document.text
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project) {
|
||||
mainEditor.document.replaceString(startIndex, endIndex, replacement)
|
||||
mainEditor.caretModel.moveToOffset(startIndex + replacement.length)
|
||||
mainEditor.scrollingModel.scrollToCaret(ScrollType.CENTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.components.AnActionLink
|
||||
|
||||
open class ToolwindowEditorActionLink(
|
||||
private val project: Project,
|
||||
title: String,
|
||||
action: AnAction,
|
||||
private val highlightedText: String,
|
||||
) : AnActionLink(title, action) {
|
||||
|
||||
private val mainEditor = project.service<FileEditorManager>().selectedTextEditor
|
||||
private val documentListener = object : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
updateActionState()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
mainEditor?.document?.addDocumentListener(documentListener)
|
||||
autoHideOnDisable = false
|
||||
}
|
||||
|
||||
private fun updateActionState() {
|
||||
val mainEditor = project.service<FileEditorManager>().selectedTextEditor
|
||||
val startIndex = mainEditor?.document?.text?.indexOf(highlightedText)
|
||||
runInEdt {
|
||||
isEnabled = startIndex != null && startIndex != -1
|
||||
toolTipText = if (isEnabled) null else "Original state has changed"
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main/kotlin/ee/carlrobert/codegpt/util/EditorDiffUtil.kt
Normal file
84
src/main/kotlin/ee/carlrobert/codegpt/util/EditorDiffUtil.kt
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package ee.carlrobert.codegpt.util
|
||||
|
||||
import com.intellij.diff.DiffContentFactory
|
||||
import com.intellij.diff.DiffManager
|
||||
import com.intellij.diff.requests.SimpleDiffRequest
|
||||
import com.intellij.diff.util.DiffUserDataKeys
|
||||
import com.intellij.diff.util.DiffUtil
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Pair
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
|
||||
object EditorDiffUtil {
|
||||
|
||||
@JvmStatic
|
||||
fun showDiff(
|
||||
project: Project,
|
||||
toolwindowEditor: Editor,
|
||||
highlightedText: String
|
||||
) {
|
||||
val mainEditor = project.service<FileEditorManager>().selectedTextEditor ?: return
|
||||
val mainEditorFile =
|
||||
service<FileDocumentManager>().getFile(mainEditor.document) ?: return
|
||||
val tempFile = createTempDiffFile(mainEditor, toolwindowEditor, highlightedText)
|
||||
DiffManager.getInstance()
|
||||
.showDiff(project, createDiffRequest(project, tempFile, mainEditor, mainEditorFile))
|
||||
}
|
||||
|
||||
private fun createTempDiffFile(
|
||||
mainEditor: Editor,
|
||||
toolwindowEditor: Editor,
|
||||
highlightedText: String
|
||||
): VirtualFile {
|
||||
val diffContent = createTempDiffContent(mainEditor, toolwindowEditor, highlightedText)
|
||||
val toolwindowEditorFile =
|
||||
service<FileDocumentManager>().getFile(toolwindowEditor.document)
|
||||
?: throw IllegalStateException("Toolwindow editor file not found")
|
||||
return LightVirtualFile("content_diff_${toolwindowEditorFile.name}", diffContent)
|
||||
}
|
||||
|
||||
private fun createTempDiffContent(
|
||||
mainEditor: Editor,
|
||||
toolwindowEditor: Editor,
|
||||
previousSelection: String
|
||||
): String {
|
||||
val mainDocumentContent = mainEditor.document.text
|
||||
val startIndex = mainDocumentContent.indexOf(previousSelection)
|
||||
val endIndex = startIndex + previousSelection.length
|
||||
return mainDocumentContent.substring(0, startIndex) +
|
||||
toolwindowEditor.document.text +
|
||||
mainDocumentContent.substring(endIndex)
|
||||
}
|
||||
|
||||
private fun createDiffRequest(
|
||||
project: Project,
|
||||
tempFile: VirtualFile,
|
||||
mainEditor: Editor,
|
||||
mainEditorFile: VirtualFile
|
||||
): SimpleDiffRequest {
|
||||
val diffContentFactory = DiffContentFactory.getInstance()
|
||||
val tempFileDiffContent = diffContentFactory.create(project, tempFile).apply {
|
||||
putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true)
|
||||
}
|
||||
|
||||
return SimpleDiffRequest(
|
||||
CodeGPTBundle.get("editor.diff.title"),
|
||||
diffContentFactory.create(project, mainEditorFile),
|
||||
tempFileDiffContent,
|
||||
mainEditorFile.name,
|
||||
CodeGPTBundle.get("editor.diff.local.content.title")
|
||||
).apply {
|
||||
putUserData(
|
||||
DiffUserDataKeys.SCROLL_TO_LINE,
|
||||
Pair.create(Side.RIGHT, DiffUtil.getCaretPosition(mainEditor).line)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,11 +85,6 @@ object FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEditorFile(editor: Editor): VirtualFile? {
|
||||
return FileDocumentManager.getInstance().getFile(editor.document)
|
||||
}
|
||||
|
||||
private fun tryCreateDirectory(directoryPath: Path) {
|
||||
Files.exists(directoryPath).takeUnless { it } ?: return
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ action.statusbar.enableCompletions.MainMenu.text=Enable Completions
|
|||
action.statusbar.disableCompletions.text=Disable Completions
|
||||
action.statusbar.disableCompletions.description=Disable Code Completions
|
||||
action.statusbar.disableCompletions.MainMenu.text=Disable Completions
|
||||
action.compareWithOriginal.title=Compare with Original
|
||||
action.applyDirectly.title=Apply Directly
|
||||
settings.displayName=CodeGPT: Settings
|
||||
settings.openaiQuotaExceeded=OpenAI quota exceeded.
|
||||
settingsConfigurable.displayName.label=Display name:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
conversation,
|
||||
ConversationType.DEFAULT,
|
||||
Message("TEST_CHAT_COMPLETION_PROMPT"),
|
||||
null,
|
||||
false))
|
||||
|
||||
assertThat(request.messages)
|
||||
|
|
@ -60,6 +61,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
conversation,
|
||||
ConversationType.DEFAULT,
|
||||
Message("TEST_CHAT_COMPLETION_PROMPT"),
|
||||
null,
|
||||
false))
|
||||
|
||||
assertThat(request.messages)
|
||||
|
|
@ -89,6 +91,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
conversation,
|
||||
ConversationType.DEFAULT,
|
||||
secondMessage,
|
||||
null,
|
||||
true))
|
||||
|
||||
assertThat(request.messages)
|
||||
|
|
@ -119,6 +122,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
conversation,
|
||||
ConversationType.DEFAULT,
|
||||
Message("TEST_CHAT_COMPLETION_PROMPT"),
|
||||
null,
|
||||
false))
|
||||
|
||||
assertThat(request.messages)
|
||||
|
|
@ -145,6 +149,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
conversation,
|
||||
ConversationType.DEFAULT,
|
||||
createDummyMessage(100),
|
||||
null,
|
||||
false)) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))))
|
||||
})
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
val message = Message("TEST_PROMPT")
|
||||
val requestHandler = CompletionRequestHandler(getRequestEventListener(message))
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
e("stop", true)))
|
||||
})
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
|
@ -152,7 +152,8 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
)
|
||||
})
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +188,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
)
|
||||
})
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
|
@ -218,7 +219,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))))
|
||||
})
|
||||
|
||||
requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false))
|
||||
requestHandler.call(CallParameters(conversation, message))
|
||||
|
||||
waitExpecting { "Hello!" == message.response }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue