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;