210 - Add diff function (#213)

* Add diff function

* modify writeSpace

* Remove unnecessary code

* Add telemetry for toolwindow editor actions

---------

Co-authored-by: wang <1625116638@qq.com>
Co-authored-by: Carl-Robert Linnupuu <carlrobertoh@gmail.com>
This commit is contained in:
wangwangxf 2023-10-02 16:24:09 +08:00 committed by GitHub
parent a2738dac0f
commit 8269ba371c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 488 additions and 274 deletions

View file

@ -8,5 +8,10 @@ public enum ActionType {
DELETE_CONVERSATION,
DISCARD_TOKEN_LIMIT,
OPEN_CONVERSATION_IN_EDITOR,
DIFF_CODE,
EDIT_CODE,
CREATE_NEW_FILE,
COPY_CODE,
REPLACE_IN_MAIN_EDITOR,
RELOAD_MESSAGE
}

View file

@ -1,268 +0,0 @@
package ee.carlrobert.codegpt.toolwindow.chat;
import static com.intellij.openapi.ui.DialogWrapper.OK_EXIT_CODE;
import static ee.carlrobert.codegpt.util.FileUtils.findFileNameExtensionMapping;
import static java.lang.String.format;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.ide.util.EditorHelper;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.EditorKind;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.ContextMenuPopupHandler;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.psi.PsiManager;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.actions.toolwindow.ReplaceCodeInMainEditorAction;
import ee.carlrobert.codegpt.toolwindow.IconActionButton;
import ee.carlrobert.codegpt.util.EditorUtils;
import ee.carlrobert.codegpt.util.FileUtils;
import ee.carlrobert.codegpt.util.OverlayUtils;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
public class ChatToolWindowTabPanelEditor implements Disposable {
private final Project project;
private final Editor editor;
private final Map.Entry<String, String> fileNameExtensionMapping;
public ChatToolWindowTabPanelEditor(
Project project,
String code,
String language,
Disposable disposableParent) {
this.project = project;
this.fileNameExtensionMapping = findFileNameExtensionMapping(language);
var timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
var fileName = "temp_" + timestamp + fileNameExtensionMapping.getValue();
var lightVirtualFile = new LightVirtualFile(
format("%s/%s", PathManager.getTempPath(), fileName), code);
var document = FileDocumentManager.getInstance().getDocument(lightVirtualFile);
if (document == null) {
document = EditorFactory.getInstance().createDocument(code);
}
EditorUtils.disableHighlighting(project, document);
editor = EditorFactory.getInstance().createEditor(
document,
project,
lightVirtualFile,
true,
EditorKind.UNTYPED);
DefaultActionGroup group = new DefaultActionGroup();
group.add(new ReplaceCodeInMainEditorAction());
String originalGroupId = ((EditorEx) editor).getContextMenuGroupId();
if (originalGroupId != null) {
AnAction originalGroup = ActionManager.getInstance().getAction(originalGroupId);
if (originalGroup instanceof ActionGroup) {
group.addAll(((ActionGroup) originalGroup).getChildren(null));
}
}
var editorEx = ((EditorEx) editor);
editorEx.installPopupHandler(new ContextMenuPopupHandler.Simple(group));
editorEx.setColorsScheme(EditorColorsManager.getInstance().getSchemeForCurrentUITheme());
var settings = editor.getSettings();
settings.setAdditionalColumnsCount(0);
settings.setAdditionalLinesCount(0);
settings.setAdditionalPageAtBottom(false);
settings.setVirtualSpace(false);
settings.setUseSoftWraps(false);
Disposer.register(disposableParent, this);
}
public JComponent getComponent() {
var wrapper = new JPanel(new BorderLayout());
wrapper.add(createHeaderComponent(fileNameExtensionMapping.getKey()), BorderLayout.NORTH);
wrapper.add(editor.getComponent(), BorderLayout.SOUTH);
return wrapper;
}
public Editor getEditor() {
return editor;
}
private JPanel createHeaderComponent(String language) {
var headerComponent = new JPanel(new BorderLayout());
headerComponent.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 1, 1, 1, 1),
JBUI.Borders.empty(8)));
headerComponent.add(new JBLabel(language), BorderLayout.LINE_START);
headerComponent.add(createHeaderActions(), BorderLayout.LINE_END);
return headerComponent;
}
private JPanel createHeaderActions() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
wrapper.add(new IconActionButton(new EditAction()));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new NewFileAction()));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new CopyAction()));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new ReplaceInMainEditorAction()));
return wrapper;
}
@Override
public void dispose() {
EditorFactory.getInstance().releaseEditor(editor);
}
class EditAction extends AnAction {
EditAction() {
super("Edit Source", "Edit Source description", Actions.EditSource);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
var editorEx = ((EditorEx) editor);
editorEx.setViewer(!editorEx.isViewer());
var viewer = editorEx.isViewer();
editorEx.setCaretVisible(!viewer);
editorEx.setCaretEnabled(!viewer);
var settings = editorEx.getSettings();
settings.setCaretRowShown(!viewer);
event.getPresentation().setIcon(viewer ? Actions.EditSource : Actions.Show);
event.getPresentation().setText(viewer ? "Edit Source" : "Disable Editing");
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
}
}
class NewFileAction extends AnAction {
NewFileAction() {
super("New File", "New File description", Actions.AddFile);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
fileChooserDescriptor.setForcedToUseIdeaFileChooser(true);
var textFieldWithBrowseButton = new TextFieldWithBrowseButton();
textFieldWithBrowseButton.addBrowseFolderListener(
new TextBrowseFolderListener(fileChooserDescriptor, project));
var fileNameTextField = new JBTextField("Untitled" + fileNameExtensionMapping.getValue());
fileNameTextField.setColumns(30);
if (showDialog(textFieldWithBrowseButton, fileNameTextField) == OK_EXIT_CODE) {
var file = FileUtils.createFile(
textFieldWithBrowseButton.getText(),
fileNameTextField.getText(),
editor.getDocument().getText());
var virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
if (virtualFile == null) {
throw new RuntimeException("Couldn't find the saved virtual file");
}
var psiFile = PsiManager.getInstance(project).findFile(virtualFile);
if (psiFile == null) {
throw new RuntimeException("Couldn't find the saved psi file");
}
EditorHelper.openInEditor(psiFile);
}
}
private int showDialog(
TextFieldWithBrowseButton textFieldWithBrowseButton,
JBTextField fileNameTextField) {
var dialogBuilder = new DialogBuilder(project)
.title("New File")
.centerPanel(FormBuilder.createFormBuilder()
.addLabeledComponent("File name:", fileNameTextField)
.addLabeledComponent("Destination:", textFieldWithBrowseButton)
.getPanel());
dialogBuilder.addOkAction();
dialogBuilder.addCancelAction();
return dialogBuilder.show();
}
}
class CopyAction extends AnAction {
CopyAction() {
super("Copy", "Copy description", Actions.Copy);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
StringSelection stringSelection = new StringSelection(editor.getDocument().getText());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
OverlayUtils.showInfoBalloon("Code copied!", locationOnScreen);
}
}
class ReplaceInMainEditorAction extends AnAction {
ReplaceInMainEditorAction() {
super("Replace in Main Editor", "Replace in Main Editor description", Actions.Replace);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
var project = event.getProject();
if (project != null) {
if (EditorUtils.isMainEditorTextSelected(project)) {
EditorUtils.replaceMainEditorSelection(project, editor.getDocument().getText());
} else {
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
OverlayUtils.showWarningBalloon(
EditorUtils.getSelectedEditor(project) == null
? "Unable to locate a selected editor"
: "Please select a target code before proceeding",
locationOnScreen);
}
}
}
}
}

View file

@ -22,7 +22,7 @@ import com.vladsch.flexmark.util.data.MutableDataSet;
import ee.carlrobert.codegpt.completions.SerpResult;
import ee.carlrobert.codegpt.settings.SettingsConfigurable;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanelEditor;
import ee.carlrobert.codegpt.toolwindow.chat.editor.ChatToolWindowTabPanelEditor;
import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer;
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
import ee.carlrobert.codegpt.toolwindow.chat.StreamResponseType;

View file

@ -0,0 +1,136 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor;
import static ee.carlrobert.codegpt.util.FileUtils.findFileNameExtensionMapping;
import static java.lang.String.format;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.EditorKind;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.ContextMenuPopupHandler;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.actions.toolwindow.ReplaceCodeInMainEditorAction;
import ee.carlrobert.codegpt.toolwindow.IconActionButton;
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.NewFileAction;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.ReplaceSelectionAction;
import ee.carlrobert.codegpt.util.EditorUtils;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class ChatToolWindowTabPanelEditor implements Disposable {
private final Editor editor;
private final String fileName;
private final String fileExtension;
public ChatToolWindowTabPanelEditor(
Project project,
String code,
String language,
Disposable disposableParent) {
var fileNameExtensionMapping = findFileNameExtensionMapping(language);
this.fileName = fileNameExtensionMapping.getKey();
this.fileExtension = fileNameExtensionMapping.getValue();
var timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
var fileName = "temp_" + timestamp + fileExtension;
var lightVirtualFile = new LightVirtualFile(
format("%s/%s", PathManager.getTempPath(), fileName), code);
var document = FileDocumentManager.getInstance().getDocument(lightVirtualFile);
if (document == null) {
document = EditorFactory.getInstance().createDocument(code);
}
EditorUtils.disableHighlighting(project, document);
editor = EditorFactory.getInstance().createEditor(
document,
project,
lightVirtualFile,
true,
EditorKind.UNTYPED);
DefaultActionGroup group = new DefaultActionGroup();
group.add(new ReplaceCodeInMainEditorAction());
String originalGroupId = ((EditorEx) editor).getContextMenuGroupId();
if (originalGroupId != null) {
AnAction originalGroup = ActionManager.getInstance().getAction(originalGroupId);
if (originalGroup instanceof ActionGroup) {
group.addAll(((ActionGroup) originalGroup).getChildren(null));
}
}
var editorEx = ((EditorEx) editor);
editorEx.installPopupHandler(new ContextMenuPopupHandler.Simple(group));
editorEx.setColorsScheme(EditorColorsManager.getInstance().getSchemeForCurrentUITheme());
var settings = editor.getSettings();
settings.setAdditionalColumnsCount(0);
settings.setAdditionalLinesCount(0);
settings.setAdditionalPageAtBottom(false);
settings.setVirtualSpace(false);
settings.setUseSoftWraps(false);
Disposer.register(disposableParent, this);
}
@Override
public void dispose() {
EditorFactory.getInstance().releaseEditor(editor);
}
public JComponent getComponent() {
var wrapper = new JPanel(new BorderLayout());
wrapper.add(createHeaderComponent(), BorderLayout.NORTH);
wrapper.add(editor.getComponent(), BorderLayout.SOUTH);
return wrapper;
}
public Editor getEditor() {
return editor;
}
private JPanel createHeaderComponent() {
var headerComponent = new JPanel(new BorderLayout());
headerComponent.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 1, 1, 1, 1),
JBUI.Borders.empty(8)));
headerComponent.add(new JBLabel(fileName), BorderLayout.LINE_START);
headerComponent.add(createHeaderActions(), BorderLayout.LINE_END);
return headerComponent;
}
private JPanel createHeaderActions() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
wrapper.add(new IconActionButton(new DiffAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new EditAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new NewFileAction(editor, fileExtension)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new CopyAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new ReplaceSelectionAction(editor)));
return wrapper;
}
}

