feat: auto apply (#743)

This commit is contained in:
Carl-Robert Linnupuu 2024-10-28 16:33:50 +00:00 committed by GitHub
parent ffa1fbbacf
commit 6fbea7d4b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 301 additions and 232 deletions

View file

@ -28,6 +28,9 @@ public final class Icons {
public static final Icon Ollama = IconLoader.getIcon("/icons/ollama.svg", Icons.class);
public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class);
public static final Icon Lightning = IconLoader.getIcon("/icons/lightning.svg", Icons.class);
public static final Icon LightningDisabled =
IconLoader.getIcon("/icons/lightning.svg", Icons.class);
public static final Icon GreenCheckmark =
IconLoader.getIcon("/icons/greenCheckmark.svg", Icons.class);
public static final Icon SendToTheLeft =

View file

@ -12,6 +12,7 @@ public enum ActionType {
EDIT_CODE,
CREATE_NEW_FILE,
COPY_CODE,
AUTO_APPLY,
REPLACE_IN_MAIN_EDITOR,
INSERT_AT_CARET,
RELOAD_MESSAGE,

View file

@ -2,7 +2,6 @@ package ee.carlrobert.codegpt.actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import javax.swing.Icon;
import org.jetbrains.annotations.NotNull;
@ -10,16 +9,13 @@ import org.jetbrains.annotations.NotNull;
public abstract class TrackableAction extends AnAction {
private final ActionType actionType;
protected final Editor editor;
public TrackableAction(
@NotNull Editor editor,
String text,
String description,
Icon icon,
ActionType actionType) {
super(text, description, icon);
this.editor = editor;
this.actionType = actionType;
}

View file

@ -116,7 +116,6 @@ abstract class ToolWindowCompletionResponseEventListener implements
ApplicationManager.getApplication().invokeLater(() -> {
try {
responsePanel.enableActions();
responseContainer.enableActions();
if (!responseContainer.isResponseReceived() && !fullMessage.isEmpty()) {
responseContainer.withResponse(fullMessage);
}

View file

@ -11,7 +11,6 @@ import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsManager;
@ -28,19 +27,15 @@ 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.AutoApplyAction;
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;
import ee.carlrobert.codegpt.util.EditorUtil;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.Box;
import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -48,7 +43,6 @@ 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,
@ -75,28 +69,16 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
}
}
configureEditor(
project,
(EditorEx) editor,
readOnly,
new ContextMenuPopupHandler.Simple(group),
findLanguageExtensionMapping(markdownLanguage).getValue());
add(editor.getComponent(), BorderLayout.CENTER);
if (highlightedText != null && !highlightedText.isEmpty()) {
directLinksPanel.setVisible(false);
directLinksPanel.setBorder(JBUI.Borders.emptyTop(8));
directLinksPanel.add(new CompareWithOriginalActionLink(project, editor, highlightedText));
directLinksPanel.add(Box.createHorizontalStrut(12));
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);
@ -107,6 +89,7 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
}
private void configureEditor(
Project project,
EditorEx editorEx,
boolean readOnly,
ContextMenuPopupHandler popupHandler,
@ -133,22 +116,28 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
editorEx.setVerticalScrollbarVisible(false);
editorEx.getContentComponent().setBorder(JBUI.Borders.emptyLeft(4));
editorEx.setBorder(IdeBorderFactory.createBorder(ColorUtil.fromHex("#48494b")));
editorEx.setPermanentHeaderComponent(createHeaderComponent(editorEx, extension, readOnly));
editorEx.setPermanentHeaderComponent(
createHeaderComponent(project, editorEx, extension, readOnly));
editorEx.setHeaderComponent(null);
}
private JPanel createHeaderComponent(EditorEx editorEx, String extension, boolean readOnly) {
var headerComponent = new JPanel(new BorderLayout());
headerComponent.setBorder(
private JPanel createHeaderComponent(
Project project,
EditorEx editorEx,
String extension,
boolean readOnly) {
var headerPanel = new JPanel(new BorderLayout());
headerPanel.setBorder(
JBUI.Borders.compound(
JBUI.Borders.customLine(ColorUtil.fromHex("#48494b"), 1, 1, 0, 1),
JBUI.Borders.empty(4)));
headerComponent.add(createExpandLink(editorEx), BorderLayout.LINE_START);
headerPanel.add(createExpandLink(editorEx), BorderLayout.LINE_START);
if (!readOnly) {
headerComponent.add(
createHeaderActions(extension, editorEx).getComponent(), BorderLayout.LINE_END);
headerPanel.add(
createHeaderActions(project, extension, editorEx, headerPanel).getComponent(),
BorderLayout.LINE_END);
}
return headerComponent;
return headerPanel;
}
private String getLinkText(boolean expanded) {
@ -178,20 +167,20 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
return expandLink;
}
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));
private ActionToolbar createHeaderActions(
Project project,
String extension,
EditorEx editorEx,
JPanel headerPanel) {
var actionGroup = new DefaultActionGroup("EDITOR_TOOLBAR_ACTION_GROUP", false);
actionGroup.add(new AutoApplyAction(project, editorEx, headerPanel));
actionGroup.add(new InsertAtCaretAction(editorEx));
actionGroup.add(new CopyAction(editorEx));
actionGroup.addSeparator();
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
wrapper.add(new IconActionButton(new CopyAction(editor)));
wrapper.add(Box.createHorizontalStrut(4));
wrapper.add(new IconActionButton(new ReplaceSelectionAction(editor)));
var menu = new JBPopupMenu();
menu.add(new JBMenuItem(new DiffAction(editorEx, menu.getLocation())));
menu.add(new JBMenuItem(new ReplaceSelectionAction(editorEx, menu.getLocation())));
menu.add(new JBMenuItem(new EditAction(editorEx)));
menu.add(new JBMenuItem(new NewFileAction(editorEx, extension)));

View file

@ -15,18 +15,20 @@ import org.jetbrains.annotations.NotNull;
public class CopyAction extends TrackableAction {
public CopyAction(@NotNull Editor editor) {
private final @NotNull Editor toolwindowEditor;
public CopyAction(@NotNull Editor toolwindowEditor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.description"),
Actions.Copy,
ActionType.COPY_CODE);
this.toolwindowEditor = toolwindowEditor;
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
StringSelection stringSelection = new StringSelection(editor.getDocument().getText());
StringSelection stringSelection = new StringSelection(toolwindowEditor.getDocument().getText());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
@ -36,8 +38,8 @@ public class CopyAction extends TrackableAction {
locationOnScreen.y = locationOnScreen.y - 16;
OverlayUtil.showInfoBalloon(
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.success"),
locationOnScreen);
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.success"),
locationOnScreen);
}
}
}

View file

@ -19,7 +19,7 @@ public class DiffAction extends AbstractAction {
private final Point locationOnScreen;
public DiffAction(EditorEx editor, @Nullable Point locationOnScreen) {
super("Diff", Actions.DiffWithClipboard);
super("Diff Selection", Actions.DiffWithClipboard);
this.editor = editor;
this.locationOnScreen = locationOnScreen;
}
@ -28,7 +28,7 @@ public class DiffAction extends AbstractAction {
public void actionPerformed(ActionEvent event) {
var project = requireNonNull(editor.getProject());
var mainEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (mainEditor != null && !EditorUtil.hasSelection(mainEditor) && locationOnScreen != null) {
if (mainEditor == null || !EditorUtil.hasSelection(mainEditor)) {
OverlayUtil.showSelectedEditorSelectionWarning(project, locationOnScreen);
return;
}
@ -36,6 +36,6 @@ public class DiffAction extends AbstractAction {
EditorDiffUtil.showDiff(
project,
editor,
mainEditor.getSelectionModel().getSelectedText());
requireNonNull(mainEditor.getSelectionModel().getSelectedText()));
}
}

View file

@ -17,13 +17,15 @@ import org.jetbrains.annotations.Nullable;
public class InsertAtCaretAction extends TrackableAction {
public InsertAtCaretAction(@NotNull Editor editor) {
private final @NotNull Editor toolwindowEditor;
public InsertAtCaretAction(@NotNull Editor toolwindowEditor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.insertAtCaret.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.insertAtCaret.description"),
Icons.SendToTheLeft,
ActionType.INSERT_AT_CARET);
this.toolwindowEditor = toolwindowEditor;
}
@Override
@ -48,7 +50,7 @@ public class InsertAtCaretAction extends TrackableAction {
@Nullable
private Editor getSelectedTextEditor() {
return Optional.ofNullable(editor.getProject())
return Optional.ofNullable(toolwindowEditor.getProject())
.map(FileEditorManager::getInstance)
.map(FileEditorManager::getSelectedTextEditor)
.orElse(null);
@ -58,7 +60,7 @@ public class InsertAtCaretAction extends TrackableAction {
runUndoTransparentWriteAction(() -> {
mainEditor.getDocument().insertString(
mainEditor.getCaretModel().getOffset(),
editor.getDocument().getText());
toolwindowEditor.getDocument().getText());
return null;
});
}

View file

@ -3,36 +3,41 @@ package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import static java.util.Objects.requireNonNull;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.actions.TrackableAction;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.util.EditorUtil;
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 ReplaceSelectionAction extends TrackableAction {
public class ReplaceSelectionAction extends AbstractAction {
public ReplaceSelectionAction(@NotNull Editor editor) {
private final @NotNull EditorEx toolwindowEditor;
private final Point locationOnScreen;
public ReplaceSelectionAction(
@NotNull EditorEx toolwindowEditor,
@Nullable Point locationOnScreen) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.replaceSelection.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.replaceSelection.description"),
Actions.Replace,
ActionType.REPLACE_IN_MAIN_EDITOR);
Actions.Replace);
this.toolwindowEditor = toolwindowEditor;
this.locationOnScreen = locationOnScreen;
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var project = requireNonNull(event.getProject());
public void actionPerformed(ActionEvent event) {
var project = requireNonNull(toolwindowEditor.getProject());
if (EditorUtil.isMainEditorTextSelected(project)) {
var mainEditor = EditorUtil.getSelectedEditor(project);
if (mainEditor != null) {
EditorUtil.replaceEditorSelection(mainEditor, editor.getDocument().getText());
EditorUtil.replaceEditorSelection(mainEditor, toolwindowEditor.getDocument().getText());
}
} else {
OverlayUtil.showSelectedEditorSelectionWarning(event);
OverlayUtil.showSelectedEditorSelectionWarning(project, locationOnScreen);
}
}
}

View file

@ -98,14 +98,6 @@ public class ChatMessageResponseBody extends JPanel {
}
}
public void enableActions() {
if (highlightedText != null
&& !highlightedText.isEmpty()
&& currentlyProcessedEditorPanel != null) {
currentlyProcessedEditorPanel.showEditorActions();
}
}
public ChatMessageResponseBody withResponse(String response) {
try {
for (var message : MarkdownUtil.splitCodeBlocks(response)) {
@ -173,7 +165,7 @@ public class ChatMessageResponseBody extends JPanel {
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>",
message);
if (responseReceived) {
add(createTextPane(errorText, false));
add(createTextPane(errorText));
} else {
currentlyProcessedTextPane.setText(errorText);
}
@ -269,12 +261,6 @@ 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);
@ -316,6 +302,10 @@ public class ChatMessageResponseBody extends JPanel {
}
}
private JTextPane createTextPane(String text) {
return createTextPane(text, false);
}
private JTextPane createTextPane(String text, boolean caretVisible) {
var textPane = UIUtil.createTextPane(text, false, event -> {
if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) {

View file

@ -128,7 +128,7 @@ public class ResponsePanel extends JPanel {
Body() {
super(new BorderLayout());
setBorder(JBUI.Borders.empty(4, 8, 8, 8));
setBorder(JBUI.Borders.empty(4, 8));
}
public void addContent(JComponent content) {