Skip to main content

reovim_driver_session/api/
buffer.rs

1//! Buffer content and lifecycle operations.
2//!
3//! This module provides the [`BufferApi`] trait for buffer manipulation.
4//! Resolvers and commands use this to query and modify buffer content.
5//!
6//! # Design
7//!
8//! Following Unix philosophy: this trait does ONE thing well - **buffer content** management.
9//!
10//! **Note (#471):** Cursor and selection are per-window, not per-buffer.
11//! Commands receive cursor position via `CommandContext::cursor_position()`,
12//! not from this trait.
13//!
14//! # Example
15//!
16//! ```ignore
17//! use reovim_driver_session::api::BufferApi;
18//! use reovim_driver_command_types::CommandContext;
19//!
20//! fn delete_word<S: BufferApi>(session: &mut S, args: &CommandContext) {
21//!     if let Some(buffer) = session.active_buffer() {
22//!         // Cursor comes from CommandContext, not BufferApi
23//!         if let Some(pos) = args.cursor_position() {
24//!             // Calculate word end and delete
25//!             let end = Position::new(pos.line, pos.column + 4);
26//!             session.delete_range(buffer, pos, end);
27//!         }
28//!     }
29//! }
30//! ```
31
32use reovim_kernel::api::v1::{BufferId, Position};
33
34/// Buffer content and lifecycle operations.
35///
36/// Provides access to buffer **content** for resolvers and commands.
37///
38/// **Note (#471):** Cursor and selection are NOT part of this trait.
39/// They are per-window properties, passed via `CommandContext`.
40pub trait BufferApi: Send {
41    // === Queries ===
42
43    /// Get the active buffer ID.
44    fn active_buffer(&self) -> Option<BufferId>;
45
46    /// Set the active buffer ID.
47    ///
48    /// Updates the session-level active buffer. Use after creating a new
49    /// buffer that should become the current editing target.
50    fn set_active_buffer(&mut self, id: Option<BufferId>);
51
52    /// Get a line from a buffer.
53    ///
54    /// Returns `None` if the buffer doesn't exist or line is out of bounds.
55    fn buffer_line(&self, buffer: BufferId, line: usize) -> Option<String>;
56
57    /// Get the line count of a buffer.
58    ///
59    /// Returns `None` if the buffer doesn't exist.
60    fn buffer_line_count(&self, buffer: BufferId) -> Option<usize>;
61
62    /// Get the length of a line in characters.
63    ///
64    /// Returns `None` if the buffer doesn't exist or line is out of bounds.
65    fn buffer_line_len(&self, buffer: BufferId, line: usize) -> Option<usize>;
66
67    /// Extract text from a range in the buffer.
68    ///
69    /// Returns the text between start and end positions, including all lines
70    /// in between. Returns `None` if the buffer doesn't exist.
71    ///
72    /// # Range Semantics
73    ///
74    /// The range is inclusive of `start` and exclusive of `end`, similar to
75    /// Rust's `start..end` range syntax.
76    fn buffer_text_range(&self, buffer: BufferId, start: Position, end: Position)
77    -> Option<String>;
78
79    /// Get full buffer content as a string.
80    ///
81    /// Returns `None` if the buffer doesn't exist.
82    fn buffer_content(&self, buffer: BufferId) -> Option<String>;
83
84    /// Get buffer's file path.
85    ///
86    /// Returns `None` if the buffer doesn't exist or has no associated file.
87    fn buffer_file_path(&self, buffer: BufferId) -> Option<String>;
88
89    /// Check if buffer has been modified since last save.
90    ///
91    /// Returns `None` if buffer doesn't exist.
92    fn is_buffer_modified(&self, buffer: BufferId) -> Option<bool>;
93
94    /// Set buffer's modified flag.
95    ///
96    /// Used to mark buffer as saved (false) or modified (true).
97    fn set_buffer_modified(&mut self, buffer: BufferId, modified: bool);
98
99    // === Content Mutations ===
100
101    /// Insert text at a position.
102    fn insert_text(&mut self, buffer: BufferId, pos: Position, text: &str);
103
104    /// Delete a range.
105    fn delete_range(&mut self, buffer: BufferId, start: Position, end: Position);
106
107    /// Replace entire buffer content.
108    ///
109    /// Atomically replaces all lines in the buffer. Used by format-on-save
110    /// and similar bulk-replacement operations. Marks the buffer as modified.
111    fn replace_content(&mut self, buffer: BufferId, content: &str);
112
113    // === Lifecycle ===
114
115    /// Create a new buffer.
116    ///
117    /// Returns the ID of the newly created buffer.
118    fn create_buffer(&mut self, name: Option<&str>, content: &str) -> BufferId;
119
120    /// Delete a buffer.
121    ///
122    /// # Errors
123    ///
124    /// Returns error if buffer doesn't exist or is the last buffer.
125    fn delete_buffer(&mut self, buffer: BufferId) -> Result<(), BufferError>;
126
127    /// Rename a buffer.
128    fn rename_buffer(&mut self, buffer: BufferId, new_name: &str);
129}
130
131/// Selection in a buffer.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct Selection {
134    /// Start position of the selection.
135    pub start: Position,
136    /// End position of the selection.
137    pub end: Position,
138    /// Selection mode (character, line, block).
139    pub mode: SelectionMode,
140}
141
142impl Selection {
143    /// Create a new selection.
144    #[must_use]
145    pub const fn new(start: Position, end: Position, mode: SelectionMode) -> Self {
146        Self { start, end, mode }
147    }
148
149    /// Create a character-mode selection.
150    #[must_use]
151    pub const fn character(start: Position, end: Position) -> Self {
152        Self::new(start, end, SelectionMode::Character)
153    }
154
155    /// Create a line-mode selection.
156    #[must_use]
157    pub const fn line(start: Position, end: Position) -> Self {
158        Self::new(start, end, SelectionMode::Line)
159    }
160
161    /// Create a block-mode selection.
162    #[must_use]
163    pub const fn block(start: Position, end: Position) -> Self {
164        Self::new(start, end, SelectionMode::Block)
165    }
166
167    /// Check if the selection is linewise.
168    #[must_use]
169    pub const fn is_linewise(&self) -> bool {
170        matches!(self.mode, SelectionMode::Line)
171    }
172}
173
174/// Selection mode.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
176pub enum SelectionMode {
177    /// Character-wise selection (v in vim).
178    #[default]
179    Character,
180    /// Line-wise selection (V in vim).
181    Line,
182    /// Block/column selection (Ctrl-v in vim).
183    Block,
184}
185
186/// Errors from buffer operations.
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub enum BufferError {
189    /// Buffer not found.
190    NotFound(BufferId),
191    /// Cannot delete the last buffer.
192    CannotDeleteLastBuffer,
193}
194
195impl std::fmt::Display for BufferError {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        match self {
198            Self::NotFound(id) => write!(f, "buffer not found: {id:?}"),
199            Self::CannotDeleteLastBuffer => write!(f, "cannot delete last buffer"),
200        }
201    }
202}
203
204impl std::error::Error for BufferError {}
205#[cfg(test)]
206#[path = "tests/buffer.rs"]
207mod tests;