diff --git a/src/main/java/mp/code/intellij/settings/CodeMPSettings.java b/src/main/java/mp/code/intellij/settings/CodeMPSettings.java index befb508..5b2817c 100644 --- a/src/main/java/mp/code/intellij/settings/CodeMPSettings.java +++ b/src/main/java/mp/code/intellij/settings/CodeMPSettings.java @@ -37,8 +37,6 @@ public class CodeMPSettings implements PersistentStateComponent { - new Thread(() -> { - try { - while(true) { - Optional c = controller.tryRecv(); - if(c.isEmpty()) break; - Cursor event = c.get(); - - CodeMP.LOGGER.debug( - "Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!", - event.user, - event.startCol, - event.startRow, - event.endCol, - event.endRow, - event.buffer - ); - - try { - ApplicationManager.getApplication().runReadAction(() -> { - Editor editor = FileUtil.getActiveEditorByPath(this.myProject, event.buffer); - if(editor == null) return; - - int startOffset = editor.getDocument().getLineStartOffset(event.startRow) + event.startCol; - int endOffset = editor.getDocument().getLineStartOffset(event.endRow) + event.endCol; - - int documentLength = editor.getDocument().getTextLength(); - if(startOffset > documentLength || endOffset > documentLength) { - CodeMP.LOGGER.debug( - "Out of bounds cursor: start was {}, end was {}, document length was {}!", - startOffset, endOffset, documentLength); - return; - } - - RangeHighlighter previous = CodeMP.HIGHLIGHTER_MAP.put( - event.user, - editor.getMarkupModel().addRangeHighlighter( - startOffset, - endOffset, - HighlighterLayer.SELECTION, - new TextAttributes( - null, - ColorUtil.hashColor(event.user), - null, - null, - Font.PLAIN - ), - HighlighterTargetArea.EXACT_RANGE - ) - ); - - if(previous != null) previous.dispose(); - }); - } catch(IndexOutOfBoundsException ignored) {} // don't crash over a bad cursor event - } - } catch(ControllerException ex) { - notifyError(project, "Error receiving change", ex); - } - }).start(); + new CursorCallback(this.myProject).accept(controller); }); if(after != null) after.run(); @@ -181,6 +119,7 @@ public class InteractionUtil { public static void leaveWorkspace(Project project, String workspaceId) { CodeMP.leaveWorkspace(); + MemoryManager.endWorkspaceLifetime(workspaceId); notifyInfo( project, "Success", @@ -201,12 +140,12 @@ public class InteractionUtil { public static Optional bufferAttach(Project project, Workspace workspace, String path) { try { BufferController controller = workspace.attachToBuffer(path); + MemoryManager.startBufferLifetime(workspace.getWorkspaceId(), path); notifyInfo(project, "Success!", String.format( "Successfully attached to buffer %s on workspace %s!", path, workspace.getWorkspaceId()) ); - return Optional.of(controller); } catch(ConnectionException e) { notifyError(project, "Failed to attach to buffer!", e); @@ -214,13 +153,22 @@ public class InteractionUtil { } } - private static void notifyInfo(Project project, String title, String msg) { + public static void bufferCreate(Project project, String path) { + try { + Workspace workspace = CodeMP.getActiveWorkspace(); + workspace.createBuffer(path); + } catch(ConnectionRemoteException e) { + notifyError(project, "Failed to create a buffer!", e); + } + } + + public static void notifyInfo(Project project, String title, String msg) { Notifications.Bus.notify(new Notification( "CodeMP", title, msg, NotificationType.INFORMATION ), project); } - private static void notifyError(Project project, String title, Throwable t) { + public static void notifyError(Project project, String title, Throwable t) { Notifications.Bus.notify(new Notification( "CodeMP", title, String.format("%s: %s", t.getClass().getCanonicalName(), t.getMessage()), diff --git a/src/main/java/mp/code/intellij/util/MemoryManager.java b/src/main/java/mp/code/intellij/util/MemoryManager.java index 9f24bf5..6a262d4 100644 --- a/src/main/java/mp/code/intellij/util/MemoryManager.java +++ b/src/main/java/mp/code/intellij/util/MemoryManager.java @@ -12,7 +12,8 @@ import java.util.concurrent.ConcurrentHashMap; * Allows association of IntelliJ {@link Disposable Disposables} with CodeMP-related * lifetimes (which are managed by a {@link Cleaner}). */ -public class CodeMPMemoryManager { +@SuppressWarnings("UnusedReturnValue") +public class MemoryManager { private static ClientDisposable clientDisposable = null; public static boolean startClientLifetime() { @@ -35,7 +36,7 @@ public class CodeMPMemoryManager { public static boolean startWorkspaceLifetime(String workspace) { if(clientDisposable.workspaces.containsKey(workspace)) return false; - clientDisposable.workspaces.put(workspace, new DisposableWorkspace()); + clientDisposable.workspaces.put(workspace, new WorkspaceDisposable()); return true; } @@ -43,29 +44,37 @@ public class CodeMPMemoryManager { return clientDisposable.workspaces.get(workspace); } - public static boolean endWorkspaceLifetime(String workspace, String buffer) { - if(clientDisposable == null) return false; - ClientDisposable tmp = clientDisposable; - clientDisposable = null; - Disposer.dispose(tmp); + public static boolean endWorkspaceLifetime(String workspace) { + WorkspaceDisposable ws = clientDisposable.workspaces.remove(workspace); + if(ws == null) return false; + Disposer.dispose(ws); return true; } public static boolean startBufferLifetime(String workspace, String buffer) { - + WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace); + if(ws == null || ws.buffers.containsKey(buffer)) return false; + ws.buffers.put(buffer, Disposer.newDisposable()); + return true; } public static @Nullable Disposable getBufferLifetime(String workspace, String buffer) { - + WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace); + if(ws == null) return null; + return ws.buffers.get(buffer); } public static boolean endBufferLifetime(String workspace, String buffer) { - + WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace); + if(ws == null) return false; + Disposable buf = ws.buffers.get(buffer); + if(buf == null) return false; + Disposer.dispose(buf); + return true; } private static class ClientDisposable implements Disposable { - private final Map workspaces = new ConcurrentHashMap<>(); - + private final Map workspaces = new ConcurrentHashMap<>(); @Override public void dispose() { this.workspaces.values().forEach(Disposer::dispose); @@ -73,9 +82,8 @@ public class CodeMPMemoryManager { } } - private static class DisposableWorkspace implements Disposable { + private static class WorkspaceDisposable implements Disposable { private final Map buffers = new ConcurrentHashMap<>(); - @Override public void dispose() { this.buffers.values().forEach(Disposer::dispose); diff --git a/src/main/java/mp/code/intellij/util/cb/BufferCallback.java b/src/main/java/mp/code/intellij/util/cb/BufferCallback.java index 64f7824..04e0c1d 100644 --- a/src/main/java/mp/code/intellij/util/cb/BufferCallback.java +++ b/src/main/java/mp/code/intellij/util/cb/BufferCallback.java @@ -1,12 +1,65 @@ -package mp.code.intellij.util; +package mp.code.intellij.util.cb; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import lombok.RequiredArgsConstructor; import mp.code.BufferController; +import mp.code.data.TextChange; +import mp.code.exceptions.ControllerException; +import mp.code.intellij.CodeMP; +import mp.code.intellij.util.FileUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Consumer; +@RequiredArgsConstructor public class BufferCallback implements Consumer { + private static final Executor BUFFER_EXECUTOR = Executors.newSingleThreadExecutor(); + private final Project project; + @Override public void accept(BufferController bufferController) { + BUFFER_EXECUTOR.execute(() -> { + ApplicationManager.getApplication().runReadAction(() -> { + Editor editor = FileUtil.getActiveEditorByPath(this.project, bufferController.getName()); + ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> { + List changeList = new ArrayList<>(); + while(true) { + Optional changeOptional; + try { + changeOptional = bufferController.tryRecv(); + } catch(ControllerException ex) { + throw new RuntimeException(ex); + } + if(changeOptional.isEmpty()) + break; + TextChange change = changeOptional.get(); + CodeMP.LOGGER.debug("Received text change {} from offset {} to {}!", + change.content, change.start, change.end); + changeList.add(change); + } + + ApplicationManager.getApplication().runWriteAction(() -> + CommandProcessor.getInstance().executeCommand( + this.project, + () -> changeList.forEach((change) -> + editor.getDocument().replaceString( + (int) change.start, (int) change.end, change.content) + ), + "CodeMPBufferReceive", + "codemp-buffer-receive", + editor.getDocument() + ) + ); + }); + }); + }); } } diff --git a/src/main/java/mp/code/intellij/util/cb/CursorCallback.java b/src/main/java/mp/code/intellij/util/cb/CursorCallback.java index f7e6af5..30c81b2 100644 --- a/src/main/java/mp/code/intellij/util/cb/CursorCallback.java +++ b/src/main/java/mp/code/intellij/util/cb/CursorCallback.java @@ -1,4 +1,91 @@ package mp.code.intellij.util.cb; -public class CursorCallback { +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.project.Project; +import lombok.RequiredArgsConstructor; +import mp.code.CursorController; +import mp.code.data.Cursor; +import mp.code.exceptions.ControllerException; +import mp.code.intellij.CodeMP; +import mp.code.intellij.util.ColorUtil; +import mp.code.intellij.util.FileUtil; +import mp.code.intellij.util.InteractionUtil; + +import java.awt.*; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +@RequiredArgsConstructor +public class CursorCallback implements Consumer { + private static final Executor CURSOR_EXECUTOR = Executors.newSingleThreadExecutor(); + private final Project project; + + @Override + public void accept(CursorController controller) { + CURSOR_EXECUTOR.execute(() -> { // necessary + try { + while(true) { + Optional c = controller.tryRecv(); + if(c.isEmpty()) break; + Cursor event = c.get(); + + CodeMP.LOGGER.debug( + "Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!", + event.user, + event.startCol, + event.startRow, + event.endCol, + event.endRow, + event.buffer + ); + + try { + ApplicationManager.getApplication().runReadAction(() -> { + Editor editor = FileUtil.getActiveEditorByPath(this.project, event.buffer); + if(editor == null) return; + + int startOffset = editor.getDocument().getLineStartOffset(event.startRow) + event.startCol; + int endOffset = editor.getDocument().getLineStartOffset(event.endRow) + event.endCol; + + int documentLength = editor.getDocument().getTextLength(); + if(startOffset > documentLength || endOffset > documentLength) { + CodeMP.LOGGER.debug( + "Out of bounds cursor: start was {}, end was {}, document length was {}!", + startOffset, endOffset, documentLength); + return; + } + + RangeHighlighter previous = CodeMP.HIGHLIGHTER_MAP.put( + event.user, + editor.getMarkupModel().addRangeHighlighter( + startOffset, + endOffset, + HighlighterLayer.SELECTION, + new TextAttributes( + null, + ColorUtil.hashColor(event.user), + null, + null, + Font.PLAIN + ), + HighlighterTargetArea.EXACT_RANGE + ) + ); + + if(previous != null) previous.dispose(); + }); + } catch(IndexOutOfBoundsException ignored) {} // don't crash over a bad cursor event + } + } catch(ControllerException ex) { + InteractionUtil.notifyError(project, "Error receiving change", ex); + } + }); + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 9d7a4b9..4c48926 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -35,7 +35,7 @@