mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-11-24 16:04:48 +01:00
feat: general refactor, buffer-aware cursors, various fixes
This commit is contained in:
parent
8c04d9b8c2
commit
f75b6191ea
12 changed files with 323 additions and 230 deletions
|
@ -1,15 +1,21 @@
|
||||||
package com.codemp.intellij;
|
package com.codemp.intellij;
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.util.SystemInfo;
|
import com.intellij.openapi.util.SystemInfo;
|
||||||
import cz.adamh.utils.NativeUtils;
|
import cz.adamh.utils.NativeUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class CodeMP {
|
public class CodeMP {
|
||||||
public static Logger LOGGER = LoggerFactory.getLogger(CodeMP.class);
|
public static Logger LOGGER = LoggerFactory.getLogger(CodeMP.class);
|
||||||
|
|
||||||
|
public static Map<String, Editor> ACTIVE_BUFFERS = new ConcurrentHashMap<>();
|
||||||
|
public static Map<Editor, String> ACTIVE_BUFFERS_REVERSE = new ConcurrentHashMap<>(); //TODO jank
|
||||||
|
|
||||||
private static boolean loadedLibrary = false;
|
private static boolean loadedLibrary = false;
|
||||||
public static void loadLibrary() {
|
public static void loadLibrary() {
|
||||||
if(!loadedLibrary) {
|
if(!loadedLibrary) {
|
||||||
|
|
|
@ -3,72 +3,27 @@ package com.codemp.intellij.actions.buffer;
|
||||||
import com.codemp.intellij.CodeMP;
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.jni.BufferHandler;
|
import com.codemp.intellij.jni.BufferHandler;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
import com.codemp.intellij.jni.TextChangeWrapper;
|
import com.codemp.intellij.task.TaskManager;
|
||||||
import com.codemp.intellij.listeners.BufferEventListener;
|
|
||||||
import com.codemp.intellij.util.ActionUtil;
|
import com.codemp.intellij.util.ActionUtil;
|
||||||
import com.codemp.intellij.util.DisposableRegistry;
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.command.CommandProcessor;
|
|
||||||
import com.intellij.openapi.editor.Document;
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicator;
|
|
||||||
import com.intellij.openapi.progress.ProgressManager;
|
|
||||||
import com.intellij.openapi.progress.Task;
|
|
||||||
import com.intellij.openapi.project.Project;
|
|
||||||
import com.intellij.openapi.ui.Messages;
|
import com.intellij.openapi.ui.Messages;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class BufferAttachAction extends AnAction {
|
public class BufferAttachAction extends AnAction {
|
||||||
|
|
||||||
public static void attach(AnActionEvent e, String buffer, boolean silent) throws Exception {
|
public static void attach(AnActionEvent e, String buffer, boolean silent) throws Exception {
|
||||||
BufferHandler bufferHandler = CodeMPHandler.attach(buffer);
|
BufferHandler bufferHandler = CodeMPHandler.attach(buffer);
|
||||||
if(!silent) Messages.showInfoMessage(String.format("Attached to buffer to %s!", buffer),
|
if(!silent) Messages.showInfoMessage(String.format("Attached to buffer to %s!", buffer),
|
||||||
"CodeMP Buffer Attach");
|
"CodeMP Buffer Attach");
|
||||||
CodeMP.LOGGER.debug("Attached to buffer to {}!", buffer);
|
CodeMP.LOGGER.debug("Attached to buffer to {}!", buffer);
|
||||||
|
|
||||||
//register buffer change listener
|
//TODO "get" the Editor corresponding to buffer, for now use the current one
|
||||||
//TODO "get" the Document corresponding to buffer, for now use the current one
|
Editor editor = ActionUtil.getCurrentEditor(e);
|
||||||
BufferEventListener listener = new BufferEventListener(buffer);
|
|
||||||
|
|
||||||
Project project = ActionUtil.getCurrentProject(e);
|
TaskManager
|
||||||
Document document = ActionUtil.getCurrentEditor(e).getDocument();
|
.getOrCreateBufferTask(ActionUtil.getCurrentProject(e))
|
||||||
document.addDocumentListener(listener, DisposableRegistry.getOrCreate(String.format("codemp-buffer-%s", buffer)));
|
.registerListener(bufferHandler, editor);
|
||||||
|
|
||||||
ProgressManager.getInstance().run(new Task.Backgroundable(e.getProject(), "Awaiting CodeMP buffer events") {
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"InfiniteLoopStatement", "UnstableApiUsage"})
|
|
||||||
public void run(@NotNull ProgressIndicator indicator) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(100); //tonioware
|
|
||||||
} catch(InterruptedException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
try {
|
|
||||||
TextChangeWrapper event = bufferHandler.recv();
|
|
||||||
|
|
||||||
CodeMP.LOGGER.debug("Received text change {} from offset {} to {}!",
|
|
||||||
event.getContent(), event.getStart(), event.getEnd());
|
|
||||||
|
|
||||||
ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> {
|
|
||||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
|
||||||
CommandProcessor.getInstance().executeCommand(
|
|
||||||
project,
|
|
||||||
() -> document.replaceString(
|
|
||||||
(int) event.getStart(), (int) event.getEnd(), event.getContent()),
|
|
||||||
"CodeMPBufferReceive",
|
|
||||||
"codemp-buffer-receive", //TODO: mark this with the name
|
|
||||||
document);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch(Exception ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -3,7 +3,8 @@ package com.codemp.intellij.actions.buffer;
|
||||||
import com.codemp.intellij.CodeMP;
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.exceptions.ide.BufferDetachException;
|
import com.codemp.intellij.exceptions.ide.BufferDetachException;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
import com.codemp.intellij.util.DisposableRegistry;
|
import com.codemp.intellij.task.BufferEventAwaiterTask;
|
||||||
|
import com.codemp.intellij.task.TaskManager;
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
import com.intellij.openapi.ui.Messages;
|
import com.intellij.openapi.ui.Messages;
|
||||||
|
@ -14,12 +15,19 @@ public class BufferDetachAction extends AnAction {
|
||||||
boolean res = CodeMPHandler.detach(buffer);
|
boolean res = CodeMPHandler.detach(buffer);
|
||||||
if(!res) throw new BufferDetachException(buffer);
|
if(!res) throw new BufferDetachException(buffer);
|
||||||
|
|
||||||
//dispose of listener's associated disposable
|
CodeMP.ACTIVE_BUFFERS.remove(buffer);
|
||||||
DisposableRegistry.disposeOf(String.format("codemp-buffer-%s", buffer));
|
BufferEventAwaiterTask task = TaskManager.getBufferTask();
|
||||||
|
if(task != null) {
|
||||||
if(!silent) Messages.showInfoMessage(String.format("Detached from buffer %s!", buffer),
|
task.unregisterListener(buffer);
|
||||||
"Detach from CodeMP Buffer" );
|
if(!silent) Messages.showInfoMessage(String.format("Detached from buffer %s!", buffer),
|
||||||
CodeMP.LOGGER.debug("Detached from buffer {}!", buffer);
|
"Detach from CodeMP Buffer");
|
||||||
|
CodeMP.LOGGER.debug("Detached from buffer {}!", buffer);
|
||||||
|
} else {
|
||||||
|
if(!silent) Messages.showErrorDialog(
|
||||||
|
String.format("Failed to detach from %s: buffer event task was dead!", buffer),
|
||||||
|
"Detach from CodeMP Buffer");
|
||||||
|
CodeMP.LOGGER.debug("Failed to detach from {}: buffer event task was dead!", buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,57 +2,32 @@ package com.codemp.intellij.actions.workspace;
|
||||||
|
|
||||||
import com.codemp.intellij.CodeMP;
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
import com.codemp.intellij.jni.CursorEventWrapper;
|
|
||||||
import com.codemp.intellij.jni.CursorHandler;
|
import com.codemp.intellij.jni.CursorHandler;
|
||||||
import com.codemp.intellij.listeners.CursorEventListener;
|
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.util.ActionUtil;
|
||||||
import com.codemp.intellij.util.ColorUtil;
|
|
||||||
import com.codemp.intellij.util.DisposableRegistry;
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.intellij.openapi.editor.EditorFactory;
|
import com.intellij.openapi.editor.EditorFactory;
|
||||||
import com.intellij.openapi.editor.markup.HighlighterLayer;
|
|
||||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
|
||||||
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
|
||||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
|
||||||
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.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.ui.Messages;
|
import com.intellij.openapi.ui.Messages;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class WorkspaceJoinAction extends AnAction {
|
public class WorkspaceJoinAction extends AnAction {
|
||||||
|
|
||||||
private static final Map<String, RangeHighlighter> highlighterMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static void join(AnActionEvent e, String workspace, boolean silent) throws Exception {
|
public static void join(AnActionEvent e, String workspace, boolean silent) throws Exception {
|
||||||
CursorHandler cursorHandler = CodeMPHandler.join(workspace);
|
CursorHandler cursorHandler = CodeMPHandler.join(workspace);
|
||||||
|
|
||||||
if(!silent) Messages.showInfoMessage(String.format("Joined workspace %s!", workspace), "CodeMP");
|
if(!silent) Messages.showInfoMessage(String.format("Joined workspace %s!", workspace), "CodeMP");
|
||||||
else CodeMP.LOGGER.debug("Joined workspace {}!", workspace);
|
else CodeMP.LOGGER.debug("Joined workspace {}!", workspace);
|
||||||
|
|
||||||
Editor editor = ActionUtil.getCurrentEditor(e);
|
CursorEventAwaiterTask task = TaskManager
|
||||||
|
.getOrCreateCursorTask(ActionUtil.getCurrentProject(e), cursorHandler);
|
||||||
CursorEventAwaiter task = new CursorEventAwaiter(
|
|
||||||
e.getProject(),
|
|
||||||
"Awaiting CodeMP cursor events",
|
|
||||||
cursorHandler,
|
|
||||||
editor
|
|
||||||
);
|
|
||||||
|
|
||||||
EditorFactory.getInstance()
|
EditorFactory.getInstance()
|
||||||
.getEventMulticaster()
|
.getEventMulticaster()
|
||||||
.addCaretListener(new CursorEventListener(),
|
.addCaretListener(new CursorEventListener(cursorHandler), task);
|
||||||
DisposableRegistry.getOrCreate(String.format("codemp-cursor-%s", workspace)));
|
|
||||||
|
|
||||||
ProgressManager.getInstance().run(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,68 +47,4 @@ public class WorkspaceJoinAction extends AnAction {
|
||||||
"CodeMP Join");
|
"CodeMP Join");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO this is janky as it shows a progress bar it doesn't use tbh
|
|
||||||
//implements disposable so i can use it as lifetime ig
|
|
||||||
private static class CursorEventAwaiter extends Task.Backgroundable {
|
|
||||||
|
|
||||||
private final CursorHandler handler;
|
|
||||||
private final Editor editor;
|
|
||||||
|
|
||||||
public CursorEventAwaiter(Project project, String title, CursorHandler handler, Editor editor) {
|
|
||||||
super(project, title);
|
|
||||||
this.handler = handler;
|
|
||||||
this.editor = editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("InfiniteLoopStatement")
|
|
||||||
public void run(@NotNull ProgressIndicator indicator) {
|
|
||||||
while(true) {
|
|
||||||
try {
|
|
||||||
CursorEventWrapper event = handler.recv();
|
|
||||||
int startOffset = this.editor.getDocument().getLineStartOffset(event.getStartRow()) + event.getStartCol();
|
|
||||||
int endOffset = this.editor.getDocument().getLineStartOffset(event.getEndRow()) + event.getEndCol();
|
|
||||||
|
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
|
||||||
try {
|
|
||||||
RangeHighlighter highlighter = highlighterMap.get(event.getUser());
|
|
||||||
if(highlighter != null)
|
|
||||||
highlighter.dispose();
|
|
||||||
|
|
||||||
CodeMP.LOGGER.debug(
|
|
||||||
"Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y with buffer {}!",
|
|
||||||
event.getUser(),
|
|
||||||
event.getStartCol(), event.getStartCol(),
|
|
||||||
event.getEndRow(), event.getEndCol(),
|
|
||||||
event.getBuffer());
|
|
||||||
|
|
||||||
highlighterMap.put(event.getUser(), this.editor
|
|
||||||
.getMarkupModel()
|
|
||||||
.addRangeHighlighter(
|
|
||||||
startOffset,
|
|
||||||
endOffset,
|
|
||||||
HighlighterLayer.SELECTION,
|
|
||||||
new TextAttributes(
|
|
||||||
null,
|
|
||||||
ColorUtil.colorFromUsername(event.getUser()),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Font.PLAIN
|
|
||||||
), HighlighterTargetArea.EXACT_RANGE
|
|
||||||
));
|
|
||||||
} catch(IllegalArgumentException ex) {
|
|
||||||
//suppress if the cursor only exceeds length by one, it's probably just him adding something at EOF
|
|
||||||
if(endOffset - this.editor.getDocument().getTextLength() != 1)
|
|
||||||
throw ex;
|
|
||||||
} catch(Exception ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch(Exception ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.codemp.intellij.actions.workspace;
|
||||||
|
|
||||||
import com.codemp.intellij.CodeMP;
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
import com.codemp.intellij.util.DisposableRegistry;
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
import com.intellij.openapi.ui.Messages;
|
import com.intellij.openapi.ui.Messages;
|
||||||
|
@ -12,9 +11,6 @@ public class WorkspaceLeaveAction extends AnAction {
|
||||||
public static void leave(boolean silent) throws Exception {
|
public static void leave(boolean silent) throws Exception {
|
||||||
CodeMPHandler.leaveWorkspace();
|
CodeMPHandler.leaveWorkspace();
|
||||||
|
|
||||||
//dispose of listener's associated disposable
|
|
||||||
DisposableRegistry.disposeOf("codemp-cursor");
|
|
||||||
|
|
||||||
if(!silent) Messages.showInfoMessage("Left workspace!",
|
if(!silent) Messages.showInfoMessage("Left workspace!",
|
||||||
"Detach from CodeMP Buffer" );
|
"Detach from CodeMP Buffer" );
|
||||||
CodeMP.LOGGER.debug("Left workspace!");
|
CodeMP.LOGGER.debug("Left workspace!");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.codemp.intellij.listeners;
|
package com.codemp.intellij.listeners;
|
||||||
|
|
||||||
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.jni.BufferHandler;
|
import com.codemp.intellij.jni.BufferHandler;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
import com.intellij.openapi.command.CommandProcessor;
|
import com.intellij.openapi.command.CommandProcessor;
|
||||||
|
@ -9,14 +10,17 @@ import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class BufferEventListener implements DocumentListener {
|
public class BufferEventListener implements DocumentListener {
|
||||||
|
|
||||||
private final String bufferName;
|
private final BufferHandler bufferHandler;
|
||||||
|
|
||||||
public BufferEventListener(String bufferName) {
|
public BufferEventListener(BufferHandler bufferHandler) {
|
||||||
this.bufferName = bufferName;
|
this.bufferHandler = bufferHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void documentChanged(@NotNull DocumentEvent event) {
|
public void documentChanged(@NotNull DocumentEvent event) {
|
||||||
|
CodeMP.LOGGER.debug("Changed {} to {} at offset {}",
|
||||||
|
event.getOldFragment(), event.getNewFragment(), event.getOffset());
|
||||||
|
|
||||||
Object group = CommandProcessor.getInstance().getCurrentCommandGroupId();
|
Object group = CommandProcessor.getInstance().getCurrentCommandGroupId();
|
||||||
if(group instanceof String groupString && groupString.startsWith("codemp-buffer-receive"))
|
if(group instanceof String groupString && groupString.startsWith("codemp-buffer-receive"))
|
||||||
return;
|
return;
|
||||||
|
@ -24,8 +28,7 @@ public class BufferEventListener implements DocumentListener {
|
||||||
try { //TODO move actions break
|
try { //TODO move actions break
|
||||||
int changeOffset = event.getOffset();
|
int changeOffset = event.getOffset();
|
||||||
CharSequence newFragment = event.getNewFragment();
|
CharSequence newFragment = event.getNewFragment();
|
||||||
BufferHandler bufferHandler = CodeMPHandler.getBuffer(this.bufferName);
|
this.bufferHandler.send(changeOffset,
|
||||||
bufferHandler.send(changeOffset,
|
|
||||||
changeOffset + event.getOldFragment().length(),
|
changeOffset + event.getOldFragment().length(),
|
||||||
newFragment.toString());
|
newFragment.toString());
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.codemp.intellij.listeners;
|
||||||
|
|
||||||
import com.codemp.intellij.CodeMP;
|
import com.codemp.intellij.CodeMP;
|
||||||
import com.codemp.intellij.jni.CodeMPHandler;
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
|
import com.codemp.intellij.jni.CursorHandler;
|
||||||
import com.intellij.openapi.editor.Caret;
|
import com.intellij.openapi.editor.Caret;
|
||||||
import com.intellij.openapi.editor.VisualPosition;
|
import com.intellij.openapi.editor.VisualPosition;
|
||||||
import com.intellij.openapi.editor.event.CaretEvent;
|
import com.intellij.openapi.editor.event.CaretEvent;
|
||||||
|
@ -9,6 +10,13 @@ import com.intellij.openapi.editor.event.CaretListener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class CursorEventListener implements CaretListener {
|
public class CursorEventListener implements CaretListener {
|
||||||
|
|
||||||
|
private final CursorHandler cursorHandler;
|
||||||
|
|
||||||
|
public CursorEventListener(CursorHandler cursorHandler) {
|
||||||
|
this.cursorHandler = cursorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void caretPositionChanged(@NotNull CaretEvent event) {
|
public void caretPositionChanged(@NotNull CaretEvent event) {
|
||||||
Caret caret = event.getCaret();
|
Caret caret = event.getCaret();
|
||||||
|
@ -20,8 +28,10 @@ public class CursorEventListener implements CaretListener {
|
||||||
VisualPosition endPos = caret.getSelectionEndPosition();
|
VisualPosition endPos = caret.getSelectionEndPosition();
|
||||||
CodeMP.LOGGER.debug("Caret moved from {}x {}y to {}x {}y",
|
CodeMP.LOGGER.debug("Caret moved from {}x {}y to {}x {}y",
|
||||||
startPos.line, startPos.column, endPos.line, endPos.column);
|
startPos.line, startPos.column, endPos.line, endPos.column);
|
||||||
CodeMPHandler.getCursor().send(
|
this.cursorHandler.send(
|
||||||
"", startPos.line, startPos.column, endPos.line, endPos.column
|
CodeMP.ACTIVE_BUFFERS_REVERSE.get(event.getEditor()),
|
||||||
|
startPos.line, startPos.column,
|
||||||
|
endPos.line, endPos.column
|
||||||
);
|
);
|
||||||
} catch(Exception ex) {
|
} catch(Exception ex) {
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.codemp.intellij.task;
|
||||||
|
|
||||||
|
import com.codemp.intellij.CodeMP;
|
||||||
|
import com.codemp.intellij.jni.BufferHandler;
|
||||||
|
import com.codemp.intellij.jni.CodeMPHandler;
|
||||||
|
import com.codemp.intellij.jni.TextChangeWrapper;
|
||||||
|
import com.codemp.intellij.listeners.BufferEventListener;
|
||||||
|
import com.intellij.openapi.Disposable;
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.command.CommandProcessor;
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
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.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) {
|
||||||
|
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) {
|
||||||
|
Disposable listener = this.bufferListeners.remove(name);
|
||||||
|
if(listener != null)
|
||||||
|
listener.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"InfiniteLoopStatement", "UnstableApiUsage"})
|
||||||
|
public void run(@NotNull ProgressIndicator indicator) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000); //tonioware
|
||||||
|
} catch(InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
String buffer = CodeMPHandler.selectBuffer();
|
||||||
|
BufferHandler handler = CodeMPHandler.getBuffer(buffer);
|
||||||
|
|
||||||
|
List<TextChangeWrapper> changeList = new ArrayList<>();
|
||||||
|
while(true) {
|
||||||
|
Optional<TextChangeWrapper> changeOptional;
|
||||||
|
try {
|
||||||
|
changeOptional = handler.tryRecv();
|
||||||
|
} catch(Exception e) {
|
||||||
|
CodeMP.LOGGER.error(e.getMessage());
|
||||||
|
if(e.getMessage().equals("Error: deadlocked! (safe to retry)"))
|
||||||
|
continue;
|
||||||
|
else throw e;
|
||||||
|
}
|
||||||
|
if(changeOptional.isEmpty())
|
||||||
|
break;
|
||||||
|
TextChangeWrapper change = changeOptional.get();
|
||||||
|
CodeMP.LOGGER.debug("Received text change {} from offset {} to {}!",
|
||||||
|
change.getContent(), change.getStart(), change.getEnd());
|
||||||
|
changeList.add(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor bufferEditor = CodeMP.ACTIVE_BUFFERS.get(buffer);
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().invokeLaterOnWriteThread(() ->
|
||||||
|
ApplicationManager.getApplication().runWriteAction(() ->
|
||||||
|
CommandProcessor.getInstance().executeCommand(
|
||||||
|
this.myProject,
|
||||||
|
() -> changeList.forEach((change) ->
|
||||||
|
bufferEditor.getDocument().replaceString(
|
||||||
|
(int) change.getStart(), (int) change.getEnd(), change.getContent())
|
||||||
|
),
|
||||||
|
"CodeMPBufferReceive",
|
||||||
|
"codemp-buffer-receive", //TODO: mark this with the name
|
||||||
|
bufferEditor.getDocument()
|
||||||
|
)));
|
||||||
|
} catch(Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception ex) {
|
||||||
|
TaskManager.nullBufferTask();
|
||||||
|
Disposer.dispose(this); //stopped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.codemp.intellij.task;
|
||||||
|
|
||||||
|
import com.codemp.intellij.CodeMP;
|
||||||
|
import com.codemp.intellij.jni.CursorEventWrapper;
|
||||||
|
import com.codemp.intellij.jni.CursorHandler;
|
||||||
|
import com.codemp.intellij.util.ColorUtil;
|
||||||
|
import com.intellij.openapi.Disposable;
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterLayer;
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
||||||
|
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
||||||
|
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.*;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
//TODO this is janky as it shows a progress bar it doesn't use tbh
|
||||||
|
//implements disposable so i can use it as lifetime ig
|
||||||
|
public class CursorEventAwaiterTask extends Task.Backgroundable implements Disposable {
|
||||||
|
private final CursorHandler handler;
|
||||||
|
private final Map<String, RangeHighlighter> highlighterMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public CursorEventAwaiterTask(@NotNull Project project, @NotNull CursorHandler handler) {
|
||||||
|
super(project, "Awaiting CodeMP cursor events", false);
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
CursorEventWrapper event = handler.recv();
|
||||||
|
|
||||||
|
Editor editor = CodeMP.ACTIVE_BUFFERS.get(event.getBuffer());
|
||||||
|
if(editor == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CodeMP.LOGGER.debug(
|
||||||
|
"Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!",
|
||||||
|
event.getUser(),
|
||||||
|
event.getStartCol(), event.getStartCol(),
|
||||||
|
event.getEndRow(), event.getEndCol(),
|
||||||
|
event.getBuffer());
|
||||||
|
|
||||||
|
int startOffset = editor.getDocument().getLineStartOffset(event.getStartRow()) + event.getStartCol();
|
||||||
|
int endOffset = editor.getDocument().getLineStartOffset(event.getEndRow()) + event.getEndCol();
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
RangeHighlighter highlighter = this.highlighterMap.get(event.getUser());
|
||||||
|
if(highlighter != null)
|
||||||
|
highlighter.dispose();
|
||||||
|
|
||||||
|
this.highlighterMap.put(event.getUser(), editor
|
||||||
|
.getMarkupModel()
|
||||||
|
.addRangeHighlighter(
|
||||||
|
startOffset,
|
||||||
|
endOffset,
|
||||||
|
HighlighterLayer.SELECTION,
|
||||||
|
new TextAttributes(
|
||||||
|
null,
|
||||||
|
ColorUtil.colorFromUsername(event.getUser()),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Font.PLAIN
|
||||||
|
), HighlighterTargetArea.EXACT_RANGE
|
||||||
|
));
|
||||||
|
} catch(IllegalArgumentException ex) {
|
||||||
|
//suppress if the cursor only exceeds length by one, it's probably just him adding something at EOF
|
||||||
|
if(endOffset - editor.getDocument().getTextLength() != 1)
|
||||||
|
throw ex;
|
||||||
|
} catch(Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception ex) { //exited
|
||||||
|
this.highlighterMap.forEach((s, r) -> r.dispose());
|
||||||
|
TaskManager.nullCursorTask();
|
||||||
|
Disposer.dispose(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/main/java/com/codemp/intellij/task/TaskManager.java
Normal file
40
src/main/java/com/codemp/intellij/task/TaskManager.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
package com.codemp.intellij.util;
|
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable;
|
|
||||||
import com.intellij.openapi.util.Disposer;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A registry holding {@link Disposable Disposables} used within the plugin,
|
|
||||||
* since there's no other way to keep track of them across different sections.
|
|
||||||
* Only parentless {@link Disposable Disposables} are handled here, since
|
|
||||||
* those with a parent will be disposed automatically.
|
|
||||||
*/
|
|
||||||
public class DisposableRegistry {
|
|
||||||
private static final Map<String, Disposable> DISPOSABLE_MAP = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static boolean exists(String name) {
|
|
||||||
return DISPOSABLE_MAP.containsKey(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Disposable get(String name) {
|
|
||||||
return DISPOSABLE_MAP.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Disposable create(String name) {
|
|
||||||
disposeOf(name); //get rid of existing ones, if there is one, to prevent memory leaks
|
|
||||||
Disposable res = Disposer.newDisposable(name);
|
|
||||||
DISPOSABLE_MAP.put(name, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Disposable getOrCreate(String name) {
|
|
||||||
Disposable disposable = DISPOSABLE_MAP.get(name);
|
|
||||||
if(disposable == null)
|
|
||||||
disposable = create(name);
|
|
||||||
return disposable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean track(String name, Disposable disposable) {
|
|
||||||
boolean replaced = exists(name);
|
|
||||||
if(replaced)
|
|
||||||
disposeOf(name);
|
|
||||||
DISPOSABLE_MAP.put(name, disposable);
|
|
||||||
return replaced;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean disposeOf(String name) {
|
|
||||||
if(exists(name)) {
|
|
||||||
Disposable disposable = DISPOSABLE_MAP.remove(name);
|
|
||||||
Disposer.dispose(disposable);
|
|
||||||
return true;
|
|
||||||
} else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean disposeOf(Disposable disposable) {
|
|
||||||
if(DISPOSABLE_MAP.containsValue(disposable)) {
|
|
||||||
return DISPOSABLE_MAP.entrySet().removeIf(entry -> {
|
|
||||||
if(entry.getValue().equals(disposable)) {
|
|
||||||
Disposer.dispose(disposable, false);
|
|
||||||
return true;
|
|
||||||
} else return false;
|
|
||||||
});
|
|
||||||
} else return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -67,6 +67,11 @@ impl CodeMPHandler {
|
||||||
fn disconnect_buffer(path: String) -> Result<bool, String> {
|
fn disconnect_buffer(path: String) -> Result<bool, String> {
|
||||||
convert(CODEMP_INSTANCE.disconnect_buffer(&path))
|
convert(CODEMP_INSTANCE.disconnect_buffer(&path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[generate_interface]
|
||||||
|
fn select_buffer() -> Result<String, String> {
|
||||||
|
convert(CODEMP_INSTANCE.select_buffer())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_buffer(result: Result<Arc<CodempBufferController>, CodempError>) -> Result<BufferHandler, String> {
|
fn convert_buffer(result: Result<Arc<CodempBufferController>, CodempError>) -> Result<BufferHandler, String> {
|
||||||
|
@ -206,6 +211,24 @@ impl BufferHandler {
|
||||||
panic!("Default constructor for BufferHandler should never be called!")
|
panic!("Default constructor for BufferHandler should never be called!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[generate_interface]
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
self.buffer.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[generate_interface]
|
||||||
|
fn try_recv(&self) -> Result<Option<TextChangeWrapper>, String> {
|
||||||
|
match self.buffer.try_recv() {
|
||||||
|
Err(err) => Err(ErrorWrapper::from(err).get_error_message()),
|
||||||
|
Ok(None) => Ok(None),
|
||||||
|
Ok(Some(change)) => Ok(Some(TextChangeWrapper {
|
||||||
|
start: change.span.start,
|
||||||
|
end: change.span.end,
|
||||||
|
content: change.content.clone()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn recv(&self) -> Result<TextChangeWrapper, String> {
|
fn recv(&self) -> Result<TextChangeWrapper, String> {
|
||||||
match self.buffer.blocking_recv(CODEMP_INSTANCE.rt()) {
|
match self.buffer.blocking_recv(CODEMP_INSTANCE.rt()) {
|
||||||
|
|
Loading…
Reference in a new issue