chore: updated to new glue (kind of)

This commit is contained in:
zaaarf 2024-08-15 23:31:12 +02:00
parent 0719ac84f4
commit ddf0e4eb71
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
35 changed files with 279 additions and 756 deletions

View file

@ -1,23 +0,0 @@
[package]
name = "codemp-intellij"
version = "0.1.0"
edition = "2021"
[dependencies]
codemp = { path = "../../lib", features = ["global", "sync", "transport"] }
#codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", branch = "workspace", features = ["global", "sync"] }
jni = { version = "0.21.1", features = ["invocation"] }
jni-sys = "0.3.0"
lazy_static = "1.4.0"
log = "0.4.20"
rifgen = { git = "https://github.com/Kofituo/rifgen.git", rev = "d27d9785b2febcf5527f1deb6a846be5d583f7d7"}
tokio = "1.35.1"
uuid = { version = "1.4.1", features = ["v4"] }
[build-dependencies]
flapigen = "0.6.0"
rifgen = { git = "https://github.com/Kofituo/rifgen.git", rev = "d27d9785b2febcf5527f1deb6a846be5d583f7d7"}
[lib]
crate_type = ["cdylib"]
path = "src/main/rust/lib.rs"

View file

@ -2,10 +2,11 @@ plugins {
id 'java'
id 'org.jetbrains.intellij' version '1.16.0'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.palantir.git-version' version '3.1.0'
}
group = 'com.codemp'
version = '0.1.0'
group = 'mp.code'
//version = versionDetails().lastTag
java {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
@ -17,9 +18,9 @@ repositories {
}
dependencies {
implementation 'com.github.adamheinrich:native-utils:master-SNAPSHOT'
implementation 'org.slf4j:slf4j-api:2.0.9'
implementation 'ch.qos.logback:logback-classic:1.4.12'
implementation files('../../lib/dist/java/build/libs/codemp-0.6.2.jar')
}
intellij {
@ -30,24 +31,14 @@ intellij {
shadowJar {
archiveClassifier.set('')
dependencies {
include(dependency('com.github.adamheinrich:native-utils:master-SNAPSHOT'))
}
}
def cargoDir = projectDir.toPath().resolve('target').resolve('release').toFile()
processResources {
from(cargoDir) {
include('*.dll')
include('*.so')
into('natives/')
include(dependency(files('../../lib/java/build/libs/codemp-6645c46.jar')))
}
}
tasks {
patchPluginXml {
sinceBuild.set('222')
untilBuild.set('232.*')
untilBuild.set('233.*')
}
signPlugin {
@ -61,27 +52,4 @@ tasks {
}
}
//run cargo build
tasks.register('cargoBuild', Exec) {
workingDir '.'
commandLine 'cargo', 'build', '--release'
}
//must act before all other tasks who mess with resources to reliably get the binary in
patchPluginXml.dependsOn cargoBuild
//delete old jni generated files
tasks.register('deleteGeneratedNativeInterface', Delete) {
delete 'src/main/java/com/codemp/intellij/jni'
}
//delete cargo build files
tasks.register('cargoClean', Exec) {
workingDir '.'
commandLine 'cargo', 'clean'
dependsOn deleteGeneratedNativeInterface
}
clean.dependsOn cargoClean
instrumentedJar.dependsOn shadowJar //TODO: instrumentedJar should use fatjar as input

View file

@ -1,45 +0,0 @@
use flapigen::{JavaConfig, LanguageConfig};
use std::{env, fs, path::Path};
use rifgen::{Generator as RifgenGenerator, TypeCases, Language};
use flapigen::Generator as FlapigenGenerator;
fn main() {
let out_dir_var = env::var("OUT_DIR")
.expect("no OUT_DIR, but cargo should provide it");
let out_dir = Path::new(&out_dir_var);
let generated_glue_file = out_dir.join("generated_glue.in");
let src_dir = Path::new("src")
.join("main")
.join("rust");
let glue_file = src_dir.join("glue.in");
RifgenGenerator::new(TypeCases::CamelCase,Language::Java, vec!(src_dir))
.generate_interface(&generated_glue_file);
let jni_path = Path::new("src")
.join("main")
.join("java")
.join("com")
.join("codemp")
.join("intellij")
.join("jni");
//create folder if it doesn't exist
fs::create_dir_all(&jni_path)
.expect("An error occurred while creating the JNI folder!");
let java_gen = FlapigenGenerator::new(LanguageConfig::JavaConfig(
JavaConfig::new(
jni_path,
"com.codemp.intellij.jni".into()
))).rustfmt_bindings(true);
java_gen.expand_many(
"codemp-intellij",
&[&generated_glue_file, &glue_file],
out_dir.join("glue.rs"),
);
println!("cargo:rerun-if-changed={}", generated_glue_file.display());
}

View file

@ -1,50 +0,0 @@
package com.codemp.intellij;
import com.codemp.intellij.exceptions.ide.NotConnectedException;
import com.codemp.intellij.jni.ClientHandler;
import com.codemp.intellij.workspace.Workspace;
import com.intellij.openapi.util.SystemInfo;
import cz.adamh.utils.NativeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CodeMP {
public static Logger LOGGER = LoggerFactory.getLogger(CodeMP.class);
public static final Map<String, Workspace> ACTIVE_WORKSPACES = new ConcurrentHashMap<>();
private static ClientHandler CLIENT = null;
public static void connect(String url) {
CodeMP.loadLibrary(); //will only load it the first time
CLIENT = new ClientHandler(url);
}
public static void disconnect() {
CLIENT = null;
}
public static ClientHandler getClient(String reason) throws NotConnectedException {
if(CLIENT == null) throw new NotConnectedException(reason);
return CLIENT;
}
private static boolean loadedLibrary = false;
public static void loadLibrary() {
if(!loadedLibrary) {
try {
if(SystemInfo.isWindows)
NativeUtils.loadLibraryFromJar("/natives/codemp_intellij.dll");
else NativeUtils.loadLibraryFromJar("/natives/libcodemp_intellij.so");
} catch(IOException e) {
throw new RuntimeException(e);
} finally {
LOGGER.info("Loaded CodeMP library!");
loadedLibrary = false;
}
}
}
}

View file

@ -1,30 +0,0 @@
package com.codemp.intellij.actions;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.util.ActionUtil;
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 ConnectAction extends AnAction {
public static void connect(AnActionEvent e, String url, boolean silent) {
CodeMP.connect(url);
if(!silent) ActionUtil.notify(e,
"Success", String.format("Connected to %s!", url));
CodeMP.LOGGER.debug("Connected to {}!", url);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
String url = Messages.showInputDialog("URL to CodeMP instance:", "CodeMP Connect",
Messages.getQuestionIcon());
try {
connect(e, url, false);
} catch(Exception ex) {
ActionUtil.notifyError(e, String.format(
"Failed to connect to %s!",
url), ex);
}
}
}

View file

@ -1,19 +0,0 @@
package com.codemp.intellij.actions;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.actions.workspace.WorkspaceJoinAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
/**
* Used exclusively to streamline debugging.
*/
public class FastForwardAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
ConnectAction.connect(e, "http://alemi.dev:50052", true);
WorkspaceJoinAction.join(e, "default", true);
CodeMP.LOGGER.debug("Completed quick startup for testing!");
}
}

View file

@ -1,7 +0,0 @@
package com.codemp.intellij.exceptions;
public class CodeMPException extends RuntimeException {
public CodeMPException(String s) {
super(s);
}
}

View file

@ -1,14 +0,0 @@
package com.codemp.intellij.exceptions.ide;
import com.codemp.intellij.exceptions.CodeMPException;
import com.intellij.openapi.actionSystem.AnActionEvent;
/**
* Fired when trying to use {@link com.intellij.openapi.actionSystem.AnActionEvent}'s context
* from a state where that use is not supported.
*/
public class BadActionEventStateException extends CodeMPException {
public BadActionEventStateException(String s) {
super(s);
}
}

View file

@ -1,9 +0,0 @@
package com.codemp.intellij.exceptions.lib;
import com.codemp.intellij.exceptions.CodeMPException;
public class ChannelException extends CodeMPException {
public ChannelException(String input) {
super(input);
}
}

View file

@ -1,9 +0,0 @@
package com.codemp.intellij.exceptions.lib;
import com.codemp.intellij.exceptions.CodeMPException;
public class DeadlockedException extends CodeMPException {
public DeadlockedException(String s) {
super(s);
}
}

View file

@ -1,9 +0,0 @@
package com.codemp.intellij.exceptions.lib;
import com.codemp.intellij.exceptions.CodeMPException;
public class InvalidStateException extends CodeMPException {
public InvalidStateException(String message) {
super(message);
}
}

View file

@ -1,9 +0,0 @@
package com.codemp.intellij.exceptions.lib;
import com.codemp.intellij.exceptions.CodeMPException;
public class TransportException extends CodeMPException {
public TransportException(String message) {
super(message);
}
}

View file

@ -0,0 +1,30 @@
package mp.code.intellij;
import mp.code.intellij.exceptions.ide.NotConnectedException;
import mp.code.intellij.workspace.IJWorkspace;
import mp.code.Client;
import mp.code.exceptions.CodeMPException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CodeMP {
public static Logger LOGGER = LoggerFactory.getLogger(CodeMP.class);
public static final Map<String, IJWorkspace> ACTIVE_WORKSPACES = new ConcurrentHashMap<>();
private static Client CLIENT = null;
public static void connect(String url, String username, String password) throws CodeMPException {
CLIENT = Client.connect(url, username, password);
}
public static void disconnect() {
CLIENT = null;
}
public static Client getClient(String reason) throws NotConnectedException {
if(CLIENT == null) throw new NotConnectedException(reason);
return CLIENT;
}
}

View file

@ -0,0 +1,35 @@
package mp.code.intellij.actions;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.ActionUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
public class ConnectAction extends AnAction {
public static void connect(AnActionEvent e, String url, String username, String password, boolean silent) throws CodeMPException {
System.out.printf("%s %s %s", url, username, password);
CodeMP.connect(url, username, password);
if(!silent) ActionUtil.notify(e,
"Success", String.format("Connected to %s!", url));
CodeMP.LOGGER.debug("Connected to {}!", url);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
/*
LoginDialog dialog = new LoginDialog(e.getProject(), "Please input your login credentials!", "Connect to CodeMP server");
if(dialog.showAndGet()) {
try {
connect(e, dialog.urlField.getText(), dialog.usernameField.getText(), dialog.passwordField.getText(), false);
} catch(Exception exception) {
ActionUtil.notifyError(
e,
String.format("Failed to connect to %s!", dialog.urlField.getText()),
exception
);
}
}*/
}
}

View file

@ -1,7 +1,7 @@
package com.codemp.intellij.actions;
package mp.code.intellij.actions;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.util.ActionUtil;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.ActionUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;

View file

@ -0,0 +1,24 @@
package mp.code.intellij.actions;
import mp.code.intellij.CodeMP;
import mp.code.intellij.actions.workspace.WorkspaceJoinAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
/**
* Used exclusively to streamline debugging.
*/
public class FastForwardAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
try {
ConnectAction.connect(e, "http://alemi.dev:50053", "", "", true);
WorkspaceJoinAction.join(e, "glue", true);
CodeMP.LOGGER.debug("Completed quick startup for testing!");
} catch(CodeMPException ex) {
throw new RuntimeException(ex);
}
}
}

