Expand/Collapse logic for toolwindow editors

This commit is contained in:
Carl-Robert Linnupuu 2023-11-10 14:52:58 +02:00
parent dea80fe8aa
commit d8e5e18998
6 changed files with 86 additions and 31 deletions

View file

@ -125,8 +125,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
(!youUserManager.isAuthenticated() || !youUserManager.isSubscribed())) {
scrollablePanel.add(new ResponsePanel().addContent(createYouCouponTextPane()));
}
scrollablePanel.repaint();
scrollablePanel.revalidate();
revalidateScrollablePanel();
}
@Override
@ -142,11 +141,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
}
var messageWrapper = createNewMessageWrapper(message.getId());
messageWrapper.add(new UserMessagePanel(
project,
message,
message.getUserMessage() != null,
this));
messageWrapper.add(new UserMessagePanel(project, message, this));
var responsePanel = new ResponsePanel()
.withReloadAction(() -> reloadMessage(message, conversation))
.withDeleteAction(() -> removeMessage(message.getId(), messageWrapper, conversation))
@ -168,8 +163,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
.filter(component -> component instanceof ResponsePanel)
.findFirst().orElseThrow();
((ChatMessageResponseBody) responsePanel.getContent()).clear();
scrollablePanel.revalidate();
scrollablePanel.repaint();
revalidateScrollablePanel();
} catch (Exception e) {
throw new RuntimeException("Couldn't delete the existing message component", e);
} finally {
@ -187,10 +181,14 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
}
}
protected void removeMessage(UUID messageId, JPanel messageWrapper, Conversation conversation) {
scrollablePanel.remove(messageWrapper);
private void revalidateScrollablePanel() {
scrollablePanel.repaint();
scrollablePanel.revalidate();
}
protected void removeMessage(UUID messageId, JPanel messageWrapper, Conversation conversation) {
scrollablePanel.remove(messageWrapper);
revalidateScrollablePanel();
visibleMessagePanels.remove(messageId);
conversation.removeMessage(messageId);
@ -207,16 +205,14 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
var messageWrapper = new JPanel();
messageWrapper.setLayout(new BoxLayout(messageWrapper, BoxLayout.PAGE_AXIS));
scrollablePanel.add(messageWrapper);
scrollablePanel.repaint();
scrollablePanel.revalidate();
revalidateScrollablePanel();
visibleMessagePanels.put(messageId, messageWrapper);
return messageWrapper;
}
protected void clearWindow() {
scrollablePanel.removeAll();
scrollablePanel.revalidate();
scrollablePanel.repaint();
revalidateScrollablePanel();
}
private void call(

View file

@ -44,6 +44,7 @@ public class ChatMessageResponseBody extends JPanel {
private final Project project;
private final Disposable parentDisposable;
private final StreamParser streamParser;
private final boolean readOnly;
private JPanel currentlyProcessedElement;
private ResponseEditor currentlyProcessedEditor;
private JTextPane currentlyProcessedTextPane;
@ -65,10 +66,20 @@ public class ChatMessageResponseBody extends JPanel {
Color backgroundColor,
boolean withGhostText,
Disposable parentDisposable) {
this(project, backgroundColor, withGhostText, false, parentDisposable);
}
public ChatMessageResponseBody(
Project project,
Color backgroundColor,
boolean withGhostText,
boolean readOnly,
Disposable parentDisposable) {
super(new BorderLayout());
this.project = project;
this.parentDisposable = parentDisposable;
this.streamParser = new StreamParser();
this.readOnly = readOnly;
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBackground(backgroundColor);
@ -237,6 +248,8 @@ public class ChatMessageResponseBody extends JPanel {
project,
code,
markdownLanguage,
readOnly,
getPanelBackgroundColor(),
parentDisposable);
currentlyProcessedElement = new ResponseWrapper();

View file

@ -14,13 +14,19 @@ import javax.swing.JPanel;
public class UserMessagePanel extends JPanel {
public UserMessagePanel(Project project, Message message, boolean displayEditorMessage, Disposable parentDisposable) {
public UserMessagePanel(Project project, Message message, Disposable parentDisposable) {
super(new BorderLayout());
setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 0, 0, 1, 0),
JBUI.Borders.empty(12, 8, 8, 8)));
add(createDisplayNameWrapper(), BorderLayout.NORTH);
add(createUserMessageTextPane(project, message, displayEditorMessage, parentDisposable), BorderLayout.SOUTH);
add(new ChatMessageResponseBody(
project,
UIUtil.getPanelBackground(),
false,
true,
parentDisposable).withResponse(message.getPrompt()),
BorderLayout.SOUTH);
}
private JPanel createDisplayNameWrapper() {
@ -30,12 +36,4 @@ public class UserMessagePanel extends JPanel {
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold()));
}
private JPanel createUserMessageTextPane(Project project, Message message, boolean displayEditorMessage, Disposable parentDisposable) {
var prompt = message.getPrompt();
if (displayEditorMessage && message.getUserMessage() != null) {
prompt = message.getUserMessage();
}
return new ChatMessageResponseBody(project, UIUtil.getPanelBackground(), false, parentDisposable).withResponse(prompt);
}
}

View file

@ -1,7 +1,9 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor;
import static ee.carlrobert.codegpt.util.file.FileUtils.findLanguageExtensionMapping;
import static java.lang.String.format;
import com.intellij.icons.AllIcons.General;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
@ -15,8 +17,10 @@ import com.intellij.openapi.editor.impl.ContextMenuPopupHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.ActionLink;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.toolwindow.ReplaceCodeInMainEditorAction;
import ee.carlrobert.codegpt.toolwindow.chat.components.IconActionButton;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction;
@ -26,6 +30,7 @@ import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.NewFileAction;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.ReplaceSelectionAction;
import ee.carlrobert.codegpt.util.EditorUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import javax.swing.Box;
import javax.swing.JPanel;
@ -40,6 +45,8 @@ public class ResponseEditor extends JPanel implements Disposable {
Project project,
String code,
String markdownLanguage,
boolean readOnly,
Color backgroundColor,
Disposable disposableParent) {
super(new BorderLayout());
@ -60,6 +67,10 @@ public class ResponseEditor extends JPanel implements Disposable {
}
var editorEx = ((EditorEx) editor);
if (readOnly) {
editorEx.setOneLineMode(true);
editorEx.setHorizontalScrollbarVisible(false);
}
editorEx.installPopupHandler(new ContextMenuPopupHandler.Simple(group));
editorEx.setColorsScheme(EditorColorsManager.getInstance().getSchemeForCurrentUITheme());
@ -72,8 +83,9 @@ public class ResponseEditor extends JPanel implements Disposable {
settings.setLineMarkerAreaShown(false);
settings.setGutterIconsShown(false);
add(createHeaderComponent(), BorderLayout.NORTH);
add(editor.getComponent(), BorderLayout.SOUTH);
add(createHeaderComponent(readOnly), BorderLayout.NORTH);
add(editor.getComponent(), BorderLayout.CENTER);
add(createFooterComponent(readOnly ? getBackground() : backgroundColor), BorderLayout.SOUTH);
Disposer.register(disposableParent, this);
}
@ -87,16 +99,50 @@ public class ResponseEditor extends JPanel implements Disposable {
return editor;
}
private JPanel createHeaderComponent() {
private JPanel createHeaderComponent(boolean readOnly) {
var headerComponent = new JPanel(new BorderLayout());
headerComponent.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 1, 1, 1, 1),
JBUI.Borders.empty(8)));
JBUI.Borders.empty(4, 8)));
headerComponent.add(new JBLabel(language), BorderLayout.LINE_START);
headerComponent.add(createHeaderActions(), BorderLayout.LINE_END);
if (!readOnly) {
headerComponent.add(createHeaderActions(), BorderLayout.LINE_END);
}
return headerComponent;
}
private String getLinkText(boolean expanded) {
return expanded ?
format(
CodeGPTBundle.get("toolwindow.chat.editor.action.expand"),
((EditorEx) editor).getDocument().getLineCount() - 1) :
CodeGPTBundle.get("toolwindow.chat.editor.action.collapse");
}
private JPanel createFooterComponent(Color backgroundColor) {
var editorEx = ((EditorEx) editor);
var linkText = getLinkText(editorEx.isOneLineMode());
var expandLink = new ActionLink(
linkText,
event -> {
var oneLineMode = editorEx.isOneLineMode();
var source = (ActionLink) event.getSource();
source.setText(getLinkText(!oneLineMode));
source.setIcon(oneLineMode ? General.ArrowUp : General.ArrowDown, true);
editorEx.setOneLineMode(!oneLineMode);
editorEx.setHorizontalScrollbarVisible(oneLineMode);
editorEx.getContentComponent().revalidate();
editorEx.getContentComponent().repaint();
});
expandLink.setIcon(editorEx.isOneLineMode() ? General.ArrowDown : General.ArrowUp, true);
var panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
panel.setBackground(backgroundColor);
panel.add(expandLink);
return panel;
}
private JPanel createHeaderActions() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
wrapper.add(new IconActionButton(new DiffAction(editor)));

View file

@ -76,7 +76,7 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel {
}
var messageWrapper = createNewMessageWrapper(message.getId());
messageWrapper.add(new UserMessagePanel(project, message, false, this));
messageWrapper.add(new UserMessagePanel(project, message, this));
messageWrapper.add(new ResponsePanel()
.withReloadAction(() -> reloadMessage(message, conversation))
.withDeleteAction(() -> removeMessage(message.getId(), messageWrapper, conversation))

View file

@ -99,6 +99,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.expand=Show More (+%s rows)
toolwindow.chat.editor.action.collapse=Show Less
toolwindow.chat.response.action.reloadResponse.text=Reload Response
toolwindow.chat.response.action.reloadResponse.description=Reload response description
toolwindow.chat.response.action.deleteResponse.text=Delete Response