From 5b7d3c95dd2a4a53b2ca783f0aca135e5b37a0c5 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Thu, 16 Nov 2023 15:54:30 +0100 Subject: [PATCH] feat: buffer send/recv --- .../intellij/actions/ConnectAction.java | 22 ++-- .../intellij/actions/FastForwardAction.java | 22 ++++ .../codemp/intellij/actions/JoinAction.java | 101 +++++++++--------- .../actions/buffer/BufferAttachAction.java | 77 +++++++++++++ .../listeners/BufferEventListener.java | 37 +++++++ .../listeners/CursorEventListener.java | 4 +- src/main/resources/META-INF/plugin.xml | 15 ++- src/main/rust/lib.rs | 92 ++++++++++++---- 8 files changed, 278 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/codemp/intellij/actions/FastForwardAction.java create mode 100644 src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java create mode 100644 src/main/java/com/codemp/intellij/listeners/BufferEventListener.java diff --git a/src/main/java/com/codemp/intellij/actions/ConnectAction.java b/src/main/java/com/codemp/intellij/actions/ConnectAction.java index 6b74508..5c5994b 100644 --- a/src/main/java/com/codemp/intellij/actions/ConnectAction.java +++ b/src/main/java/com/codemp/intellij/actions/ConnectAction.java @@ -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"); } diff --git a/src/main/java/com/codemp/intellij/actions/FastForwardAction.java b/src/main/java/com/codemp/intellij/actions/FastForwardAction.java new file mode 100644 index 0000000..59e1451 --- /dev/null +++ b/src/main/java/com/codemp/intellij/actions/FastForwardAction.java @@ -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); + } + } +} diff --git a/src/main/java/com/codemp/intellij/actions/JoinAction.java b/src/main/java/com/codemp/intellij/actions/JoinAction.java index d7d3172..ed65f30 100644 --- a/src/main/java/com/codemp/intellij/actions/JoinAction.java +++ b/src/main/java/com/codemp/intellij/actions/JoinAction.java @@ -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!", diff --git a/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java b/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java new file mode 100644 index 0000000..6d4213c --- /dev/null +++ b/src/main/java/com/codemp/intellij/actions/buffer/BufferAttachAction.java @@ -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"); + } + } +} diff --git a/src/main/java/com/codemp/intellij/listeners/BufferEventListener.java b/src/main/java/com/codemp/intellij/listeners/BufferEventListener.java new file mode 100644 index 0000000..e07320a --- /dev/null +++ b/src/main/java/com/codemp/intellij/listeners/BufferEventListener.java @@ -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 + + + */ diff --git a/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java b/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java index 8a6260f..c2cba56 100644 --- a/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java +++ b/src/main/java/com/codemp/intellij/listeners/CursorEventListener.java @@ -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 ); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d3896b0..55553dd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -25,17 +25,14 @@ - - - - - - + + + - - + text="Create Buffer"/> + diff --git a/src/main/rust/lib.rs b/src/main/rust/lib.rs index 95194ab..9a60239 100644 --- a/src/main/rust/lib.rs +++ b/src/main/rust/lib.rs @@ -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 { - Ok(self.start_row) + fn get_start_row(&self) -> i32 { + self.start_row } #[generate_interface] - fn get_start_col(&self) -> Result { - Ok(self.start_col) + fn get_start_col(&self) -> i32 { + self.start_col } #[generate_interface] - fn get_end_row(&self) -> Result { - Ok(self.end_row) + fn get_end_row(&self) -> i32 { + self.end_row } #[generate_interface] - fn get_end_col(&self) -> Result { - Ok(self.end_col) + fn get_end_col(&self) -> i32 { + self.end_col } } @@ -139,16 +139,14 @@ impl CursorHandler { fn recv(&self) -> Result { 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 { + 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()) + } + } }