feat: janky buffer share

This commit is contained in:
zaaarf 2024-09-28 17:04:37 +02:00
parent d809462891
commit da101168e1
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
10 changed files with 273 additions and 198 deletions

View file

@ -0,0 +1,74 @@
package mp.code.intellij.actions.buffer;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import mp.code.BufferController;
import mp.code.data.TextChange;
import mp.code.exceptions.ControllerException;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.FileUtil;
import mp.code.intellij.util.InteractionUtil;
import mp.code.intellij.util.cb.BufferCallback;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Optional;
import java.util.OptionalLong;
public class BufferShareAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project proj = e.getProject();
FileEditor currentEditor = FileEditorManager.getInstance(proj).getSelectedEditor();
if(currentEditor == null) {
Messages.showErrorDialog(
"No file is currently open!",
"CodeMP Buffer Share"
);
return;
}
String path = FileUtil.getRelativePath(proj, currentEditor.getFile());
if(path == null) {
Messages.showErrorDialog(
"File must belong to project!",
"CodeMP Buffer Share"
);
return;
}
InteractionUtil.createBuffer(proj, path);
Optional<BufferController> controller = InteractionUtil.bufferAttach(proj, CodeMP.getActiveWorkspace(), path);
if(controller.isEmpty()) {
Messages.showErrorDialog(
"An unknown error has occurred!",
"CodeMP Buffer Share"
);
return;
}
try {
controller.get().send(new TextChange(
0,
0,
new String(currentEditor.getFile().contentsToByteArray()),
OptionalLong.empty()
));
ApplicationManager.getApplication().runWriteAction(() -> {
try {
FileUtil.getAndRegisterBufferEquivalent(this, proj, controller.get());
} catch(Exception ex) {
throw new RuntimeException(ex);
}
});
controller.get().callback(buf -> new BufferCallback(proj).accept(buf));
} catch(ControllerException | IOException ex) {
throw new RuntimeException(ex);
}
}
}

View file

@ -15,6 +15,7 @@ public class WorkspaceDeleteAction extends AnAction {
"You do not own any workspaces. Ensure you own at least one!",
"CodeMP Delete Workspace"
);
return;
}
int choice = Messages.showDialog( // TODO NOT THE ONE

View file

@ -19,6 +19,7 @@ public class WorkspaceInviteAction extends AnAction {
"You do not own any workspaces. Ensure you own at least one!",
"CodeMP Invite To Workspace"
);
return;
}
int choice = Messages.showDialog( // TODO NOT THE ONE

View file

@ -17,6 +17,7 @@ public class WorkspaceJoinAction extends AnAction {
"There are no available workspaces. Ensure you have rights to access at least one!",
"CodeMP Join Workspace"
);
return;
}
int choice = Messages.showDialog( // TODO NOT THE ONE

View file

@ -16,7 +16,7 @@ public class WorkspaceLeaveAction extends AnAction {
"CodeMP Workspace Leave",
Messages.getQuestionIcon());
InteractionUtil.leaveWorkspace(e.getProject(), workspaceId);
InteractionUtil.leaveWorkspace(e.getProject(), workspaceId, null);
}
@Override

View file

