From 7c5c851f6f02538f70cd48ce5a68d0a3492e19f4 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 31 Jan 2024 22:56:45 +0100 Subject: [PATCH] feat: workspace support, initial impl --- Cargo.toml | 3 +- build.gradle | 4 +- src/main/java/com/codemp/intellij/CodeMP.java | 22 ++++++- .../intellij/actions/ConnectAction.java | 4 +- .../intellij/actions/DisconnectAction.java | 28 +++++++++ .../actions/buffer/BufferAttachAction.java | 4 +- .../actions/buffer/BufferCreateAction.java | 1 - .../buffer/BufferCreateWithContentAction.java | 1 - .../actions/buffer/BufferDetachAction.java | 3 - .../actions/buffer/BufferSyncAction.java | 3 - .../workspace/WorkspaceJoinAction.java | 34 ++++------- .../workspace/WorkspaceLeaveAction.java | 18 ++++-- .../exceptions/ide/NotConnectedException.java | 14 +++++ .../listeners/CursorEventListener.java | 17 ++++-- .../WorkspaceFileClosedListener.java | 38 ++++++++++++ .../WorkspaceFileOpenedListener.java | 50 ++++++++++++++++ .../intellij/task/BufferEventAwaiterTask.java | 51 ++++++---------- .../intellij/task/CursorEventAwaiterTask.java | 21 ++++--- .../com/codemp/intellij/task/TaskManager.java | 40 ------------- .../com/codemp/intellij/util/ColorUtil.java | 5 +- .../com/codemp/intellij/util/FileUtil.java | 34 +++++++++++ .../codemp/intellij/workspace/Workspace.java | 60 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 3 +- src/main/rust/lib.rs | 12 ++-- 24 files changed, 324 insertions(+), 146 deletions(-) create mode 100644 src/main/java/com/codemp/intellij/actions/DisconnectAction.java create mode 100644 src/main/java/com/codemp/intellij/exceptions/ide/NotConnectedException.java create mode 100644 src/main/java/com/codemp/intellij/listeners/WorkspaceFileClosedListener.java create mode 100644 src/main/java/com/codemp/intellij/listeners/WorkspaceFileOpenedListener.java delete mode 100644 src/main/java/com/codemp/intellij/task/TaskManager.java create mode 100644 src/main/java/com/codemp/intellij/util/FileUtil.java create mode 100644 src/main/java/com/codemp/intellij/workspace/Workspace.java diff --git a/Cargo.toml b/Cargo.toml index 36df636..9ce0c14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", branch = "workspace", features = ["global", "sync"] } +codemp = { path = "../../lib", features = ["global", "sync", "transport"] } +#codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", branch = "workspace", features = ["global", "sync"] } jni = { version = "0.21.1", features = ["invocation"] } jni-sys = "0.3.0" lazy_static = "1.4.0" diff --git a/build.gradle b/build.gradle index d23ca77..0dbc3ea 100644 --- a/build.gradle +++ b/build.gradle @@ -19,11 +19,11 @@ repositories { dependencies { implementation 'com.github.adamheinrich:native-utils:master-SNAPSHOT' implementation 'org.slf4j:slf4j-api:2.0.9' - implementation 'ch.qos.logback:logback-classic:1.4.6' + implementation 'ch.qos.logback:logback-classic:1.4.12' } intellij { - version.set('2022.2.5') + version.set('2023.3') type.set('IC') } diff --git a/src/main/java/com/codemp/intellij/CodeMP.java b/src/main/java/com/codemp/intellij/CodeMP.java index 1a2b0a6..52d572d 100644 --- a/src/main/java/com/codemp/intellij/CodeMP.java +++ b/src/main/java/com/codemp/intellij/CodeMP.java @@ -1,6 +1,8 @@ package com.codemp.intellij; -import com.intellij.openapi.editor.Editor; +import com.codemp.intellij.exceptions.ide.NotConnectedException; +import com.codemp.intellij.jni.ClientHandler; +import com.codemp.intellij.workspace.Workspace; import com.intellij.openapi.util.SystemInfo; import cz.adamh.utils.NativeUtils; import org.slf4j.Logger; @@ -12,11 +14,25 @@ import java.util.concurrent.ConcurrentHashMap; public class CodeMP { public static Logger LOGGER = LoggerFactory.getLogger(CodeMP.class); + public static final Map ACTIVE_WORKSPACES = new ConcurrentHashMap<>(); + private static ClientHandler CLIENT = null; - public static final Map ACTIVE_BUFFERS = new ConcurrentHashMap<>(); - public static final Map ACTIVE_BUFFERS_REVERSE = new ConcurrentHashMap<>(); + public static void connect(String url) { + CodeMP.loadLibrary(); //will only load it the first time + CLIENT = new ClientHandler(url); + } + + public static void disconnect() { + CLIENT = null; + } + + public static ClientHandler getClient(String reason) throws NotConnectedException { + if(CLIENT == null) throw new NotConnectedException(reason); + return CLIENT; + } private static boolean loadedLibrary = false; + public static void loadLibrary() { if(!loadedLibrary) { try { diff --git a/src/main/java/com/codemp/intellij/actions/ConnectAction.java b/src/main/java/com/codemp/intellij/actions/ConnectAction.java index 8f1154a..3ad68d8 100644 --- a/src/main/java/com/codemp/intellij/actions/ConnectAction.java +++ b/src/main/java/com/codemp/intellij/actions/ConnectAction.java @@ -1,7 +1,6 @@ package com.codemp.intellij.actions; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -10,8 +9,7 @@ import org.jetbrains.annotations.NotNull; public class ConnectAction extends AnAction { public static void connect(AnActionEvent e, String url, boolean silent) { - CodeMP.loadLibrary(); //will only load it the first time - CodeMPHandler.connect(url); + CodeMP.connect(url); if(!silent) ActionUtil.notify(e, "Success", String.format("Connected to %s!", url)); diff --git a/src/main/java/com/codemp/intellij/actions/DisconnectAction.java b/src/main/java/com/codemp/intellij/actions/DisconnectAction.java new file mode 100644 index 0000000..2a6e7dc --- /dev/null +++ b/src/main/java/com/codemp/intellij/actions/DisconnectAction.java @@ -0,0 +1,28 @@ +package com.codemp.intellij.actions; + +import com.codemp.intellij.CodeMP; +import com.codemp.intellij.util.ActionUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import org.jetbrains.annotations.NotNull; + +public class DisconnectAction extends AnAction { + public static void disconnect(AnActionEvent e, boolean silent) { + String url = CodeMP.getClient("disconnect").getUrl(); + + CodeMP.disconnect(); + + if(!silent) ActionUtil.notify(e, + "Success", String.format("Disconnected from %s!", url)); + CodeMP.LOGGER.debug("Connected to {}!", url); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + try { + disconnect(e, false); + } catch(Exception ex) { + ActionUtil.notifyError(e, "Failed to disconnect!", ex); + } + } +} diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java index aba5052..198fab0 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java @@ -2,8 +2,6 @@ package com.codemp.intellij.actions.buffer; import com.codemp.intellij.CodeMP; import com.codemp.intellij.jni.BufferHandler; -import com.codemp.intellij.jni.CodeMPHandler; -import com.codemp.intellij.task.TaskManager; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -21,7 +19,7 @@ public class BufferAttachAction extends AnAction { //TODO "get" the Editor corresponding to buffer, for now use the current one Editor editor = ActionUtil.getCurrentEditor(e); - TaskManager + WorkspaceManager .getOrCreateBufferTask(ActionUtil.getCurrentProject(e)) .registerListener(bufferHandler, editor); } diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateAction.java index 0db6055..ebb1849 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateAction.java @@ -1,7 +1,6 @@ package com.codemp.intellij.actions.buffer; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateWithContentAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateWithContentAction.java index 80df1d5..d839ff0 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateWithContentAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferCreateWithContentAction.java @@ -1,7 +1,6 @@ package com.codemp.intellij.actions.buffer; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java index a96bb86..62ee4fe 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java @@ -2,9 +2,7 @@ package com.codemp.intellij.actions.buffer; import com.codemp.intellij.CodeMP; import com.codemp.intellij.exceptions.ide.BufferDetachException; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.task.BufferEventAwaiterTask; -import com.codemp.intellij.task.TaskManager; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -17,7 +15,6 @@ public class BufferDetachAction extends AnAction { if(!res) throw new BufferDetachException(buffer); CodeMP.ACTIVE_BUFFERS.remove(buffer); - BufferEventAwaiterTask task = TaskManager.getBufferTask(); if(task != null) { task.unregisterListener(buffer); if(!silent) ActionUtil.notify(e, "Success", diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferSyncAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferSyncAction.java index c4c3cbb..26229b1 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferSyncAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferSyncAction.java @@ -1,15 +1,12 @@ package com.codemp.intellij.actions.buffer; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; -import com.codemp.intellij.task.TaskManager; import com.codemp.intellij.util.ActionUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull; public class BufferSyncAction extends AnAction { diff --git a/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java index 9375c41..001dfbb 100644 --- a/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java +++ b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java @@ -1,47 +1,39 @@ package com.codemp.intellij.actions.workspace; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; -import com.codemp.intellij.jni.CursorHandler; -import com.codemp.intellij.listeners.CursorEventListener; -import com.codemp.intellij.task.CursorEventAwaiterTask; -import com.codemp.intellij.task.TaskManager; import com.codemp.intellij.util.ActionUtil; +import com.codemp.intellij.workspace.Workspace; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull; public class WorkspaceJoinAction extends AnAction { - public static void join(AnActionEvent e, String workspace, boolean silent) { - CursorHandler cursorHandler = CodeMPHandler.join(workspace); + public static void join(AnActionEvent e, String workspaceId, boolean silent) { + CodeMP.ACTIVE_WORKSPACES.put(workspaceId, new Workspace( + workspaceId, CodeMP.getClient("join workspace"), + false, e.getProject() //TODO: implement remote projects + )); if(!silent) ActionUtil.notify(e, - "Success", String.format("Joined workspace %s!", workspace)); - CodeMP.LOGGER.debug("Joined workspace {}!", workspace); + "Success", String.format("Joined workspace %s!", workspaceId)); + CodeMP.LOGGER.debug("Joined workspace {}!", workspaceId); - CursorEventAwaiterTask task = TaskManager - .getOrCreateCursorTask(ActionUtil.getCurrentProject(e), cursorHandler); - - EditorFactory.getInstance() - .getEventMulticaster() - .addCaretListener(new CursorEventListener(cursorHandler), task); } @Override public void actionPerformed(@NotNull AnActionEvent e) { - String session = Messages.showInputDialog( - "Session to connect to:", + String workspaceId = Messages.showInputDialog( + "Workspace to connect to:", "CodeMP Join", Messages.getQuestionIcon()); try { - join(e, session, false); + join(e, workspaceId, false); } catch(Exception ex) { ActionUtil.notifyError(e, String.format( - "Failed to join session %s!", - session), ex); + "Failed to join workspace %s!", + workspaceId), ex); } } } diff --git a/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java index 19122d9..5cc12e5 100644 --- a/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java +++ b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java @@ -1,25 +1,33 @@ package com.codemp.intellij.actions.workspace; import com.codemp.intellij.CodeMP; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.util.ActionUtil; +import com.codemp.intellij.workspace.Workspace; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.NotNull; public class WorkspaceLeaveAction extends AnAction { - public static void leave(AnActionEvent e, boolean silent) { - CodeMPHandler.leaveWorkspace(); + public static void leave(AnActionEvent e, String workspaceId, boolean silent) { + CodeMP.getClient("leave workspace") + .leaveWorkspace(workspaceId); + Disposer.dispose(CodeMP.ACTIVE_WORKSPACES.remove(workspaceId)); - if(!silent) ActionUtil.notify(e, "Success", "Left workspace"); + if(!silent) ActionUtil.notify(e, "Success", String.format("Left workspace %s!", workspaceId)); CodeMP.LOGGER.debug("Left workspace!"); } @Override public void actionPerformed(@NotNull AnActionEvent e) { + String workspaceId = Messages.showInputDialog( + "Workspace to leave:", + "CodeMP Workspace Leave", + Messages.getQuestionIcon()); + try { - leave(e, false); + leave(e, workspaceId, false); } catch(Exception ex) { ActionUtil.notifyError(e, "Failed to leave workspace!", ex); } diff --git a/src/main/java/com/codemp/intellij/exceptions/ide/NotConnectedException.java b/src/main/java/com/codemp/intellij/exceptions/ide/NotConnectedException.java new file mode 100644 index 0000000..abb6fee --- /dev/null +++ b/src/main/java/com/codemp/intellij/exceptions/ide/NotConnectedException.java @@ -0,0 +1,14 @@ +package com.codemp.intellij.exceptions.ide; + +import com.codemp.intellij.exceptions.CodeMPException; + +/** + * Fired when trying to access the CodeMP client without first connecting + * to a server. + */ +public class NotConnectedException extends CodeMPException { + + public NotConnectedException(String service) { + super(String.format("Failed to %s, you are not connected to a server!", service)); + } +} diff --git a/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java b/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java index b400ccf..f112a07 100644 --- a/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java +++ b/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java @@ -2,18 +2,23 @@ package com.codemp.intellij.listeners; import com.codemp.intellij.CodeMP; import com.codemp.intellij.jni.CursorHandler; +import com.codemp.intellij.task.CursorEventAwaiterTask; +import com.codemp.intellij.util.FileUtil; +import com.codemp.intellij.workspace.Workspace; import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.event.CaretEvent; import com.intellij.openapi.editor.event.CaretListener; +import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; public class CursorEventListener implements CaretListener { - private final CursorHandler cursorHandler; + private final CursorHandler handler; - public CursorEventListener(CursorHandler cursorHandler) { - this.cursorHandler = cursorHandler; + public CursorEventListener(CursorHandler handler) { + this.handler = handler; } @Override @@ -26,8 +31,10 @@ public class CursorEventListener implements CaretListener { VisualPosition endPos = caret.getSelectionEndPosition(); CodeMP.LOGGER.debug("Caret moved from {}x {}y to {}x {}y", startPos.line, startPos.column, endPos.line, endPos.column); - this.cursorHandler.send( - CodeMP.ACTIVE_BUFFERS_REVERSE.get(event.getEditor()), + + Editor editor = event.getEditor(); + this.handler.send( + FileUtil.getRelativePath(editor.getProject(), editor.getVirtualFile()), startPos.line, startPos.column, endPos.line, endPos.column ); diff --git a/src/main/java/com/codemp/intellij/listeners/WorkspaceFileClosedListener.java b/src/main/java/com/codemp/intellij/listeners/WorkspaceFileClosedListener.java new file mode 100644 index 0000000..9e4c829 --- /dev/null +++ b/src/main/java/com/codemp/intellij/listeners/WorkspaceFileClosedListener.java @@ -0,0 +1,38 @@ +package com.codemp.intellij.listeners; + +import com.codemp.intellij.jni.WorkspaceHandler; +import com.codemp.intellij.task.BufferEventAwaiterTask; +import com.codemp.intellij.util.FileUtil; +import com.codemp.intellij.workspace.Workspace; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +public class WorkspaceFileClosedListener implements FileEditorManagerListener.Before { + private final WorkspaceHandler handler; + private final BufferEventAwaiterTask task; + + public WorkspaceFileClosedListener(WorkspaceHandler handler, BufferEventAwaiterTask task) { + this.handler = handler; + this.task = task; + } + + @Override + public void beforeFileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + String path = FileUtil.getRelativePath(source.getProject(), file); + if(path == null) return; + + Disposable disp = this.task.activeBuffers.remove(path); + if(disp == null) return; + + this.handler.detachFromBuffer(path); + Disposer.dispose(disp); + } +} diff --git a/src/main/java/com/codemp/intellij/listeners/WorkspaceFileOpenedListener.java b/src/main/java/com/codemp/intellij/listeners/WorkspaceFileOpenedListener.java new file mode 100644 index 0000000..feacea6 --- /dev/null +++ b/src/main/java/com/codemp/intellij/listeners/WorkspaceFileOpenedListener.java @@ -0,0 +1,50 @@ +package com.codemp.intellij.listeners; + +import com.codemp.intellij.jni.BufferHandler; +import com.codemp.intellij.jni.WorkspaceHandler; +import com.codemp.intellij.task.BufferEventAwaiterTask; +import com.codemp.intellij.util.FileUtil; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileOpenedSyncListener; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class WorkspaceFileOpenedListener implements FileOpenedSyncListener { + + private final WorkspaceHandler handler; + private final BufferEventAwaiterTask task; + + public WorkspaceFileOpenedListener(WorkspaceHandler handler, BufferEventAwaiterTask task) { + this.handler = handler; + this.task = task; + } + + @Override + public void fileOpenedSync(@NotNull FileEditorManager source, + @NotNull VirtualFile file, + @NotNull List editorsWithProviders) { + editorsWithProviders + .stream() + .map(FileEditorWithProvider::component1) + .filter(fe -> fe instanceof TextEditor) + .map(fe -> (TextEditor) fe) + .map(TextEditor::getEditor) + .forEach(editor -> { + String path = FileUtil.getRelativePath(editor.getProject(), file); + if(path == null) return; + + BufferHandler bufferHandler = this.handler.attachToBuffer(path); + Disposable disp = Disposer.newDisposable(String.format("codemp-buffer-%s", path)); + editor.getDocument().addDocumentListener(new BufferEventListener(bufferHandler), disp); + + editor.getDocument().setText(""); //empty it so we can start receiving + this.task.activeBuffers.put(path, disp); + }); + } +} diff --git a/src/main/java/com/codemp/intellij/task/BufferEventAwaiterTask.java b/src/main/java/com/codemp/intellij/task/BufferEventAwaiterTask.java index 3ad4384..7f088e0 100644 --- a/src/main/java/com/codemp/intellij/task/BufferEventAwaiterTask.java +++ b/src/main/java/com/codemp/intellij/task/BufferEventAwaiterTask.java @@ -4,10 +4,10 @@ import com.codemp.intellij.CodeMP; import com.codemp.intellij.exceptions.lib.ChannelException; import com.codemp.intellij.exceptions.lib.DeadlockedException; import com.codemp.intellij.jni.BufferHandler; -import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.jni.StringVec; import com.codemp.intellij.jni.TextChangeWrapper; -import com.codemp.intellij.listeners.BufferEventListener; +import com.codemp.intellij.jni.WorkspaceHandler; +import com.codemp.intellij.util.FileUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; @@ -22,44 +22,23 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class BufferEventAwaiterTask extends Task.Backgroundable implements Disposable { - private final Map bufferListeners = new ConcurrentHashMap<>(); - - public BufferEventAwaiterTask(@NotNull Project project) { + public final Map activeBuffers; + private final WorkspaceHandler handler; + public BufferEventAwaiterTask(@NotNull Project project, @NotNull WorkspaceHandler handler) { super(project, "Awaiting CodeMP buffer events", false); - } - - public void registerListener(BufferHandler handler, Editor editor) { - CodeMP.ACTIVE_BUFFERS.put(handler.getName(), editor); //mark as active - CodeMP.ACTIVE_BUFFERS_REVERSE.put(editor, handler.getName()); - - Disposable disposable = Disposer - .newDisposable(this, String.format("codemp-buffer-%s", handler.getName())); - - editor.getDocument() - .addDocumentListener(new BufferEventListener(handler), disposable); - - bufferListeners.put(handler.getName(), disposable); - } - - public void unregisterListener(String name) { - CodeMP.ACTIVE_BUFFERS_REVERSE.remove(CodeMP.ACTIVE_BUFFERS.remove(name)); - Disposable listener = this.bufferListeners.remove(name); - if(listener != null) - listener.dispose(); + this.activeBuffers = new ConcurrentHashMap<>(); + this.handler = handler; } @Override - public void dispose() {} - - @Override - @SuppressWarnings({"InfiniteLoopStatement", "UnstableApiUsage"}) + @SuppressWarnings("InfiniteLoopStatement") public void run(@NotNull ProgressIndicator indicator) { try { while(true) { StringVec buffers = new StringVec(); //jni moment - CodeMP.ACTIVE_BUFFERS.keySet().forEach(buffers::push); + this.activeBuffers.keySet().forEach(buffers::push); - Optional bufferOptional = CodeMPHandler.selectBuffer(buffers, 100L); + Optional bufferOptional = this.handler.selectBuffer(buffers, 100L); if(bufferOptional.isEmpty()) continue; BufferHandler buffer = bufferOptional.get(); @@ -81,7 +60,7 @@ public class BufferEventAwaiterTask extends Task.Backgroundable implements Dispo changeList.add(change); } - Editor bufferEditor = CodeMP.ACTIVE_BUFFERS.get(buffer.getName()); + Editor bufferEditor = FileUtil.getActiveEditorByPath(this.myProject, buffer.getName()); ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> ApplicationManager.getApplication().runWriteAction(() -> CommandProcessor.getInstance().executeCommand( @@ -96,8 +75,14 @@ public class BufferEventAwaiterTask extends Task.Backgroundable implements Dispo ))); } } catch(ChannelException ex) { //exited - TaskManager.nullBufferTask(); + //TODO handle stop Disposer.dispose(this); //stopped } } + + @Override + public void dispose() { + this.activeBuffers.values().forEach(Disposable::dispose); + this.activeBuffers.clear(); + } } diff --git a/src/main/java/com/codemp/intellij/task/CursorEventAwaiterTask.java b/src/main/java/com/codemp/intellij/task/CursorEventAwaiterTask.java index de6cd3d..9fe5814 100644 --- a/src/main/java/com/codemp/intellij/task/CursorEventAwaiterTask.java +++ b/src/main/java/com/codemp/intellij/task/CursorEventAwaiterTask.java @@ -5,6 +5,7 @@ import com.codemp.intellij.exceptions.lib.ChannelException; import com.codemp.intellij.jni.CursorEventWrapper; import com.codemp.intellij.jni.CursorHandler; import com.codemp.intellij.util.ColorUtil; +import com.codemp.intellij.util.FileUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; @@ -15,7 +16,6 @@ import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -33,17 +33,13 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo this.handler = handler; } - @Override - public void dispose() {} - @Override @SuppressWarnings("InfiniteLoopStatement") public void run(@NotNull ProgressIndicator indicator) { - assert myProject != null; //will never fail try { while(true) { - CursorEventWrapper event = handler.recv(); - Editor editor = CodeMP.ACTIVE_BUFFERS.get(event.getBuffer()); + CursorEventWrapper event = this.handler.recv(); + Editor editor = FileUtil.getActiveEditorByPath(this.myProject, event.getBuffer()); if(editor == null) continue; @@ -77,7 +73,7 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo HighlighterLayer.SELECTION, new TextAttributes( null, - ColorUtil.colorFromUsername(event.getUser()), + ColorUtil.hashColor(event.getUser()), null, null, Font.PLAIN @@ -90,9 +86,12 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo } catch(IndexOutOfBoundsException ignored) {} } } catch(ChannelException ex) { //exited - this.highlighterMap.forEach((s, r) -> r.dispose()); - TaskManager.nullCursorTask(); - Disposer.dispose(this); + this.run(indicator); } } + + @Override + public void dispose() { + this.highlighterMap.forEach((s, r) -> r.dispose()); + } } diff --git a/src/main/java/com/codemp/intellij/task/TaskManager.java b/src/main/java/com/codemp/intellij/task/TaskManager.java deleted file mode 100644 index 70b059d..0000000 --- a/src/main/java/com/codemp/intellij/task/TaskManager.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.codemp.intellij.task; - -import com.codemp.intellij.jni.CursorHandler; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; - -public class TaskManager { - private static CursorEventAwaiterTask cursorTask = null; - - //TODO in the future joining a workspace will give you a project matching remote - public static CursorEventAwaiterTask getOrCreateCursorTask(Project project, CursorHandler handler) { - if(cursorTask != null) - return cursorTask; - cursorTask = new CursorEventAwaiterTask(project, handler); - ProgressManager.getInstance().run(cursorTask); - return cursorTask; - } - - public static void nullCursorTask() { - cursorTask = null; - } - - private static BufferEventAwaiterTask bufferTask = null; - - public static BufferEventAwaiterTask getBufferTask() { - return bufferTask; - } - - public static BufferEventAwaiterTask getOrCreateBufferTask(Project project) { - if(bufferTask != null) - return bufferTask; - bufferTask = new BufferEventAwaiterTask(project); - ProgressManager.getInstance().run(bufferTask); - return bufferTask; - } - - public static void nullBufferTask() { - bufferTask = null; - } -} diff --git a/src/main/java/com/codemp/intellij/util/ColorUtil.java b/src/main/java/com/codemp/intellij/util/ColorUtil.java index 1d5f209..66edf01 100644 --- a/src/main/java/com/codemp/intellij/util/ColorUtil.java +++ b/src/main/java/com/codemp/intellij/util/ColorUtil.java @@ -5,12 +5,9 @@ import com.intellij.ui.JBColor; import java.awt.Color; public class ColorUtil { - public static JBColor colorFromUsername(String username) { + public static JBColor hashColor(String username) { int hash = username.hashCode(); - - @SuppressWarnings("all") Color hashColor = new Color(hash | (0xFF << 24)); - return new JBColor(hashColor, hashColor.darker()); } } diff --git a/src/main/java/com/codemp/intellij/util/FileUtil.java b/src/main/java/com/codemp/intellij/util/FileUtil.java new file mode 100644 index 0000000..d8ddb94 --- /dev/null +++ b/src/main/java/com/codemp/intellij/util/FileUtil.java @@ -0,0 +1,34 @@ +package com.codemp.intellij.util; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.Arrays; + +public class FileUtil { + public static String getRelativePath(Project project, VirtualFile vf) { + if(ProjectRootManager.getInstance(project).getFileIndex().isInContent(vf)) + return Arrays.stream(ProjectRootManager.getInstance(project).getContentRoots()) + .filter(r -> VfsUtilCore.isAncestor(r, vf, false)) + .findAny() + .map(root -> VfsUtilCore.getRelativePath(vf, root)) + .orElse(null); + return null; + } + + public static Editor getActiveEditorByPath(Project project, String path) { + return Arrays.stream(FileEditorManager.getInstance(project).getAllEditors()) + .filter(fe -> fe instanceof TextEditor) + .map(fe -> { + TextEditor te = (TextEditor) fe; + return te.getEditor(); + }).filter(editor -> path.equals(FileUtil.getRelativePath(editor.getProject(), editor.getVirtualFile()))) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/com/codemp/intellij/workspace/Workspace.java b/src/main/java/com/codemp/intellij/workspace/Workspace.java new file mode 100644 index 0000000..9755534 --- /dev/null +++ b/src/main/java/com/codemp/intellij/workspace/Workspace.java @@ -0,0 +1,60 @@ +package com.codemp.intellij.workspace; + +import com.codemp.intellij.jni.ClientHandler; +import com.codemp.intellij.jni.WorkspaceHandler; +import com.codemp.intellij.listeners.*; +import com.codemp.intellij.task.BufferEventAwaiterTask; +import com.codemp.intellij.task.CursorEventAwaiterTask; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.FileOpenedSyncListener; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.util.messages.MessageBusConnection; + +public class Workspace implements Disposable { + public final String id; + public final String url; + public final boolean isRemote; + public final WorkspaceHandler handler; + public final Project project; + public final BufferEventAwaiterTask bufferTask; + public final CursorEventAwaiterTask cursorTask; + + /** + * The constructor, that will also take care of creating the tasks and listeners associated with it. + * @param id unique id of the workspace on the server + * @param client the {@link ClientHandler} to use + * @param isRemote whether the project is remote + * @param project the {@link Project} to use + */ + public Workspace(String id, ClientHandler client, boolean isRemote, Project project) { + this.id = id; + this.url = client.getUrl(); + this.isRemote = isRemote; + this.handler = client.joinWorkspace(id); + this.project = project; + + this.cursorTask = new CursorEventAwaiterTask(project, this.handler.getCursor()); + ProgressManager.getInstance().run(this.cursorTask); + + this.bufferTask = new BufferEventAwaiterTask(project, this.handler); + ProgressManager.getInstance().run(this.bufferTask); + + // buffer listening + MessageBusConnection conn = this.project.getMessageBus().connect(this); + conn.subscribe(FileOpenedSyncListener.TOPIC, + new WorkspaceFileOpenedListener(this.handler, this.bufferTask)); + conn.subscribe(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER, + new WorkspaceFileClosedListener(this.handler, this.bufferTask)); + + // cursor listening + EditorFactory.getInstance() + .getEventMulticaster() + .addCaretListener(new CursorEventListener(this.handler.getCursor()), this.cursorTask); + } + + @Override + public void dispose() {} +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8df0177..2b2d9c8 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -11,7 +11,8 @@ - + + CodeMPHandler { - CodeMPHandler { + fn new(address: &str) -> ClientHandler { + ClientHandler { client: RT.block_on(CodempClient::new(address)).unwrap(), url: address.to_string() } @@ -83,7 +83,7 @@ impl WorkspaceHandler { #[generate_interface] /// attach to a buffer and get a [crate::BufferHandler] for it - fn attach_buffer(&mut self, path: &str) -> CodempResult { + fn attach_to_buffer(&mut self, path: &str) -> CodempResult { RT.block_on(RT.block_on(self.workspace.write()).attach(path)) .map(|buffer| BufferHandler { buffer }) } @@ -124,7 +124,7 @@ impl WorkspaceHandler { #[generate_interface] /// detach from a buffer - fn detach_buffer(&mut self, path: &str) -> bool { + fn detach_from_buffer(&mut self, path: &str) -> bool { RT.block_on(self.workspace.write()).detach(path) }