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!", "You do not own any workspaces. Ensure you own at least one!",
"CodeMP Delete Workspace" "CodeMP Delete Workspace"
); );
return;
} }
int choice = Messages.showDialog( // TODO NOT THE ONE 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!", "You do not own any workspaces. Ensure you own at least one!",
"CodeMP Invite To Workspace" "CodeMP Invite To Workspace"
); );
return;
} }
int choice = Messages.showDialog( // TODO NOT THE ONE 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!", "There are no available workspaces. Ensure you have rights to access at least one!",
"CodeMP Join Workspace" "CodeMP Join Workspace"
); );
return;
} }
int choice = Messages.showDialog( // TODO NOT THE ONE int choice = Messages.showDialog( // TODO NOT THE ONE

View file

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

View file

@ -13,6 +13,7 @@ import mp.code.data.TextChange;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong; import java.util.OptionalLong;
public class BufferEventListener implements DocumentListener { public class BufferEventListener implements DocumentListener {
@ -37,19 +38,21 @@ public class BufferEventListener implements DocumentListener {
if(file == null) return; if(file == null) return;
CodeMP.getActiveWorkspace().getBuffer(CodeMP.BUFFER_MAPPER.get(file.toNioPath())).ifPresent(controller -> { Optional.ofNullable(CodeMP.BUFFER_MAPPER.get(file.toNioPath()))
int changeOffset = event.getOffset(); .flatMap(c -> CodeMP.getActiveWorkspace().getBuffer(c))
CharSequence newFragment = event.getNewFragment(); .ifPresent(controller -> {
try { int changeOffset = event.getOffset();
controller.send(new TextChange( CharSequence newFragment = event.getNewFragment();
changeOffset, try {
changeOffset + event.getOldFragment().length(), controller.send(new TextChange(
newFragment.toString(), changeOffset,
OptionalLong.empty() changeOffset + event.getOldFragment().length(),
)); newFragment.toString(),
} catch(ControllerException e) { OptionalLong.empty()
throw new RuntimeException(e); ));
} } catch(ControllerException e) {
}); throw new RuntimeException(e);
}
});
} }
} }

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; package mp.code.intellij.ui;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content; import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory; 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.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 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 { public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
@Override @Override
public void createToolWindowContent( public void createToolWindowContent(
@ -56,104 +41,4 @@ public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
CONNECTED, CONNECTED,
JOINED 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.ProgressManager;
import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindowManager;
import mp.code.BufferController; import mp.code.BufferController;
import mp.code.Client; import mp.code.Client;
import mp.code.Workspace; import mp.code.Workspace;
@ -20,6 +21,7 @@ import mp.code.intellij.CodeMP;
import mp.code.intellij.listeners.BufferEventListener; import mp.code.intellij.listeners.BufferEventListener;
import mp.code.intellij.listeners.CursorEventListener; import mp.code.intellij.listeners.CursorEventListener;
import mp.code.intellij.settings.CodeMPSettings; import mp.code.intellij.settings.CodeMPSettings;
import mp.code.intellij.ui.CodeMPToolWindow;
import mp.code.intellij.util.cb.CursorCallback; import mp.code.intellij.util.cb.CursorCallback;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -33,7 +35,7 @@ import java.util.Optional;
* like notifications and error handling. * like notifications and error handling.
*/ */
public class InteractionUtil { 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...") { ProgressManager.getInstance().run(new Task.Backgroundable(project, "Connecting to CodeMP server...") {
@Override @Override
public void run(@NotNull ProgressIndicator indicator) { public void run(@NotNull ProgressIndicator indicator) {
@ -48,6 +50,7 @@ public class InteractionUtil {
if(after != null) after.run(); if(after != null) after.run();
refreshToolWindow(project);
notifyInfo(project, "Success", "Connected to server!"); notifyInfo(project, "Success", "Connected to server!");
} catch(NullPointerException e) { } catch(NullPointerException e) {
Notifications.Bus.notify(new Notification( 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(); CodeMP.disconnect();
MemoryManager.endClientLifetime(); MemoryManager.endClientLifetime();
refreshToolWindow(project);
notifyInfo(project, "Success", "Disconnected from server!"); 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)) { ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Creating workspace %s...", workspaceId)) {
@Override @Override
public void run(@NotNull ProgressIndicator indicator) { 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 { try {
CodeMP.getClient("workspace create").createWorkspace(workspaceId); CodeMP.getClient("workspace create").createWorkspace(workspaceId);
if(after != null) after.run(); if(after != null) after.run();
refreshToolWindow(project);
notifyInfo( notifyInfo(
project, project,
"Success", "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)) { ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Inviting %s to workspace %s...", userName, workspaceId)) {
@Override @Override
public void run(@NotNull ProgressIndicator indicator) { 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 { try {
CodeMP.getClient("workspace invite").inviteToWorkspace(workspaceId, userName); CodeMP.getClient("workspace invite").inviteToWorkspace(workspaceId, userName);
if(after != null) after.run(); if(after != null) after.run();
refreshToolWindow(project);
notifyInfo( notifyInfo(
project, project,
"Success", "Success",
String.format("Joined workspace %s!", workspaceId) String.format("Invited %s to workspace %s!", userName, workspaceId)
); );
} catch(ConnectionException e) { } catch(ConnectionException e) {
InteractionUtil.notifyError(project, String.format( 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)) { ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Joining workspace %s...", workspaceId)) {
@Override @Override
public void run(@NotNull ProgressIndicator indicator) { 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 { try {
CodeMP.joinWorkspace(workspaceId); CodeMP.joinWorkspace(workspaceId);
MemoryManager.startWorkspaceLifetime(workspaceId); MemoryManager.startWorkspaceLifetime(workspaceId);
refreshToolWindow(project);
} catch(ConnectionException e) { } catch(ConnectionException e) {
InteractionUtil.notifyError(project, String.format( InteractionUtil.notifyError(project, String.format(
"Failed to join workspace %s!", "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)) { ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Deleting workspace %s...", workspaceId)) {
@Override @Override
public void run(@NotNull ProgressIndicator indicator) { 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 { try {
Client client = CodeMP.getClient("workspace delete"); Client client = CodeMP.getClient("workspace delete");
client.deleteWorkspace(workspaceId); client.deleteWorkspace(workspaceId);
@ -202,11 +169,13 @@ public class InteractionUtil {
Optional<Workspace> ws = client.getWorkspace("workspace leave"); Optional<Workspace> ws = client.getWorkspace("workspace leave");
if(ws.isPresent() && ws.get().getWorkspaceId().equals(workspaceId)) { if(ws.isPresent() && ws.get().getWorkspaceId().equals(workspaceId)) {
CodeMP.leaveWorkspace(); CodeMP.leaveWorkspace();
MemoryManager.startWorkspaceLifetime(workspaceId); MemoryManager.endWorkspaceLifetime(workspaceId);
} }
if(after != null) after.run(); if(after != null) after.run();
refreshToolWindow(project);
notifyInfo( notifyInfo(
project, project,
"Success", "Success",
@ -222,14 +191,21 @@ public class InteractionUtil {
}); });
} }
public static void leaveWorkspace(Project project, String workspaceId) { public static void leaveWorkspace(@NotNull Project project, @NotNull String workspaceId, @Nullable Runnable after) {
CodeMP.leaveWorkspace(); ProgressManager.getInstance().run(new Task.Backgroundable(project, String.format("Leaving workspace %s...", workspaceId)) {
MemoryManager.endWorkspaceLifetime(workspaceId); @Override
notifyInfo( public void run(@NotNull ProgressIndicator indicator) {
project, CodeMP.leaveWorkspace();
"Success", MemoryManager.endWorkspaceLifetime(workspaceId);
String.format("Left workspace %s!", 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) { public static String[] listWorkspaces(Project project, boolean owned, boolean invited) {
@ -258,10 +234,11 @@ public class InteractionUtil {
} }
} }
public static void bufferCreate(Project project, String path) { public static void createBuffer(Project project, String path) {
try { try {
Workspace workspace = CodeMP.getActiveWorkspace(); Workspace workspace = CodeMP.getActiveWorkspace();
workspace.createBuffer(path); workspace.createBuffer(path);
refreshToolWindow(project);
} catch(ConnectionRemoteException e) { } catch(ConnectionRemoteException e) {
notifyError(project, "Failed to create a buffer!", e); notifyError(project, "Failed to create a buffer!", e);
} }
@ -281,4 +258,10 @@ public class InteractionUtil {
), project); ), project);
CodeMP.LOGGER.error(title, t); 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

@ -10,19 +10,27 @@
<actions> <actions>
<group id="codemp" text="CodeMP" popup="true"> <group id="codemp" text="CodeMP" popup="true">
<add-to-group group-id="ToolsMenu" anchor="first"/> <add-to-group group-id="ToolsMenu" anchor="first"/>
<action id="codemp.connect" class="mp.code.intellij.actions.ConnectAction" text="Connect"/> <action id="codemp.connect" class="mp.code.intellij.actions.ConnectAction" text="Connect"/>
<action id="codemp.disconnect" class="mp.code.intellij.actions.DisconnectAction" text="Disconnect"/> <action id="codemp.disconnect" class="mp.code.intellij.actions.DisconnectAction" text="Disconnect"/>
<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"
text="Join Workspace"/>
<action id="codemp.workspace.invite" class="mp.code.intellij.actions.workspace.WorkspaceInviteAction"
text="Invite To Workspace"/>
<action id="codemp.workspace.delete" class="mp.code.intellij.actions.workspace.WorkspaceDeleteAction"
text="Delete Workspace"/>
<action id="codemp.workspace.leave" class="mp.code.intellij.actions.workspace.WorkspaceLeaveAction"
text="Leave Workspace"/>
</group> </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"
text="Join Workspace"/>
<action id="codemp.workspace.invite" class="mp.code.intellij.actions.workspace.WorkspaceInviteAction"
text="Invite To Workspace"/>
<action id="codemp.workspace.delete" class="mp.code.intellij.actions.workspace.WorkspaceDeleteAction"
text="Delete Workspace"/>
<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> </actions>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">