feat: recv, buffer_list, tweaks, gradle

This commit is contained in:
zaaarf 2024-08-07 10:22:01 +02:00
parent 84996489e1
commit 6212718e99
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
13 changed files with 266 additions and 84 deletions

11
.gitignore vendored
View file

@ -18,3 +18,14 @@ java/*.iml
java/.idea/
java/*.h
java/**/*.class
java/build/
java/.classpath
java/.gradle/
java/.project
java/.settings/
java/bin/
# intellij insists on creating the wrapper every time even if it's not strictly necessary
java/gradle/
java/gradlew
java/gradlew.bat

49
java/build.gradle Normal file
View file

@ -0,0 +1,49 @@
plugins {
id 'java'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.palantir.git-version' version '3.1.0'
}
group = 'mp.code'
version = versionDetails().lastTag
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
sourceSets {
main.java.srcDirs = ['src/']
}
dependencies {
implementation 'com.github.adamheinrich:native-utils:master-SNAPSHOT'
}
shadowJar {
archiveClassifier.set('')
dependencies {
include(dependency('com.github.adamheinrich:native-utils:master-SNAPSHOT'))
}
}
def rustDir = projectDir.toPath()
.parent
.resolve('target')
.resolve('release')
.toFile()
processResources {
from(rustDir) {
include('*.dll')
include('*.so')
into('natives/')
}
}
tasks.register('cargoBuild', Exec) {
workingDir '.'
commandLine 'cargo', 'build', '--release', '--features=java'
}
build.dependsOn cargoBuild

1
java/settings.gradle Normal file
View file

@ -0,0 +1 @@
rootProject.name = 'codemp'

View file

@ -1,8 +1,11 @@
package mp.code;
import mp.code.data.Cursor;
import mp.code.data.TextChange;
import mp.code.exceptions.CodeMPException;
import java.util.Optional;
public class BufferController {
private final long ptr;
@ -21,8 +24,13 @@ public class BufferController {
}
private static native TextChange try_recv(long self) throws CodeMPException;
public TextChange tryRecv() throws CodeMPException {
return try_recv(this.ptr);
public Optional<TextChange> tryRecv() throws CodeMPException {
return Optional.ofNullable(try_recv(this.ptr));
}
private static native Cursor recv(long self) throws CodeMPException;
public Cursor recv() throws CodeMPException {
return recv(this.ptr);
}
private static native void send(long self, TextChange change) throws CodeMPException;

View file

@ -1,23 +1,22 @@
package mp.code;
import cz.adamh.utils.NativeUtils;
import mp.code.exceptions.CodeMPException;
import java.io.IOException;
import java.util.Optional;
public class Client {
private final long ptr;
private final String url;
public static native long setup_tracing(String path);
private static native long connect(String url) throws CodeMPException;
public Client(String url) throws CodeMPException {
this.ptr = connect(url);
this.url = url;
public static native Client connect(String url) throws CodeMPException;
Client(long ptr) {
this.ptr = ptr;
}
private static native String get_url(long self);
public String getUrl() {
return this.url;
return get_url(this.ptr);
}
private static native void login(long self, String username, String password, String workspace) throws CodeMPException;
@ -25,19 +24,14 @@ public class Client {
login(this.ptr, username, password, workspace);
}
private static native long join_workspace(long self, String id) throws CodeMPException;
private static native Workspace join_workspace(long self, String id) throws CodeMPException;
public Workspace joinWorkspace(String id) throws CodeMPException {
return new Workspace(join_workspace(this.ptr, id));
return join_workspace(this.ptr, id);
}
private static native long get_workspace(long self);
private static native Workspace get_workspace(long self);
public Optional<Workspace> getWorkspace() {
long ptr = get_workspace(this.ptr);
if(ptr == 0) { // TODO it would be better to init in rust directly
return Optional.empty();
} else {
return Optional.of(new Workspace(ptr));
}
return Optional.ofNullable(get_workspace(this.ptr));
}
private static native void free(long self);
@ -46,5 +40,17 @@ public class Client {
protected void finalize() {
free(this.ptr);
}
private static native void setup_tracing(String path);
static {
try {
if(System.getProperty("os.name").startsWith("Windows"))
NativeUtils.loadLibraryFromJar("/natives/codemp_intellij.dll");
else NativeUtils.loadLibraryFromJar("/natives/libcodemp_intellij.so");
setup_tracing(System.getenv().get("CODEMP_TRACING_LOG"));
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -3,6 +3,8 @@ package mp.code;
import mp.code.data.Cursor;
import mp.code.exceptions.CodeMPException;
import java.util.Optional;
public class CursorController {
private final long ptr;
@ -11,8 +13,13 @@ public class CursorController {
}
private static native Cursor try_recv(long self) throws CodeMPException;
public Cursor tryRecv() throws CodeMPException {
return try_recv(this.ptr);
public Optional<Cursor> tryRecv() throws CodeMPException {
return Optional.ofNullable(try_recv(this.ptr));
}
private static native Cursor recv(long self) throws CodeMPException;
public Cursor recv() throws CodeMPException {
return recv(this.ptr);
}
private static native void send(long self, Cursor cursor) throws CodeMPException;

View file

@ -1,5 +1,7 @@
package mp.code;
import java.util.Optional;
import mp.code.exceptions.CodeMPException;
public class Workspace {
@ -14,14 +16,14 @@ public class Workspace {
return get_workspace_id(this.ptr);
}
private static native long get_cursor(long self);
private static native CursorController get_cursor(long self);
public CursorController getCursor() {
return new CursorController(get_cursor(this.ptr));
return get_cursor(this.ptr);
}
private static native long get_buffer(long self, String path);
public BufferController getBuffer(String path) {
return new BufferController(get_buffer(this.ptr, path));
private static native BufferController get_buffer(long self, String path);
public Optional<BufferController> getBuffer(String path) {
return Optional.ofNullable(get_buffer(this.ptr, path));
}
private static native String[] get_file_tree(long self);
@ -34,9 +36,9 @@ public class Workspace {
return new BufferController(create_buffer(path));
}
private static native long attach_to_buffer(long self) throws CodeMPException;
public BufferController attachToBuffer() throws CodeMPException {
return new BufferController(attach_to_buffer(ptr));
private static native BufferController attach_to_buffer(long self, String path) throws CodeMPException;
public BufferController attachToBuffer(String path) throws CodeMPException {
return attach_to_buffer(ptr, path);
}
private static native void fetch_buffers(long self) throws CodeMPException;
@ -60,8 +62,8 @@ public class Workspace {
}
private static native BufferController select_buffer(long self, long timeout) throws CodeMPException;
public BufferController selectBuffer(long timeout) throws CodeMPException {
return select_buffer(this.ptr, timeout);
public Optional<BufferController> selectBuffer(long timeout) throws CodeMPException {
return Optional.ofNullable(select_buffer(this.ptr, timeout));
}
private static native void free(long self);

View file

@ -1,5 +1,4 @@
use crate::{Error, api::Controller};
use std::sync::Arc;
use tokio::sync::mpsc;
/// invoke .poll() on all given buffer controllers and wait, returning the first one ready
@ -13,15 +12,16 @@ use tokio::sync::mpsc;
///
/// returns an error if all buffers returned errors while polling.
pub async fn select_buffer(
buffers: &[Arc<crate::buffer::Controller>],
buffers: &[crate::buffer::Controller],
timeout: Option<std::time::Duration>,
) -> crate::Result<Option<Arc<crate::buffer::Controller>>> {
runtime: &tokio::runtime::Runtime
) -> crate::Result<Option<crate::buffer::Controller>> {
let (tx, mut rx) = mpsc::unbounded_channel();
let mut tasks = Vec::new();
for buffer in buffers {
let _tx = tx.clone();
let _buffer = buffer.clone();
tasks.push(tokio::spawn(async move {
tasks.push(runtime.spawn(async move {
match _buffer.poll().await {
Ok(()) => _tx.send(Ok(Some(_buffer))),
Err(_) => _tx.send(Err(Error::Channel { send: true })),
@ -30,7 +30,7 @@ pub async fn select_buffer(
}
if let Some(d) = timeout {
let _tx = tx.clone();
tasks.push(tokio::spawn(async move {
tasks.push(runtime.spawn(async move {
tokio::time::sleep(d).await;
_tx.send(Ok(None))
}));

View file

@ -2,7 +2,7 @@ use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring},
use crate::api::Controller;
use super::util::JExceptable;
use super::{util::JExceptable, RT};
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_get_1name(
@ -37,7 +37,23 @@ pub extern "system" fn Java_mp_code_BufferController_try_1recv(
self_ptr: jlong,
) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
match controller.try_recv().jexcept(&mut env) {
let change = controller.try_recv().jexcept(&mut env);
recv_jni(&mut env, change)
}
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_recv(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
let change = RT.block_on(controller.recv()).map(Some).jexcept(&mut env);
recv_jni(&mut env, change)
}
fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject {
match change {
None => JObject::null().as_raw(),
Some(event) => {
let class = env.find_class("mp/code/data/TextChange").expect("Couldn't find class!");
@ -52,6 +68,7 @@ pub extern "system" fn Java_mp_code_BufferController_try_1recv(
).expect("failed creating object").into_raw()
}
}
}
#[no_mangle]

View file

@ -1,4 +1,4 @@
use jni::{objects::{JClass, JString}, sys::jlong, JNIEnv};
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv};
use crate::{client::Client, Workspace};
use super::{util::JExceptable, RT};
@ -9,48 +9,21 @@ pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, in
let _ = unsafe { Box::from_raw(input as *mut Client) };
}
/// Sets up tracing subscriber
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
path: JString<'local>
) {
let path: Option<String> = if path.is_null() {
None
} else {
Some(env.get_string(&path).expect("Couldn't get java string!").into())
};
super::setup_logger(true, path);
}
/// Connects to a given URL and returns a [Client] to interact with that server.
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_connect<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
input: JString<'local>
) -> jlong {
) -> jobject {
let url: String = env.get_string(&input).expect("Couldn't get java string!").into();
RT.block_on(crate::Client::new(&url))
.map(|client| Box::into_raw(Box::new(client)) as jlong)
.jexcept(&mut env)
}
/// Gets a [Workspace] by name and returns a pointer to it.
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>(
env: JNIEnv<'local>,
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) -> jlong {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") };
client.get_workspace(workspace_id.to_str().expect("Not UTF-8"))
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
.unwrap_or_default()
.map(|ptr| {
let class = env.find_class("mp/code/Client").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])
.expect("Failed to initialise object")
}).jexcept(&mut env).as_raw()
}
/// Logs in to a specific [Workspace].
@ -78,13 +51,17 @@ pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>(
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) -> jlong {
) -> jobject {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") };
RT.block_on(client.join_workspace(workspace_id.to_str().expect("Not UTF-8")))
.map(|workspace| spawn_updater(workspace.clone()))
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
.jexcept(&mut env)
.map(|ptr| {
let class = env.find_class("mp/code/Workspace").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])
.expect("Failed to initialise object")
}).jexcept(&mut env).as_raw()
}
// TODO: this stays until we get rid of the arc then i'll have to find a better way
@ -99,3 +76,37 @@ fn spawn_updater(workspace: Workspace) -> Workspace {
});
workspace
}
/// Gets a [Workspace] by name and returns a pointer to it.
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) -> jobject {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") };
client.get_workspace(workspace_id.to_str().expect("Not UTF-8"))
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
.map(|ptr| {
let class = env.find_class("mp/code/Workspace").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])
.expect("Failed to initialise object")
}).unwrap_or(JObject::null()).as_raw()
}
/// Sets up the tracing subscriber.
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
path: JString<'local>
) {
super::setup_logger(
true,
Some(path)
.filter(|p| p.is_null())
.map(|p| env.get_string(&p).expect("couldn't get java string").into())
);
}

View file

@ -1,6 +1,8 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv};
use crate::{api::Controller, ffi::java::util::JExceptable};
use super::RT;
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_try_1recv(
mut env: JNIEnv,
@ -8,7 +10,23 @@ pub extern "system" fn Java_mp_code_CursorController_try_1recv(
self_ptr: jlong,
) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
match controller.try_recv().jexcept(&mut env) {
let cursor = controller.try_recv().jexcept(&mut env);
jni_recv(&mut env, cursor)
}
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_recv(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
let cursor = RT.block_on(controller.recv()).map(Some).jexcept(&mut env);
jni_recv(&mut env, cursor)
}
fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
match cursor {
None => JObject::null().as_raw(),
Some(event) => {
let class = env.find_class("mp/code/data/Cursor").expect("Couldn't find class!");

View file

@ -1,4 +1,4 @@
use jni::{objects::{JClass, JObject, JString}, sys::{jlong, jobjectArray, jstring}, JNIEnv};
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject, jobjectArray, jstring}, JNIEnv};
use crate::Workspace;
use super::{util::JExceptable, RT};
@ -25,25 +25,35 @@ pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>(
/// Gets a cursor controller by name and returns a pointer to it.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>(
_env: JNIEnv<'local>,
mut env: JNIEnv<'local>,
_class: JClass<'local>,
self_ptr: jlong
) -> jlong {
) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
Box::into_raw(Box::new(workspace.cursor())) as jlong
let class = env.find_class("mp/code/CursorController").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)])
.expect("Failed to initialise object")
.as_raw()
}
/// Gets a buffer controller by name and returns a pointer to it.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>(
env: JNIEnv<'local>,
mut env: JNIEnv<'local>,
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) -> jlong {
) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") };
Box::into_raw(Box::new(workspace.buffer_by_name(path.to_str().expect("Not UTF-8")))) as jlong
if let Some(buf) = workspace.buffer_by_name(path.to_str().expect("Not UTF-8!")) {
let class = env.find_class("mp/code/BufferController").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
.expect("Failed to initialise object")
.as_raw()
} else {
JObject::null().as_raw()
}
}
/// Creates a new buffer.
@ -87,12 +97,16 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>(
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) -> jlong {
) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") };
RT.block_on(workspace.attach(path.to_str().expect("Not UTF-8!")))
.map(|buffer| Box::into_raw(Box::new(buffer)) as jlong)
.jexcept(&mut env)
.map(|ptr| {
let class = env.find_class("mp/code/BufferController").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])
.expect("Failed to initialise object")
}).jexcept(&mut env).as_raw()
}
/// Updates the local buffer list.
@ -155,3 +169,36 @@ pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>(
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
RT.block_on(workspace.delete(buffer.to_str().expect("Not UTF-8!"))).jexcept(&mut env);
}
/// Polls a list of buffers, returning the first ready one.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_select_1buffer(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
timeout: jlong
) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let buffers = workspace.buffer_list();
let mut controllers = Vec::default();
for buffer in buffers {
if let Some(controller) = workspace.buffer_by_name(&buffer) {
controllers.push(controller);
}
}
let active = RT.block_on(crate::buffer::tools::select_buffer(
&controllers,
Some(std::time::Duration::from_millis(timeout as u64)),
&RT,
)).jexcept(&mut env);
if let Some(buf) = active {
let class = env.find_class("mp/code/BufferController").expect("Failed to find class");
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
.expect("Failed to initialise object")
.as_raw()
} else {
JObject::null().as_raw()
}
}

View file

@ -241,6 +241,11 @@ impl Workspace {
self.0.buffers.get(path).map(|x| x.clone())
}
/// get a list of all the currently attached to buffers
pub fn buffer_list(&self) -> Vec<String> {
self.0.buffers.iter().map(|elem| elem.key().clone()).collect()
}
/// get the currently cached "filetree"
pub fn filetree(&self) -> Vec<String> {
self.0.filetree.iter().map(|f| f.clone()).collect()