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.authority.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.authority.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 .authority
156 .filesystem
157 .metadata(&temp_path)
158 .map(|m| m.size as usize)
159 .unwrap_or(last_known);
160
161 if self.stdin_stream.record_growth(current_size) {
163 if let Some(editor_state) = self.buffers.get_mut(&buffer_id) {
164 editor_state
165 .buffer
166 .extend_streaming(&temp_path, current_size);
167 }
168 self.status_message =
169 Some(t!("stdin.streaming_bytes", bytes = current_size).to_string());
170 changed = true;
171 }
172
173 if let Some(outcome) = self.stdin_stream.take_finished_thread_outcome() {
175 match outcome {
176 ThreadOutcome::Success => {
177 tracing::info!("Stdin streaming completed successfully");
178 }
179 ThreadOutcome::Error(msg) => {
180 tracing::warn!("Stdin streaming error: {}", msg);
181 self.status_message = Some(t!("stdin.read_error", error = msg).to_string());
182 }
183 ThreadOutcome::Panic => {
184 tracing::warn!("Stdin streaming thread panicked");
185 self.status_message = Some(t!("stdin.read_error_panic").to_string());
186 }
187 }
188 self.complete_stdin_streaming();
189 changed = true;
190 }
191
192 changed
193 }
194
195 pub fn complete_stdin_streaming(&mut self) {
198 let Some(buffer_id) = self.stdin_stream.buffer_id() else {
199 return;
200 };
201 let Some(temp_path) = self.stdin_stream.temp_path().map(Path::to_path_buf) else {
202 return;
203 };
204
205 self.stdin_stream.mark_complete();
206
207 let final_size = self
209 .authority
210 .filesystem
211 .metadata(&temp_path)
212 .map(|m| m.size as usize)
213 .unwrap_or(self.stdin_stream.last_known_size());
214
215 if self.stdin_stream.record_growth(final_size) {
216 if let Some(editor_state) = self.buffers.get_mut(&buffer_id) {
217 editor_state.buffer.extend_streaming(&temp_path, final_size);
218 }
219 }
220
221 self.status_message = Some(
222 t!(
223 "stdin.read_complete",
224 bytes = self.stdin_stream.last_known_size()
225 )
226 .to_string(),
227 );
228 }
229
230 pub fn is_stdin_streaming(&self) -> bool {
232 self.stdin_stream.is_active()
233 }
234
235 pub fn create_virtual_buffer(
245 &mut self,
246 name: String,
247 mode: String,
248 read_only: bool,
249 ) -> BufferId {
250 let buffer_id = BufferId(self.next_buffer_id);
251 self.next_buffer_id += 1;
252
253 let mut state = EditorState::new(
254 self.terminal_width,
255 self.terminal_height,
256 self.config.editor.large_file_threshold_bytes as usize,
257 Arc::clone(&self.authority.filesystem),
258 );
259 state.set_language_from_name(&name, &self.grammar_registry);
263
264 state
266 .margins
267 .configure_for_line_numbers(self.config.editor.line_numbers);
268
269 self.buffers.insert(buffer_id, state);
270 self.event_logs
271 .insert(buffer_id, crate::model::event::EventLog::new());
272
273 let metadata = super::types::BufferMetadata::virtual_buffer(name, mode, read_only);
275 self.buffer_metadata.insert(buffer_id, metadata);
276
277 let active_split = self.split_manager.active_split();
279 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
280 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
281 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
282 view_state.add_buffer(buffer_id);
283 let buf_state = view_state.ensure_buffer_state(buffer_id);
284 buf_state.apply_config_defaults(
285 self.config.editor.line_numbers,
286 self.config.editor.highlight_current_line,
287 line_wrap,
288 self.config.editor.wrap_indent,
289 wrap_column,
290 self.config.editor.rulers.clone(),
291 );
292 } else {
293 let mut view_state =
295 SplitViewState::with_buffer(self.terminal_width, self.terminal_height, buffer_id);
296 view_state.apply_config_defaults(
297 self.config.editor.line_numbers,
298 self.config.editor.highlight_current_line,
299 line_wrap,
300 self.config.editor.wrap_indent,
301 wrap_column,
302 self.config.editor.rulers.clone(),
303 );
304 self.split_view_states.insert(active_split, view_state);
305 }
306
307 buffer_id
308 }
309
310 pub fn set_virtual_buffer_content(
316 &mut self,
317 buffer_id: BufferId,
318 entries: Vec<crate::primitives::text_property::TextPropertyEntry>,
319 ) -> Result<(), String> {
320 let state = self
321 .buffers
322 .get_mut(&buffer_id)
323 .ok_or_else(|| "Buffer not found".to_string())?;
324
325 let (text, properties, collected_overlays) =
327 crate::primitives::text_property::TextPropertyManager::from_entries(entries);
328
329 state.overlays.clear(&mut state.marker_list);
334
335 let current_len = state.buffer.len();
336 if current_len > 0 {
337 state.buffer.delete_bytes(0, current_len);
338 }
339 state.buffer.insert(0, &text);
340
341 state.buffer.clear_modified();
343
344 state.text_properties = properties;
346
347 {
352 use crate::view::overlay::{Overlay, OverlayFace};
353 use fresh_core::overlay::OverlayNamespace;
354
355 let inline_ns = OverlayNamespace::from_string("_inline".to_string());
356 let mut new_overlays = Vec::with_capacity(collected_overlays.len());
357
358 for co in collected_overlays {
359 let face = OverlayFace::from_options(&co.options);
360 let mut overlay = Overlay::with_namespace(
361 &mut state.marker_list,
362 co.range,
363 face,
364 inline_ns.clone(),
365 );
366 overlay.extend_to_line_end = co.options.extend_to_line_end;
367 if let Some(url) = co.options.url {
368 overlay.url = Some(url);
369 }
370 new_overlays.push(overlay);
371 }
372 state.overlays.extend(new_overlays);
373 }
374
375 let new_len = state.buffer.len();
379 let buffer = &self
383 .buffers
384 .get(&buffer_id)
385 .expect("buffer still present")
386 .buffer;
387 for view_state in self.split_view_states.values_mut() {
388 let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) else {
389 continue;
390 };
391 buf_state.cursors.map(|cursor| {
392 let pos = cursor.position.min(new_len);
393 cursor.position = buffer.snap_to_char_boundary(pos);
394 if let Some(anchor) = cursor.anchor {
395 let clamped = anchor.min(new_len);
396 cursor.anchor = Some(buffer.snap_to_char_boundary(clamped));
397 }
398 });
399 }
400 Ok(())
401 }
402}