@ -13,6 +13,7 @@ import mp.code.data.TextChange;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
public class BufferEventListener implements DocumentListener {
@ -37,7 +38,9 @@ public class BufferEventListener implements DocumentListener {
if(file == null) return;
CodeMP.getActiveWorkspace().getBuffer(CodeMP.BUFFER_MAPPER.get(file.toNioPath())).ifPresent(controller -> {
Optional.ofNullable(CodeMP.BUFFER_MAPPER.get(file.toNioPath()))
.flatMap(c -> CodeMP.getActiveWorkspace().getBuffer(c))
.ifPresent(controller -> {
int changeOffset = event.getOffset();
CharSequence newFragment = event.getNewFragment();
try {

View file

@ -0,0 +1,119 @@
package mp.code.intellij.ui;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.ui.treeStructure.Tree;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.FileUtil;
import mp.code.intellij.util.InteractionUtil;
import mp.code.intellij.util.cb.BufferCallback;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.util.Optional;
public class CodeMPToolWindow extends JPanel {
public CodeMPToolWindow(Project project) {
this.draw(project);
}
public void redraw(Project project) {
this.draw(project);
this.repaint();
}
private void draw(Project project) {
this.removeAll();
switch(CodeMPWindowFactory.getWindowState()) {
case DISCONNECTED -> {
JButton connectButton = new JButton(new AbstractAction("Connect...") {
@Override
public void actionPerformed(ActionEvent e) {
InteractionUtil.connect(project, () -> CodeMPToolWindow.this.redraw(project));
}
});
this.add(connectButton);
}
case CONNECTED -> {
this.setLayout(new GridLayout(0, 1));
JTree tree = drawTree(InteractionUtil.listWorkspaces(project, true, true));
tree.addMouseListener(new SimpleMouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() < 2) return;
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path == null) return;
String workspaceName = path.getLastPathComponent().toString();
InteractionUtil.joinWorkspace(
project,
workspaceName,
() -> CodeMPToolWindow.this.redraw(project)
);
}
});
this.add(tree);
}
case JOINED -> {
JButton createButton = new JButton(new AbstractAction("Create buffer") {
@Override
public void actionPerformed(ActionEvent e) {
String bufferPath = Messages.showInputDialog(
"Name of buffer:",
"CodeMP Buffer Create",
Messages.getQuestionIcon()
);
InteractionUtil.createBuffer(project, bufferPath);
CodeMPToolWindow.this.redraw(project);
}
});
createButton.setSize(createButton.getPreferredSize());
JTree tree = drawTree(CodeMP.getActiveWorkspace().getFileTree(Optional.empty(), false));
tree.addMouseListener(new SimpleMouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() < 2) return;
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path == null) return;
InteractionUtil.bufferAttach(
project,
CodeMP.getActiveWorkspace(),
path.getLastPathComponent().toString()
).ifPresent(controller -> {
try {
Thread.sleep(1000); // TODO: this sucks
} catch(InterruptedException ignored) {
}
ApplicationManager.getApplication().runWriteAction(() -> {
try {
FileUtil.getAndRegisterBufferEquivalent(this, project, controller);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
});
controller.callback(buf -> new BufferCallback(project).accept(buf));
});
}
});
this.add(createButton);
this.add(tree);
}
}
}
private JTree drawTree(String[] contents) {
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
for(String content : contents) {
root.add(new DefaultMutableTreeNode(content));
}
return new Tree(root);
}
}

View file

@ -1,29 +1,14 @@
package mp.code.intellij.ui;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.treeStructure.Tree;
import com.jgoodies.forms.layout.FormLayout;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.cb.BufferCallback;
import mp.code.intellij.util.FileUtil;
import mp.code.intellij.util.InteractionUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.util.Optional;
public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
@Override
public void createToolWindowContent(
@ -56,104 +41,4 @@ public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
CONNECTED,
JOINED
}
public static class CodeMPToolWindow extends JPanel {
public CodeMPToolWindow(Project project) {
this.draw(project);
}
private void redraw(Project project) {
this.draw(project);
this.repaint();
}
private void draw(Project project) {
this.removeAll();
switch(getWindowState()) {
case DISCONNECTED -> {
JButton connectButton = new JButton(new AbstractAction("Connect...") {
@Override
public void actionPerformed(ActionEvent e) {
InteractionUtil.connect(project, () -> CodeMPToolWindow.this.redraw(project));
}
});
this.add(connectButton);
}
case CONNECTED -> {
this.setLayout(new GridLayout(0, 1));
JTree tree = drawTree(InteractionUtil.listWorkspaces(project, true, true));
tree.addMouseListener(new SimpleMouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() < 2) return;
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path == null) return;
String workspaceName = path.getLastPathComponent().toString();
InteractionUtil.joinWorkspace(
project,
workspaceName,
() -> CodeMPToolWindow.this.redraw(project)
);
}
});
this.add(tree);
}
case JOINED -> {
JButton createButton = new JButton(new AbstractAction("Create buffer") {
@Override
public void actionPerformed(ActionEvent e) {
String bufferPath = Messages.showInputDialog(
"Name of buffer:",
"CodeMP Buffer Create",
Messages.getQuestionIcon()
);
InteractionUtil.bufferCreate(project, bufferPath);
CodeMPToolWindow.this.redraw(project);
}
});
createButton.setSize(createButton.getPreferredSize());
JTree tree = drawTree(CodeMP.getActiveWorkspace().getFileTree(Optional.empty(), false));
tree.addMouseListener(new SimpleMouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() < 2) return;
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path == null) return;
InteractionUtil.bufferAttach(
project,
CodeMP.getActiveWorkspace(),
path.getLastPathComponent().toString()
).ifPresent(controller -> {
try {
Thread.sleep(1000); // TODO: this sucks
} catch(InterruptedException ignored) {}
ApplicationManager.getApplication().runWriteAction(() -> {
try {
FileUtil.getAndRegisterBufferEquivalent(this, project, controller);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
});
controller.callback(buf -> new BufferCallback(project).accept(buf));
});
}
});
this.add(createButton);
this.add(tree);
}
}
}
private JTree drawTree(String[] contents) {
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
for(String content : contents) {
root.add(new DefaultMutableTreeNode(content));
}
return new Tree(root);
}
}
}

