From 8c04d9b8c28ad36c67704af8aec875899f514dcf Mon Sep 17 00:00:00 2001 From: zaaarf Date: Fri, 17 Nov 2023 13:03:37 +0100 Subject: [PATCH] feat: buffer and workspace disconnect, lifetime system for listeners --- .../intellij/actions/FastForwardAction.java | 3 +- .../actions/buffer/BufferAttachAction.java | 9 ++- .../actions/buffer/BufferDetachAction.java | 40 +++++++++++ .../WorkspaceJoinAction.java} | 26 ++++---- .../workspace/WorkspaceLeaveAction.java | 33 ++++++++++ .../exceptions/ide/BufferDetachException.java | 13 ++++ .../intellij/util/DisposableRegistry.java | 66 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 30 +++++---- src/main/rust/error.rs | 6 +- src/main/rust/lib.rs | 24 +++---- 10 files changed, 210 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java rename src/main/java/com/codemp/intellij/actions/{JoinAction.java => workspace/WorkspaceJoinAction.java} (85%) create mode 100644 src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java create mode 100644 src/main/java/com/codemp/intellij/exceptions/ide/BufferDetachException.java create mode 100644 src/main/java/com/codemp/intellij/util/DisposableRegistry.java diff --git a/src/main/java/com/codemp/intellij/actions/FastForwardAction.java b/src/main/java/com/codemp/intellij/actions/FastForwardAction.java index a6502c2..e2ba196 100644 --- a/src/main/java/com/codemp/intellij/actions/FastForwardAction.java +++ b/src/main/java/com/codemp/intellij/actions/FastForwardAction.java @@ -1,6 +1,7 @@ package com.codemp.intellij.actions; import com.codemp.intellij.actions.buffer.BufferAttachAction; +import com.codemp.intellij.actions.workspace.WorkspaceJoinAction; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import org.jetbrains.annotations.NotNull; @@ -13,7 +14,7 @@ public class FastForwardAction extends AnAction { public void actionPerformed(@NotNull AnActionEvent e) { try { ConnectAction.connect("http://alemi.dev:50052", true); - JoinAction.join(e, "default", true); + WorkspaceJoinAction.join(e, "default", true); BufferAttachAction.attach(e, "test", true); } catch(Exception ex) { throw new RuntimeException(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 4fac03a..dea40da 100644 --- a/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java @@ -6,6 +6,7 @@ import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.jni.TextChangeWrapper; import com.codemp.intellij.listeners.BufferEventListener; import com.codemp.intellij.util.ActionUtil; +import com.codemp.intellij.util.DisposableRegistry; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; @@ -32,12 +33,18 @@ public class BufferAttachAction extends AnAction { Project project = ActionUtil.getCurrentProject(e); Document document = ActionUtil.getCurrentEditor(e).getDocument(); - document.addDocumentListener(listener); + document.addDocumentListener(listener, DisposableRegistry.getOrCreate(String.format("codemp-buffer-%s", buffer))); 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(); diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java new file mode 100644 index 0000000..b91b308 --- /dev/null +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferDetachAction.java @@ -0,0 +1,40 @@ +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.util.DisposableRegistry; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.NotNull; + +public class BufferDetachAction extends AnAction { + public static void detach(String buffer, boolean silent) throws Exception { + boolean res = CodeMPHandler.detach(buffer); + if(!res) throw new BufferDetachException(buffer); + + //dispose of listener's associated disposable + DisposableRegistry.disposeOf(String.format("codemp-buffer-%s", buffer)); + + if(!silent) Messages.showInfoMessage(String.format("Detached from buffer %s!", buffer), + "Detach from CodeMP Buffer" ); + CodeMP.LOGGER.debug("Detached from buffer {}!", buffer); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + String buffer = Messages.showInputDialog( + "Buffer name:", + "Detach from CodeMP Buffer", + Messages.getQuestionIcon()); + + try { + detach(buffer, false); + } catch(Exception ex) { + Messages.showErrorDialog(String.format( + "Failed to detach from buffer with name %s: %s!", + buffer, ex.getMessage()), "Detach from CodeMP Buffer"); + } + } +} diff --git a/src/main/java/com/codemp/intellij/actions/JoinAction.java b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java similarity index 85% rename from src/main/java/com/codemp/intellij/actions/JoinAction.java rename to src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java index 18f3dfb..cfcef52 100644 --- a/src/main/java/com/codemp/intellij/actions/JoinAction.java +++ b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceJoinAction.java @@ -1,4 +1,4 @@ -package com.codemp.intellij.actions; +package com.codemp.intellij.actions.workspace; import com.codemp.intellij.CodeMP; import com.codemp.intellij.jni.CodeMPHandler; @@ -7,7 +7,7 @@ import com.codemp.intellij.jni.CursorHandler; import com.codemp.intellij.listeners.CursorEventListener; import com.codemp.intellij.util.ActionUtil; import com.codemp.intellij.util.ColorUtil; -import com.intellij.openapi.Disposable; +import com.codemp.intellij.util.DisposableRegistry; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; @@ -25,18 +25,18 @@ import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull; import java.awt.*; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -public class JoinAction extends AnAction { +public class WorkspaceJoinAction extends AnAction { - private static final Map highlighterMap = new HashMap<>(); + private static final Map highlighterMap = new ConcurrentHashMap<>(); - public static void join(AnActionEvent e, String session, boolean silent) throws Exception { - CursorHandler cursorHandler = CodeMPHandler.join(session); + public static void join(AnActionEvent e, String workspace, boolean silent) throws Exception { + CursorHandler cursorHandler = CodeMPHandler.join(workspace); - if(!silent) Messages.showInfoMessage(String.format("Joined session %s!", session), "CodeMP"); - else CodeMP.LOGGER.debug("Joined session {}!", session); + if(!silent) Messages.showInfoMessage(String.format("Joined workspace %s!", workspace), "CodeMP"); + else CodeMP.LOGGER.debug("Joined workspace {}!", workspace); Editor editor = ActionUtil.getCurrentEditor(e); @@ -49,7 +49,8 @@ public class JoinAction extends AnAction { EditorFactory.getInstance() .getEventMulticaster() - .addCaretListener(new CursorEventListener(), task); + .addCaretListener(new CursorEventListener(), + DisposableRegistry.getOrCreate(String.format("codemp-cursor-%s", workspace))); ProgressManager.getInstance().run(task); } @@ -74,7 +75,7 @@ public class JoinAction extends AnAction { //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 implements Disposable { + private static class CursorEventAwaiter extends Task.Backgroundable { private final CursorHandler handler; private final Editor editor; @@ -85,9 +86,6 @@ public class JoinAction extends AnAction { this.editor = editor; } - @Override - public void dispose() {} - @Override @SuppressWarnings("InfiniteLoopStatement") public void run(@NotNull ProgressIndicator indicator) { diff --git a/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java new file mode 100644 index 0000000..9122f4c --- /dev/null +++ b/src/main/java/com/codemp/intellij/actions/workspace/WorkspaceLeaveAction.java @@ -0,0 +1,33 @@ +package com.codemp.intellij.actions.workspace; + +import com.codemp.intellij.CodeMP; +import com.codemp.intellij.jni.CodeMPHandler; +import com.codemp.intellij.util.DisposableRegistry; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.NotNull; + +public class WorkspaceLeaveAction extends AnAction { + public static void leave(boolean silent) throws Exception { + CodeMPHandler.leaveWorkspace(); + + //dispose of listener's associated disposable + DisposableRegistry.disposeOf("codemp-cursor"); + + if(!silent) Messages.showInfoMessage("Left workspace!", + "Detach from CodeMP Buffer" ); + CodeMP.LOGGER.debug("Left workspace!"); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + try { + leave(false); + } catch(Exception ex) { + Messages.showErrorDialog(String.format( + "Failed to leave workspace: %s!", + ex.getMessage()), "Leave CodeMP Workspace"); + } + } +} diff --git a/src/main/java/com/codemp/intellij/exceptions/ide/BufferDetachException.java b/src/main/java/com/codemp/intellij/exceptions/ide/BufferDetachException.java new file mode 100644 index 0000000..5f860fa --- /dev/null +++ b/src/main/java/com/codemp/intellij/exceptions/ide/BufferDetachException.java @@ -0,0 +1,13 @@ +package com.codemp.intellij.exceptions.ide; + +import com.codemp.intellij.exceptions.CodeMPException; + +/** + * Thrown upon failure to detach from a buffer. + */ +public class BufferDetachException extends CodeMPException { + + public BufferDetachException(String name) { + super(String.format("Could not detach from buffer named \"%s\"!", name)); + } +} diff --git a/src/main/java/com/codemp/intellij/util/DisposableRegistry.java b/src/main/java/com/codemp/intellij/util/DisposableRegistry.java new file mode 100644 index 0000000..3be0988 --- /dev/null +++ b/src/main/java/com/codemp/intellij/util/DisposableRegistry.java @@ -0,0 +1,66 @@ +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 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; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 03cf9b9..082ce41 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -27,16 +27,24 @@ - - - - - - + + + + + + + + + + + @@ -44,4 +52,4 @@ - \ No newline at end of file + diff --git a/src/main/rust/error.rs b/src/main/rust/error.rs index dd32f0d..58d54a4 100644 --- a/src/main/rust/error.rs +++ b/src/main/rust/error.rs @@ -1,3 +1,4 @@ +use codemp::Error; use codemp::prelude::CodempError; pub struct ErrorWrapper(CodempError); @@ -15,6 +16,7 @@ impl ErrorWrapper { format!("Error {}: {}", status, message), CodempError::InvalidState { msg } => msg.to_string(), CodempError::Filler { message } => message.to_string(), + Error::Deadlocked => { "Error: deadlocked! (safe to retry)".to_string() } CodempError::Channel { send } => { if *send { "Error while sending message on channel: the channel was closed!".to_string() @@ -22,6 +24,6 @@ impl ErrorWrapper { "Error while reading message from channel: the channel was closed!".to_string() } } - } + } } -} \ No newline at end of file +} diff --git a/src/main/rust/lib.rs b/src/main/rust/lib.rs index a51de31..671a310 100644 --- a/src/main/rust/lib.rs +++ b/src/main/rust/lib.rs @@ -43,6 +43,11 @@ impl CodeMPHandler { convert_buffer(CODEMP_INSTANCE.attach(&path)) } + #[generate_interface] + fn detach(path: String) -> Result { + convert(CODEMP_INSTANCE.disconnect_buffer(&path)) + } + #[generate_interface] fn get_cursor() -> Result { convert_cursor(CODEMP_INSTANCE.get_cursor()) @@ -154,8 +159,8 @@ impl CursorHandler { fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> Result<(), String> { self.cursor.send(CodempCursorPosition { buffer, - start: Some(CodempRowCol { row: start_row, col: start_col }), - end: Some(CodempRowCol { row: end_row, col: end_col }) + start: CodempRowCol::wrap(start_row, start_col), + end: CodempRowCol::wrap(end_row, end_col) }).map_err(|err| ErrorWrapper::from(err).get_error_message()) } } @@ -205,20 +210,17 @@ impl BufferHandler { fn recv(&self) -> Result { match self.buffer.blocking_recv(CODEMP_INSTANCE.rt()) { Err(err) => Err(ErrorWrapper::from(err).get_error_message()), - Ok(change) => { - println!("test {:?}", change); - Ok(TextChangeWrapper { - start: change.span.start, - end: change.span.end, - content: change.content.clone() - }) - } + Ok(change) => Ok(TextChangeWrapper { + start: change.span.start, + end: change.span.end, + content: change.content.clone() + }) } } #[generate_interface] fn send(&self, start_offset: usize, end_offset: usize, content: String) -> Result<(), String> { - self.buffer.send(CodempTextChange { span: start_offset..end_offset, content, after: "".to_string() }) + self.buffer.send(CodempTextChange { span: start_offset..end_offset, content }) .map_err(|err| ErrorWrapper::from(err).get_error_message()) } }