From fa1e2a486bf2f9032224da27eb344ed659ae519f Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Wed, 1 Mar 2023 19:00:34 +0000 Subject: [PATCH] 1.1.6 - Ability to execute custom prompts, add block caret when typing, other improvements --- build.gradle.kts | 2 +- .../client/chatgpt/ChatGPTBodySubscriber.java | 29 +++++++--- .../chatgpt/ide/action/BaseAction.java | 23 +++++--- .../ide/action/CustomPromptAction.java | 37 ++++++++++++ .../ide/action/CustomPromptDialog.java | 57 +++++++++++++++++++ .../chatgpt/ide/action/ExplainAction.java | 10 ++-- .../chatgpt/ide/action/FindBugsAction.java | 10 ++-- .../chatgpt/ide/action/OptimizeAction.java | 10 ++-- .../chatgpt/ide/action/RefactorAction.java | 10 ++-- .../chatgpt/ide/action/WriteTestsAction.java | 10 ++-- .../ide/toolwindow/ToolWindowService.java | 21 ++++--- .../toolwindow/components/SyntaxTextArea.java | 25 ++++---- src/main/resources/META-INF/plugin.xml | 3 +- .../messages/BasicActionsBundle.properties | 12 +--- 14 files changed, 192 insertions(+), 67 deletions(-) create mode 100644 src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptAction.java create mode 100644 src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptDialog.java diff --git a/build.gradle.kts b/build.gradle.kts index a31f77a9..41b75701 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "ee.carlrobert" -version = "1.1.5" +version = "1.1.6" repositories { mavenCentral() diff --git a/src/main/java/ee/carlrobert/chatgpt/client/chatgpt/ChatGPTBodySubscriber.java b/src/main/java/ee/carlrobert/chatgpt/client/chatgpt/ChatGPTBodySubscriber.java index b802f6fd..7538655c 100644 --- a/src/main/java/ee/carlrobert/chatgpt/client/chatgpt/ChatGPTBodySubscriber.java +++ b/src/main/java/ee/carlrobert/chatgpt/client/chatgpt/ChatGPTBodySubscriber.java @@ -1,6 +1,8 @@ package ee.carlrobert.chatgpt.client.chatgpt; +import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import ee.carlrobert.chatgpt.client.Subscriber; import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponse; @@ -27,11 +29,11 @@ public class ChatGPTBodySubscriber extends Subscriber { } protected void onErrorOccurred() { - responseConsumer.accept("Something went wrong. Please try again later."); + responseConsumer.accept("\nSomething went wrong. Please try again later."); } protected void send(String responsePayload, String token) { - if (!responsePayload.isEmpty()) { + if (!responsePayload.isEmpty() && isValidJson(responsePayload)) { try { var response = objectMapper.readValue(responsePayload, ChatGPTResponse.class); var author = response.getMessage().getAuthor(); @@ -47,11 +49,13 @@ public class ChatGPTBodySubscriber extends Subscriber { throw new RuntimeException("Unable to deserialize the payload", e); } } else { - try { - var response = objectMapper.readValue(token, ChatGPTResponseDetail.class); - this.responseConsumer.accept(response.getDetail()); - } catch (JsonProcessingException e) { - tryProcessingErrorResponse(token); + if (token != null && !token.isEmpty() && isValidJson(token)) { + try { + var response = objectMapper.readValue(token, ChatGPTResponseDetail.class); + this.responseConsumer.accept(response.getDetail()); + } catch (JsonProcessingException e) { + tryProcessingErrorResponse(token); + } } } } @@ -67,4 +71,15 @@ public class ChatGPTBodySubscriber extends Subscriber { future.completeExceptionally(e); } } + + private boolean isValidJson(String json) { + ObjectMapper mapper = new ObjectMapper() + .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + try { + mapper.readTree(json); + } catch (JacksonException e) { + return false; + } + return true; + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/BaseAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/BaseAction.java index d17ce0af..a2654f42 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/BaseAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/BaseAction.java @@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import ee.carlrobert.chatgpt.client.ClientFactory; import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService; @@ -11,26 +13,29 @@ import org.jetbrains.annotations.NotNull; public abstract class BaseAction extends AnAction { - protected abstract String getPrompt(String selectedText); - protected abstract void initToolWindow(ToolWindow toolWindow); + protected abstract void actionPerformed(Project project, Editor editor, String selectedText); + public void actionPerformed(@NotNull AnActionEvent event) { var project = event.getProject(); var editor = event.getData(PlatformDataKeys.EDITOR); if (editor != null && project != null) { - var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class); - var selectedText = editor.getSelectionModel().getSelectedText(); - new ClientFactory().getClient().clearPreviousSession(); - initToolWindow(toolWindowService.getToolWindow(project)); - toolWindowService.removeAll(); - toolWindowService.paintUserMessage(selectedText); - toolWindowService.sendMessage(getPrompt(selectedText), project, null); + actionPerformed(project, editor, editor.getSelectionModel().getSelectedText()); } } public void update(AnActionEvent e) { e.getPresentation().setEnabledAndVisible(e.getProject() != null); } + + protected void sendMessage(Project project, String prompt) { + var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class); + new ClientFactory().getClient().clearPreviousSession(); + initToolWindow(toolWindowService.getToolWindow(project)); + toolWindowService.removeAll(); + toolWindowService.paintUserMessage(prompt); + toolWindowService.sendMessage(prompt, project, null); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptAction.java new file mode 100644 index 00000000..e36308dc --- /dev/null +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptAction.java @@ -0,0 +1,37 @@ +package ee.carlrobert.chatgpt.ide.action; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.SwingUtilities; + +public class CustomPromptAction extends BaseAction { + + protected void initToolWindow(ToolWindow toolWindow) { + toolWindow.setTitle("Custom Prompt"); + toolWindow.show(); + } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + if (selectedText != null && !selectedText.isEmpty()) { + var fileExtension = getFileExtension(((EditorImpl) editor).getVirtualFile().getName()); + var dialog = new CustomPromptDialog(selectedText, fileExtension); + if (dialog.showAndGet()) { + SwingUtilities.invokeLater(() -> sendMessage(project, dialog.getPrompt())); + } + } + } + + private String getFileExtension(String filename) { + Pattern pattern = Pattern.compile("[^.]+$"); + Matcher matcher = pattern.matcher(filename); + + if (matcher.find()) { + return matcher.group(); + } + return null; + } +} diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptDialog.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptDialog.java new file mode 100644 index 00000000..a31b9a5c --- /dev/null +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/CustomPromptDialog.java @@ -0,0 +1,57 @@ +package ee.carlrobert.chatgpt.ide.action; + +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UI; +import ee.carlrobert.chatgpt.ide.toolwindow.components.SyntaxTextArea; +import javax.annotation.Nullable; +import javax.swing.JComponent; +import javax.swing.JTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; + +public class CustomPromptDialog extends DialogWrapper { + + private final String selectedText; + private final String fileExtension; + private final JTextArea prefixTextArea = new JTextArea(); + private final SyntaxTextArea syntaxTextArea = new SyntaxTextArea(false, false, SyntaxConstants.SYNTAX_STYLE_MARKDOWN); + + public CustomPromptDialog(String selectedText, String fileExtension) { + super(true); + this.selectedText = selectedText; + this.fileExtension = fileExtension; + setTitle("Custom Prompt"); + setSize(460, 320); + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + prefixTextArea.setLineWrap(true); + prefixTextArea.setWrapStyleWord(true); + prefixTextArea.setMargin(JBUI.insets(5)); + + syntaxTextArea.setText(selectedText.trim()); + // syntaxTextArea.setSyntaxEditingStyle(getSyntaxForFileExtension(fileExtension)); + syntaxTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + + return FormBuilder.createFormBuilder() + .addComponent(UI.PanelFactory.panel(prefixTextArea) + .withLabel("Prefix:") + .moveLabelOnTop() + .withComment( + "Example: Find bugs in the following code") + .createPanel()) + .addVerticalGap(16) + .addComponent(new JBScrollPane(syntaxTextArea)) + .getPanel(); + } + + public String getPrompt() { + return prefixTextArea.getText() + "\n\n" + syntaxTextArea.getText(); + } +} + diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/ExplainAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/ExplainAction.java index b4597306..bd604752 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/ExplainAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/ExplainAction.java @@ -1,15 +1,17 @@ package ee.carlrobert.chatgpt.ide.action; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; public class ExplainAction extends BaseAction { - protected String getPrompt(String selectedText) { - return "Explain the following code:\n" + selectedText; - } - protected void initToolWindow(ToolWindow toolWindow) { toolWindow.setTitle("Explain Code"); toolWindow.show(); } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, "Explain the following code:\n\n" + selectedText); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/FindBugsAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/FindBugsAction.java index 743f4e3c..dc1150fd 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/FindBugsAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/FindBugsAction.java @@ -1,15 +1,17 @@ package ee.carlrobert.chatgpt.ide.action; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; public class FindBugsAction extends BaseAction { - protected String getPrompt(String selectedText) { - return "Find bugs in the following code:\n" + selectedText; - } - protected void initToolWindow(ToolWindow toolWindow) { toolWindow.setTitle("Find Bugs"); toolWindow.show(); } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, "Find bugs in the following code:\n\n" + selectedText); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/OptimizeAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/OptimizeAction.java index 270a2556..82b99764 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/OptimizeAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/OptimizeAction.java @@ -1,15 +1,17 @@ package ee.carlrobert.chatgpt.ide.action; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; public class OptimizeAction extends BaseAction { - protected String getPrompt(String selectedText) { - return "Optimize the following code:\n" + selectedText; - } - protected void initToolWindow(ToolWindow toolWindow) { toolWindow.setTitle("Optimize Code"); toolWindow.show(); } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, "Optimize the following code:\n\n" + selectedText); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/RefactorAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/RefactorAction.java index bdcdaf5d..2c2a085f 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/RefactorAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/RefactorAction.java @@ -1,15 +1,17 @@ package ee.carlrobert.chatgpt.ide.action; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; public class RefactorAction extends BaseAction { - protected String getPrompt(String selectedText) { - return "Refactor the following code:\n" + selectedText; - } - protected void initToolWindow(ToolWindow toolWindow) { toolWindow.setTitle("Refactor Code"); toolWindow.show(); } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, "Refactor the following code:\n\n" + selectedText); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/action/WriteTestsAction.java b/src/main/java/ee/carlrobert/chatgpt/ide/action/WriteTestsAction.java index 6dcc33a1..9eb9b2b5 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/action/WriteTestsAction.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/action/WriteTestsAction.java @@ -1,15 +1,17 @@ package ee.carlrobert.chatgpt.ide.action; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; public class WriteTestsAction extends BaseAction { - protected String getPrompt(String selectedText) { - return "Generate unit tests for the following code:\n" + selectedText; - } - protected void initToolWindow(ToolWindow toolWindow) { toolWindow.setTitle("Write Tests"); toolWindow.show(); } + + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, "Generate unit tests for the following code:\n\n" + selectedText); + } } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/ToolWindowService.java b/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/ToolWindowService.java index 0a1dc093..db682265 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/ToolWindowService.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/ToolWindowService.java @@ -29,6 +29,8 @@ import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.jetbrains.annotations.NotNull; public class ToolWindowService implements LafManagerListener { @@ -65,20 +67,21 @@ public class ToolWindowService implements LafManagerListener { } else if (settings.isChatGPTOptionSelected && settings.accessToken.isEmpty()) { notifyMissingCredential(project, "Access token not provided."); } else { - var textArea = new SyntaxTextArea(); + var textArea = new SyntaxTextArea(true, true, SyntaxConstants.SYNTAX_STYLE_MARKDOWN); scrollablePanel.add(textArea); textAreas.add(textArea); var client = new ClientFactory().getClient(); - client.getCompletionsAsync(prompt, message -> { - textArea.append(message); - - if (scrollToBottom != null) { - scrollToBottom.run(); - } - }, () -> { + client.getCompletionsAsync(prompt, message -> SwingUtilities.invokeLater( + () -> { + textArea.append(message); + if (scrollToBottom != null) { + scrollToBottom.run(); + } + } + ), () -> { textArea.displayCopyButton(); - textArea.enableSelection(); + textArea.hideCaret(); }); } diff --git a/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/components/SyntaxTextArea.java b/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/components/SyntaxTextArea.java index c3af402a..b3a23607 100644 --- a/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/components/SyntaxTextArea.java +++ b/src/main/java/ee/carlrobert/chatgpt/ide/toolwindow/components/SyntaxTextArea.java @@ -11,14 +11,14 @@ import java.awt.datatransfer.StringSelection; import java.io.IOException; import javax.swing.JButton; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.CaretStyle; public class SyntaxTextArea extends RSyntaxTextArea { - public SyntaxTextArea() { + public SyntaxTextArea(boolean isReadOnly, boolean withBlockCaret, String syntax) { super(""); - setStyles(); + setStyles(isReadOnly, withBlockCaret, syntax); } public void displayCopyButton() { @@ -27,9 +27,8 @@ public class SyntaxTextArea extends RSyntaxTextArea { cb.install(this); } - public void enableSelection() { - setEditable(false); - setEnabled(true); + public void hideCaret() { + getCaret().setVisible(false); } public void changeStyleViaThemeXml() { @@ -43,20 +42,24 @@ public class SyntaxTextArea extends RSyntaxTextArea { } } - private void setStyles() { + private void setStyles(boolean isReadOnly, boolean withBlockCaret, String syntax) { setMargin(JBUI.insets(5)); setAntiAliasingEnabled(true); - setEnabled(false); + setEnabled(true); + setEditable(!isReadOnly); setPaintTabLines(false); setHighlightCurrentLine(false); setLineWrap(true); - setWrapStyleWord(true); - setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_MARKDOWN); + if (withBlockCaret) { + setCaretStyle(0, CaretStyle.BLOCK_STYLE); + getCaret().setVisible(true); + } + setSyntaxEditingStyle(syntax); changeStyleViaThemeXml(); } private void copyToClipboard() { - StringSelection stringSelection = new StringSelection(getText()); + StringSelection stringSelection = new StringSelection(getText().trim()); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index de8db7ce..d94970ae 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -21,7 +21,7 @@ -
  • 1.1.5 Disable block caret when typing
  • +
  • 1.1.6 Ability to execute custom prompts, add block caret when typing, other improvements
  • ]]>
    @@ -55,6 +55,7 @@ class="ee.carlrobert.chatgpt.ide.action.ActionGroup" popup="true"> + diff --git a/src/main/resources/messages/BasicActionsBundle.properties b/src/main/resources/messages/BasicActionsBundle.properties index 4a73915d..c73130ba 100644 --- a/src/main/resources/messages/BasicActionsBundle.properties +++ b/src/main/resources/messages/BasicActionsBundle.properties @@ -1,17 +1,11 @@ group.ee.carlrobert.chatgpt.ide.action.ActionGroup.text=ChatGPT options -group.ee.carlrobert.chatgpt.ide.action.ActionGroup.description=Explore ChatGPT options action.ee.carlrobert.chatgpt.ide.action.AskAction.text=Ask ChatGPT -action.ee.carlrobert.chatgpt.ide.action.AskAction.description=TBD action.ee.carlrobert.chatgpt.ide.action.ExplainAction.text=Explain -action.ee.carlrobert.chatgpt.ide.action.ExplainAction.description=TBD action.ee.carlrobert.chatgpt.ide.action.RefactorAction.text=Refactor -action.ee.carlrobert.chatgpt.ide.action.RefactorAction.description=TBD -action.ee.carlrobert.chatgpt.ide.action.FindBugsAction.text=Find bugs -action.ee.carlrobert.chatgpt.ide.action.FindBugsAction.description=TBD +action.ee.carlrobert.chatgpt.ide.action.FindBugsAction.text=Find Bugs action.ee.carlrobert.chatgpt.ide.action.OptimizeAction.text=Optimize -action.ee.carlrobert.chatgpt.ide.action.OptimizeAction.description=TBD -action.ee.carlrobert.chatgpt.ide.action.WriteTestsAction.text=Write tests -action.ee.carlrobert.chatgpt.ide.action.WriteTestsAction.description=TBD +action.ee.carlrobert.chatgpt.ide.action.WriteTestsAction.text=Write Tests +action.ee.carlrobert.chatgpt.ide.action.CustomPromptAction.text=Custom Prompt ee.carlrobert.chatgpt.ide.notification.key=ChatGPT-notification