View file

@ -1,16 +1,17 @@
package com.codemp.intellij.actions.workspace;
package mp.code.intellij.actions.workspace;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.util.ActionUtil;
import com.codemp.intellij.workspace.Workspace;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.ActionUtil;
import mp.code.intellij.workspace.IJWorkspace;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
public class WorkspaceJoinAction extends AnAction {
public static void join(AnActionEvent e, String workspaceId, boolean silent) {
CodeMP.ACTIVE_WORKSPACES.put(workspaceId, new Workspace(
public static void join(AnActionEvent e, String workspaceId, boolean silent) throws CodeMPException {
CodeMP.ACTIVE_WORKSPACES.put(workspaceId, new IJWorkspace(
workspaceId, CodeMP.getClient("join workspace"),
false, e.getProject() //TODO: implement remote projects
));

View file

@ -1,7 +1,7 @@
package com.codemp.intellij.actions.workspace;
package mp.code.intellij.actions.workspace;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.util.ActionUtil;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.ActionUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
@ -10,8 +10,8 @@ import org.jetbrains.annotations.NotNull;
public class WorkspaceLeaveAction extends AnAction {
public static void leave(AnActionEvent e, String workspaceId, boolean silent) {
CodeMP.getClient("leave workspace")
.leaveWorkspace(workspaceId);
//CodeMP.getClient("leave workspace").leaveWorkspace(workspaceId);
// TODO
Disposer.dispose(CodeMP.ACTIVE_WORKSPACES.remove(workspaceId));
if(!silent) ActionUtil.notify(e, "Success", String.format("Left workspace %s!", workspaceId));

View file

@ -0,0 +1,7 @@
package mp.code.intellij.exceptions;
public class CodeMPIJException extends RuntimeException {
public CodeMPIJException(String s) {
super(s);
}
}

View file

@ -0,0 +1,13 @@
package mp.code.intellij.exceptions.ide;
import mp.code.intellij.exceptions.CodeMPIJException;
/**
* Fired when trying to use {@link com.intellij.openapi.actionSystem.AnActionEvent}'s context
* from a state where that use is not supported.
*/
public class BadActionEventStateException extends CodeMPIJException {
public BadActionEventStateException(String s) {
super(s);
}
}

View file

@ -1,11 +1,11 @@
package com.codemp.intellij.exceptions.ide;
package mp.code.intellij.exceptions.ide;
import com.codemp.intellij.exceptions.CodeMPException;
import mp.code.intellij.exceptions.CodeMPIJException;
/**
* Thrown upon failure to detach from a buffer.
*/
public class BufferDetachException extends CodeMPException {
public class BufferDetachException extends CodeMPIJException {
public BufferDetachException(String name) {
super(String.format("Could not detach from buffer named \"%s\"!", name));

View file

@ -1,12 +1,12 @@
package com.codemp.intellij.exceptions.ide;
package mp.code.intellij.exceptions.ide;
import com.codemp.intellij.exceptions.CodeMPException;
import mp.code.intellij.exceptions.CodeMPIJException;
/**
* Fired when trying to access the CodeMP client without first connecting
* to a server.
*/
public class NotConnectedException extends CodeMPException {
public class NotConnectedException extends CodeMPIJException {
public NotConnectedException(String service) {
super(String.format("Failed to %s, you are not connected to a server!", service));

View file

@ -1,23 +1,26 @@
package com.codemp.intellij.listeners;
package mp.code.intellij.listeners;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.exceptions.CodeMPException;
import com.codemp.intellij.jni.BufferHandler;
import mp.code.intellij.CodeMP;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import mp.code.BufferController;
import mp.code.data.TextChange;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class BufferEventListener implements DocumentListener {
private final BufferHandler bufferHandler;
private final BufferController controller;
public BufferEventListener(BufferHandler bufferHandler) {
this.bufferHandler = bufferHandler;
public BufferEventListener(BufferController controller) {
this.controller = controller;
}
@Override
public void documentChanged(@NotNull DocumentEvent event) throws CodeMPException {
public void documentChanged(@NotNull DocumentEvent event) {
CodeMP.LOGGER.debug("Changed {} to {} at offset {}",
event.getOldFragment(), event.getNewFragment(), event.getOffset());
@ -29,8 +32,15 @@ public class BufferEventListener implements DocumentListener {
//TODO move actions break
int changeOffset = event.getOffset();
CharSequence newFragment = event.getNewFragment();
this.bufferHandler.send(changeOffset,
try {
this.controller.send(new TextChange(
changeOffset,
changeOffset + event.getOldFragment().length(),
newFragment.toString());
newFragment.toString(),
0L
));
} catch(CodeMPException ignored) {
// TODO actually give a shit
}
}
}

View file

@ -1,21 +1,23 @@
package com.codemp.intellij.listeners;
package mp.code.intellij.listeners;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.jni.CursorHandler;
import com.codemp.intellij.util.FileUtil;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.FileUtil;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.CaretListener;
import mp.code.CursorController;
import mp.code.data.Cursor;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
public class CursorEventListener implements CaretListener {
private final CursorHandler handler;
private final CursorController controller;
public CursorEventListener(CursorHandler handler) {
this.handler = handler;
public CursorEventListener(CursorController controller) {
this.controller = controller;
}
@Override
@ -27,13 +29,21 @@ public class CursorEventListener implements CaretListener {
VisualPosition startPos = caret.getSelectionStartPosition();
VisualPosition endPos = caret.getSelectionEndPosition();
CodeMP.LOGGER.debug("Caret moved from {}x {}y to {}x {}y",
startPos.line, startPos.column, endPos.line, endPos.column);
startPos.line, startPos.column, endPos.line, endPos.column
);
Editor editor = event.getEditor();
this.handler.send(
try {
this.controller.send(new Cursor(
startPos.line,
startPos.column,
endPos.line,
endPos.column,
FileUtil.getRelativePath(editor.getProject(), editor.getVirtualFile()),
startPos.line, startPos.column,
endPos.line, endPos.column
);
null
));
} catch(CodeMPException e) {
// TODO zzzzz
}
}
}

View file

@ -1,20 +1,20 @@
package com.codemp.intellij.listeners;
package mp.code.intellij.listeners;
import com.codemp.intellij.jni.WorkspaceHandler;
import com.codemp.intellij.task.BufferEventAwaiterTask;
import com.codemp.intellij.util.FileUtil;
import mp.code.intellij.task.BufferEventAwaiterTask;
import mp.code.intellij.util.FileUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import mp.code.Workspace;
import org.jetbrains.annotations.NotNull;
public class WorkspaceFileClosedListener implements FileEditorManagerListener.Before {
private final WorkspaceHandler handler;
private final Workspace handler;
private final BufferEventAwaiterTask task;
public WorkspaceFileClosedListener(WorkspaceHandler handler, BufferEventAwaiterTask task) {
public WorkspaceFileClosedListener(Workspace handler, BufferEventAwaiterTask task) {
this.handler = handler;
this.task = task;
}
@ -27,7 +27,7 @@ public class WorkspaceFileClosedListener implements FileEditorManagerListener.Be
Disposable disp = this.task.activeBuffers.remove(path);
if(disp == null) return;
this.handler.detachFromBuffer(path);
// TODO : this.handler.detachFromBuffer(path);
Disposer.dispose(disp);
}
}

View file

@ -1,10 +1,7 @@
package com.codemp.intellij.listeners;
package mp.code.intellij.listeners;
import com.codemp.intellij.exceptions.lib.TransportException;
import com.codemp.intellij.jni.BufferHandler;
import com.codemp.intellij.jni.WorkspaceHandler;
import com.codemp.intellij.task.BufferEventAwaiterTask;
import com.codemp.intellij.util.FileUtil;
import mp.code.intellij.task.BufferEventAwaiterTask;
import mp.code.intellij.util.FileUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileOpenedSyncListener;
@ -12,15 +9,18 @@ import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import mp.code.BufferController;
import mp.code.Workspace;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class WorkspaceFileOpenedListener implements FileOpenedSyncListener {
private final WorkspaceHandler handler;
private final Workspace handler;
private final BufferEventAwaiterTask task;
public WorkspaceFileOpenedListener(WorkspaceHandler handler, BufferEventAwaiterTask task) {
public WorkspaceFileOpenedListener(Workspace handler, BufferEventAwaiterTask task) {
this.handler = handler;
this.task = task;
}
@ -39,9 +39,9 @@ public class WorkspaceFileOpenedListener implements FileOpenedSyncListener {
String path = FileUtil.getRelativePath(editor.getProject(), file);
if(path == null) return;
BufferHandler bufferHandler = this.getBufferForPath(path);
BufferController bufferController = this.getBufferForPath(path);
Disposable disp = Disposer.newDisposable(String.format("codemp-buffer-%s", path));
editor.getDocument().addDocumentListener(new BufferEventListener(bufferHandler), disp);
editor.getDocument().addDocumentListener(new BufferEventListener(bufferController), disp);
editor.getDocument().setText(""); //empty it so we can start receiving
this.task.activeBuffers.put(path, disp);
@ -51,13 +51,17 @@ public class WorkspaceFileOpenedListener implements FileOpenedSyncListener {
/**
* Attach to a buffer or, if it does not exist, implicitly create it.
* @param path the buffer's name (which is the path relative to project root)
* @return the {@link BufferHandler} for it
* @return the {@link BufferController} for it
*/
private BufferHandler getBufferForPath(String path) {
private BufferController getBufferForPath(String path) {
try {
return this.handler.attachToBuffer(path);
} catch (TransportException ignored) {
} catch (CodeMPException ignored) {
try {
return this.handler.createBuffer(path);
} catch(CodeMPException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -1,12 +1,7 @@
package com.codemp.intellij.task;
package mp.code.intellij.task;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.exceptions.lib.DeadlockedException;
import com.codemp.intellij.jni.BufferHandler;
import com.codemp.intellij.jni.StringVec;
import com.codemp.intellij.jni.TextChangeWrapper;
import com.codemp.intellij.jni.WorkspaceHandler;
import com.codemp.intellij.util.FileUtil;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.FileUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
@ -14,6 +9,11 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import mp.code.BufferController;
import mp.code.Workspace;
import mp.code.data.TextChange;
import mp.code.exceptions.CodeMPException;
import mp.code.exceptions.DeadlockedException;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -24,8 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class BufferEventAwaiterTask extends Task.Backgroundable implements Disposable {
public final Map<String, Disposable> activeBuffers;
private final WorkspaceHandler handler;
public BufferEventAwaiterTask(@NotNull Project project, @NotNull WorkspaceHandler handler) {
private final Workspace handler;
public BufferEventAwaiterTask(@NotNull Project project, @NotNull Workspace handler) {
super(project, "Awaiting CodeMP buffer events", false);
this.activeBuffers = new ConcurrentHashMap<>();
this.handler = handler;
@ -35,29 +35,33 @@ public class BufferEventAwaiterTask extends Task.Backgroundable implements Dispo
@SuppressWarnings("InfiniteLoopStatement")
public void run(@NotNull ProgressIndicator indicator) {
while(true) {
StringVec buffers = new StringVec(); //jni moment
this.activeBuffers.keySet().forEach(buffers::push);
Optional<BufferHandler> bufferOptional = this.handler.selectBuffer(buffers, 100L);
Optional<BufferController> bufferOptional;
try {
bufferOptional = this.handler.selectBuffer(100L);
} catch(CodeMPException e) {
bufferOptional = Optional.empty(); // TODO error handling
}
if(bufferOptional.isEmpty())
continue;
BufferHandler buffer = bufferOptional.get();
BufferController buffer = bufferOptional.get();
List<TextChangeWrapper> changeList = new ArrayList<>();
List<TextChange> changeList = new ArrayList<>();
while(true) {
Optional<TextChangeWrapper> changeOptional;
Optional<TextChange> changeOptional;
try {
changeOptional = buffer.tryRecv();
} catch(DeadlockedException e) {
CodeMP.LOGGER.error(e.getMessage());
continue;
} catch(CodeMPException e) {
throw new RuntimeException(e);
}
if(changeOptional.isEmpty())
break;
TextChangeWrapper change = changeOptional.get();
TextChange change = changeOptional.get();
CodeMP.LOGGER.debug("Received text change {} from offset {} to {}!",
change.getContent(), change.getStart(), change.getEnd());
change.content, change.start, change.end);
changeList.add(change);
}
@ -68,7 +72,7 @@ public class BufferEventAwaiterTask extends Task.Backgroundable implements Dispo
this.myProject,
() -> changeList.forEach((change) ->
bufferEditor.getDocument().replaceString(
(int) change.getStart(), (int) change.getEnd(), change.getContent())
(int) change.start, (int) change.end, change.content)
),
"CodeMPBufferReceive",
"codemp-buffer-receive", //TODO: mark this with the name

View file

@ -1,10 +1,8 @@
package com.codemp.intellij.task;
package mp.code.intellij.task;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.jni.CursorEventWrapper;
import com.codemp.intellij.jni.CursorHandler;
import com.codemp.intellij.util.ColorUtil;
import com.codemp.intellij.util.FileUtil;
import mp.code.intellij.CodeMP;
import mp.code.intellij.util.ColorUtil;
import mp.code.intellij.util.FileUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
@ -16,6 +14,9 @@ import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import mp.code.CursorController;
import mp.code.data.Cursor;
import mp.code.exceptions.CodeMPException;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
@ -25,10 +26,10 @@ import java.util.concurrent.ConcurrentHashMap;
//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
public class CursorEventAwaiterTask extends Task.Backgroundable implements Disposable {
private final CursorHandler handler;
private final CursorController handler;
private final Map<String, RangeHighlighter> highlighterMap = new ConcurrentHashMap<>();
public CursorEventAwaiterTask(@NotNull Project project, @NotNull CursorHandler handler) {
public CursorEventAwaiterTask(@NotNull Project project, @NotNull CursorController handler) {
super(project, "Awaiting CodeMP cursor events", false);
this.handler = handler;
}
@ -37,23 +38,29 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo
@SuppressWarnings("InfiniteLoopStatement")
public void run(@NotNull ProgressIndicator indicator) {
while(true) {
CursorEventWrapper event = this.handler.recv();
Editor editor = FileUtil.getActiveEditorByPath(this.myProject, event.getBuffer());
Cursor event;
try {
event = this.handler.recv();
} catch(CodeMPException ex) {
continue; // TODO proper handling
}
Editor editor = FileUtil.getActiveEditorByPath(this.myProject, event.buffer);
if(editor == null)
continue;
CodeMP.LOGGER.debug(
"Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!",
event.getUser(),
event.getStartCol(), event.getStartCol(),
event.getEndRow(), event.getEndCol(),
event.getBuffer());
event.user,
event.startCol, event.startRow,
event.endCol, event.endRow,
event.buffer
);
try {
int startOffset = editor.getDocument()
.getLineStartOffset(event.getStartRow()) + event.getStartCol();
.getLineStartOffset(event.startRow) + event.startCol;
int endOffset = editor.getDocument()
.getLineStartOffset(event.getEndRow()) + event.getEndCol();
.getLineStartOffset(event.startRow) + event.startCol;
ApplicationManager.getApplication().invokeLater(() -> {
int documentLength = editor.getDocument().getTextLength();
@ -64,7 +71,7 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo
return;
}
RangeHighlighter previous = this.highlighterMap.put(event.getUser(), editor
RangeHighlighter previous = this.highlighterMap.put(event.user, editor
.getMarkupModel()
.addRangeHighlighter(
startOffset,
@ -72,7 +79,7 @@ public class CursorEventAwaiterTask extends Task.Backgroundable implements Dispo
HighlighterLayer.SELECTION,
new TextAttributes(
null,
ColorUtil.hashColor(event.getUser()),
ColorUtil.hashColor(event.user),
null,
null,
Font.PLAIN

View file

@ -1,7 +1,7 @@
package com.codemp.intellij.util;
package mp.code.intellij.util;
import com.codemp.intellij.CodeMP;
import com.codemp.intellij.exceptions.ide.BadActionEventStateException;
import mp.code.intellij.CodeMP;
import mp.code.intellij.exceptions.ide.BadActionEventStateException;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;

View file

@ -1,4 +1,4 @@
package com.codemp.intellij.util;
package mp.code.intellij.util;
import com.intellij.ui.JBColor;

View file

@ -1,4 +1,4 @@
package com.codemp.intellij.util;
package mp.code.intellij.util;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;

View file

@ -1,10 +1,8 @@
package com.codemp.intellij.workspace;
package mp.code.intellij.workspace;
import com.codemp.intellij.jni.ClientHandler;
import com.codemp.intellij.jni.WorkspaceHandler;
import com.codemp.intellij.listeners.*;
import com.codemp.intellij.task.BufferEventAwaiterTask;
import com.codemp.intellij.task.CursorEventAwaiterTask;
import mp.code.intellij.listeners.WorkspaceFileOpenedListener;
import mp.code.intellij.task.BufferEventAwaiterTask;
import mp.code.intellij.task.CursorEventAwaiterTask;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
@ -12,12 +10,17 @@ import com.intellij.openapi.fileEditor.FileOpenedSyncListener;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.util.messages.MessageBusConnection;
import mp.code.Client;
import mp.code.Workspace;
import mp.code.exceptions.CodeMPException;
import mp.code.intellij.listeners.CursorEventListener;
import mp.code.intellij.listeners.WorkspaceFileClosedListener;
public class Workspace implements Disposable {
public class IJWorkspace implements Disposable {
public final String id;
public final String url;
public final boolean isRemote;
public final WorkspaceHandler handler;
public final Workspace handler;
public final Project project;
public final BufferEventAwaiterTask bufferTask;
public final CursorEventAwaiterTask cursorTask;
@ -25,11 +28,11 @@ public class Workspace implements Disposable {
/**
* The constructor, that will also take care of creating the tasks and listeners associated with it.
* @param id unique id of the workspace on the server
* @param client the {@link ClientHandler} to use
* @param client the {@link Client} to use
* @param isRemote whether the project is remote
* @param project the {@link Project} to use
*/
public Workspace(String id, ClientHandler client, boolean isRemote, Project project) {
public IJWorkspace(String id, Client client, boolean isRemote, Project project) throws CodeMPException {
this.id = id;
this.url = client.getUrl();
this.isRemote = isRemote;

View file

@ -1,5 +1,5 @@
<idea-plugin>
<id>com.codemp.intellij</id>
<id>mp.code.intellij</id>
<name>CodeMP</name>
<vendor email="me@zaaarf.foo" url="https://zaaarf.foo">CodeMP</vendor>
@ -10,12 +10,12 @@
<actions>
<group id="codemp" text="CodeMP" popup="true">
<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.connect" class="com.codemp.intellij.actions.DisconnectAction" text="Disconnect"/>
<action id="codemp.workspace.join" class="com.codemp.intellij.actions.workspace.WorkspaceJoinAction"
<action id="codemp.fast-forward" class="mp.code.intellij.actions.FastForwardAction" text="Just Hurry"/>
<action id="codemp.connect" class="mp.code.intellij.actions.ConnectAction" text="Connect..."/>
<action id="codemp.disconnect" class="mp.code.intellij.actions.DisconnectAction" text="Disconnect"/>
<action id="codemp.workspace.join" class="mp.code.intellij.actions.workspace.WorkspaceJoinAction"
text="Join Workspace"/>
<action id="codemp.workspace.leave" class="com.codemp.intellij.actions.workspace.WorkspaceLeaveAction"
<action id="codemp.workspace.leave" class="mp.code.intellij.actions.workspace.WorkspaceLeaveAction"
text="Leave Workspace"/>
</group>
</actions>

View file

@ -1,39 +0,0 @@
foreign_typemap!( //thanks @tasn on GitHub for the idea
($p:r_type) <T> CodempResult<T> => swig_i_type!(T) {
$out = match $p {
Ok(x) => {
swig_from_rust_to_i_type!(T, x, ret)
ret
}
Err(err) => {
let (msg, exception_class) = match err {
CodempError::Filler { message } => (
message,
swig_jni_find_class!(CODEMP_EXCEPTION, "com/codemp/intellij/exceptions/CodeMPException")
),
CodempError::Transport { status, message } => (
format!("Status {}: {}", status, message),
swig_jni_find_class!(TRANSPORT_EXCEPTION, "com/codemp/intellij/exceptions/lib/TransportException")
),
CodempError::InvalidState { msg } => (
msg, swig_jni_find_class!(INVALID_STATE_EXCEPTION, "com/codemp/intellij/exceptions/lib/InvalidStateException")
),
CodempError::Deadlocked => (
"WOOT deadlocked (safe to retry)!".to_string(),
swig_jni_find_class!(DEADLOCKED_EXCEPTION, "com/codemp/intellij/exceptions/lib/DeadlockedException")
),
CodempError::Channel { send } => {
let verb = if send { "sending" } else { "reading" };
(
format!("Error while {} message on channel: the channel was closed!", verb),
swig_jni_find_class!(CHANNEL_EXCEPTION, "com/codemp/intellij/exceptions/lib/ChannelException")
)
}
};
jni_throw(env, exception_class, &msg);
return <swig_i_type!(T)>::jni_invalid_value();
}
};
};
($p:f_type, unique_prefix="/*err*/") => "/*err*/swig_f_type!(T)" "swig_foreign_from_i_type!(T, $p)";
);

View file

@ -1,330 +0,0 @@
use std::sync::Arc;
use tokio::sync::RwLock;
use std::time::Duration;
use codemp::prelude::*;
use codemp::tools;
use lazy_static::lazy_static;
use rifgen::rifgen_attr::{generate_access_methods, generate_interface, generate_interface_doc};
use uuid::Uuid;
pub mod glue { //rifgen generated code
include!(concat!(env!("OUT_DIR"), "/glue.rs"));
}
lazy_static! {
/// the tokio runtime, since we can't easily have Java and Rust async work together
static ref RT: tokio::runtime::Runtime = tokio::runtime::Runtime::new()
.expect("could not start tokio runtime");
}
#[generate_interface_doc]
/// the handler class that represent an instance of a CodeMP client
struct ClientHandler {
client: CodempClient,
url: String
}
impl ClientHandler {
#[generate_interface(constructor)]
/// constructor required by flapigen, DO NOT CALL THIS
fn new(address: &str) -> ClientHandler {
ClientHandler {
client: RT.block_on(CodempClient::new(address)).unwrap(),
url: address.to_string()
}
}
#[generate_interface]
/// create a new workspace
fn create_workspace(&mut self, workspace_id: &str) -> CodempResult<WorkspaceHandler> {
RT.block_on(self.client.create_workspace(workspace_id))
.map(|workspace| {
Self::spawn_updater(workspace.clone());
WorkspaceHandler { workspace }
})
}
#[generate_interface]
/// join a workspace by name
fn join_workspace(&mut self, workspace_id: &str) -> CodempResult<WorkspaceHandler> {
RT.block_on(self.client.join_workspace(workspace_id))
.map(|workspace| {
Self::spawn_updater(workspace.clone());
WorkspaceHandler { workspace }
})
}
#[generate_interface]
/// leave a workspace
fn leave_workspace(&mut self, workspace_id: &str) -> CodempResult<()> {
RT.block_on(self.client.leave_workspace(workspace_id))
}
fn spawn_updater(workspace: Arc<RwLock<CodempWorkspace>>) {
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(60)).await;
workspace.write().await.fetch_buffers().await.unwrap();
workspace.write().await.fetch_users().await.unwrap();
}
});
}
#[generate_interface]
/// get the url you are currently connected to
fn get_url(&self) -> String {
self.url.clone()
}
}
#[generate_interface_doc]
/// wraps a [codemp::workspace::Workspace] to be handled by Java
struct WorkspaceHandler {
workspace: Arc<RwLock<CodempWorkspace>>
}
impl WorkspaceHandler {
#[generate_interface(constructor)]
/// constructor required by flapigen, DO NOT CALL THIS
fn new() -> WorkspaceHandler {
unimplemented!()
}
#[generate_interface]
/// create a new buffer in current workspace
fn create_buffer(&mut self, path: &str) -> CodempResult<BufferHandler> {
RT.block_on(RT.block_on(self.workspace.write()).create(path))?;
Ok(self.get_buffer(path).unwrap())
}
#[generate_interface]
/// attach to a buffer and get a [crate::BufferHandler] for it
fn attach_to_buffer(&mut self, path: &str) -> CodempResult<BufferHandler> {
RT.block_on(RT.block_on(self.workspace.write()).attach(path))
.map(|buffer| BufferHandler { buffer })
}
#[generate_interface]
/// updates the local list of the workspace's buffers
fn fetch_buffers(&mut self) -> CodempResult<()> {
RT.block_on(RT.block_on(self.workspace.write()).fetch_buffers())
}
#[generate_interface]
/// updates the local list of the workspace's users
fn fetch_users(&mut self) -> CodempResult<()> {
RT.block_on(RT.block_on(self.workspace.write()).fetch_users())
}
#[generate_interface]
/// gets a list of all users in a buffer
fn list_buffer_users(&mut self, path: &str) -> CodempResult<StringVec> {
let mut res = StringVec::new();
RT.block_on(RT.block_on(self.workspace.write())
.list_buffer_users(path))?
.iter().for_each(|u| res.push(Uuid::from(u.clone()).to_string()));
Ok(res)
}
#[generate_interface]
/// delete a buffer
fn delete_buffer(&mut self, path: &str) -> CodempResult<()> {
RT.block_on(RT.block_on(self.workspace.write()).delete(path))
}
#[generate_interface]
/// detach from a buffer
fn detach_from_buffer(&mut self, path: &str) -> bool {
RT.block_on(self.workspace.write()).detach(path)
}
#[generate_interface]
/// get the workspace id
fn get_workspace_id(&self) -> String {
RT.block_on(self.workspace.read()).id().clone()
}
#[generate_interface]
/// get a [crate::CursorHandler] for the workspace's cursor
fn get_cursor(&self) -> CursorHandler {
CursorHandler { cursor: RT.block_on(self.workspace.read()).cursor().clone() }
}
#[generate_interface]
/// get a [crate::BufferHandler] for one of the workspace's buffers
fn get_buffer(&self, path: &str) -> Option<BufferHandler> {
RT.block_on(self.workspace.read()).buffer_by_name(path)
.map(|buffer| BufferHandler { buffer })
}
#[generate_interface]
/// get the names of all buffers available in the workspace
fn get_filetree(&self) -> StringVec {
StringVec { v: RT.block_on(self.workspace.read()).filetree() }
}
#[generate_interface]
/// polls a list of buffers, returning the first ready one
fn select_buffer(&mut self, mut buffer_ids: StringVec, timeout: i64) -> CodempResult<Option<BufferHandler>> {
let mut buffers = Vec::new();
for id in buffer_ids.v.iter_mut() {
match self.get_buffer(id.as_str()) {
Some(buf) => buffers.push(buf.buffer),
None => continue
}
}
let result = RT.block_on(tools::select_buffer(
buffers.as_slice(),
Some(Duration::from_millis(timeout as u64))
));
match result {
Err(e) => Err(e),
Ok(buffer) =>
Ok(buffer.map(|buffer| BufferHandler { buffer }))
}
}
}
#[generate_interface_doc]
#[generate_access_methods]
/// wraps a [codemp::proto::cursor::CursorEvent] to be handled by Java
struct CursorEventWrapper {
user: String,
buffer: String,
start_row: i32,
start_col: i32,
end_row: i32,
end_col: i32
}
#[generate_interface_doc]
/// a handler providing Java access to [codemp::cursor::Controller] methods
struct CursorHandler {
pub cursor: Arc<CodempCursorController>
}
impl CursorHandler {
#[generate_interface(constructor)]
/// constructor required by flapigen, DO NOT CALL THIS
fn new() -> CursorHandler {
unimplemented!()
}
#[generate_interface]
/// get next cursor event from current workspace, or block until one is available
fn recv(&self) -> CodempResult<CursorEventWrapper> {
match RT.block_on(self.cursor.recv()) {
Err(err) => Err(err),
Ok(event) => Ok(CursorEventWrapper {
user: Uuid::from(event.user).to_string(),
buffer: event.position.buffer.clone(),
start_row: event.position.start.row,
start_col: event.position.start.col,
end_row: event.position.end.row,
end_col: event.position.end.col
})
}
}
#[generate_interface]
/// broadcast a cursor event
/// will automatically fix start and end if they are accidentally inverted
fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> CodempResult<()> {
self.cursor.send(CodempCursorPosition {
buffer,
start: CodempRowCol::from((start_row, start_col)),
end: CodempRowCol::from((end_row, end_col))
})
}
}
#[generate_interface_doc]
#[generate_access_methods]
/// wraps a [codemp::api::change::TextChange] to make it accessible from Java
struct TextChangeWrapper {
start: usize,
end: usize, //not inclusive
content: String
}
#[generate_interface_doc]
/// a handler providing Java access to [codemp::buffer::Controller] methods
struct BufferHandler {
pub buffer: Arc<CodempBufferController>
}
impl BufferHandler {
#[generate_interface(constructor)]
/// constructor required by flapigen, DO NOT CALL THIS
fn new() -> BufferHandler {
unimplemented!()
}
#[generate_interface]
/// get the name of the buffer
fn get_name(&self) -> String {
self.buffer.name().to_string()
}
#[generate_interface]
/// get the contents of the buffer
fn get_content(&self) -> String {
self.buffer.content()
}
#[generate_interface]
/// if a text change is available on the buffer, return it immediately
fn try_recv(&self) -> CodempResult<Option<TextChangeWrapper>> {
match self.buffer.try_recv() {
Err(err) => Err(err),
Ok(None) => Ok(None),
Ok(Some(change)) => Ok(Some(TextChangeWrapper {
start: change.span.start,
end: change.span.end,
content: change.content.clone()
}))
}
}
#[generate_interface]
/// broadcast a text change on the buffer
fn send(&self, start_offset: usize, end_offset: usize, content: String) -> CodempResult<()> {
self.buffer.send(CodempTextChange { span: start_offset..end_offset, content })
}
}
#[generate_interface_doc]
/// a convenience struct allowing Java access to a Rust vector
struct StringVec { //jni moment
v: Vec<String>
}
impl StringVec {
#[generate_interface(constructor)]
/// initialize an empty vector
fn new() -> StringVec {
Self { v: Vec::new() }
}
#[generate_interface]
/// push a new value onto the vector
fn push(&mut self, s: String) {
self.v.push(s);
}
#[generate_interface]
/// get the length of the underlying vector
fn length(&self) -> i64 {
self.v.len() as i64
}
#[generate_interface]
/// access the element at a given index
fn get(&self, idx: i64) -> Option<String> {
let elem: Option<&String> = self.v.get(idx as usize);
elem.map(|s| s.clone())
}
}