mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-11-21 22:54:48 +01:00
feat: buffer and workspace disconnect, lifetime system for listeners
This commit is contained in:
parent
dfae6d6b9d
commit
8c04d9b8c2
10 changed files with 210 additions and 40 deletions
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, RangeHighlighter> highlighterMap = new HashMap<>();
|
||||
private static final Map<String, RangeHighlighter> 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) {
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<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;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,13 @@
|
|||
<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.join" class="com.codemp.intellij.actions.JoinAction" text="Join..."/>
|
||||
|
||||
<group id="codemp.workspace" text="Workspace" popup="true">
|
||||
<action id="codemp.workspace.join" class="com.codemp.intellij.actions.workspace.WorkspaceJoinAction"
|
||||
text="Join Workspace"/>
|
||||
<action id="codemp.workspace.leave" class="com.codemp.intellij.actions.workspace.WorkspaceLeaveAction"
|
||||
text="Leave Workspace"/>
|
||||
</group>
|
||||
<group id="codemp.buffer" text="Buffer" popup="true">
|
||||
<action id="codemp.buffer.create" class="com.codemp.intellij.actions.buffer.BufferCreateAction"
|
||||
text="Create Buffer"/>
|
||||
|
@ -36,6 +42,8 @@
|
|||
text="Create Buffer with Content"/>
|
||||
<action id="codemp.buffer.attach" class="com.codemp.intellij.actions.buffer.BufferAttachAction"
|
||||
text="Attach to Buffer"/>
|
||||
<action id="codemp.buffer.detach" class="com.codemp.intellij.actions.buffer.BufferDetachAction"
|
||||
text="Detach from Buffer"/>
|
||||
</group>
|
||||
</group>
|
||||
</actions>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -43,6 +43,11 @@ impl CodeMPHandler {
|
|||
convert_buffer(CODEMP_INSTANCE.attach(&path))
|
||||
}
|
||||
|
||||
#[generate_interface]
|
||||
fn detach(path: String) -> Result<bool, String> {
|
||||
convert(CODEMP_INSTANCE.disconnect_buffer(&path))
|
||||
}
|
||||
|
||||
#[generate_interface]
|
||||
fn get_cursor() -> Result<CursorHandler, String> {
|
||||
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<TextChangeWrapper, String> {
|
||||
match self.buffer.blocking_recv(CODEMP_INSTANCE.rt()) {
|
||||
Err(err) => Err(ErrorWrapper::from(err).get_error_message()),
|
||||
Ok(change) => {
|
||||
println!("test {:?}", change);
|
||||
Ok(TextChangeWrapper {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue