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 if let PathKind::SetExists(path) | PathKind::SetAbsent(path) = &self.path {
146 let path = path.clone();
147 if self.text.has_unsaved_changes() {
148 let bytes = self
149 .text
150 .write_to(std::io::BufWriter::new(fs::File::create(&path)?))
151 .inspect(|_| self.path = PathKind::SetExists(path.clone()))?;
152
153 let path = path.to_string_lossy().to_string();
154 hook::queue(FileWritten((path, bytes)));
155
156 Ok(Some(bytes))
157 } else {
158 Ok(None)
159 }
160 } else {
161 Err(txt!("No file was set").build())
162 }
163 }
164
165 pub fn write_to(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<Option<usize>> {
169 if self.text.has_unsaved_changes() {
170 let path = path.as_ref();
171 let res = self
172 .text
173 .write_to(std::io::BufWriter::new(fs::File::create(path)?))
174 .map(Some);
175
176 if let Ok(Some(bytes)) = res.as_ref() {
177 hook::queue(FileWritten((path.to_string_lossy().to_string(), *bytes)));
178 }
179
180 res
181 } else {
182 Ok(None)
183 }
184 }
185
186 pub fn path(&self) -> String {
192 self.path.path()
193 }
194
195 pub fn path_set(&self) -> Option<String> {
199 self.path.path_set()
200 }
201
202 pub fn name(&self) -> String {
206 self.path.name()
207 }
208
209 pub fn name_set(&self) -> Option<String> {
213 self.path.name_set()
214 }
215
216 pub fn path_kind(&self) -> PathKind {
222 self.path.clone()
223 }
224
225 pub fn printed_lines(&self) -> &[(usize, bool)] {
231 &self.printed_lines
232 }
233
234 pub fn len_bytes(&self) -> usize {
238 self.text.len().byte()
239 }
240
241 pub fn len_chars(&self) -> usize {
243 self.text.len().char()
244 }
245
246 pub fn len_lines(&self) -> usize {
248 self.text.len().line()
249 }
250
251 pub fn selections(&self) -> &Selections {
254 self.text.selections().unwrap()
255 }
256
257 pub fn selections_mut(&mut self) -> Option<&mut Selections> {
259 self.text.selections_mut()
260 }
261
262 pub fn exists(&self) -> bool {
264 self.path_set()
265 .is_some_and(|p| std::fs::exists(PathBuf::from(&p)).is_ok_and(|e| e))
266 }
267
268 pub fn add_reader(&mut self, pa: &mut Pass, cfg: impl ReaderCfg<U>) {
272 if let Err(err) = self.readers.add(pa, self.text.bytes_mut(), cfg) {
273 context::error!("{err}");
274 }
275 }
276
277 pub fn get_reader<R: Reader<U>>(&self) -> Option<RwData<R>> {
279 self.readers.get()
280 }
281}
282
283impl<U: Ui> Widget<U> for File<U> {
284 type Cfg = FileCfg;
285
286 fn cfg() -> Self::Cfg {
287 FileCfg::new()
288 }
289
290 fn update(pa: &mut Pass, handle: Handle<Self, U>) {
291 let (widget, area) = (handle.widget(), handle.area());
292 let (map, readers) = widget.read(pa, |file| {
293 (BytesDataMap(widget.clone()), file.readers.clone())
294 });
295
296 let moments = widget.acquire_mut(pa).text.last_unprocessed_moment();
297 if let Some(moments) = moments {
298 for moment in moments {
299 readers.process_moment(map.clone(), moment);
300 }
301 }
302
303 let mut file = widget.acquire_mut(pa);
304
305 if let Some(main) = file.text().selections().and_then(Selections::get_main) {
306 area.scroll_around_point(file.text(), main.caret(), file.print_cfg());
307 }
308
309 if file.readers.needs_update() {
310 let (start, _) = area.first_points(&file.text, file.cfg);
311 let (end, _) = area.last_points(&file.text, file.cfg);
312
313 let mut pa = unsafe { Pass::new() };
316 readers.update_range(&mut pa, &mut file.text, start.byte()..end.byte());
317 }
318
319 file.text.update_bounds();
320 }
321
322 fn text(&self) -> &Text {
323 &self.text
324 }
325
326 fn text_mut(&mut self) -> &mut Text {
327 &mut self.text
328 }
329
330 fn needs_update(&self) -> bool {
331 false
332 }
333
334 fn print_cfg(&self) -> PrintCfg {
335 self.cfg
336 }
337
338 fn print(&mut self, painter: Painter, area: &<U as Ui>::Area) {
339 let (start, _) = area.first_points(&self.text, self.cfg);
340
341 let mut last_line = area
342 .rev_print_iter(self.text.iter_rev(start), self.cfg)
343 .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
344
345 self.printed_lines.clear();
346 let printed_lines = &mut self.printed_lines;
347
348 let mut has_wrapped = false;
349
350 area.print_with(&mut self.text, self.cfg, painter, move |caret, item| {
351 has_wrapped |= caret.wrap;
352 if has_wrapped && item.part.is_char() {
353 has_wrapped = false;
354 let line = item.line();
355 let wrapped = last_line.is_some_and(|ll| ll == line);
356 last_line = Some(line);
357 printed_lines.push((line, wrapped));
358 }
359 })
360 }
361
362 fn once() -> Result<(), Text> {
363 Ok(())
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
369pub enum PathKind {
370 SetExists(PathBuf),
372 SetAbsent(PathBuf),
374 NotSet(usize),
382}
383
384impl PathKind {
385 fn new_unset() -> PathKind {
387 use std::sync::atomic::{AtomicUsize, Ordering};
388 static UNSET_COUNT: AtomicUsize = AtomicUsize::new(1);
389
390 PathKind::NotSet(UNSET_COUNT.fetch_add(1, Ordering::Relaxed))
391 }
392
393 pub fn path(&self) -> String {
397 match self {
398 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
399 path.to_string_lossy().to_string()
400 }
401 PathKind::NotSet(id) => {
402 format!("*scratch file*#{id}")
403 }
404 }
405 }
406
407 pub fn path_set(&self) -> Option<String> {
411 match self {
412 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
413 Some(path.to_string_lossy().to_string())
414 }
415 PathKind::NotSet(_) => None,
416 }
417 }
418
419 pub fn name(&self) -> String {
423 match self {
424 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
425 let cur_dir = context::cur_dir();
426 if let Ok(path) = path.strip_prefix(cur_dir) {
427 path.to_string_lossy().to_string()
428 } else {
429 path.to_string_lossy().to_string()
430 }
431 }
432 PathKind::NotSet(id) => format!("*scratch file #{id}*"),
433 }
434 }
435
436 pub fn name_set(&self) -> Option<String> {
440 match self {
441 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
442 let cur_dir = context::cur_dir();
443 Some(if let Ok(path) = path.strip_prefix(cur_dir) {
444 path.to_string_lossy().to_string()
445 } else {
446 path.to_string_lossy().to_string()
447 })
448 }
449 PathKind::NotSet(_) => None,
450 }
451 }
452}
453
454#[derive(Default, Clone)]
456enum TextOp {
457 #[default]
458 NewBuffer,
459 TakeBuf(Bytes, PathKind, bool),
460 OpenPath(PathBuf),
461}
462
463#[derive(Clone)]
466pub struct BytesDataMap<U: Ui>(RwData<File<U>>);
467
468impl<U: Ui> BytesDataMap<U> {
469 pub fn read<Ret>(&self, pa: &Pass, f: impl FnOnce(&Bytes) -> Ret) -> Ret {
482 self.0.read(pa, |file| f(file.text.bytes()))
483 }
484
485 pub fn read_and_write_reader<Ret, Rd: Reader<U>>(
498 &self,
499 pa: &mut Pass,
500 rd: &RwData<Rd>,
501 f: impl FnOnce(&Bytes, &mut Rd) -> Ret,
502 ) -> Ret {
503 unsafe {
506 self.0
507 .read_unsafe(|file| rd.write(pa, |rd| f(file.text.bytes(), rd)))
508 }
509 }
510
511 pub fn has_changed(&self) -> bool {
527 self.0.has_changed()
528 }
529}