feat: buffer send/recv

This commit is contained in:
zaaarf 2023-11-16 15:54:30 +01:00
parent 1228f786b7
commit 5b7d3c95dd
No known key found for this signature in database
GPG key ID: 82240E075E31FA4C
8 changed files with 278 additions and 92 deletions

View file

@ -10,26 +10,30 @@ import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
public class ConnectAction extends AnAction { public class ConnectAction extends AnAction {
static { public ConnectAction() {
super();
/*try { /*try {
NativeUtils.loadLibraryFromJar("/resources/libHelloJNI.so"); NativeUtils.loadLibraryFromJar("/resources/libHelloJNI.so");
} catch(IOException e) { } catch(IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
}*/ }*/
System.load("/home/zaaarf/dev/irl/rust/codemp/client/intellij/target/debug/libcodemp_intellij.so"); //System.load("/home/zaaarf/dev/irl/rust/codemp/client/intellij/target/debug/libcodemp_intellij.so");
//System.load("O:/dev/IRL/Rust/codemp/client/intellij/target/debug/codemp_intellij.dll"); System.load("O:/dev/IRL/Rust/codemp/client/intellij/target/debug/codemp_intellij.dll");
}
public void connect(String url) throws Exception {
CodeMPHandler.connect(url);
//Messages.showInfoMessage(String.format("Connected to %s!", url), "CodeMP");
System.out.printf("Connected to %s!\n", url);
} }
@Override @Override
public void actionPerformed(@NotNull AnActionEvent e) { public void actionPerformed(@NotNull AnActionEvent e) {
String url = Messages.showInputDialog("URL to CodeMP instance:", "CodeMP Connect", Messages.getQuestionIcon()); String url = Messages.showInputDialog("URL to CodeMP instance:", "CodeMP Connect",
if(url == null || url.isBlank()) Messages.getQuestionIcon());
url = "http://alemi.dev:50051";
try { try {
CodeMPHandler.connect(url); this.connect(url);
//Messages.showInfoMessage(String.format("Connected to %s!", url), "CodeMP");
System.out.printf("Connected to %s!\n", url);
} catch(Exception ex) { } catch(Exception ex) {
Messages.showErrorDialog(String.format("Failed to connect to %s: %s!", url, ex.getMessage()), "CodeMP"); Messages.showErrorDialog(String.format("Failed to connect to %s: %s!", url, ex.getMessage()), "CodeMP");
} }

View file

@ -0,0 +1,22 @@
package com.codemp.intellij.actions;
import com.codemp.intellij.actions.buffer.BufferAttachAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
public class FastForwardAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
ConnectAction connectAction = new ConnectAction();
JoinAction joinAction = new JoinAction();
BufferAttachAction attachAction = new BufferAttachAction();
try {
connectAction.connect("http://alemi.dev:50051");
joinAction.join(e, "default");
attachAction.attach(e, "test");
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}

View file

@ -36,22 +36,13 @@ public class JoinAction extends AnAction {
null, JBColor.BLUE, null, null, Font.PLAIN null, JBColor.BLUE, null, null, Font.PLAIN
); );
@Override public void join(AnActionEvent e, String session) throws Exception {
public void actionPerformed(@NotNull AnActionEvent e) {
String session = Messages.showInputDialog(
"Session to connect to:",
"CodeMP Join",
Messages.getQuestionIcon());
if(session == null || session.isBlank())
session = "default";
try {
CursorHandler cursorHandler = CodeMPHandler.join(session); CursorHandler cursorHandler = CodeMPHandler.join(session);
EditorFactory.getInstance() EditorFactory.getInstance()
.getEventMulticaster() .getEventMulticaster()
.addCaretListener(new CursorEventListener()); .addCaretListener(new CursorEventListener());
//Messages.showInfoMessage(String.format("Joined %s!", session), "CodeMP"); //Messages.showInfoMessage(String.format("Joined %s!", session), "CodeMP");
System.out.printf(String.format("Joined %s!", session)); System.out.printf(String.format("Joined %s!\n", session));
Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(e.getProject())) Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(e.getProject()))
.getSelectedTextEditor(); .getSelectedTextEditor();
assert editor != null; assert editor != null;
@ -93,7 +84,17 @@ public class JoinAction extends AnAction {
} }
} }
}); });
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
String session = Messages.showInputDialog(
"Session to connect to:",
"CodeMP Join",
Messages.getQuestionIcon());
try {
this.join(e, session);
} catch(Exception ex) { } catch(Exception ex) {
Messages.showErrorDialog(String.format( Messages.showErrorDialog(String.format(
"Failed to join session %s: %s!", "Failed to join session %s: %s!",

View file

@ -0,0 +1,77 @@
package com.codemp.intellij.actions.buffer;
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.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
import javax.print.Doc;
import java.util.Objects;
public class BufferAttachAction extends AnAction {
public void attach(AnActionEvent e, String buffer) throws Exception {
BufferHandler bufferHandler = CodeMPHandler.attach(buffer);
//Messages.showInfoMessage(String.format("Connected to %s!", url), "CodeMP");
//register buffer change listener
//TODO "get" the Document corresponding to buffer, for now use the current one
BufferEventListener listener = new BufferEventListener(buffer);
assert e.getProject() != null;
Editor editor = FileEditorManager.getInstance(e.getProject()).getSelectedTextEditor();
assert editor != null;
Document document = editor.getDocument();
document.addDocumentListener(listener);
ProgressManager.getInstance().run(new Task.Backgroundable(e.getProject(), "Awaiting CodeMP buffer events") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
while(true) {
try {
TextChangeWrapper event = bufferHandler.recv();
ApplicationManager.getApplication().invokeLaterOnWriteThread(() ->
ApplicationManager.getApplication().runWriteAction(() -> {
System.out.printf("Received text change %s from offset %d to %d!\n",
event.getContent(), event.getStart(), event.getEnd());
document.replaceString( //TODO this doesn't work
(int) event.getStart(),
(int) event.getEnd(),
event.getContent()
);
}));
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}
});
System.out.printf("Created buffer %s!\n", buffer);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
String buffer = Messages.showInputDialog(
"Buffer name:",
"Attach to CodeMP Buffer",
Messages.getQuestionIcon());
try {
this.attach(e, buffer);
} catch(Exception ex) {
Messages.showErrorDialog(String.format(
"Failed to attach to buffer %s: %s!",
buffer, ex.getMessage()), "Attach to CodeMP Buffer");
}
}
}

View file

@ -0,0 +1,37 @@
package com.codemp.intellij.listeners;
import com.codemp.intellij.jni.BufferHandler;
import com.codemp.intellij.jni.CodeMPHandler;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import org.jetbrains.annotations.NotNull;
public class BufferEventListener implements DocumentListener {
private final String bufferName;
public BufferEventListener(String bufferName) {
this.bufferName = bufferName;
}
@Override
public void documentChanged(@NotNull DocumentEvent event) {
try {
int changeOffset = event.getOffset();
CharSequence newFragment = event.getNewFragment();
BufferHandler bufferHandler = CodeMPHandler.getBuffer(this.bufferName);
bufferHandler.send(changeOffset, changeOffset + event.getOldFragment().length(),
newFragment.toString());
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
/*
ABCD
ABAACD
getOffset() -> B + 1
*/

View file

@ -2,7 +2,6 @@ package com.codemp.intellij.listeners;
import com.codemp.intellij.jni.CodeMPHandler; import com.codemp.intellij.jni.CodeMPHandler;
import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.EditorFactory;
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;
import com.intellij.openapi.editor.event.CaretListener; import com.intellij.openapi.editor.event.CaretListener;
@ -20,7 +19,8 @@ public class CursorEventListener implements CaretListener {
try { try {
VisualPosition startPos = caret.getSelectionStartPosition(); VisualPosition startPos = caret.getSelectionStartPosition();
VisualPosition endPos = caret.getSelectionEndPosition(); VisualPosition endPos = caret.getSelectionEndPosition();
System.out.printf("start %dx %dy end %dx %dy", startPos.line, startPos.column, endPos.line, endPos.column); System.out.printf("start %dx %dy end %dx %dy",
startPos.line, startPos.column, endPos.line, endPos.column);
CodeMPHandler.getCursor().send( CodeMPHandler.getCursor().send(
"", startPos.line, startPos.column, endPos.line, endPos.column "", startPos.line, startPos.column, endPos.line, endPos.column
); );

View file

@ -25,17 +25,14 @@
<actions> <actions>
<group id="codemp" text="CodeMP" popup="true"> <group id="codemp" text="CodeMP" popup="true">
<add-to-group group-id="ToolsMenu" anchor="first"/> <add-to-group group-id="ToolsMenu" anchor="first"/>
<action id="codemp.connect" class="com.codemp.intellij.actions.ConnectAction" text="Connect..."> <action id="codemp.fast-forward" class="com.codemp.intellij.actions.FastForwardAction" text="Pls hurry"/>
<add-to-group group-id="ToolsMenu" anchor="first"/> <action id="codemp.connect" class="com.codemp.intellij.actions.ConnectAction" text="Connect..."/>
</action> <action id="codemp.join" class="com.codemp.intellij.actions.JoinAction" text="Join..."/>
<action id="codemp.join" class="com.codemp.intellij.actions.JoinAction" text="Join...">
<add-to-group group-id="ToolsMenu" anchor="first"/>
</action>
<group id="codemp.buffer" text="Buffer" popup="true"> <group id="codemp.buffer" text="Buffer" popup="true">
<action id="codemp.buffer.create" class="com.codemp.intellij.actions.buffer.BufferCreateAction" <action id="codemp.buffer.create" class="com.codemp.intellij.actions.buffer.BufferCreateAction"
text="Create Buffer"> text="Create Buffer"/>
<add-to-group group-id="ToolsMenu" anchor="first"/> <action id="codemp.buffer.attach" class="com.codemp.intellij.actions.buffer.BufferAttachAction"
</action> text="Attach to Buffer"/>
</group> </group>
</group> </group>
</actions> </actions>

View file

@ -93,33 +93,33 @@ impl CursorEventWrapper {
} }
#[generate_interface] #[generate_interface]
fn get_user(&self) -> Result<&str, String> { fn get_user(&self) -> &str {
Ok(&self.user) &self.user
} }
#[generate_interface] #[generate_interface]
fn get_buffer(&self) -> Result<&str, String> { fn get_buffer(&self) -> &str {
Ok(&self.buffer) &self.buffer
} }
#[generate_interface] #[generate_interface]
fn get_start_row(&self) -> Result<i32, String> { fn get_start_row(&self) -> i32 {
Ok(self.start_row) self.start_row
} }
#[generate_interface] #[generate_interface]
fn get_start_col(&self) -> Result<i32, String> { fn get_start_col(&self) -> i32 {
Ok(self.start_col) self.start_col
} }
#[generate_interface] #[generate_interface]
fn get_end_row(&self) -> Result<i32, String> { fn get_end_row(&self) -> i32 {
Ok(self.end_row) self.end_row
} }
#[generate_interface] #[generate_interface]
fn get_end_col(&self) -> Result<i32, String> { fn get_end_col(&self) -> i32 {
Ok(self.end_col) self.end_col
} }
} }
@ -139,8 +139,7 @@ impl CursorHandler {
fn recv(&self) -> Result<CursorEventWrapper, String> { fn recv(&self) -> Result<CursorEventWrapper, String> {
match self.cursor.blocking_recv(CODEMP_INSTANCE.rt()) { match self.cursor.blocking_recv(CODEMP_INSTANCE.rt()) {
Err(err) => Err(ErrorWrapper::from(err).get_error_message()), Err(err) => Err(ErrorWrapper::from(err).get_error_message()),
Ok(event) => { Ok(event) => Ok(CursorEventWrapper {
Ok(CursorEventWrapper {
user: event.user, user: event.user,
buffer: event.position.as_ref().unwrap().buffer.clone(), buffer: event.position.as_ref().unwrap().buffer.clone(),
start_row: event.position.as_ref().unwrap().start().row, start_row: event.position.as_ref().unwrap().start().row,
@ -150,7 +149,6 @@ impl CursorHandler {
}) })
} }
} }
}
#[generate_interface] #[generate_interface]
fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> Result<(), String> { fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> Result<(), String> {
@ -162,6 +160,35 @@ impl CursorHandler {
} }
} }
#[generate_interface_doc]
struct TextChangeWrapper {
start: usize,
end: usize, //not inclusive
content: String
}
impl TextChangeWrapper {
#[generate_interface(constructor)]
fn new() -> TextChangeWrapper {
panic!("Default constructor for TextChangeWrapper should never be called!")
}
#[generate_interface]
fn get_start(&self) -> usize {
self.start
}
#[generate_interface]
fn get_end(&self) -> usize {
self.end
}
#[generate_interface]
fn get_content(&self) -> &str {
&self.content
}
}
#[generate_interface_doc] #[generate_interface_doc]
struct BufferHandler { struct BufferHandler {
#[allow(unused)] #[allow(unused)]
@ -173,4 +200,25 @@ impl BufferHandler {
fn new() -> BufferHandler { fn new() -> BufferHandler {
panic!("Default constructor for BufferHandler should never be called!") panic!("Default constructor for BufferHandler should never be called!")
} }
#[generate_interface]
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) => 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> {
match self.buffer.delta(start_offset, &content, end_offset) {
None => Err("Cannot send a no-op".to_string()),
Some(op_seq) => self.buffer.send(op_seq)
.map_err(|err| ErrorWrapper::from(err).get_error_message())
}
}
} }