Add tab reload on external change
This commit is contained in:
parent
68675b1a99
commit
f207db0e84
4 changed files with 242 additions and 6 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
|
@ -253,6 +253,24 @@ dependencies = [
|
|||
"libloading 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7370b58af1d7e96df3ca0f454b57e69acf9aa42ed2d7337bd206923bae0d5754"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.5.1"
|
||||
|
|
@ -329,7 +347,7 @@ dependencies = [
|
|||
"polling 2.8.0",
|
||||
"rustix 0.37.27",
|
||||
"slab",
|
||||
"socket2",
|
||||
"socket2 0.4.10",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
|
|
@ -645,6 +663,12 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.18.2"
|
||||
|
|
@ -963,11 +987,13 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"libcosmic",
|
||||
"log",
|
||||
"notify",
|
||||
"rfd",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"syntect",
|
||||
"systemicons",
|
||||
"tokio",
|
||||
"two-face",
|
||||
]
|
||||
|
||||
|
|
@ -2416,6 +2442,7 @@ dependencies = [
|
|||
"futures",
|
||||
"iced_core",
|
||||
"log",
|
||||
"tokio",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-timer",
|
||||
]
|
||||
|
|
@ -2829,6 +2856,7 @@ version = "0.1.0"
|
|||
source = "git+https://github.com/pop-os/libcosmic#001fd744c5f80c9ce058eb0e22ae92f19d12c844"
|
||||
dependencies = [
|
||||
"apply",
|
||||
"ashpd",
|
||||
"cosmic-config",
|
||||
"cosmic-theme",
|
||||
"css-color",
|
||||
|
|
@ -2850,9 +2878,11 @@ dependencies = [
|
|||
"slotmap",
|
||||
"taffy",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4813,6 +4843,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.3.3"
|
||||
|
|
@ -5198,6 +5238,24 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio 0.8.9",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.5.5",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
|
|
@ -5472,6 +5530,7 @@ dependencies = [
|
|||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6419,6 +6478,7 @@ dependencies = [
|
|||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"winapi",
|
||||
|
|
@ -6499,6 +6559,7 @@ dependencies = [
|
|||
"libc",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"url",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@ license = "GPL-3.0-only"
|
|||
env_logger = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.20"
|
||||
notify = "6.1.1"
|
||||
#TODO: this is using gtk for file dialogues
|
||||
rfd = { version = "0.12.0", optional = true }
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
# Extra syntax highlighting
|
||||
syntect = "5.1.0"
|
||||
two-face = "0.3.0"
|
||||
# Internationalization
|
||||
|
|
@ -27,7 +30,7 @@ features = ["syntect", "vi"]
|
|||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
default-features = false
|
||||
features = ["winit", "wgpu"]
|
||||
features = ["tokio", "winit", "wgpu"]
|
||||
#path = "../libcosmic"
|
||||
|
||||
#TODO: clean up and send changes upstream
|
||||
|
|
|
|||
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