1use std::{fs, marker::PhantomData, path::PathBuf};
15
16use self::reader::Readers;
17pub use self::reader::{RangeList, Reader, ReaderCfg};
18use crate::{
19 cfg::PrintCfg,
20 context::{self, FileHandle, Handle, load_cache},
21 data::{Pass, RwData},
22 form::Painter,
23 hook::{self, FileWritten},
24 mode::Selections,
25 text::{Bytes, Text, txt},
26 ui::{PushSpecs, RawArea, Ui, Widget, WidgetCfg},
27};
28
29mod reader;
30
31#[derive(Default, Clone)]
33#[doc(hidden)]
34pub struct FileCfg {
35 text_op: TextOp,
36 cfg: PrintCfg,
37}
38
39impl FileCfg {
40 pub(crate) fn new() -> Self {
42 FileCfg {
43 text_op: TextOp::NewBuffer,
44 cfg: PrintCfg::default_for_input(),
45 }
46 }
47
48 pub(crate) fn open_path(self, path: PathBuf) -> Self {
50 Self { text_op: TextOp::OpenPath(path), ..self }
51 }
52
53 pub(crate) fn take_from_prev(
55 self,
56 bytes: Bytes,
57 pk: PathKind,
58 has_unsaved_changes: bool,
59 ) -> Self {
60 Self {
61 text_op: TextOp::TakeBuf(bytes, pk, has_unsaved_changes),
62 ..self
63 }
64 }
65
66 pub(crate) fn set_print_cfg(&mut self, cfg: PrintCfg) {
68 self.cfg = cfg;
69 }
70}
71
72impl<U: Ui> WidgetCfg<U> for FileCfg {
73 type Widget = File<U>;
74
75 fn build(self, _: &mut Pass, _: Option<FileHandle<U>>) -> (Self::Widget, PushSpecs) {
76 let (text, path) = match self.text_op {
77 TextOp::NewBuffer => (Text::new_with_history(), PathKind::new_unset()),
78 TextOp::TakeBuf(bytes, pk, has_unsaved_changes) => match &pk {
79 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
80 let selections = {
81 let cursor = load_cache(path).unwrap_or_default();
82 Selections::new_with_main(cursor)
83 };
84 let text = Text::from_file(bytes, selections, path, has_unsaved_changes);
85 (text, pk)
86 }
87 PathKind::NotSet(_) => (
88 Text::from_bytes(bytes, Some(Selections::default()), true),
89 pk,
90 ),
91 },
92 TextOp::OpenPath(path) => {
93 let canon_path = path.canonicalize();
94 if let Ok(path) = &canon_path
95 && let Ok(file) = std::fs::read_to_string(path)
96 {
97 let selections = {
98 let cursor = load_cache(path).unwrap_or_default();
99 Selections::new_with_main(cursor)
100 };
101 let text = Text::from_file(Bytes::new(&file), selections, path, false);
102 (text, PathKind::SetExists(path.clone()))
103 } else if canon_path.is_err()
104 && let Ok(mut canon_path) = path.with_file_name(".").canonicalize()
105 {
106 canon_path.push(path.file_name().unwrap());
107 (Text::new_with_history(), PathKind::SetAbsent(canon_path))
108 } else {
109 (Text::new_with_history(), PathKind::new_unset())
110 }
111 }
112 };
113
114 let file = File {
115 path,
116 text,
117 cfg: self.cfg,
118 printed_lines: (0..40).map(|i| (i, i == 1)).collect(),
119 readers: Readers::default(),
120 layout_order: 0,
121 _ghost: PhantomData,
122 };
123
124 (file, PushSpecs::above())
126 }
127}
128
129pub struct File<U: Ui> {
131 path: PathKind,
132 text: Text,
133 cfg: PrintCfg,
134 printed_lines: Vec<(usize, bool)>,
135 readers: Readers<U>,
136 pub(crate) layout_order: usize,
137 _ghost: PhantomData<U>,
138}
139
140impl<U: Ui> File<U> {
141 pub fn write(&mut self) -> Result<Option<usize>, Text> {
145 self.write_quit(false)
146 }
147
148 pub(crate) fn write_quit(&mut self, quit: bool) -> Result<Option<usize>, Text> {
149 if let PathKind::SetExists(path) | PathKind::SetAbsent(path) = &self.path {
150 let path = path.clone();
151 if self.text.has_unsaved_changes() {
152 let bytes = self
153 .text
154 .write_to(std::io::BufWriter::new(fs::File::create(&path)?))
155 .inspect(|_| self.path = PathKind::SetExists(path.clone()))?;
156
157 let path = path.to_string_lossy().to_string();
158 hook::queue(FileWritten((path, bytes, quit)));
159
160 Ok(Some(bytes))
161 } else {
162 Ok(None)
163 }
164 } else {
165 Err(txt!("No file was set").build())
166 }
167 }
168
169 pub fn write_to(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<Option<usize>> {
173 self.write_quit_to(path, false)
174 }
175
176 pub(crate) fn write_quit_to(
180 &self,
181 path: impl AsRef<std::path::Path>,
182 quit: bool,
183 ) -> std::io::Result<Option<usize>> {
184 if self.text.has_unsaved_changes() {
185 let path = path.as_ref();
186 let res = self
187 .text
188 .write_to(std::io::BufWriter::new(fs::File::create(path)?))
189 .map(Some);
190
191 if let Ok(Some(bytes)) = res.as_ref() {
192 hook::queue(FileWritten((
193 path.to_string_lossy().to_string(),
194 *bytes,
195 quit,
196 )));
197 }
198
199 res
200 } else {
201 Ok(None)
202 }
203 }
204
205 pub fn path(&self) -> String {
211 self.path.path()
212 }
213
214 pub fn path_set(&self) -> Option<String> {
218 self.path.path_set()
219 }
220
221 pub fn name(&self) -> String {
225 self.path.name()
226 }
227
228 pub fn name_set(&self) -> Option<String> {
232 self.path.name_set()
233 }
234
235 pub fn path_kind(&self) -> PathKind {
241 self.path.clone()
242 }
243
244 pub fn printed_lines(&self) -> &[(usize, bool)] {
250 &self.printed_lines
251 }
252
253 pub fn len_bytes(&self) -> usize {
257 self.text.len().byte()
258 }
259
260 pub fn len_chars(&self) -> usize {
262 self.text.len().char()
263 }
264
265 pub fn len_lines(&self) -> usize {
267 self.text.len().line()
268 }
269
270 pub fn selections(&self) -> &Selections {
273 self.text.selections().unwrap()
274 }
275
276 pub fn selections_mut(&mut self) -> Option<&mut Selections> {
278 self.text.selections_mut()
279 }
280
281 pub fn exists(&self) -> bool {
283 self.path_set()
284 .is_some_and(|p| std::fs::exists(PathBuf::from(&p)).is_ok_and(|e| e))
285 }
286
287 pub fn add_reader(&mut self, pa: &mut Pass, cfg: impl ReaderCfg<U>) {
291 if let Err(err) = self.readers.add(pa, self.text.bytes_mut(), cfg) {
292 context::error!("{err}");
293 }
294 }
295
296 pub fn get_reader<R: Reader<U>>(&self) -> Option<RwData<R>> {
298 self.readers.get()
299 }
300}
301
302impl<U: Ui> Widget<U> for File<U> {
303 type Cfg = FileCfg;
304
305 fn cfg() -> Self::Cfg {
306 FileCfg::new()
307 }
308
309 fn update(pa: &mut Pass, handle: Handle<Self, U>) {
310 let (widget, area) = (handle.widget(), handle.area());
311 let (map, readers) = widget.read(pa, |file| {
312 (BytesDataMap(widget.clone()), file.readers.clone())
313 });
314
315 let moments = widget.acquire_mut(pa).text.last_unprocessed_moment();
316 if let Some(moments) = moments {
317 for moment in moments {
318 readers.process_moment(map.clone(), moment);
319 }
320 }
321
322 let mut file = widget.acquire_mut(pa);
323
324 if let Some(main) = file.text().selections().and_then(Selections::get_main) {
325 area.scroll_around_point(file.text(), main.caret(), file.print_cfg());
326 }
327
328 if file.readers.needs_update() {
329 let (start, _) = area.start_points(&file.text, file.cfg);
330 let (end, _) = area.end_points(&file.text, file.cfg);
331
332 let mut pa = unsafe { Pass::new() };
335 readers.update_range(&mut pa, &mut file.text, start.byte()..end.byte());
336 }
337
338 file.text.update_bounds();
339 }
340
341 fn needs_update(&self) -> bool {
342 false
343 }
344
345 fn text(&self) -> &Text {
346 &self.text
347 }
348
349 fn text_mut(&mut self) -> &mut Text {
350 &mut self.text
351 }
352
353 fn print_cfg(&self) -> PrintCfg {
354 self.cfg
355 }
356
357 fn print(&mut self, painter: Painter, area: &<U as Ui>::Area) {
358 let (start, _) = area.start_points(&self.text, self.cfg);
359
360 let mut last_line = area
361 .rev_print_iter(self.text.iter_rev(start), self.cfg)
362 .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
363
364 self.printed_lines.clear();
365 let printed_lines = &mut self.printed_lines;
366
367 let mut has_wrapped = false;
368
369 area.print_with(&mut self.text, self.cfg, painter, move |caret, item| {
370 has_wrapped |= caret.wrap;
371 if has_wrapped && item.part.is_char() {
372 has_wrapped = false;
373 let line = item.line();
374 let wrapped = last_line.is_some_and(|ll| ll == line);
375 last_line = Some(line);
376 printed_lines.push((line, wrapped));
377 }
378 })
379 }
380
381 fn once() -> Result<(), Text> {
382 Ok(())
383 }
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
388pub enum PathKind {
389 SetExists(PathBuf),
391 SetAbsent(PathBuf),
393 NotSet(usize),
401}
402
403impl PathKind {
404 fn new_unset() -> PathKind {
406 use std::sync::atomic::{AtomicUsize, Ordering};
407 static UNSET_COUNT: AtomicUsize = AtomicUsize::new(1);
408
409 PathKind::NotSet(UNSET_COUNT.fetch_add(1, Ordering::Relaxed))
410 }
411
412 pub fn path(&self) -> String {
416 match self {
417 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
418 path.to_string_lossy().to_string()
419 }
420 PathKind::NotSet(id) => {
421 format!("*scratch file*#{id}")
422 }
423 }
424 }
425
426 pub fn path_set(&self) -> Option<String> {
430 match self {
431 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
432 Some(path.to_string_lossy().to_string())
433 }
434 PathKind::NotSet(_) => None,
435 }
436 }
437
438 pub fn name(&self) -> String {
442 match self {
443 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
444 let cur_dir = context::cur_dir();
445 if let Ok(path) = path.strip_prefix(cur_dir) {
446 path.to_string_lossy().to_string()
447 } else {
448 path.to_string_lossy().to_string()
449 }
450 }
451 PathKind::NotSet(id) => format!("*scratch file #{id}*"),
452 }
453 }
454
455 pub fn name_set(&self) -> Option<String> {
459 match self {
460 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
461 let cur_dir = context::cur_dir();
462 Some(if let Ok(path) = path.strip_prefix(cur_dir) {
463 path.to_string_lossy().to_string()
464 } else {
465 path.to_string_lossy().to_string()
466 })
467 }
468 PathKind::NotSet(_) => None,
469 }
470 }
471}
472
473#[derive(Default, Clone)]
475enum TextOp {
476 #[default]
477 NewBuffer,
478 TakeBuf(Bytes, PathKind, bool),
479 OpenPath(PathBuf),
480}
481
482#[derive(Clone)]
485pub struct BytesDataMap<U: Ui>(RwData<File<U>>);
486
487impl<U: Ui> BytesDataMap<U> {
488 pub fn read<Ret>(&self, pa: &Pass, f: impl FnOnce(&Bytes) -> Ret) -> Ret {
501 self.0.read(pa, |file| f(file.text.bytes()))
502 }
503
504 pub fn write_with_reader<Ret, Rd: Reader<U>>(
517 &self,
518 pa: &mut Pass,
519 rd: &RwData<Rd>,
520 f: impl FnOnce(&mut Bytes, &mut Rd) -> Ret,
521 ) -> Ret {
522 unsafe {
525 self.0
526 .write_unsafe(|file| rd.write(pa, |rd| f(file.text.bytes_mut(), rd)))
527 }
528 }
529
530 pub fn has_changed(&self) -> bool {
546 self.0.has_changed()
547 }
548}