Add tab reload on external change
This commit is contained in:
parent
68675b1a99
commit
f207db0e84
4 changed files with 242 additions and 6 deletions
139
src/main.rs
139
src/main.rs
|
|
@ -5,7 +5,9 @@ use cosmic::{
|
|||
cosmic_config::{self, CosmicConfigEntry},
|
||||
cosmic_theme, executor,
|
||||
iced::{
|
||||
clipboard, event, keyboard, subscription,
|
||||
clipboard, event,
|
||||
futures::{self, SinkExt},
|
||||
keyboard, subscription,
|
||||
widget::{row, text},
|
||||
window, Alignment, Length, Point,
|
||||
},
|
||||
|
|
@ -20,6 +22,7 @@ use std::{
|
|||
process,
|
||||
sync::Mutex,
|
||||
};
|
||||
use tokio::time;
|
||||
|
||||
use config::{Action, AppTheme, Config, CONFIG_VERSION};
|
||||
mod config;
|
||||
|
|
@ -104,6 +107,23 @@ pub struct Flags {
|
|||
config: Config,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WatcherWrapper {
|
||||
watcher_opt: Option<notify::RecommendedWatcher>,
|
||||
}
|
||||
|
||||
impl Clone for WatcherWrapper {
|
||||
fn clone(&self) -> Self {
|
||||
Self { watcher_opt: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WatcherWrapper {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Message {
|
||||
|
|
@ -118,6 +138,8 @@ pub enum Message {
|
|||
Key(keyboard::Modifiers, keyboard::KeyCode),
|
||||
NewFile,
|
||||
NewWindow,
|
||||
NotifyEvent(notify::Event),
|
||||
NotifyWatcher(WatcherWrapper),
|
||||
OpenFileDialog,
|
||||
OpenFile(PathBuf),
|
||||
OpenProjectDialog,
|
||||
|
|
@ -171,6 +193,7 @@ pub struct App {
|
|||
font_sizes: Vec<u16>,
|
||||
theme_names: Vec<String>,
|
||||
context_page: ContextPage,
|
||||
watcher_opt: Option<notify::RecommendedWatcher>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -304,6 +327,7 @@ impl App {
|
|||
|
||||
let mut tab = Tab::new(&self.config);
|
||||
tab.open(canonical);
|
||||
tab.watch(&mut self.watcher_opt);
|
||||
tab
|
||||
}
|
||||
None => Tab::new(&self.config),
|
||||
|
|
@ -337,9 +361,7 @@ impl App {
|
|||
log::error!("failed to save config: {}", err);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
//TODO: log that there is no handler?
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.update_config()
|
||||
}
|
||||
|
|
@ -485,6 +507,7 @@ impl Application for App {
|
|||
font_sizes,
|
||||
theme_names,
|
||||
context_page: ContextPage::Settings,
|
||||
watcher_opt: None,
|
||||
};
|
||||
|
||||
for arg in env::args().skip(1) {
|
||||
|
|
@ -681,6 +704,57 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
Message::NotifyEvent(event) => {
|
||||
let mut needs_reload = Vec::new();
|
||||
for entity in self.tab_model.iter() {
|
||||
match self.tab_model.data::<Tab>(entity) {
|
||||
Some(tab) => {
|
||||
if let Some(path) = &tab.path_opt {
|
||||
if event.paths.contains(&path) {
|
||||
if tab.changed() {
|
||||
log::warn!(
|
||||
"file changed externally before being saved: {:?}",
|
||||
path
|
||||
);
|
||||
} else {
|
||||
needs_reload.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
for entity in needs_reload {
|
||||
match self.tab_model.data_mut::<Tab>(entity) {
|
||||
Some(tab) => {
|
||||
tab.reload();
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to find tab {:?} that needs reload", entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take()
|
||||
{
|
||||
Some(watcher) => {
|
||||
self.watcher_opt = Some(watcher);
|
||||
|
||||
for entity in self.tab_model.iter() {
|
||||
match self.tab_model.data::<Tab>(entity) {
|
||||
Some(tab) => {
|
||||
tab.watch(&mut self.watcher_opt);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("message did not contain notify watcher");
|
||||
}
|
||||
},
|
||||
Message::OpenFileDialog => {
|
||||
#[cfg(feature = "rfd")]
|
||||
return Command::perform(
|
||||
|
|
@ -1113,6 +1187,63 @@ impl Application for App {
|
|||
}) => Some(Message::Key(modifiers, key_code)),
|
||||
_ => None,
|
||||
}),
|
||||
subscription::channel(0, 100, |mut output| async move {
|
||||
let watcher_res = {
|
||||
let mut output = output.clone();
|
||||
notify::recommended_watcher(
|
||||
move |event_res: Result<notify::Event, notify::Error>| match event_res {
|
||||
Ok(event) => {
|
||||
match &event.kind {
|
||||
notify::EventKind::Access(_)
|
||||
| notify::EventKind::Modify(
|
||||
notify::event::ModifyKind::Metadata(_),
|
||||
) => {
|
||||
// Data not mutated
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match futures::executor::block_on(async {
|
||||
output.send(Message::NotifyEvent(event)).await
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to send notify event: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch files: {:?}", err);
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
match watcher_res {
|
||||
Ok(watcher) => {
|
||||
match output
|
||||
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||
watcher_opt: Some(watcher),
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to send notify watcher: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create file watcher: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: how to properly kill this task?
|
||||
loop {
|
||||
time::sleep(time::Duration::new(1, 0)).await;
|
||||
}
|
||||
}),
|
||||
cosmic_config::config_subscription(0, Self::APP_ID.into(), CONFIG_VERSION).map(
|
||||
|(_, res)| match res {
|
||||
Ok(config) => Message::Config(config),
|
||||
|
|
|
|||
41
src/tab.rs
41
src/tab.rs
|
|
@ -5,6 +5,7 @@ use cosmic::{
|
|||
widget::{icon, Icon},
|
||||
};
|
||||
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||
use notify::Watcher;
|
||||
use std::{fs, path::PathBuf, sync::Mutex};
|
||||
|
||||
use crate::{fl, mime_icon, Config, FALLBACK_MIME_ICON, FONT_SYSTEM, SYNTAX_SYSTEM};
|
||||
|
|
@ -82,6 +83,31 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
||||
let mut editor = editor.borrow_with(&mut font_system);
|
||||
if let Some(path) = &self.path_opt {
|
||||
// Save scroll
|
||||
let scroll = editor.buffer().scroll();
|
||||
//TODO: save/restore more?
|
||||
|
||||
match editor.load_text(path, self.attrs) {
|
||||
Ok(()) => {
|
||||
log::info!("reloaded {:?}", path);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to reload {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore scroll
|
||||
editor.buffer_mut().set_scroll(scroll);
|
||||
} else {
|
||||
log::warn!("tried to reload with no path");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
if let Some(path) = &self.path_opt {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
|
|
@ -104,6 +130,21 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn watch(&self, watcher_opt: &mut Option<notify::RecommendedWatcher>) {
|
||||
if let Some(path) = &self.path_opt {
|
||||
if let Some(watcher) = watcher_opt {
|
||||
match watcher.watch(&path, notify::RecursiveMode::NonRecursive) {
|
||||
Ok(()) => {
|
||||
log::info!("watching {:?} for changes", path);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch {:?} for changes: {:?}", path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changed(&self) -> bool {
|
||||
let editor = self.editor.lock().unwrap();
|
||||
editor.changed()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue