diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java b/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java index 5afdfc2d..a85f4699 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java @@ -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; + } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index 298d2179..10895862 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -118,6 +118,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) @@ -138,7 +145,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)); @@ -148,7 +156,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); }); @@ -157,8 +165,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))); @@ -170,12 +180,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)); } @@ -196,7 +214,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); @@ -250,12 +269,14 @@ public class ChatToolWindowTabPanel implements Disposable { private Unit handleSubmit(String text, List 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(); if (selectedText != null && !selectedText.isEmpty()) { var fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName()); message = new Message(text + format("%n```%s%n%s%n```", fileExtension, selectedText)); + highlightedText = selectedText; selectionModel.removeSelection(); } } @@ -280,7 +301,7 @@ public class ChatToolWindowTabPanel implements Disposable { CodeGPTKeys.ADDED_PERSONA.set(project, null); } - sendMessage(message, ConversationType.DEFAULT); + sendMessage(message, ConversationType.DEFAULT, highlightedText); return Unit.INSTANCE; } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java index 7d7615e0..0f8b7fe3 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java @@ -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 { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.java index cf86ce58..d55bc2c4 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.java @@ -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); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/DiffAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/DiffAction.java index c7186b5a..373787a4 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/DiffAction.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/DiffAction.java @@ -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()); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java index d1fcdf09..f5613936 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java @@ -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); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java index f9588e37..efb75823 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java @@ -97,6 +97,7 @@ public class UserMessagePanel extends JPanel { Disposable parentDisposable) { return new ChatMessageResponseBody( project, + null, false, true, false, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt new file mode 100644 index 00000000..317074ef --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt @@ -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) + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt new file mode 100644 index 00000000..27d130ac --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt @@ -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) + } + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ToolwindowEditorActionLink.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ToolwindowEditorActionLink.kt new file mode 100644 index 00000000..702eb123 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ToolwindowEditorActionLink.kt @@ -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().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().selectedTextEditor + val startIndex = mainEditor?.document?.text?.indexOf(highlightedText) + runInEdt { + isEnabled = startIndex != null && startIndex != -1 + toolTipText = if (isEnabled) null else "Original state has changed" + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/EditorDiffUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/EditorDiffUtil.kt new file mode 100644 index 00000000..f8e6fd04 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/EditorDiffUtil.kt @@ -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().selectedTextEditor ?: return + val mainEditorFile = + service().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().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) + ) + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt index 6e0fef67..ecdb849e 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt @@ -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 { diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 5e2b7778..a576ac98 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -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: diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt index efcc96cf..ee330f4a 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt @@ -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)) } } diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt index 470ca072..8dd8530a 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt @@ -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 } }