use std::{borrow::Cow, ops::Range};
pub use reovim_arch::Color;
#[derive(Debug)]
pub enum ProbeResult {
Success,
Defer(String),
Failed(ClientModuleError),
}
#[derive(Debug)]
pub enum ClientModuleError {
InitFailed {
reason: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
ExitFailed { reason: String },
NotificationParse { kind: String, detail: String },
Other(String),
}
impl ClientModuleError {
#[must_use]
pub fn other(msg: impl Into<String>) -> Self {
Self::Other(msg.into())
}
#[must_use]
pub fn init_failed(
reason: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InitFailed {
reason: reason.into(),
source,
}
}
#[must_use]
pub fn exit_failed(reason: impl Into<String>) -> Self {
Self::ExitFailed {
reason: reason.into(),
}
}
#[must_use]
pub fn notification_parse(kind: impl Into<String>, detail: impl Into<String>) -> Self {
Self::NotificationParse {
kind: kind.into(),
detail: detail.into(),
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::InitFailed { reason, .. }
| Self::ExitFailed { reason }
| Self::NotificationParse { detail: reason, .. } => reason,
Self::Other(msg) => msg,
}
}
}
impl std::fmt::Display for ClientModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InitFailed { reason, .. } => write!(f, "init failed: {reason}"),
Self::ExitFailed { reason } => write!(f, "exit failed: {reason}"),
Self::NotificationParse { kind, detail } => {
write!(f, "notification parse error ({kind}): {detail}")
}
Self::Other(msg) => f.write_str(msg),
}
}
}
impl std::error::Error for ClientModuleError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InitFailed {
source: Some(src), ..
} => Some(src.as_ref()),
_ => None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl Version {
#[must_use]
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
pub const CLIENT_MODULE_API_VERSION: Version = Version::new(0, 3, 0);
#[must_use]
pub const fn is_client_compatible(required: Version, provided: Version) -> bool {
if required.major != provided.major {
return false;
}
required.minor <= provided.minor
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ClientModuleProbe {
pub id: [u8; 64],
pub name: [u8; 128],
pub version: Version,
pub api_version: Version,
pub required_deps_count: u8,
pub required_deps: [[u8; 64]; 8],
pub optional_deps_count: u8,
pub optional_deps: [[u8; 64]; 8],
pub capabilities: u32,
}
impl ClientModuleProbe {
#[must_use]
pub const fn new(id: &str, name: &str, version: Version, api_version: Version) -> Self {
let mut probe = Self {
id: [0; 64],
name: [0; 128],
version,
api_version,
required_deps_count: 0,
required_deps: [[0; 64]; 8],
optional_deps_count: 0,
optional_deps: [[0; 64]; 8],
capabilities: 0,
};
let id_bytes = id.as_bytes();
let id_len = if id_bytes.len() < 64 {
id_bytes.len()
} else {
64
};
let mut i = 0;
while i < id_len {
probe.id[i] = id_bytes[i];
i += 1;
}
let name_bytes = name.as_bytes();
let name_len = if name_bytes.len() < 128 {
name_bytes.len()
} else {
128
};
i = 0;
while i < name_len {
probe.name[i] = name_bytes[i];
i += 1;
}
probe
}
#[must_use]
pub fn id_str(&self) -> &str {
let len = self
.id
.iter()
.position(|&b| b == 0)
.unwrap_or(self.id.len());
std::str::from_utf8(&self.id[..len]).unwrap_or("")
}
#[must_use]
pub fn name_str(&self) -> &str {
let len = self
.name
.iter()
.position(|&b| b == 0)
.unwrap_or(self.name.len());
std::str::from_utf8(&self.name[..len]).unwrap_or("")
}
#[must_use]
pub fn required_deps(&self) -> Vec<&str> {
(0..self.required_deps_count as usize)
.filter_map(|i| {
let len = self.required_deps[i]
.iter()
.position(|&b| b == 0)
.unwrap_or(64);
std::str::from_utf8(&self.required_deps[i][..len]).ok()
})
.collect()
}
#[must_use]
pub fn optional_deps(&self) -> Vec<&str> {
(0..self.optional_deps_count as usize)
.filter_map(|i| {
let len = self.optional_deps[i]
.iter()
.position(|&b| b == 0)
.unwrap_or(64);
std::str::from_utf8(&self.optional_deps[i][..len]).ok()
})
.collect()
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] #[cfg_attr(coverage_nightly, coverage(off))]
pub const fn with_required_dep(mut self, index: usize, dep: &str) -> Self {
if index < 8 {
let dep_bytes = dep.as_bytes();
let dep_len = if dep_bytes.len() < 64 {
dep_bytes.len()
} else {
64
};
let mut i = 0;
while i < dep_len {
self.required_deps[index][i] = dep_bytes[i];
i += 1;
}
if index >= self.required_deps_count as usize {
self.required_deps_count = (index + 1) as u8;
}
}
self
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] #[cfg_attr(coverage_nightly, coverage(off))]
pub const fn with_optional_dep(mut self, index: usize, dep: &str) -> Self {
if index < 8 {
let dep_bytes = dep.as_bytes();
let dep_len = if dep_bytes.len() < 64 {
dep_bytes.len()
} else {
64
};
let mut i = 0;
while i < dep_len {
self.optional_deps[index][i] = dep_bytes[i];
i += 1;
}
if index >= self.optional_deps_count as usize {
self.optional_deps_count = (index + 1) as u8;
}
}
self
}
#[must_use]
pub const fn with_capabilities(
mut self,
has_chrome: bool,
has_buffer_contrib: bool,
has_annotations: bool,
) -> Self {
self.capabilities = 0;
if has_chrome {
self.capabilities |= 1;
}
if has_buffer_contrib {
self.capabilities |= 1 << 1;
}
if has_annotations {
self.capabilities |= 1 << 2;
}
self
}
#[must_use]
pub const fn has_chrome(&self) -> bool {
self.capabilities & 1 != 0
}
#[must_use]
pub const fn has_buffer_contrib(&self) -> bool {
self.capabilities & (1 << 1) != 0
}
#[must_use]
pub const fn has_annotations(&self) -> bool {
self.capabilities & (1 << 2) != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BufferId(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorDepth {
Monochrome,
Ansi16,
Ansi256,
TrueColor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderingModel {
CellGrid,
Canvas,
NativeLayout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
#[must_use]
pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
#[must_use]
pub fn intersect(&self, other: &Self) -> Option<Self> {
let x1 = self.x.max(other.x);
let y1 = self.y.max(other.y);
let x2 = (self.x.saturating_add(self.width)).min(other.x.saturating_add(other.width));
let y2 = (self.y.saturating_add(self.height)).min(other.y.saturating_add(other.height));
if x1 < x2 && y1 < y2 {
Some(Self::new(x1, y1, x2 - x1, y2 - y1))
} else {
None
}
}
#[must_use]
pub const fn contains_point(&self, x: u16, y: u16) -> bool {
x >= self.x
&& y >= self.y
&& x < self.x.saturating_add(self.width)
&& y < self.y.saturating_add(self.height)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Insets {
pub top: u16,
pub bottom: u16,
pub left: u16,
pub right: u16,
}
impl Insets {
pub const ZERO: Self = Self {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
#[must_use]
pub const fn new(top: u16, bottom: u16, left: u16, right: u16) -> Self {
Self {
top,
bottom,
left,
right,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OptionValue {
Bool(bool),
Integer(i64),
String(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptionKind {
Bool,
Integer,
String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OptionMetadata {
pub name: String,
pub description: String,
pub default_value: Option<OptionValue>,
pub kind: OptionKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RenderBehavior {
Highlight,
Conceal { replacement: Cow<'static, str> },
Background(Color),
Hide,
FullWidthLine { ch: char, style: Style },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransformedLine {
pub segments: Vec<(String, Option<Style>)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VirtualLine {
pub buffer_line: usize,
pub position: VirtualLinePosition,
pub content: String,
pub style: Style,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VirtualLinePosition {
Before,
After,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineDecoration {
pub col_start: u16,
pub col_end: u16,
pub style: Style,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChromePosition {
Top,
Bottom,
Left,
Right,
Overlay,
}
#[derive(Debug, Clone)]
pub struct AnnotationContext {
pub buffer_id: BufferId,
pub total_lines: usize,
pub visible_range: (usize, usize),
pub cursor_line: usize,
pub gutter_style: Style,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColumnWidth {
Fixed(u16),
Dynamic(u16),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GutterCell {
pub text: String,
pub style: Style,
}
#[derive(Debug, Clone)]
pub struct BufferUpdateEvent {
pub buffer_id: BufferId,
pub revision: u64,
pub changed_range: Range<usize>,
pub new_lines: Vec<String>,
pub total_lines: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowId(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WindowLayout {
pub window_id: WindowId,
pub bounds: Rect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineNumberMode {
#[default]
None,
Absolute,
Relative,
Hybrid,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CursorInfo {
pub line: u64,
pub column: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionMode {
Char,
Line,
Block,
}
#[derive(Debug, Clone)]
pub struct SelectionInfo {
pub start_line: u64,
pub start_col: u64,
pub end_line: u64,
pub end_col: u64,
pub mode: SelectionMode,
pub color: Color,
}
#[derive(Debug, Clone)]
pub struct RemoteClientInfo {
pub client_id: u64,
pub display_name: String,
pub cursor_line: u64,
pub cursor_col: u64,
pub mode: String,
pub cursor_color: Color,
pub selection: Option<SelectionInfo>,
}
#[derive(Debug)]
pub struct ViewportContext<'a> {
pub buffer_id: Option<BufferId>,
pub buffer_lines: Option<&'a [String]>,
pub cursor: Option<CursorInfo>,
pub scroll_top: usize,
pub local_selection: Option<SelectionInfo>,
pub remote_clients: &'a [RemoteClientInfo],
pub fold_ranges: &'a [(usize, usize)],
pub virtual_lines: &'a [VirtualLine],
pub opacity: f32,
pub line_number_mode: LineNumberMode,
pub gutter_width: u16,
pub sidebar_width: u16,
pub is_insert_mode: bool,
pub render_self_cursor: bool,
pub my_client_id: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Attributes(u8);
impl Attributes {
pub const BOLD: Self = Self(0b0000_0001);
pub const ITALIC: Self = Self(0b0000_0010);
pub const UNDERLINE: Self = Self(0b0000_0100);
pub const STRIKETHROUGH: Self = Self(0b0000_1000);
pub const REVERSE: Self = Self(0b0001_0000);
pub const DIM: Self = Self(0b0010_0000);
#[must_use]
pub const fn new() -> Self {
Self(0)
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn set(&mut self, other: Self) {
self.0 |= other.0;
}
pub const fn unset(&mut self, other: Self) {
self.0 &= !other.0;
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
}
impl std::ops::BitOr for Attributes {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitAnd for Attributes {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub attributes: Attributes,
}
impl Style {
#[must_use]
pub const fn new() -> Self {
Self {
fg: None,
bg: None,
attributes: Attributes::new(),
}
}
#[must_use]
pub const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
#[must_use]
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
#[must_use]
pub const fn bold(mut self) -> Self {
self.attributes.set(Attributes::BOLD);
self
}
#[must_use]
pub const fn italic(mut self) -> Self {
self.attributes.set(Attributes::ITALIC);
self
}
#[must_use]
pub const fn underline(mut self) -> Self {
self.attributes.set(Attributes::UNDERLINE);
self
}
#[must_use]
pub const fn dim(mut self) -> Self {
self.attributes.set(Attributes::DIM);
self
}
#[must_use]
pub const fn reverse(mut self) -> Self {
self.attributes.set(Attributes::REVERSE);
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum InputEvent {
Key(KeyEvent),
Pointer(PointerEvent),
Touch(TouchEvent),
Focus(FocusEvent),
Paste(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: Modifiers,
}
impl KeyEvent {
#[must_use]
pub const fn new(code: KeyCode, modifiers: Modifiers) -> Self {
Self { code, modifiers }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyCode {
Char(char),
Enter,
Esc,
Tab,
Backspace,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Insert,
Delete,
F(u8),
Null,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Modifiers(u8);
impl Modifiers {
pub const NONE: Self = Self(0);
pub const SHIFT: Self = Self(0b0000_0001);
pub const CTRL: Self = Self(0b0000_0010);
pub const ALT: Self = Self(0b0000_0100);
pub const SUPER: Self = Self(0b0000_1000);
#[must_use]
pub const fn new() -> Self {
Self(0)
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn set(&mut self, other: Self) {
self.0 |= other.0;
}
pub const fn unset(&mut self, other: Self) {
self.0 &= !other.0;
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
}
impl std::ops::BitOr for Modifiers {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitAnd for Modifiers {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PointerEvent {
pub kind: PointerKind,
pub x: u16,
pub y: u16,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerKind {
Down(PointerButton),
Up(PointerButton),
Drag(PointerButton),
Move,
ScrollUp,
ScrollDown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerButton {
Left,
Right,
Middle,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TouchEvent {
pub kind: TouchKind,
pub id: u64,
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchKind {
Start,
Move,
End,
Cancel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FocusEvent {
Gained,
Lost,
}
#[cfg(test)]
mod tests;