mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-25 16:34:48 +01:00
Updated Javascript Glue with Napi
Co-authored-by: alemi.dev <me@alemi.dev>
This commit is contained in:
parent
e4b6e82485
commit
ffa8d8ea82
9 changed files with 125 additions and 144 deletions
|
@ -42,8 +42,8 @@ derive_more = { version = "0.99.17", optional = true }
|
|||
|
||||
# glue (js)
|
||||
rmpv = { version = "1", optional = true }
|
||||
napi = { version = "2", features = ["full"], optional = true }
|
||||
napi-derive = { version="2", optional = true}
|
||||
napi = { version = "2.16", features = ["full"], optional = true }
|
||||
napi-derive = { version="2.16", optional = true}
|
||||
futures = { version = "0.3.28", optional = true }
|
||||
|
||||
# glue (python)
|
||||
|
@ -60,7 +60,7 @@ napi-build = { version = "2", optional = true }
|
|||
pyo3-build-config = { version = "0.19.2", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["js"]
|
||||
lua = ["mlua", "derive_more", "lazy_static", "tracing-subscriber"]
|
||||
java = ["lazy_static", "jni", "tracing-subscriber"]
|
||||
java-artifact = ["java"] # also builds the jar
|
||||
|
|
|
@ -30,11 +30,15 @@ pub struct Op(pub(crate) woot::crdt::Op);
|
|||
/// to delete a the fourth character we should send a
|
||||
/// `TextChange { span: 3..4, content: "".into() }`
|
||||
///
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
||||
pub struct TextChange {
|
||||
/// range of text change, as char indexes in buffer previous state
|
||||
pub span: std::ops::Range<usize>,
|
||||
/// range start of text change, as char indexes in buffer previous state
|
||||
pub start: u32,
|
||||
/// range end of text change, as char indexes in buffer previous state
|
||||
pub end: u32,
|
||||
/// new content of text inside span
|
||||
pub content: String,
|
||||
}
|
||||
|
@ -65,11 +69,16 @@ impl TextChange {
|
|||
let end_after = after.len() - end;
|
||||
|
||||
TextChange {
|
||||
span: start..end_before,
|
||||
start: start as u32,
|
||||
end: end_before as u32,
|
||||
content: after[start..end_after].to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> std::ops::Range<usize> {
|
||||
self.start as usize .. self.end as usize
|
||||
}
|
||||
|
||||
/// consume the [TextChange], transforming it into a Vec of [Op]
|
||||
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
|
||||
let mut out = Vec::new();
|
||||
|
@ -77,19 +86,19 @@ impl TextChange {
|
|||
return Ok(out);
|
||||
} // no-op
|
||||
let view = woot.view();
|
||||
let Some(span) = view.get(self.span.clone()) else {
|
||||
let Some(span) = view.get(self.span()) else {
|
||||
return Err(crate::woot::WootError::OutOfBounds);
|
||||
};
|
||||
let diff = similar::TextDiff::from_chars(span, &self.content);
|
||||
for (i, diff) in diff.iter_all_changes().enumerate() {
|
||||
match diff.tag() {
|
||||
similar::ChangeTag::Equal => {}
|
||||
similar::ChangeTag::Delete => match woot.delete_one(self.span.start + i) {
|
||||
similar::ChangeTag::Delete => match woot.delete_one(self.span().start + i) {
|
||||
Err(e) => tracing::error!("could not create deletion: {}", e),
|
||||
Ok(op) => out.push(Op(op)),
|
||||
},
|
||||
similar::ChangeTag::Insert => {
|
||||
match woot.insert(self.span.start + i, diff.value()) {
|
||||
match woot.insert(self.span().start + i, diff.value()) {
|
||||
Ok(ops) => {
|
||||
for op in ops {
|
||||
out.push(Op(op))
|
||||
|
@ -105,7 +114,7 @@ impl TextChange {
|
|||
|
||||
/// returns true if this TextChange deletes existing text
|
||||
pub fn is_deletion(&self) -> bool {
|
||||
!self.span.is_empty()
|
||||
!self.span().is_empty()
|
||||
}
|
||||
|
||||
/// returns true if this TextChange adds new text
|
||||
|
@ -120,9 +129,9 @@ impl TextChange {
|
|||
|
||||
/// applies this text change to given text, returning a new string
|
||||
pub fn apply(&self, txt: &str) -> String {
|
||||
let pre_index = std::cmp::min(self.span.start, txt.len());
|
||||
let pre_index = std::cmp::min(self.span().start, txt.len());
|
||||
let pre = txt.get(..pre_index).unwrap_or("").to_string();
|
||||
let post = txt.get(self.span.end..).unwrap_or("").to_string();
|
||||
let post = txt.get(self.span().end..).unwrap_or("").to_string();
|
||||
format!("{}{}{}", pre, self.content, post)
|
||||
}
|
||||
|
||||
|
@ -144,7 +153,7 @@ mod tests {
|
|||
"sphinx of black quartz, judge my vow",
|
||||
"sphinx of quartz, judge my vow",
|
||||
);
|
||||
assert_eq!(change.span, 10..16);
|
||||
assert_eq!(change.span(), 10..16);
|
||||
assert_eq!(change.content, "");
|
||||
}
|
||||
|
||||
|
@ -154,7 +163,7 @@ mod tests {
|
|||
"sphinx of quartz, judge my vow",
|
||||
"sphinx of black quartz, judge my vow",
|
||||
);
|
||||
assert_eq!(change.span, 10..10);
|
||||
assert_eq!(change.span(), 10..10);
|
||||
assert_eq!(change.content, "black ");
|
||||
}
|
||||
|
||||
|
@ -164,14 +173,14 @@ mod tests {
|
|||
"sphinx of black quartz, judge my vow",
|
||||
"sphinx who watches the desert, judge my vow",
|
||||
);
|
||||
assert_eq!(change.span, 7..22);
|
||||
assert_eq!(change.span(), 7..22);
|
||||
assert_eq!(change.content, "who watches the desert");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn textchange_apply_works_for_insertions() {
|
||||
let change = super::TextChange {
|
||||
span: 5..5,
|
||||
start: 5, end: 5,
|
||||
content: " cruel".to_string(),
|
||||
};
|
||||
let result = change.apply("hello world!");
|
||||
|
@ -181,7 +190,7 @@ mod tests {
|
|||
#[test]
|
||||
fn textchange_apply_works_for_deletions() {
|
||||
let change = super::TextChange {
|
||||
span: 5..11,
|
||||
start: 5, end: 11,
|
||||
content: "".to_string(),
|
||||
};
|
||||
let result = change.apply("hello cruel world!");
|
||||
|
@ -191,7 +200,7 @@ mod tests {
|
|||
#[test]
|
||||
fn textchange_apply_works_for_replacements() {
|
||||
let change = super::TextChange {
|
||||
span: 5..11,
|
||||
start: 5, end: 11,
|
||||
content: " not very pleasant".to_string(),
|
||||
};
|
||||
let result = change.apply("hello cruel world!");
|
||||
|
@ -201,7 +210,7 @@ mod tests {
|
|||
#[test]
|
||||
fn textchange_apply_never_panics() {
|
||||
let change = super::TextChange {
|
||||
span: 100..110,
|
||||
start: 100, end: 110,
|
||||
content: "a very long string \n which totally matters".to_string(),
|
||||
};
|
||||
let result = change.apply("a short text");
|
||||
|
@ -220,7 +229,7 @@ mod tests {
|
|||
#[test]
|
||||
fn empty_textchange_doesnt_alter_buffer() {
|
||||
let change = super::TextChange {
|
||||
span: 42..42,
|
||||
start: 42, end: 42,
|
||||
content: "".to_string(),
|
||||
};
|
||||
let result = change.apply("some important text");
|
||||
|
|
|
@ -22,6 +22,7 @@ use crate::api::TextChange;
|
|||
/// upon dropping this handle will stop the associated worker
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||
pub struct BufferController(Arc<BufferControllerInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -30,6 +30,7 @@ use codemp_proto::cursor::{CursorEvent, CursorPosition};
|
|||
/// upon dropping this handle will stop the associated worker
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||
pub struct CursorController(Arc<CursorControllerInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -1,56 +1,25 @@
|
|||
use std::sync::Arc;
|
||||
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
||||
use napi_derive::napi;
|
||||
use crate::api::Controller;
|
||||
use crate::api::TextChange;
|
||||
use crate::ffi::js::JsCodempError;
|
||||
use crate::api::Controller;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// BUFFER
|
||||
#[napi(object)]
|
||||
pub struct JsTextChange {
|
||||
pub span: JsRange,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct JsRange{
|
||||
pub start: i32,
|
||||
pub end: i32,
|
||||
}
|
||||
|
||||
impl From::<crate::api::TextChange> for JsTextChange {
|
||||
fn from(value: crate::api::TextChange) -> Self {
|
||||
JsTextChange {
|
||||
// TODO how is x.. represented ? span.end can never be None
|
||||
span: JsRange { start: value.span.start as i32, end: value.span.end as i32 },
|
||||
content: value.content,
|
||||
impl From<crate::Error> for napi::Error {
|
||||
fn from(value: crate::Error) -> Self {
|
||||
let msg = format!("{value}");
|
||||
match value {
|
||||
crate::Error::Deadlocked => napi::Error::new(napi::Status::WouldDeadlock, msg),
|
||||
_ => napi::Error::new(napi::Status::GenericFailure, msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From::<Arc<crate::buffer::Controller>> for JsBufferController {
|
||||
fn from(value: Arc<crate::buffer::Controller>) -> Self {
|
||||
JsBufferController(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[napi]
|
||||
pub struct JsBufferController(Arc<crate::buffer::Controller>);
|
||||
|
||||
|
||||
/*#[napi]
|
||||
pub fn delta(string : String, start: i64, txt: String, end: i64 ) -> Option<JsCodempOperationSeq> {
|
||||
Some(JsCodempOperationSeq(string.diff(start as usize, &txt, end as usize)?))
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[napi]
|
||||
impl JsBufferController {
|
||||
impl CodempBufferController {
|
||||
|
||||
|
||||
#[napi(ts_args_type = "fun: (event: JsTextChange) => void")]
|
||||
|
@ -58,10 +27,10 @@ impl JsBufferController {
|
|||
let tsfn : ThreadsafeFunction<crate::api::TextChange, Fatal> =
|
||||
fun.create_threadsafe_function(0,
|
||||
|ctx : ThreadSafeCallContext<crate::api::TextChange>| {
|
||||
Ok(vec![JsTextChange::from(ctx.value)])
|
||||
Ok(vec![ctx.value])
|
||||
}
|
||||
)?;
|
||||
let _controller = self.0.clone();
|
||||
let _controller = self.clone();
|
||||
tokio::spawn(async move {
|
||||
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
loop {
|
||||
|
@ -79,32 +48,13 @@ impl JsBufferController {
|
|||
}
|
||||
|
||||
|
||||
#[napi]
|
||||
pub fn content(&self) -> String {
|
||||
self.0.content()
|
||||
#[napi(js_name = "recv")]
|
||||
pub async fn jsrecv(&self) -> napi::Result<TextChange> {
|
||||
Ok(self.recv().await?.into())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name().to_string()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn recv(&self) -> napi::Result<JsTextChange> {
|
||||
Ok(
|
||||
self.0.recv().await
|
||||
.map_err(|e| napi::Error::from(JsCodempError(e)))?
|
||||
.into()
|
||||
)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn send(&self, op: JsTextChange) -> napi::Result<()> {
|
||||
// TODO might be nice to take ownership of the opseq
|
||||
let new_text_change = crate::api::TextChange {
|
||||
span: op.span.start as usize .. op.span.end as usize,
|
||||
content: op.content,
|
||||
};
|
||||
Ok(self.0.send(new_text_change).map_err(JsCodempError)?)
|
||||
pub fn send(&self, op: TextChange) -> napi::Result<()> {
|
||||
Ok(self.send(op)?)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use napi_derive::napi;
|
||||
use crate::ffi::js::JsCodempError;
|
||||
use crate::ffi::js::workspace::JsWorkspace;
|
||||
|
||||
#[napi]
|
||||
/// main codemp client session
|
||||
|
@ -10,8 +9,7 @@ pub struct JsCodempClient(tokio::sync::RwLock<crate::Client>);
|
|||
/// connect to codemp servers and return a client session
|
||||
pub async fn connect(addr: Option<String>) -> napi::Result<JsCodempClient>{
|
||||
let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"))
|
||||
.await
|
||||
.map_err(JsCodempError)?;
|
||||
.await?;
|
||||
|
||||
Ok(JsCodempClient(tokio::sync::RwLock::new(client)))
|
||||
}
|
||||
|
@ -21,14 +19,14 @@ impl JsCodempClient {
|
|||
#[napi]
|
||||
/// login against AuthService with provided credentials, optionally requesting access to a workspace
|
||||
pub async fn login(&self, username: String, password: String, workspace_id: Option<String>) -> napi::Result<()> {
|
||||
self.0.read().await.login(username, password, workspace_id).await.map_err(JsCodempError)?;
|
||||
self.0.read().await.login(username, password, workspace_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// join workspace with given id (will start its cursor controller)
|
||||
pub async fn join_workspace(&self, workspace: String) -> napi::Result<JsWorkspace> {
|
||||
Ok(JsWorkspace::from(self.0.write().await.join_workspace(&workspace).await.map_err(JsCodempError)?))
|
||||
Ok(JsWorkspace::from(self.0.write().await.join_workspace(&workspace).await?))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
|
|
|
@ -3,34 +3,64 @@ use napi_derive::napi;
|
|||
use uuid::Uuid;
|
||||
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy};
|
||||
use crate::api::Controller;
|
||||
use crate::ffi::js::JsCodempError;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[napi]
|
||||
pub struct JsCursorController(Arc<crate::cursor::Controller>);
|
||||
|
||||
impl From::<Arc<crate::cursor::Controller>> for JsCursorController {
|
||||
fn from(value: Arc<crate::cursor::Controller>) -> Self {
|
||||
JsCursorController(value)
|
||||
|
||||
|
||||
#[napi(js_name = "Cursor")]
|
||||
pub struct JsCursor {
|
||||
/// range of text change, as char indexes in buffer previous state
|
||||
pub start_row: i32,
|
||||
pub start_col: i32,
|
||||
pub end_row: i32,
|
||||
pub end_col: i32,
|
||||
pub buffer: String,
|
||||
pub user: Option<String>,
|
||||
}
|
||||
|
||||
impl From<JsCursor> for CodempCursor {
|
||||
fn from(value: JsCursor) -> Self {
|
||||
CodempCursor {
|
||||
start : (value.start_row, value.start_col),
|
||||
end: (value.end_row, value.end_col),
|
||||
buffer: value.buffer,
|
||||
user: value.user.map(|x| uuid::Uuid::parse_str(&x).expect("invalid uuid")),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<CodempCursor> for JsCursor {
|
||||
fn from(value: CodempCursor) -> Self {
|
||||
JsCursor {
|
||||
start_row : value.start.0,
|
||||
start_col : value.start.1,
|
||||
end_row : value.end.0,
|
||||
end_col: value.end.1,
|
||||
buffer: value.buffer,
|
||||
user: value.user.map(|x| x.to_string())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[napi]
|
||||
impl JsCursorController {
|
||||
impl CodempCursorController {
|
||||
|
||||
#[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")]
|
||||
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
||||
let tsfn : ThreadsafeFunction<codemp_proto::cursor::CursorEvent, ErrorStrategy::Fatal> =
|
||||
let tsfn : ThreadsafeFunction<JsCursor, ErrorStrategy::Fatal> =
|
||||
fun.create_threadsafe_function(0,
|
||||
|ctx : ThreadSafeCallContext<codemp_proto::cursor::CursorEvent>| {
|
||||
Ok(vec![JsCursorEvent::from(ctx.value)])
|
||||
|ctx : ThreadSafeCallContext<JsCursor>| {
|
||||
Ok(vec![ctx.value])
|
||||
}
|
||||
)?;
|
||||
let _controller = self.0.clone();
|
||||
let _controller = self.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match _controller.recv().await {
|
||||
Ok(event) => {
|
||||
tsfn.call(event.clone(), ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
||||
tsfn.call(event.into(), ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
||||
},
|
||||
Err(crate::Error::Deadlocked) => continue,
|
||||
Err(e) => break tracing::warn!("error receiving: {}", e),
|
||||
|
@ -41,13 +71,8 @@ impl JsCursorController {
|
|||
}
|
||||
|
||||
#[napi]
|
||||
pub fn send(&self, buffer: String, start: (i32, i32), end: (i32, i32)) -> napi::Result<()> {
|
||||
let pos = codemp_proto::cursor::CursorPosition {
|
||||
buffer: buffer.into(),
|
||||
start: codemp_proto::cursor::RowCol::from(start),
|
||||
end: codemp_proto::cursor::RowCol::from(end),
|
||||
};
|
||||
Ok(self.0.send(pos).map_err(JsCodempError)?)
|
||||
pub fn send(&self, pos: &CodempCursorController) -> napi::Result<()> {
|
||||
Ok(self.send(pos)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,39 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
use crate::ffi::js::{JsCodempError, buffer::JsBufferController, cursor::JsCursorController};
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
#[napi]
|
||||
/// a reference to a codemp workspace
|
||||
pub struct JsWorkspace(Arc<crate::Workspace>);
|
||||
|
||||
impl From<Arc<crate::Workspace>> for JsWorkspace {
|
||||
fn from(value: Arc<crate::Workspace>) -> Self {
|
||||
JsWorkspace(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl JsWorkspace {
|
||||
|
||||
#[napi]
|
||||
pub fn id(&self) -> String {
|
||||
self.0.id()
|
||||
impl CodempWorkspace {
|
||||
#[napi(js_name = "id")]
|
||||
pub fn js_id(&self) -> String {
|
||||
self.id()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn filetree(&self) -> Vec<String> {
|
||||
self.0.filetree()
|
||||
#[napi(js_name = "filetree")]
|
||||
pub fn js_filetree(&self) -> Vec<String> {
|
||||
self.filetree()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn cursor(&self) -> JsCursorController {
|
||||
JsCursorController::from(self.0.cursor())
|
||||
#[napi(js_name = "cursor")]
|
||||
pub fn js_cursor(&self) -> CodempCursorController {
|
||||
self.cursor()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn buffer_by_name(&self, path: String) -> Option<JsBufferController> {
|
||||
self.0.buffer_by_name(&path).map(|b| JsBufferController::from(b))
|
||||
#[napi(js_name = "buffer_by_name")]
|
||||
pub fn js_buffer_by_name(&self, path: String) -> Option<CodempBufferController> {
|
||||
self.buffer_by_name(&path)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn create(&self, path: String) -> napi::Result<()> {
|
||||
Ok(self.0.create(&path).await.map_err(JsCodempError)?)
|
||||
#[napi(js_name = "create")]
|
||||
pub async fn js_create(&self, path: String) -> napi::Result<()> {
|
||||
Ok(self.create(&path).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn attach(&self, path: String) -> napi::Result<JsBufferController> {
|
||||
Ok(JsBufferController::from(self.0.attach(&path).await.map_err(JsCodempError)?))
|
||||
#[napi(js_name = "attach")]
|
||||
pub async fn js_attach(&self, path: String) -> napi::Result<CodempBufferController> {
|
||||
Ok(self.attach(&path).await?)
|
||||
}
|
||||
|
||||
/*#[napi]
|
||||
|
|
|
@ -21,6 +21,9 @@ use tokio::sync::mpsc;
|
|||
use tonic::Streaming;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "js")]
|
||||
use napi_derive::napi;
|
||||
|
||||
//TODO may contain more info in the future
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserInfo {
|
||||
|
@ -29,6 +32,7 @@ pub struct UserInfo {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi)]
|
||||
pub struct Workspace(Arc<WorkspaceInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -227,26 +231,31 @@ impl Workspace {
|
|||
}
|
||||
|
||||
/// get the id of the workspace
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn id(&self) -> String {
|
||||
self.0.id.clone()
|
||||
}
|
||||
|
||||
/// return a reference to current cursor controller, if currently in a workspace
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn cursor(&self) -> cursor::Controller {
|
||||
self.0.cursor.clone()
|
||||
}
|
||||
|
||||
/// get a new reference to a buffer controller, if any is active to given path
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn buffer_by_name(&self, path: &str) -> Option<buffer::Controller> {
|
||||
self.0.buffers.get(path).map(|x| x.clone())
|
||||
}
|
||||
|
||||
/// get a list of all the currently attached to buffers
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn buffer_list(&self) -> Vec<String> {
|
||||
self.0.buffers.iter().map(|elem| elem.key().clone()).collect()
|
||||
}
|
||||
|
||||
/// get the currently cached "filetree"
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn filetree(&self) -> Vec<String> {
|
||||
self.0.filetree.iter().map(|f| f.clone()).collect()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue