Added file watching and reloading
This commit is contained in:
parent
e3e32baf83
commit
32589dab6d
5 changed files with 92 additions and 25 deletions
|
|
@ -44,7 +44,10 @@ pub enum Action {
|
|||
SelectToken, // Fully select the token under the cursor
|
||||
SelectAll, // Fully select the entire input
|
||||
Save, // Save the current buffer
|
||||
Overwrite, // Save the current buffer, forcefully
|
||||
Reload, // Reload the current file from disk, losing unsaved changes
|
||||
Mouse(MouseAction, [isize; 2], bool, usize), // (action, pos, is_ctrl, drag_id)
|
||||
Confirm(String, Box<Self>),
|
||||
Undo,
|
||||
Redo,
|
||||
Copy,
|
||||
|
|
|
|||
76
src/state.rs
76
src/state.rs
|
|
@ -6,6 +6,7 @@ use std::{
|
|||
io,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
new_key_type! {
|
||||
|
|
@ -140,14 +141,15 @@ impl Text {
|
|||
#[derive(Default)]
|
||||
pub struct Buffer {
|
||||
pub unsaved: bool,
|
||||
pub diverged: bool,
|
||||
pub text: Text,
|
||||
pub lang: LangPack,
|
||||
pub highlights: Highlights,
|
||||
pub cursors: HopSlotMap<CursorId, Cursor>,
|
||||
pub dir: Option<PathBuf>,
|
||||
pub path: Option<PathBuf>,
|
||||
pub undo: Vec<Change>,
|
||||
pub redo: Vec<Change>,
|
||||
opened_at: Option<SystemTime>,
|
||||
action_counter: usize,
|
||||
most_recent_rank: usize,
|
||||
}
|
||||
|
|
@ -178,34 +180,24 @@ impl Change {
|
|||
|
||||
impl Buffer {
|
||||
pub fn from_file(path: PathBuf) -> Result<Self, Error> {
|
||||
let (unsaved, dir, chars, s) = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => {
|
||||
let mut path = path.canonicalize()?;
|
||||
path.pop();
|
||||
(false, Some(path), s.chars().collect(), s)
|
||||
}
|
||||
let (unsaved, chars, s) = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => (false, s.chars().collect(), s),
|
||||
// If the file doesn't exist, create a new file
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
let dir = path
|
||||
.parent()
|
||||
.filter(|p| p.to_str() != Some(""))
|
||||
.map(Path::to_owned)
|
||||
.or_else(|| std::env::current_dir().ok());
|
||||
(true, dir, Vec::new(), String::new())
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => (true, Vec::new(), String::new()),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
let lang = LangPack::from_file_name(&path);
|
||||
Ok(Self {
|
||||
unsaved,
|
||||
diverged: false,
|
||||
highlights: lang.highlighter.highlight(&chars),
|
||||
lang,
|
||||
text: Text { chars },
|
||||
cursors: HopSlotMap::default(),
|
||||
dir,
|
||||
path: Some(path),
|
||||
path: Some(path.canonicalize()?),
|
||||
undo: Vec::new(),
|
||||
redo: Vec::new(),
|
||||
opened_at: Some(SystemTime::now()),
|
||||
action_counter: 0,
|
||||
most_recent_rank: 0,
|
||||
})
|
||||
|
|
@ -216,6 +208,8 @@ impl Buffer {
|
|||
self.path.as_ref().expect("buffer must have path to save"),
|
||||
self.text.to_string(),
|
||||
)?;
|
||||
self.diverged = false;
|
||||
self.opened_at = Some(SystemTime::now());
|
||||
self.unsaved = false;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -223,7 +217,16 @@ impl Buffer {
|
|||
pub fn name(&self) -> Option<String> {
|
||||
Some(
|
||||
match self.path.as_ref()?.file_name().and_then(|n| n.to_str()) {
|
||||
Some(name) => format!("{}{name}", if self.unsaved { "* " } else { "" }),
|
||||
Some(name) => format!(
|
||||
"{}{name}",
|
||||
if self.diverged {
|
||||
"! "
|
||||
} else if self.unsaved {
|
||||
"* "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
None => "<error>".to_string(),
|
||||
},
|
||||
)
|
||||
|
|
@ -844,6 +847,40 @@ impl Buffer {
|
|||
path.canonicalize().ok().map_or(false, |path| *p == path)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
if let Some(path) = &self.path {
|
||||
if let Ok(text) = std::fs::read_to_string(path) {
|
||||
self.text = Text {
|
||||
chars: text.chars().collect(),
|
||||
};
|
||||
self.opened_at = Some(SystemTime::now());
|
||||
self.diverged = false;
|
||||
self.unsaved = false;
|
||||
self.undo.clear();
|
||||
self.redo.clear();
|
||||
self.update_highlights();
|
||||
} else {
|
||||
self.diverged = true;
|
||||
self.unsaved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
if let Some(path) = &self.path {
|
||||
let stale = std::fs::metadata(path)
|
||||
.and_then(|m| m.modified())
|
||||
.ok()
|
||||
.and_then(|lm| Some(lm > self.opened_at?));
|
||||
match (stale, self.unsaved) {
|
||||
(Some(false), _) => {}
|
||||
(Some(true), true) => self.diverged = true,
|
||||
(Some(true), false) => self.reload(),
|
||||
(None, _) => self.diverged = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLassify the character by property
|
||||
|
|
@ -901,6 +938,9 @@ impl State {
|
|||
|
||||
pub fn tick(&mut self) {
|
||||
self.tick += 1;
|
||||
for b in self.buffers.values_mut() {
|
||||
b.tick();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_most_recent(&mut self, buffer: BufferId) {
|
||||
|
|
|
|||
|
|
@ -63,9 +63,13 @@ impl Element for Doc {
|
|||
return Err(event);
|
||||
};
|
||||
|
||||
let open_path = buffer
|
||||
.dir
|
||||
let mut open_path = buffer
|
||||
.path
|
||||
.to_owned()
|
||||
.map(|mut p| {
|
||||
p.pop();
|
||||
p
|
||||
})
|
||||
.unwrap_or_else(|| std::env::current_dir().expect("no working dir"));
|
||||
|
||||
match event.to_action(|e| {
|
||||
|
|
@ -125,12 +129,26 @@ impl Element for Doc {
|
|||
Action::Show(Some(format!("Could not create file")), format!("{err}")).into(),
|
||||
))),
|
||||
},
|
||||
Some(Action::Save) => {
|
||||
let event = buffer.save().err().map(|err| {
|
||||
Action::Show(Some("Could not save file".to_string()), err.to_string()).into()
|
||||
});
|
||||
Ok(Resp::handled(event))
|
||||
Some(Action::Overwrite) => Ok(Resp::handled(buffer.save().err().map(|err| {
|
||||
Action::Show(Some("Could not save file".to_string()), err.to_string()).into()
|
||||
}))),
|
||||
Some(Action::Reload) => {
|
||||
buffer.reload();
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Some(Action::Save) => Ok(Resp::handled(if buffer.diverged {
|
||||
Some(
|
||||
Action::Confirm(
|
||||
format!("File has diverged on disk. Are you sure you wish to save (y/n)?"),
|
||||
Box::new(Action::Overwrite),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
buffer.save().err().map(|err| {
|
||||
Action::Show(Some("Could not save file".to_string()), err.to_string()).into()
|
||||
})
|
||||
})),
|
||||
_ => {
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return Err(event);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ impl Prompt {
|
|||
- version\n\
|
||||
- pane_move_left\n\
|
||||
- pane_move_right\n\
|
||||
- reload : Reload the current file from disk, dropping unsaved changes\n\
|
||||
- help"
|
||||
),
|
||||
)),
|
||||
|
|
@ -58,6 +59,7 @@ impl Prompt {
|
|||
let needle = cmd.get(arg0.len()..).unwrap().trim().to_string();
|
||||
Ok(Action::BeginSearch(needle))
|
||||
}
|
||||
Some("reload") => Ok(Action::Reload),
|
||||
Some(cmd) => Err(format!("Unknown command `{cmd}`")),
|
||||
None => Err(format!("No command entered")),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,10 @@ impl Element<()> for Root {
|
|||
}));
|
||||
}
|
||||
}
|
||||
Action::Confirm(q, action) => self.tasks.push(Task::Confirm(Confirm {
|
||||
label: Label(q),
|
||||
action: *action,
|
||||
})),
|
||||
Action::Show(title, text) => self.tasks.push(Task::Show(Show {
|
||||
title,
|
||||
label: Label(text),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue