mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-12-22 20:44:53 +01:00
feat: workspace support, initial impl
This commit is contained in:
parent
085488ba36
commit
7c5c851f6f
24 changed files with 324 additions and 146 deletions
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, Workspace> ACTIVE_WORKSPACES = new ConcurrentHashMap<>();
|
||||
private static ClientHandler CLIENT = null;
|
||||
|
||||
public static final Map<String, Editor> ACTIVE_BUFFERS = new ConcurrentHashMap<>();
|
||||
public static final Map<Editor, String> 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 {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<FileEditorWithProvider> 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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<String, Disposable> bufferListeners = new ConcurrentHashMap<>();
|
||||
|
||||
public BufferEventAwaiterTask(@NotNull Project project) {
|
||||
public final Map<String, Disposable> 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<BufferHandler> bufferOptional = CodeMPHandler.selectBuffer(buffers, 100L);
|
||||
Optional<BufferHandler> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
34
src/main/java/com/codemp/intellij/util/FileUtil.java
Normal file
34
src/main/java/com/codemp/intellij/util/FileUtil.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
60
src/main/java/com/codemp/intellij/workspace/Workspace.java
Normal file
60
src/main/java/com/codemp/intellij/workspace/Workspace.java
Normal file
|
@ -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() {}
|
||||
}
|
|
@ -11,7 +11,8 @@
|
|||
<group id="codemp" text="CodeMP" popup="true">
|
||||
<add-to-group group-id="ToolsMenu" anchor="first"/>
|
||||
<action id="codemp.fast-forward" class="com.codemp.intellij.actions.FastForwardAction" text="Just Hurry"/>
|
||||
<action id="codemp.connect" class="com.codemp.intellij.actions.ConnectAction" text="Connect..."/>
|
||||
<action id="codemp.connect" class="com.codemp.intellij.actions.ConnectAction" text="Connect..."/>
|
||||
<action id="codemp.connect" class="com.codemp.intellij.actions.DisconnectAction" text="Disconnect"/>
|
||||
|
||||
<group id="codemp.workspace" text="Workspace" popup="true">
|
||||
<action id="codemp.workspace.join" class="com.codemp.intellij.actions.workspace.WorkspaceJoinAction"
|
||||
|
|
|
@ -19,16 +19,16 @@ lazy_static! {
|
|||
|
||||
#[generate_interface_doc]
|
||||
/// the handler class that represent an instance of a CodeMP client
|
||||
struct CodeMPHandler {
|
||||
struct ClientHandler {
|
||||
client: CodempClient,
|
||||
url: String
|
||||
}
|
||||
|
||||
impl CodeMPHandler {
|
||||
impl ClientHandler {
|
||||
#[generate_interface(constructor)]
|
||||
/// constructor required by flapigen, DO NOT CALL THIS
|
||||
fn new(address: &str) -> 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<BufferHandler> {
|
||||
fn attach_to_buffer(&mut self, path: &str) -> CodempResult<BufferHandler> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue