#![allow(clippy::result_large_err)]
use std::sync::Arc;
use {
reovim_driver_codec::CodecSessionState,
reovim_kernel::api::v1::BufferId,
reovim_protocol::v2::{
BufferInfo, CodecMetadata, GetAnnotationsRequest, GetAnnotationsResponse,
GetLineCountRequest, GetLineCountResponse, GetRawContentRequest, GetRawContentResponse,
LineAnnotation, ListBuffersRequest, ListBuffersResponse, OpenFileRequest, OpenFileResponse,
SetContentRequest, SetContentResponse, WriteFileRequest, WriteFileResponse,
buffer_service_server::BufferService,
},
tonic::{Request, Response, Status},
};
use crate::session::{ClientId, Session, SessionId, SessionRegistry};
pub struct BufferServiceImpl {
sessions: Arc<SessionRegistry>,
default_session_id: SessionId,
}
impl BufferServiceImpl {
#[must_use]
pub const fn new(sessions: Arc<SessionRegistry>, default_session_id: SessionId) -> Self {
Self {
sessions,
default_session_id,
}
}
fn get_session(&self) -> Result<Arc<Session>, Status> {
self.sessions
.get(&self.default_session_id)
.ok_or_else(|| Status::not_found("No active session"))
}
}
#[tonic::async_trait]
impl BufferService for BufferServiceImpl {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::significant_drop_tightening)]
async fn get_raw_content(
&self,
request: Request<GetRawContentRequest>,
) -> Result<Response<GetRawContentResponse>, Status> {
let client_id = request.extensions().get::<ClientId>().copied();
let req = request.into_inner();
let session = self.get_session()?;
let client_active = client_id.and_then(|cid| {
session.with_clients(|clients| clients.get(&cid).and_then(|c| c.state.active_buffer))
});
session
.with_state(|state| {
let buffer_id = req
.buffer_id
.map(|id| BufferId::from_raw(id as usize))
.or(client_active)
.or_else(|| state.app.kernel.buffers.list().first().copied())
.ok_or_else(|| Status::not_found("No active buffer"))?;
let buffer_arc = state.buffer(buffer_id).ok_or_else(|| {
Status::not_found(format!("Buffer {} not found", buffer_id.as_usize()))
})?;
let buffer = buffer_arc.read();
let total_lines = buffer.line_count();
let start = req.start_line.unwrap_or(0) as usize;
let end = req
.end_line
.map_or(total_lines, |e| e as usize)
.min(total_lines);
let lines: Vec<String> = (start..end)
.filter_map(|i| buffer.line(i).map(ToString::to_string))
.collect();
Ok(Response::new(GetRawContentResponse {
buffer_id: buffer_id.as_usize() as u64,
lines,
start_line: start as u64,
total_lines: total_lines as u64,
}))
})
.await
}
#[allow(clippy::cast_possible_truncation)]
async fn get_line_count(
&self,
request: Request<GetLineCountRequest>,
) -> Result<Response<GetLineCountResponse>, Status> {
let client_id = request.extensions().get::<ClientId>().copied();
let req = request.into_inner();
let session = self.get_session()?;
let client_active = client_id.and_then(|cid| {
session.with_clients(|clients| clients.get(&cid).and_then(|c| c.state.active_buffer))
});
session
.with_state(|state| {
let buffer_id = req
.buffer_id
.map(|id| BufferId::from_raw(id as usize))
.or(client_active)
.or_else(|| state.app.kernel.buffers.list().first().copied())
.ok_or_else(|| Status::not_found("No active buffer"))?;
let buffer_arc = state.buffer(buffer_id).ok_or_else(|| {
Status::not_found(format!("Buffer {} not found", buffer_id.as_usize()))
})?;
let buffer = buffer_arc.read();
Ok(Response::new(GetLineCountResponse {
buffer_id: buffer_id.as_usize() as u64,
line_count: buffer.line_count() as u64,
}))
})
.await
}
#[allow(clippy::cast_possible_truncation)]
async fn get_annotations(
&self,
request: Request<GetAnnotationsRequest>,
) -> Result<Response<GetAnnotationsResponse>, Status> {
let client_id = request.extensions().get::<ClientId>().copied();
let req = request.into_inner();
let session = self.get_session()?;
let client_active = client_id.and_then(|cid| {
session.with_clients(|clients| clients.get(&cid).and_then(|c| c.state.active_buffer))
});
session
.with_state(|state| {
let buffer_id = req
.buffer_id
.map(|id| BufferId::from_raw(id as usize))
.or(client_active)
.or_else(|| state.app.kernel.buffers.list().first().copied())
.ok_or_else(|| Status::not_found("No active buffer"))?;
Ok(Response::new(GetAnnotationsResponse {
buffer_id: buffer_id.as_usize() as u64,
annotations: Vec::<LineAnnotation>::new(),
}))
})
.await
}
#[allow(clippy::significant_drop_tightening)]
async fn list(
&self,
_request: Request<ListBuffersRequest>,
) -> Result<Response<ListBuffersResponse>, Status> {
let session = self.get_session()?;
session
.with_state(|state| {
let buffers: Vec<BufferInfo> = state
.app
.kernel
.buffers
.list()
.iter()
.filter_map(|&id| {
state.app.kernel.buffers.get(id).map(|arc| {
let buf = arc.read();
let name = buf
.file_path()
.and_then(|p| std::path::Path::new(p).file_name())
.map_or_else(
|| format!("[Buffer {}]", id.as_usize()),
|n| n.to_string_lossy().into_owned(),
);
let codec_meta = state
.app
.extensions
.get::<CodecSessionState>()
.and_then(|css| css.get(id))
.map(|m| CodecMetadata {
codec_name: m
.content_type()
.as_str()
.strip_prefix("text/")
.unwrap_or_else(|| m.content_type().as_str())
.to_string(),
line_ending: m.get("line_ending").map(String::from),
has_bom: m.get("bom") == Some("true"),
});
BufferInfo {
id: id.as_usize() as u64,
name,
path: buf.file_path().map(String::from),
line_count: buf.line_count() as u64,
modified: buf.is_modified(),
content_type: None,
readonly: None,
codec_metadata: codec_meta,
}
})
})
.collect();
Ok(Response::new(ListBuffersResponse { buffers }))
})
.await
}
async fn open_file(
&self,
_request: Request<OpenFileRequest>,
) -> Result<Response<OpenFileResponse>, Status> {
Err(Status::unimplemented("OpenFile not yet implemented"))
}
async fn write_file(
&self,
_request: Request<WriteFileRequest>,
) -> Result<Response<WriteFileResponse>, Status> {
Err(Status::unimplemented("WriteFile not yet implemented"))
}
async fn set_content(
&self,
_request: Request<SetContentRequest>,
) -> Result<Response<SetContentResponse>, Status> {
Err(Status::unimplemented("SetContent not yet implemented"))
}
}
#[cfg(test)]
#[path = "buffer_tests.rs"]
mod tests;