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;
public class ConnectAction extends AnAction {
static {
public ConnectAction() {
super();
/*try {
NativeUtils.loadLibraryFromJar("/resources/libHelloJNI.so");
} catch(IOException e) {
throw new RuntimeException(e);
}*/
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("/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");
}
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
public void actionPerformed(@NotNull AnActionEvent e) {
String url = Messages.showInputDialog("URL to CodeMP instance:", "CodeMP Connect", Messages.getQuestionIcon());
if(url == null || url.isBlank())
url = "http://alemi.dev:50051";
String url = Messages.showInputDialog("URL to CodeMP instance:", "CodeMP Connect",
Messages.getQuestionIcon());
try {
CodeMPHandler.connect(url);
//Messages.showInfoMessage(String.format("Connected to %s!", url), "CodeMP");
System.out.printf("Connected to %s!\n", url);
this.connect(url);
} catch(Exception ex) {
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,64 +36,65 @@ public class JoinAction extends AnAction {
null, JBColor.BLUE, null, null, Font.PLAIN
);
public void join(AnActionEvent e, String session) throws Exception {
CursorHandler cursorHandler = CodeMPHandler.join(session);
EditorFactory.getInstance()
.getEventMulticaster()
.addCaretListener(new CursorEventListener());
//Messages.showInfoMessage(String.format("Joined %s!", session), "CodeMP");
System.out.printf(String.format("Joined %s!\n", session));
Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(e.getProject()))
.getSelectedTextEditor();
assert editor != null;
Document document = editor.getDocument();
ProgressManager.getInstance().run(new Task.Backgroundable(e.getProject(), "Awaiting CodeMP cursor events") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
while(true) {
try {
CursorEventWrapper event = cursorHandler.recv();
ApplicationManager.getApplication().invokeLater(() -> {
try {
RangeHighlighter h = highlighterMap.get(event.getUser());
if(h != null)
h.dispose();
System.out.printf(
"Cursor moved by user %s! Start pos: x%d y%d; end pos: x%d y%d with buffer %s!\n",
event.getUser(),
event.getStartCol(), event.getStartCol(),
event.getEndRow(), event.getEndCol(),
event.getBuffer());
highlighterMap.put(event.getUser(), editor
.getMarkupModel()
.addRangeHighlighter(TextAttributesKey.createTextAttributesKey("codemp", HIGHLIGHTED),
document.getLineStartOffset(event.getStartRow()) + event.getStartCol(),
document.getLineStartOffset(event.getEndRow()) + event.getEndCol(),
HighlighterLayer.SELECTION,
HighlighterTargetArea.EXACT_RANGE
));
} catch(Exception ex) {
throw new RuntimeException();
}
});
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}
});
}
@Override
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);
EditorFactory.getInstance()
.getEventMulticaster()
.addCaretListener(new CursorEventListener());
//Messages.showInfoMessage(String.format("Joined %s!", session), "CodeMP");
System.out.printf(String.format("Joined %s!", session));
Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(e.getProject()))
.getSelectedTextEditor();
assert editor != null;
Document document = editor.getDocument();
ProgressManager.getInstance().run(new Task.Backgroundable(e.getProject(), "Awaiting CodeMP cursor events") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
while(true) {
try {
CursorEventWrapper event = cursorHandler.recv();
ApplicationManager.getApplication().invokeLater(() -> {
try {
RangeHighlighter h = highlighterMap.get(event.getUser());
if(h != null)
h.dispose();
System.out.printf(
"Cursor moved by user %s! Start pos: x%d y%d; end pos: x%d y%d with buffer %s!\n",
event.getUser(),
event.getStartCol(), event.getStartCol(),
event.getEndRow(), event.getEndCol(),
event.getBuffer());
highlighterMap.put(event.getUser(), editor
.getMarkupModel()
.addRangeHighlighter(TextAttributesKey.createTextAttributesKey("codemp", HIGHLIGHTED),
document.getLineStartOffset(event.getStartRow()) + event.getStartCol(),
document.getLineStartOffset(event.getEndRow()) + event.getEndCol(),
HighlighterLayer.SELECTION,
HighlighterTargetArea.EXACT_RANGE
));
} catch(Exception ex) {
throw new RuntimeException();
}
});
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}
});
this.join(e, session);
} catch(Exception ex) {
Messages.showErrorDialog(String.format(
"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.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.CaretListener;
@ -20,7 +19,8 @@ public class CursorEventListener implements CaretListener {
try {
VisualPosition startPos = caret.getSelectionStartPosition();
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(
"", startPos.line, startPos.column, endPos.line, endPos.column
);

View file

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

View file

@ -93,33 +93,33 @@ impl CursorEventWrapper {
}
#[generate_interface]
fn get_user(&self) -> Result<&str, String> {
Ok(&self.user)
fn get_user(&self) -> &str {
&self.user
}
#[generate_interface]
fn get_buffer(&self) -> Result<&str, String> {
Ok(&self.buffer)
fn get_buffer(&self) -> &str {
&self.buffer
}
#[generate_interface]
fn get_start_row(&self) -> Result<i32, String> {
Ok(self.start_row)
fn get_start_row(&self) -> i32 {
self.start_row
}
#[generate_interface]
fn get_start_col(&self) -> Result<i32, String> {
Ok(self.start_col)
fn get_start_col(&self) -> i32 {
self.start_col
}
#[generate_interface]
fn get_end_row(&self) -> Result<i32, String> {
Ok(self.end_row)
fn get_end_row(&self) -> i32 {
self.end_row
}
#[generate_interface]
fn get_end_col(&self) -> Result<i32, String> {
Ok(self.end_col)
fn get_end_col(&self) -> i32 {
self.end_col
}
}
@ -139,16 +139,14 @@ impl CursorHandler {
fn recv(&self) -> Result<CursorEventWrapper, String> {
match self.cursor.blocking_recv(CODEMP_INSTANCE.rt()) {
Err(err) => Err(ErrorWrapper::from(err).get_error_message()),
Ok(event) => {
Ok(CursorEventWrapper {
user: event.user,
buffer: event.position.as_ref().unwrap().buffer.clone(),
start_row: event.position.as_ref().unwrap().start().row,
start_col: event.position.as_ref().unwrap().start().col,
end_row: event.position.as_ref().unwrap().end().row,
end_col: event.position.as_ref().unwrap().end().col
})
}
Ok(event) => Ok(CursorEventWrapper {
user: event.user,
buffer: event.position.as_ref().unwrap().buffer.clone(),
start_row: event.position.as_ref().unwrap().start().row,
start_col: event.position.as_ref().unwrap().start().col,
end_row: event.position.as_ref().unwrap().end().row,
end_col: event.position.as_ref().unwrap().end().col
})
}
}
@ -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]
struct BufferHandler {
#[allow(unused)]
@ -173,4 +200,25 @@ impl BufferHandler {
fn new() -> BufferHandler {
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())
}
}
}