use {
reovim_driver_layout::RootCompositor,
reovim_kernel::api::v1::{
BufferId, HistoryRing, Jumplist, MarkBank, ModeId, ModeStack, Position, RegisterBank,
WindowId,
},
};
use crate::{api::Selection as ApiSelection, extension::ExtensionMap};
pub struct SessionShared {
pub compositor: Option<Box<dyn RootCompositor>>,
home_mode: ModeId,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl std::fmt::Debug for SessionShared {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SessionShared")
.field("compositor", &self.compositor.as_ref().map(|_| "..."))
.field("home_mode", &self.home_mode)
.finish()
}
}
impl SessionShared {
#[must_use]
pub fn new(home_mode: ModeId) -> Self {
Self {
compositor: None,
home_mode,
}
}
pub fn set_compositor(&mut self, compositor: Box<dyn RootCompositor>) {
self.compositor = Some(compositor);
}
#[must_use]
pub fn compositor(&self) -> Option<&dyn RootCompositor> {
self.compositor.as_deref()
}
pub fn compositor_mut(&mut self) -> Option<&mut (dyn RootCompositor + 'static)> {
self.compositor.as_deref_mut()
}
#[must_use]
pub const fn home_mode(&self) -> &ModeId {
&self.home_mode
}
}
#[derive(Debug)]
pub struct BootstrapState {
pub mode_stack: ModeStack,
pub windows: WindowLayout,
pub pending_keys: KeySequence,
pub extensions: ExtensionMap,
}
impl BootstrapState {
#[must_use]
pub fn new(home_mode: ModeId) -> Self {
Self {
mode_stack: ModeStack::new(home_mode),
windows: WindowLayout::empty(),
pending_keys: KeySequence::new(),
extensions: ExtensionMap::new(),
}
}
#[must_use]
pub fn with_buffer(home_mode: ModeId, buffer_id: BufferId) -> Self {
let mut state = Self::new(home_mode);
state.windows.add(Window::with_buffer(buffer_id));
state
}
}
pub struct ClientContext<'a> {
pub mode_stack: &'a mut ModeStack,
pub windows: &'a mut WindowLayout,
pub extensions: &'a mut ExtensionMap,
pub compositor: &'a mut Option<Box<dyn RootCompositor>>,
pub tabs: &'a mut crate::TabPageSet,
pub registers: &'a mut RegisterBank,
pub clipboard_history: &'a mut HistoryRing,
pub local_marks: &'a mut MarkBank,
pub jumplist: &'a mut Jumplist,
pub active_buffer: &'a mut Option<BufferId>,
pub terminal_size: &'a mut (u16, u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextObjRange {
pub start: Position,
pub end: Position,
pub is_linewise: bool,
}
impl TextObjRange {
#[must_use]
pub const fn characterwise(start: Position, end: Position) -> Self {
Self {
start,
end,
is_linewise: false,
}
}
#[must_use]
pub const fn linewise(start: Position, end: Position) -> Self {
Self {
start,
end,
is_linewise: true,
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.start.line == self.end.line && self.start.column == self.end.column
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClientId(pub usize);
impl ClientId {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn as_usize(&self) -> usize {
self.0
}
}
impl std::fmt::Display for ClientId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "client-{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Viewport {
pub width: u16,
pub height: u16,
pub scroll_top: usize,
pub scroll_left: usize,
}
impl Viewport {
#[must_use]
pub const fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
scroll_top: 0,
scroll_left: 0,
}
}
#[must_use]
pub const fn default_size() -> Self {
Self::new(80, 24)
}
#[must_use]
pub const fn last_visible_line(&self) -> usize {
self.scroll_top + self.height as usize - 1
}
#[must_use]
pub const fn last_visible_column(&self) -> usize {
self.scroll_left + self.width as usize - 1
}
#[must_use]
pub const fn is_line_visible(&self, line: usize) -> bool {
line >= self.scroll_top && line <= self.last_visible_line()
}
#[must_use]
pub const fn is_column_visible(&self, column: usize) -> bool {
column >= self.scroll_left && column <= self.last_visible_column()
}
#[must_use]
pub const fn is_position_visible(&self, line: usize, column: usize) -> bool {
self.is_line_visible(line) && self.is_column_visible(column)
}
pub const fn ensure_cursor_visible(&mut self, cursor_line: usize) -> bool {
if self.height == 0 {
return false;
}
let old = self.scroll_top;
let h = self.height as usize;
if cursor_line < self.scroll_top {
self.scroll_top = cursor_line;
} else if cursor_line >= self.scroll_top + h {
self.scroll_top = cursor_line - h + 1;
}
self.scroll_top != old
}
}
impl Default for Viewport {
fn default() -> Self {
Self::default_size()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CursorPosition {
pub line: usize,
pub column: usize,
}
impl CursorPosition {
#[must_use]
pub const fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
#[must_use]
pub const fn origin() -> Self {
Self::new(0, 0)
}
}
impl From<Position> for CursorPosition {
fn from(pos: Position) -> Self {
Self::new(pos.line, pos.column)
}
}
impl From<CursorPosition> for Position {
fn from(cursor: CursorPosition) -> Self {
Self::new(cursor.line, cursor.column)
}
}
#[derive(Debug, Clone)]
pub struct Window {
pub id: WindowId,
pub buffer_id: Option<BufferId>,
pub cursor: CursorPosition,
pub viewport: Viewport,
pub selection: Option<ApiSelection>,
}
impl Window {
#[must_use]
pub fn new() -> Self {
Self {
id: WindowId::new(),
buffer_id: None,
cursor: CursorPosition::origin(),
viewport: Viewport::default(),
selection: None,
}
}
#[must_use]
pub fn with_buffer(buffer_id: BufferId) -> Self {
Self {
id: WindowId::new(),
buffer_id: Some(buffer_id),
cursor: CursorPosition::origin(),
viewport: Viewport::default(),
selection: None,
}
}
#[must_use]
pub fn with_id_and_buffer(id: WindowId, buffer_id: BufferId) -> Self {
Self {
id,
buffer_id: Some(buffer_id),
cursor: CursorPosition::origin(),
viewport: Viewport::default(),
selection: None,
}
}
#[must_use]
pub const fn split_from(id: WindowId, source: &Self) -> Self {
Self {
id,
buffer_id: source.buffer_id,
cursor: source.cursor,
viewport: source.viewport,
selection: None,
}
}
}
impl Default for Window {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default, Clone)]
pub struct WindowLayout {
pub windows: Vec<Window>,
active_index: Option<usize>,
}
impl WindowLayout {
#[must_use]
pub const fn empty() -> Self {
Self {
windows: Vec::new(),
active_index: None,
}
}
#[must_use]
pub fn single(window: Window) -> Self {
Self {
windows: vec![window],
active_index: Some(0),
}
}
pub fn add(&mut self, window: Window) {
self.windows.push(window);
if self.active_index.is_none() {
self.active_index = Some(0);
}
}
#[must_use]
pub fn active(&self) -> Option<&Window> {
if let Some(idx) = self.active_index
&& let Some(window) = self.windows.get(idx)
{
return Some(window);
}
self.windows.first()
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn active_mut(&mut self) -> Option<&mut Window> {
if let Some(idx) = self.active_index
&& idx < self.windows.len()
{
return self.windows.get_mut(idx);
}
self.windows.first_mut()
}
#[must_use]
pub fn active_id(&self) -> Option<WindowId> {
self.active().map(|w| w.id)
}
pub fn set_active(&mut self, id: WindowId) -> bool {
if let Some(idx) = self.windows.iter().position(|w| w.id == id) {
self.active_index = Some(idx);
true
} else {
false
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.windows.is_empty()
}
#[must_use]
pub const fn len(&self) -> usize {
self.windows.len()
}
pub fn clear(&mut self) {
self.windows.clear();
self.active_index = None;
}
#[must_use]
pub fn get(&self, id: WindowId) -> Option<&Window> {
self.windows.iter().find(|w| w.id == id)
}
pub fn get_mut(&mut self, id: WindowId) -> Option<&mut Window> {
self.windows.iter_mut().find(|w| w.id == id)
}
pub fn remove(&mut self, id: WindowId) -> bool {
if let Some(idx) = self.windows.iter().position(|w| w.id == id) {
self.windows.remove(idx);
match self.active_index {
Some(active) if active == idx => {
self.active_index = if self.windows.is_empty() {
None
} else {
Some(0)
};
}
Some(active) if active > idx => {
self.active_index = Some(active - 1);
}
_ => {}
}
true
} else {
false
}
}
}
#[derive(Debug, Clone, Default)]
pub struct KeySequence {
keys: Vec<String>,
}
impl KeySequence {
#[must_use]
pub const fn new() -> Self {
Self { keys: Vec::new() }
}
pub fn push(&mut self, key: String) {
self.keys.push(key);
}
pub fn clear(&mut self) {
self.keys.clear();
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.keys.is_empty()
}
#[must_use]
pub fn keys(&self) -> &[String] {
&self.keys
}
#[must_use]
pub fn as_string(&self) -> String {
self.keys.join("")
}
}
pub struct Session {
pub id: ClientId,
pub shared: SessionShared,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl std::fmt::Debug for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Session")
.field("id", &self.id)
.field("shared", &self.shared)
.finish()
}
}
impl Session {
#[must_use]
pub fn new(id: ClientId, home_mode: ModeId) -> Self {
Self {
id,
shared: SessionShared::new(home_mode),
}
}
#[must_use]
pub fn bootstrap(id: ClientId, home_mode: ModeId) -> (Self, BootstrapState) {
(Self::new(id, home_mode.clone()), BootstrapState::new(home_mode))
}
pub fn set_compositor(&mut self, compositor: Box<dyn RootCompositor>) {
self.shared.set_compositor(compositor);
}
#[must_use]
pub fn compositor(&self) -> Option<&dyn RootCompositor> {
self.shared.compositor()
}
pub fn compositor_mut(&mut self) -> Option<&mut (dyn RootCompositor + 'static)> {
self.shared.compositor_mut()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CursorSnapshot {
pub line: u32,
pub col: u32,
pub buffer_id: u64,
}
impl CursorSnapshot {
#[must_use]
pub const fn new(line: u32, col: u32, buffer_id: u64) -> Self {
Self {
line,
col,
buffer_id,
}
}
}
impl crate::SessionExtension for CursorSnapshot {
fn create() -> Self {
Self {
line: u32::MAX,
col: u32::MAX,
buffer_id: 0,
}
}
}
#[cfg(test)]
#[path = "types_tests.rs"]
mod tests;