View file

@ -11,6 +11,7 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindowManager;
import mp.code.BufferController;
import mp.code.Client;
import mp.code.Workspace;
@ -20,6 +21,7 @@ import mp.code.intellij.CodeMP;
import mp.code.intellij.listeners.BufferEventListener;
import mp.code.intellij.listeners.CursorEventListener;
import mp.code.intellij.settings.CodeMPSettings;
import mp.code.intellij.ui.CodeMPToolWindow;
import mp.code.intellij.util.cb.CursorCallback;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -33,7 +35,7 @@ import java.util.Optional;
* like notifications and error handling.
*/
public class InteractionUtil {
public static void connect(@Nullable Project project, @Nullable Runnable after) {
public static void connect(@NotNull Project project, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Connecting to CodeMP server...") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
@ -48,6 +50,7 @@ public class InteractionUtil {
if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(project, "Success", "Connected to server!");
} catch(NullPointerException e) {
Notifications.Bus.notify(new Notification(
@ -63,29 +66,21 @@ public class InteractionUtil {
});
}
public static void disconnect(@Nullable Project project) {
public static void disconnect(@NotNull Project project) {
CodeMP.disconnect();
MemoryManager.endClientLifetime();
refreshToolWindow(project);
notifyInfo(project, "Success", "Disconnected from server!");
}
public static void createWorkspace(Project project, @NotNull String workspaceId, @Nullable Runnable after) {
public static void createWorkspace(@NotNull Project project, @NotNull String workspaceId, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Creating workspace %s...", workspaceId)) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
if(project == null) {
Notifications.Bus.notify(new Notification(
"CodeMP",
"No project found",
"Please ensure that you have an open project before attempting to create a workspace.",
NotificationType.ERROR
), null);
return;
}
try {
CodeMP.getClient("workspace create").createWorkspace(workspaceId);
if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(
project,
"Success",
@ -101,27 +96,18 @@ public class InteractionUtil {
});
}
public static void inviteToWorkspace(Project project, @NotNull String workspaceId, @NotNull String userName, @Nullable Runnable after) {
public static void inviteToWorkspace(@NotNull Project project, @NotNull String workspaceId, @NotNull String userName, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Inviting %s to workspace %s...", userName, workspaceId)) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
if(project == null) {
Notifications.Bus.notify(new Notification(
"CodeMP",
"No project found",
"Please ensure that you have an open project before attempting to join a workspace.",
NotificationType.ERROR
), null);
return;
}
try {
CodeMP.getClient("workspace invite").inviteToWorkspace(workspaceId, userName);
if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(
project,
"Success",
String.format("Joined workspace %s!", workspaceId)
String.format("Invited %s to workspace %s!", userName, workspaceId)
);
} catch(ConnectionException e) {
InteractionUtil.notifyError(project, String.format(
@ -133,23 +119,14 @@ public class InteractionUtil {
});
}
public static void joinWorkspace(Project project, @NotNull String workspaceId, @Nullable Runnable after) {
public static void joinWorkspace(@NotNull Project project, @NotNull String workspaceId, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Joining workspace %s...", workspaceId)) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
if(project == null) {
Notifications.Bus.notify(new Notification(
"CodeMP",
"No project found",
"Please ensure that you have an open project before attempting to join a workspace.",
NotificationType.ERROR
), null);
return;
}
try {
CodeMP.joinWorkspace(workspaceId);
MemoryManager.startWorkspaceLifetime(workspaceId);
refreshToolWindow(project);
} catch(ConnectionException e) {
InteractionUtil.notifyError(project, String.format(
"Failed to join workspace %s!",
@ -181,20 +158,10 @@ public class InteractionUtil {
});
}
public static void deleteWorkspace(Project project, @NotNull String workspaceId, @Nullable Runnable after) {
public static void deleteWorkspace(@NotNull Project project, @NotNull String workspaceId, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Deleting workspace %s...", workspaceId)) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
if(project == null) {
Notifications.Bus.notify(new Notification(
"CodeMP",
"No project found",
"Please ensure that you have an open project before attempting to delete a workspace.",
NotificationType.ERROR
), null);
return;
}
try {
Client client = CodeMP.getClient("workspace delete");
client.deleteWorkspace(workspaceId);
@ -202,11 +169,13 @@ public class InteractionUtil {
Optional<Workspace> ws = client.getWorkspace("workspace leave");
if(ws.isPresent() && ws.get().getWorkspaceId().equals(workspaceId)) {
CodeMP.leaveWorkspace();
MemoryManager.startWorkspaceLifetime(workspaceId);
MemoryManager.endWorkspaceLifetime(workspaceId);
}
if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(
project,
"Success",
@ -222,15 +191,22 @@ public class InteractionUtil {
});
}
public static void leaveWorkspace(Project project, String workspaceId) {
public static void leaveWorkspace(@NotNull Project project, @NotNull String workspaceId, @Nullable Runnable after) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Leaving workspace %s...", workspaceId)) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
CodeMP.leaveWorkspace();
MemoryManager.endWorkspaceLifetime(workspaceId);
if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(
project,
"Success",
String.format("Left workspace %s!", workspaceId)
);
}
});
}
public static String[] listWorkspaces(Project project, boolean owned, boolean invited) {
try {
@ -258,10 +234,11 @@ public class InteractionUtil {
}
}
public static void bufferCreate(Project project, String path) {
public static void createBuffer(Project project, String path) {
try {
Workspace workspace = CodeMP.getActiveWorkspace();
workspace.createBuffer(path);
refreshToolWindow(project);
} catch(ConnectionRemoteException e) {
notifyError(project, "Failed to create a buffer!", e);
}
@ -281,4 +258,10 @@ public class InteractionUtil {
), project);
CodeMP.LOGGER.error(title, t);
}
public static void refreshToolWindow(Project project) {
CodeMPToolWindow w = (CodeMPToolWindow) ToolWindowManager.getInstance(project).getToolWindow("CodeMP");
if(w == null) return;
w.redraw(project);
}
}

View file

@ -12,6 +12,9 @@
<add-to-group group-id="ToolsMenu" anchor="first"/>
<action id="codemp.connect" class="mp.code.intellij.actions.ConnectAction" text="Connect"/>
<action id="codemp.disconnect" class="mp.code.intellij.actions.DisconnectAction" text="Disconnect"/>
</group>
<group id="codemp.workspace" text="Workspace">
<add-to-group group-id="codemp" anchor="first"/>
<action id="codemp.workspace.create" class="mp.code.intellij.actions.workspace.WorkspaceCreateAction"
text="Create Workspace"/>
<action id="codemp.workspace.join" class="mp.code.intellij.actions.workspace.WorkspaceJoinAction"
@ -23,6 +26,11 @@
<action id="codemp.workspace.leave" class="mp.code.intellij.actions.workspace.WorkspaceLeaveAction"
text="Leave Workspace"/>
</group>
<group id="codemp.buffer" text="Buffer">
<add-to-group group-id="codemp" anchor="first"/>
<action id="codemp.buffer.share" class="mp.code.intellij.actions.buffer.BufferShareAction"
text="Share Current Buffer"/>
</group>
</actions>
<extensions defaultExtensionNs="com.intellij">