View file

@ -0,0 +1,37 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.util.OverlayUtils;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import org.jetbrains.annotations.NotNull;
public class CopyAction extends TrackableAction {
public CopyAction(@NotNull Editor editor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.description"),
Actions.Copy,
ActionType.COPY_CODE);
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
StringSelection stringSelection = new StringSelection(editor.getDocument().getText());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
OverlayUtils.showInfoBalloon("Code copied!", locationOnScreen);
}
}

View file

@ -0,0 +1,58 @@
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.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.util.Pair;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.util.EditorUtils;
import ee.carlrobert.codegpt.util.FileUtils;
import ee.carlrobert.codegpt.util.OverlayUtils;
import org.jetbrains.annotations.NotNull;
public class DiffAction extends TrackableAction {
public DiffAction(@NotNull Editor editor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.diff.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.diff.description"),
Actions.DiffWithClipboard,
ActionType.DIFF_CODE);
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var project = requireNonNull(event.getProject());
var selectedTextEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (!EditorUtils.hasSelection(selectedTextEditor)) {
OverlayUtils.showSelectedEditorSelectionWarning(event);
return;
}
var resultEditorFile = FileUtils.getEditorFile(selectedTextEditor);
var diffContentFactory = DiffContentFactory.getInstance();
var request = new SimpleDiffRequest(
CodeGPTBundle.get("editor.diff.title"),
diffContentFactory.create(project, FileUtils.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);
}
}

