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",
|
"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]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -329,7 +347,7 @@ dependencies = [
|
||||||
"polling 2.8.0",
|
"polling 2.8.0",
|
||||||
"rustix 0.37.27",
|
"rustix 0.37.27",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2 0.4.10",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -645,6 +663,12 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-sys-rs"
|
name = "cairo-sys-rs"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
|
|
@ -963,11 +987,13 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libcosmic",
|
"libcosmic",
|
||||||
"log",
|
"log",
|
||||||
|
"notify",
|
||||||
"rfd",
|
"rfd",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"syntect",
|
"syntect",
|
||||||
"systemicons",
|
"systemicons",
|
||||||
|
"tokio",
|
||||||
"two-face",
|
"two-face",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2416,6 +2442,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"log",
|
"log",
|
||||||
|
"tokio",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-timer",
|
"wasm-timer",
|
||||||
]
|
]
|
||||||
|
|
@ -2829,6 +2856,7 @@ version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic#001fd744c5f80c9ce058eb0e22ae92f19d12c844"
|
source = "git+https://github.com/pop-os/libcosmic#001fd744c5f80c9ce058eb0e22ae92f19d12c844"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apply",
|
"apply",
|
||||||
|
"ashpd",
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
"cosmic-theme",
|
"cosmic-theme",
|
||||||
"css-color",
|
"css-color",
|
||||||
|
|
@ -2850,9 +2878,11 @@ dependencies = [
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"taffy",
|
"taffy",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4813,6 +4843,16 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "softbuffer"
|
name = "softbuffer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -5198,6 +5238,24 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
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]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
|
|
@ -5472,6 +5530,7 @@ dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6419,6 +6478,7 @@ dependencies = [
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"sha1",
|
"sha1",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
|
@ -6499,6 +6559,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
|
"url",
|
||||||
"zvariant_derive",
|
"zvariant_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,12 @@ license = "GPL-3.0-only"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
notify = "6.1.1"
|
||||||
#TODO: this is using gtk for file dialogues
|
#TODO: this is using gtk for file dialogues
|
||||||
rfd = { version = "0.12.0", optional = true }
|
rfd = { version = "0.12.0", optional = true }
|
||||||
serde = { version = "1", features = ["serde_derive"] }
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
|
tokio = { version = "1", features = ["time"] }
|
||||||
|
# Extra syntax highlighting
|
||||||
syntect = "5.1.0"
|
syntect = "5.1.0"
|
||||||
two-face = "0.3.0"
|
two-face = "0.3.0"
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
|
@ -27,7 +30,7 @@ features = ["syntect", "vi"]
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
git = "https://github.com/pop-os/libcosmic"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["winit", "wgpu"]
|
features = ["tokio", "winit", "wgpu"]
|
||||||
#path = "../libcosmic"
|
#path = "../libcosmic"
|
||||||
|
|
||||||
#TODO: clean up and send changes upstream
|
#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_config::{self, CosmicConfigEntry},
|
||||||
cosmic_theme, executor,
|
cosmic_theme, executor,
|
||||||
iced::{
|
iced::{
|
||||||
clipboard, event, keyboard, subscription,
|
clipboard, event,
|
||||||
|
futures::{self, SinkExt},
|
||||||
|
keyboard, subscription,
|
||||||
widget::{row, text},
|
widget::{row, text},
|
||||||
window, Alignment, Length, Point,
|
window, Alignment, Length, Point,
|
||||||
},
|
},
|
||||||
|
|
@ -20,6 +22,7 @@ use std::{
|
||||||
process,
|
process,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
use config::{Action, AppTheme, Config, CONFIG_VERSION};
|
use config::{Action, AppTheme, Config, CONFIG_VERSION};
|
||||||
mod config;
|
mod config;
|
||||||
|
|
@ -104,6 +107,23 @@ pub struct Flags {
|
||||||
config: Config,
|
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)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
|
|
@ -118,6 +138,8 @@ pub enum Message {
|
||||||
Key(keyboard::Modifiers, keyboard::KeyCode),
|
Key(keyboard::Modifiers, keyboard::KeyCode),
|
||||||
NewFile,
|
NewFile,
|
||||||
NewWindow,
|
NewWindow,
|
||||||
|
NotifyEvent(notify::Event),
|
||||||
|
NotifyWatcher(WatcherWrapper),
|
||||||
OpenFileDialog,
|
OpenFileDialog,
|
||||||
OpenFile(PathBuf),
|
OpenFile(PathBuf),
|
||||||
OpenProjectDialog,
|
OpenProjectDialog,
|
||||||
|
|
@ -171,6 +193,7 @@ pub struct App {
|
||||||
font_sizes: Vec<u16>,
|
font_sizes: Vec<u16>,
|
||||||
theme_names: Vec<String>,
|
theme_names: Vec<String>,
|
||||||
context_page: ContextPage,
|
context_page: ContextPage,
|
||||||
|
watcher_opt: Option<notify::RecommendedWatcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -304,6 +327,7 @@ impl App {
|
||||||
|
|
||||||
let mut tab = Tab::new(&self.config);
|
let mut tab = Tab::new(&self.config);
|
||||||
tab.open(canonical);
|
tab.open(canonical);
|
||||||
|
tab.watch(&mut self.watcher_opt);
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
None => Tab::new(&self.config),
|
None => Tab::new(&self.config),
|
||||||
|
|
@ -337,9 +361,7 @@ impl App {
|
||||||
log::error!("failed to save config: {}", err);
|
log::error!("failed to save config: {}", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {}
|
||||||
//TODO: log that there is no handler?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.update_config()
|
self.update_config()
|
||||||
}
|
}
|
||||||
|
|
@ -485,6 +507,7 @@ impl Application for App {
|
||||||
font_sizes,
|
font_sizes,
|
||||||
theme_names,
|
theme_names,
|
||||||
context_page: ContextPage::Settings,
|
context_page: ContextPage::Settings,
|
||||||
|
watcher_opt: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for arg in env::args().skip(1) {
|
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 => {
|
Message::OpenFileDialog => {
|
||||||
#[cfg(feature = "rfd")]
|
#[cfg(feature = "rfd")]
|
||||||
return Command::perform(
|
return Command::perform(
|
||||||
|
|
@ -1113,6 +1187,63 @@ impl Application for App {
|
||||||
}) => Some(Message::Key(modifiers, key_code)),
|
}) => Some(Message::Key(modifiers, key_code)),
|
||||||
_ => None,
|
_ => 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(
|
cosmic_config::config_subscription(0, Self::APP_ID.into(), CONFIG_VERSION).map(
|
||||||
|(_, res)| match res {
|
|(_, res)| match res {
|
||||||
Ok(config) => Message::Config(config),
|
Ok(config) => Message::Config(config),
|
||||||
|
|
|
||||||
41
src/tab.rs
41
src/tab.rs
|
|
@ -5,6 +5,7 @@ use cosmic::{
|
||||||
widget::{icon, Icon},
|
widget::{icon, Icon},
|
||||||
};
|
};
|
||||||
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||||
|
use notify::Watcher;
|
||||||
use std::{fs, path::PathBuf, sync::Mutex};
|
use std::{fs, path::PathBuf, sync::Mutex};
|
||||||
|
|
||||||
use crate::{fl, mime_icon, Config, FALLBACK_MIME_ICON, FONT_SYSTEM, SYNTAX_SYSTEM};
|
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) {
|
pub fn save(&mut self) {
|
||||||
if let Some(path) = &self.path_opt {
|
if let Some(path) = &self.path_opt {
|
||||||
let mut editor = self.editor.lock().unwrap();
|
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 {
|
pub fn changed(&self) -> bool {
|
||||||
let editor = self.editor.lock().unwrap();
|
let editor = self.editor.lock().unwrap();
|
||||||
editor.changed()
|
editor.changed()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue