diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ca9885..5ddda3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- 'Direct Apply' and 'Compare with Original' tool window editor actions for generated code +- Multiple tool window editor actions for generated code: + - Direct apply: Directly replaces the highlighted code in the main editor with the generated code + - Compare with Original: Opens a diff view to compare the generated code with the highlighted code + - Insert at Caret: Inserts the generated code at the exact location of the caret in the main editor - Vision support for Azure models - General improvements to code completions, including: - Proper streaming support diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index bc618476..fb680fac 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -28,5 +28,7 @@ public final class Icons { public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class); public static final Icon GreenCheckmark = IconLoader.getIcon("/icons/greenCheckmark.svg", Icons.class); + public static final Icon SendToTheLeft = + IconLoader.getIcon("/icons/sendToTheLeft.svg", Icons.class); public static final Icon StatusBarCompletionInProgress = new AnimatedIcon.Default(); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java b/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java index 0ba9b313..f8ab4a56 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java @@ -13,6 +13,7 @@ public enum ActionType { CREATE_NEW_FILE, COPY_CODE, REPLACE_IN_MAIN_EDITOR, + INSERT_AT_CARET, RELOAD_MESSAGE, CHANGE_PROVIDER } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/ReplaceCodeInMainEditorAction.java b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/ReplaceCodeInMainEditorAction.java index 6c6b7402..1ba9aacd 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/ReplaceCodeInMainEditorAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/ReplaceCodeInMainEditorAction.java @@ -14,7 +14,7 @@ public class ReplaceCodeInMainEditorAction extends AnAction { public ReplaceCodeInMainEditorAction() { - super("Replace in Main Editor", "Replace code in main editor", AllIcons.Actions.Replace); + super("Replace Selection", "Replace selected code in main editor", AllIcons.Actions.Replace); EditorActionsUtil.registerAction(this); } 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 d55bc2c4..f259ddf0 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 @@ -33,6 +33,7 @@ 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; +import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.InsertAtCaretAction; import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.NewFileAction; import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.ReplaceSelectionAction; import ee.carlrobert.codegpt.ui.IconActionButton; @@ -82,9 +83,9 @@ public ResponseEditorPanel( if (highlightedText != null && !highlightedText.isEmpty()) { directLinksPanel.setVisible(false); - directLinksPanel.setBorder(JBUI.Borders.emptyTop(4)); + directLinksPanel.setBorder(JBUI.Borders.emptyTop(8)); directLinksPanel.add(new CompareWithOriginalActionLink(project, editor, highlightedText)); - directLinksPanel.add(Box.createHorizontalStrut(8)); + directLinksPanel.add(Box.createHorizontalStrut(12)); directLinksPanel.add(new DirectApplyActionLink(project, editor, highlightedText)); add(directLinksPanel, BorderLayout.SOUTH); } @@ -179,6 +180,7 @@ private ActionToolbar createHeaderActions(String extension, EditorEx editorEx) { var actionGroup = new DefaultCompactActionGroup("EDITOR_TOOLBAR_ACTION_GROUP", false); actionGroup.add(new CopyAction(editor)); actionGroup.add(new ReplaceSelectionAction(editor)); + actionGroup.add(new InsertAtCaretAction(editor)); actionGroup.addSeparator(); var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 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 373787a4..b1ea4d4c 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 @@ -15,18 +15,18 @@ public class DiffAction extends AbstractAction { - private final EditorEx toolwindowEditor; + private final EditorEx editor; private final Point locationOnScreen; - public DiffAction(EditorEx toolwindowEditor, @Nullable Point locationOnScreen) { + public DiffAction(EditorEx editor, @Nullable Point locationOnScreen) { super("Diff", Actions.DiffWithClipboard); - this.toolwindowEditor = toolwindowEditor; + this.editor = editor; this.locationOnScreen = locationOnScreen; } @Override public void actionPerformed(ActionEvent event) { - var project = requireNonNull(toolwindowEditor.getProject()); + var project = requireNonNull(editor.getProject()); var mainEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); if (mainEditor != null && !EditorUtil.hasSelection(mainEditor) && locationOnScreen != null) { OverlayUtil.showSelectedEditorSelectionWarning(project, locationOnScreen); @@ -35,7 +35,7 @@ public void actionPerformed(ActionEvent event) { EditorDiffUtil.showDiff( project, - toolwindowEditor, + editor, mainEditor.getSelectionModel().getSelectedText()); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/InsertAtCaretAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/InsertAtCaretAction.java new file mode 100644 index 00000000..83ead8fe --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/InsertAtCaretAction.java @@ -0,0 +1,70 @@ +package ee.carlrobert.codegpt.toolwindow.chat.editor.actions; + +import static com.intellij.openapi.application.ActionsKt.runUndoTransparentWriteAction; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import ee.carlrobert.codegpt.CodeGPTBundle; +import ee.carlrobert.codegpt.Icons; +import ee.carlrobert.codegpt.actions.ActionType; +import ee.carlrobert.codegpt.actions.TrackableAction; +import ee.carlrobert.codegpt.ui.OverlayUtil; +import java.awt.Point; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class InsertAtCaretAction extends TrackableAction { + + public InsertAtCaretAction(@NotNull Editor editor) { + super( + editor, + CodeGPTBundle.get("toolwindow.chat.editor.action.insertAtCaret.title"), + CodeGPTBundle.get("toolwindow.chat.editor.action.insertAtCaret.description"), + Icons.SendToTheLeft, + ActionType.INSERT_AT_CARET); + } + + @Override + public void handleAction(@NotNull AnActionEvent event) { + Point locationOnScreen = getLocationOnScreen(event); + Editor mainEditor = getSelectedTextEditor(); + + if (mainEditor == null) { + OverlayUtil.showWarningBalloon("Active editor not found", locationOnScreen); + return; + } + + insertTextAtCaret(mainEditor, locationOnScreen); + } + + @Nullable + private Point getLocationOnScreen(AnActionEvent event) { + return Optional.ofNullable(event.getInputEvent()) + .map(inputEvent -> inputEvent.getComponent().getLocationOnScreen()) + .orElse(null); + } + + @Nullable + private Editor getSelectedTextEditor() { + return Optional.ofNullable(editor.getProject()) + .map(FileEditorManager::getInstance) + .map(FileEditorManager::getSelectedTextEditor) + .orElse(null); + } + + private void insertTextAtCaret(Editor mainEditor, @Nullable Point locationOnScreen) { + runUndoTransparentWriteAction(() -> { + mainEditor.getDocument().insertString( + mainEditor.getCaretModel().getOffset(), + editor.getDocument().getText()); + if (locationOnScreen != null) { + OverlayUtil.showInfoBalloon( + "Text successfully inserted at the current cursor position.", + locationOnScreen); + } + return null; + }); + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt index 317074ef..6927ff10 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/CompareWithOriginalActionLink.kt @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.toolwindow.chat +import com.intellij.icons.AllIcons.Actions import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.Editor @@ -18,6 +19,10 @@ class CompareWithOriginalActionLink( highlightedText ) { + init { + setIcon(Actions.Diff) + } + class CompareWithOriginalAction( private val project: Project, private val toolwindowEditor: Editor, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt index 27d130ac..09a6863f 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/DirectApplyActionLink.kt @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.toolwindow.chat +import com.intellij.icons.AllIcons.Actions import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.command.WriteCommandAction @@ -20,6 +21,10 @@ class DirectApplyActionLink( highlightedText ) { + init { + setIcon(Actions.Selectall) + } + class DirectApplyAction( private val project: Project, private val toolwindowEditor: Editor, diff --git a/src/main/resources/icons/sendToTheLeft.svg b/src/main/resources/icons/sendToTheLeft.svg new file mode 100644 index 00000000..b130d9ee --- /dev/null +++ b/src/main/resources/icons/sendToTheLeft.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/icons/sendToTheLeft_dark.svg b/src/main/resources/icons/sendToTheLeft_dark.svg new file mode 100644 index 00000000..5cd95941 --- /dev/null +++ b/src/main/resources/icons/sendToTheLeft_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index a576ac98..c1892cdb 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -181,6 +181,8 @@ toolwindow.chat.editor.action.newFile.title=New File toolwindow.chat.editor.action.newFile.description=Create new file from generated code toolwindow.chat.editor.action.replaceSelection.title=Replace Selection toolwindow.chat.editor.action.replaceSelection.description=Replace main editor selected code +toolwindow.chat.editor.action.insertAtCaret.title=Insert at Caret +toolwindow.chat.editor.action.insertAtCaret.description=Insert generated code after main editor caret position toolwindow.chat.editor.action.expand=Show More (+%s rows) toolwindow.chat.editor.action.collapse=Show Less toolwindow.chat.response.action.reloadResponse.text=Reload Response