View file

@ -0,0 +1,41 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
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 java.awt.event.MouseEvent;
import org.jetbrains.annotations.NotNull;
public class EditAction extends TrackableAction {
public EditAction(@NotNull Editor editor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.edit.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.edit.description"),
Actions.EditSource,
ActionType.EDIT_CODE);
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var editorEx = ((EditorEx) editor);
editorEx.setViewer(!editorEx.isViewer());
var viewer = editorEx.isViewer();
editorEx.setCaretVisible(!viewer);
editorEx.setCaretEnabled(!viewer);
var settings = editorEx.getSettings();
settings.setCaretRowShown(!viewer);
event.getPresentation().setIcon(viewer ? Actions.EditSource : Actions.Show);
event.getPresentation().setText(viewer ? "Edit Source" : "Disable Editing");
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
}
}

View file

@ -0,0 +1,81 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import static com.intellij.openapi.ui.DialogWrapper.OK_EXIT_CODE;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.ide.util.EditorHelper;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.psi.PsiManager;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.util.FileUtils;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class NewFileAction extends TrackableAction {
private final String fileExtension;
public NewFileAction(@NotNull Editor editor, String fileExtension) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.newFile.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.newFile.description"),
Actions.AddFile,
ActionType.CREATE_NEW_FILE);
this.fileExtension = fileExtension;
}
@Override
public void handleAction(@NotNull AnActionEvent e) {
var project = Objects.requireNonNull(e.getProject());
var fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
fileChooserDescriptor.setForcedToUseIdeaFileChooser(true);
var textFieldWithBrowseButton = new TextFieldWithBrowseButton();
textFieldWithBrowseButton.addBrowseFolderListener(
new TextBrowseFolderListener(fileChooserDescriptor, project));
var fileNameTextField = new JBTextField("Untitled" + fileExtension);
fileNameTextField.setColumns(30);
if (showDialog(project, textFieldWithBrowseButton, fileNameTextField) == OK_EXIT_CODE) {
var file = FileUtils.createFile(
textFieldWithBrowseButton.getText(),
fileNameTextField.getText(),
editor.getDocument().getText());
var virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
if (virtualFile == null) {
throw new RuntimeException("Couldn't find the saved virtual file");
}
var psiFile = PsiManager.getInstance(project).findFile(virtualFile);
if (psiFile == null) {
throw new RuntimeException("Couldn't find the saved psi file");
}
EditorHelper.openInEditor(psiFile);
}
}
private int showDialog(
Project project,
TextFieldWithBrowseButton textFieldWithBrowseButton,
JBTextField fileNameTextField) {
var dialogBuilder = new DialogBuilder(project)
.title("New File")
.centerPanel(FormBuilder.createFormBuilder()
.addLabeledComponent("File name:", fileNameTextField)
.addLabeledComponent("Destination:", textFieldWithBrowseButton)
.getPanel());
dialogBuilder.addOkAction();
dialogBuilder.addCancelAction();
return dialogBuilder.show();
}
}

View file

@ -0,0 +1,34 @@
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 ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.util.EditorUtils;
import ee.carlrobert.codegpt.util.OverlayUtils;
import org.jetbrains.annotations.NotNull;
public class ReplaceSelectionAction extends TrackableAction {
public ReplaceSelectionAction(@NotNull Editor editor) {
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);
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var project = requireNonNull(event.getProject());
if (EditorUtils.isMainEditorTextSelected(project)) {
EditorUtils.replaceMainEditorSelection(project, editor.getDocument().getText());
} else {
OverlayUtils.showSelectedEditorSelectionWarning(event);
}
}
}

