fresh/app/
virtual_buffers.rs1use std::path::Path;
12use std::sync::Arc;
13
14use anyhow::Result as AnyhowResult;
15use rust_i18n::t;
16
17use crate::model::event::BufferId;
18use crate::state::EditorState;
19use crate::view::split::SplitViewState;
20
21use super::Editor;
22
23impl Editor {
24 pub fn open_stdin_buffer(
30 &mut self,
31 temp_path: &Path,
32 thread_handle: Option<std::thread::JoinHandle<anyhow::Result<()>>>,
33 ) -> AnyhowResult<BufferId> {
34 self.position_history.commit_pending_movement();
36
37 let cursors = self.active_cursors();
39 let position = cursors.primary().position;
40 let anchor = cursors.primary().anchor;
41 self.position_history
42 .record_movement(self.active_buffer(), position, anchor);
43 self.position_history.commit_pending_movement();
44
45 let replace_current = {
48 let current_state = self.buffers.get(&self.active_buffer()).unwrap();
49 !current_state.is_composite_buffer
50 && current_state.buffer.is_empty()
51 && !current_state.buffer.is_modified()
52 && current_state.buffer.file_path().is_none()
53 };
54
55 let buffer_id = if replace_current {
56 self.active_buffer()
58 } else {
59 let id = BufferId(self.next_buffer_id);
61 self.next_buffer_id += 1;
62 id
63 };
64
65 let file_size = self.filesystem.metadata(temp_path)?.size as usize;
67
68 let mut state = EditorState::from_file_with_languages(
71 temp_path,
72 self.terminal_width,
73 self.terminal_height,
74 self.config.editor.large_file_threshold_bytes as usize,
75 &self.grammar_registry,
76 &self.config.languages,
77 Arc::clone(&self.filesystem),
78 )?;
79
80 state.buffer.clear_file_path();
83 state.buffer.clear_modified();
85
86 state.buffer_settings.tab_size = self.config.editor.tab_size;
88 state.buffer_settings.auto_close = self.config.editor.auto_close;
89 state.buffer_settings.auto_surround = self.config.editor.auto_surround;
90
91 state
93 .margins
94 .configure_for_line_numbers(self.config.editor.line_numbers);
95
96 self.buffers.insert(buffer_id, state);
97 self.event_logs
98 .insert(buffer_id, crate::model::event::EventLog::new());
99
100 let metadata =
102 super::types::BufferMetadata::new_unnamed(t!("stdin.display_name").to_string());
103 self.buffer_metadata.insert(buffer_id, metadata);
104
105 let active_split = self.split_manager.active_split();
107 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
108 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
109 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
110 view_state.add_buffer(buffer_id);
111 let buf_state = view_state.ensure_buffer_state(buffer_id);
112 buf_state.apply_config_defaults(
113 self.config.editor.line_numbers,
114 self.config.editor.highlight_current_line,
115 line_wrap,
116 self.config.editor.wrap_indent,
117 wrap_column,
118 self.config.editor.rulers.clone(),
119 );
120 }
121
122 self.set_active_buffer(buffer_id);
123
124 self.stdin_stream
128 .start(temp_path.to_path_buf(), buffer_id, file_size, thread_handle);
129
130 self.status_message = Some(t!("stdin.streaming").to_string());
132
133 Ok(buffer_id)
134 }
135
136 pub fn poll_stdin_streaming(&mut self) -> bool {
139 use super::stdin_stream::ThreadOutcome;
140
141 if !self.stdin_stream.is_active() {
142 return false;
143 }
144
145 let Some(buffer_id) = self.stdin_stream.buffer_id() else {
146 return false;
147 };
148 let temp_path = self.stdin_stream.temp_path().unwrap().to_path_buf();
149 let last_known = self.stdin_stream.last_known_size();
150
151 let mut changed = false;
152
153 let current_size = self
155 .filesystem
156 .metadata(&temp_path)
157 .map(|m| m.size as usize)
158 .unwrap_or(last_known);
159
160 if self.stdin_stream.record_growth(current_size) {
162 if let Some(editor_state) = self.buffers.get_mut(&buffer_id) {
163 editor_state
164 .buffer
165 .extend_streaming(&temp_path, current_size);
166 }
167 self.status_message =
168 Some(t!("stdin.streaming_bytes", bytes = current_size).to_string());
169 changed = true;
170 }
171
172 if let Some(outcome) = self.stdin_stream.take_finished_thread_outcome() {
174 match outcome {
175 ThreadOutcome::Success => {
176 tracing::info!("Stdin streaming completed successfully");
177 }
178 ThreadOutcome::Error(msg) => {
179 tracing::warn!("Stdin streaming error: {}", msg);
180 self.status_message = Some(t!("stdin.read_error", error = msg).to_string());
181 }
182 ThreadOutcome::Panic => {
183 tracing::warn!("Stdin streaming thread panicked");
184 self.status_message = Some(t!("stdin.read_error_panic").to_string());
185 }
186 }
187 self.complete_stdin_streaming();
188 changed = true;
189 }
190
191 changed
192 }
193
194 pub fn complete_stdin_streaming(&mut self) {
197 let Some(buffer_id) = self.stdin_stream.buffer_id() else {
198 return;
199 };
200 let Some(temp_path) = self.stdin_stream.temp_path().map(Path::to_path_buf) else {
201 return;
202 };
203
204 self.stdin_stream.mark_complete();
205
206 let final_size = self
208 .filesystem
209 .metadata(&temp_path)
210 .map(|m| m.size as usize)
211 .unwrap_or(self.stdin_stream.last_known_size());
212
213 if self.stdin_stream.record_growth(final_size) {
214 if let Some(editor_state) = self.buffers.get_mut(&buffer_id) {
215 editor_state.buffer.extend_streaming(&temp_path, final_size);
216 }
217 }
218
219 self.status_message = Some(
220 t!(
221 "stdin.read_complete",
222 bytes = self.stdin_stream.last_known_size()
223 )
224 .to_string(),
225 );
226 }
227
228 pub fn is_stdin_streaming(&self) -> bool {
230 self.stdin_stream.is_active()
231 }
232
233 pub fn create_virtual_buffer(
243 &mut self,
244 name: String,
245 mode: String,
246 read_only: bool,
247 ) -> BufferId {
248 let buffer_id = BufferId(self.next_buffer_id);
249 self.next_buffer_id += 1;
250
251 let mut state = EditorState::new(
252 self.terminal_width,
253 self.terminal_height,
254 self.config.editor.large_file_threshold_bytes as usize,
255 Arc::clone(&self.filesystem),
256 );
257 state.set_language_from_name(&name, &self.grammar_registry);
261
262 state
264 .margins
265 .configure_for_line_numbers(self.config.editor.line_numbers);
266
267 self.buffers.insert(buffer_id, state);
268 self.event_logs
269 .insert(buffer_id, crate::model::event::EventLog::new());
270
271 let metadata = super::types::BufferMetadata::virtual_buffer(name, mode, read_only);
273 self.buffer_metadata.insert(buffer_id, metadata);
274
275 let active_split = self.split_manager.active_split();
277 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
278 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
279 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
280 view_state.add_buffer(buffer_id);
281 let buf_state = view_state.ensure_buffer_state(buffer_id);
282 buf_state.apply_config_defaults(
283 self.config.editor.line_numbers,
284 self.config.editor.highlight_current_line,
285 line_wrap,
286 self.config.editor.wrap_indent,
287 wrap_column,
288 self.config.editor.rulers.clone(),
289 );
290 } else {
291 let mut view_state =
293 SplitViewState::with_buffer(self.terminal_width, self.terminal_height, buffer_id);
294 view_state.apply_config_defaults(
295 self.config.editor.line_numbers,
296 self.config.editor.highlight_current_line,
297 line_wrap,
298 self.config.editor.wrap_indent,
299 wrap_column,
300 self.config.editor.rulers.clone(),
301 );
302 self.split_view_states.insert(active_split, view_state);
303 }
304
305 buffer_id
306 }
307
308 pub fn set_virtual_buffer_content(
314 &mut self,
315 buffer_id: BufferId,
316 entries: Vec<crate::primitives::text_property::TextPropertyEntry>,
317 ) -> Result<(), String> {
318 let state = self
319 .buffers
320 .get_mut(&buffer_id)
321 .ok_or_else(|| "Buffer not found".to_string())?;
322
323 let (text, properties, collected_overlays) =
325 crate::primitives::text_property::TextPropertyManager::from_entries(entries);
326
327 state.overlays.clear(&mut state.marker_list);
332
333 let current_len = state.buffer.len();
334 if current_len > 0 {
335 state.buffer.delete_bytes(0, current_len);
336 }
337 state.buffer.insert(0, &text);
338
339 state.buffer.clear_modified();
341
342 state.text_properties = properties;
344
345 {
350 use crate::view::overlay::{Overlay, OverlayFace};
351 use fresh_core::overlay::OverlayNamespace;
352
353 let inline_ns = OverlayNamespace::from_string("_inline".to_string());
354 let mut new_overlays = Vec::with_capacity(collected_overlays.len());
355
356 for co in collected_overlays {
357 let face = OverlayFace::from_options(&co.options);
358 let mut overlay = Overlay::with_namespace(
359 &mut state.marker_list,
360 co.range,
361 face,
362 inline_ns.clone(),
363 );
364 overlay.extend_to_line_end = co.options.extend_to_line_end;
365 if let Some(url) = co.options.url {
366 overlay.url = Some(url);
367 }
368 new_overlays.push(overlay);
369 }
370 state.overlays.extend(new_overlays);
371 }
372
373 let new_len = state.buffer.len();
377 let buffer = &self
381 .buffers
382 .get(&buffer_id)
383 .expect("buffer still present")
384 .buffer;
385 for view_state in self.split_view_states.values_mut() {
386 let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) else {
387 continue;
388 };
389 buf_state.cursors.map(|cursor| {
390 let pos = cursor.position.min(new_len);
391 cursor.position = buffer.snap_to_char_boundary(pos);
392 if let Some(anchor) = cursor.anchor {
393 let clamped = anchor.min(new_len);
394 cursor.anchor = Some(buffer.snap_to_char_boundary(clamped));
395 }
396 });
397 }
398 Ok(())
399 }
400}