View file

@ -0,0 +1,47 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import javax.swing.Icon;
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;
}
abstract void handleAction(@NotNull AnActionEvent e);
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
try {
handleAction(e);
} catch (Exception ex) {
TelemetryAction.IDE_ACTION_ERROR
.createActionMessage()
.error(ex)
.send();
throw ex;
} finally {
TelemetryAction.IDE_ACTION
.createActionMessage()
.property("group", null)
.property("action", actionType.name())
.send();
}
}
}

View file

@ -4,7 +4,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
@ -19,6 +22,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
public class FileUtils {
@ -27,12 +31,19 @@ public class FileUtils {
public static File createFile(String directoryPath, String fileName, String fileContent) {
try {
tryCreateDirectory(directoryPath);
return Files.writeString(Path.of(directoryPath, fileName), fileContent, StandardOpenOption.CREATE).toFile();
return Files.writeString(
Path.of(directoryPath, fileName),
fileContent,
StandardOpenOption.CREATE).toFile();
} catch (IOException e) {
throw new RuntimeException("Failed to create file", e);
}
}
public static VirtualFile getEditorFile(@NotNull Editor editor) {
return FileDocumentManager.getInstance().getFile(editor.getDocument());
}
public static void tryCreateDirectory(String directoryPath) {
try {
if (!FileUtil.exists(directoryPath)) {
@ -63,8 +74,12 @@ public class FileUtils {
List<FileExtensionLanguageDetails> fileExtensionLanguageMappings;
List<LanguageFileExtensionDetails> languageFileExtensionMappings;
try {
fileExtensionLanguageMappings = mapper.readValue(getResourceContent("/fileExtensionLanguageMappings.json"), new TypeReference<>() {});
languageFileExtensionMappings = mapper.readValue(getResourceContent("/languageFileExtensionMappings.json"), new TypeReference<>() {});
fileExtensionLanguageMappings = mapper.readValue(
getResourceContent("/fileExtensionLanguageMappings.json"), new TypeReference<>() {
});
languageFileExtensionMappings = mapper.readValue(
getResourceContent("/languageFileExtensionMappings.json"), new TypeReference<>() {
});
} catch (JsonProcessingException e) {
LOG.error("Unable to extract file extension", e);
return defaultValue;
@ -74,7 +89,8 @@ public class FileUtils {
.orElseGet(() -> fileExtensionLanguageMappings.stream()
.filter(it -> it.getExtension().equalsIgnoreCase(language))
.findFirst()
.map(it -> findFirstExtension(languageFileExtensionMappings, it.getValue()).orElse(defaultValue))
.map(it -> findFirstExtension(languageFileExtensionMappings, it.getValue())
.orElse(defaultValue))
.orElse(defaultValue));
}

View file

@ -3,11 +3,13 @@ package ee.carlrobert.codegpt.util;
import static com.intellij.openapi.ui.Messages.CANCEL;
import static com.intellij.openapi.ui.Messages.OK;
import static ee.carlrobert.codegpt.Icons.DefaultIcon;
import static java.util.Objects.requireNonNull;
import com.intellij.execution.ExecutionBundle;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DoNotAskOption;
@ -24,6 +26,7 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.indexes.FolderStructureTreePanel;
import java.awt.Point;
import java.awt.event.MouseEvent;
import org.jetbrains.annotations.NotNull;
public class OverlayUtils {
@ -88,6 +91,17 @@ public class OverlayUtils {
.guessWindowAndAsk() ? OK : CANCEL;
}
public static void showSelectedEditorSelectionWarning(AnActionEvent event) {
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
showWarningBalloon(
EditorUtils.getSelectedEditor(requireNonNull(event.getProject())) == null
? "Unable to locate a selected editor"
: "Please select a target code before proceeding",
locationOnScreen);
}
public static void showWarningBalloon(String content, Point locationOnScreen) {
showBalloon(content, MessageType.WARNING, locationOnScreen);
}