Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto save files after timeout #146

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions i18n/en/cosmic_edit.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ prompt-save-changes-title = Unsaved changes
prompt-unsaved-changes = You have unsaved changes. Save?
discard = Discard changes

## Session
session = Session
seconds = seconds
auto-save-secs = Auto save (seconds)

## Settings
settings = Settings

Expand Down
4 changes: 3 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use cosmic::{
};
use cosmic_text::Metrics;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::PathBuf};
use std::{collections::VecDeque, path::PathBuf, num::NonZeroU64};

pub const CONFIG_VERSION: u64 = 1;

Expand Down Expand Up @@ -40,6 +40,7 @@ pub struct Config {
pub tab_width: u16,
pub vim_bindings: bool,
pub word_wrap: bool,
pub auto_save_secs: Option<NonZeroU64>
}

impl Default for Config {
Expand All @@ -56,6 +57,7 @@ impl Default for Config {
tab_width: 4,
vim_bindings: false,
word_wrap: true,
auto_save_secs: None
}
}
}
Expand Down
119 changes: 117 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use std::{
any::TypeId,
collections::HashMap,
env, fs, io,
num::NonZeroU64,
path::{Path, PathBuf},
process,
sync::{Mutex, OnceLock},
Expand Down Expand Up @@ -61,6 +62,9 @@ mod project;
use self::search::ProjectSearchResult;
mod search;

mod session;
use session::{auto_save_subscription, AutoSaveMessage};

use self::tab::{EditorTab, GitDiffTab, Tab};
mod tab;

Expand Down Expand Up @@ -303,6 +307,8 @@ impl PartialEq for WatcherWrapper {
#[derive(Clone, Debug)]
pub enum Message {
AppTheme(AppTheme),
AutoSaveSender(futures::channel::mpsc::Sender<AutoSaveMessage>),
AutoSaveTimeout(Option<NonZeroU64>),
Config(Config),
ConfigState(ConfigState),
CloseFile,
Expand Down Expand Up @@ -345,6 +351,7 @@ pub enum Message {
Quit,
Redo,
Save,
SaveAny(segmented_button::Entity),
SaveAsDialog,
SaveAsResult(segmented_button::Entity, DialogResult),
SelectAll,
Expand Down Expand Up @@ -430,6 +437,7 @@ pub struct App {
project_search_value: String,
project_search_result: Option<ProjectSearchResult>,
watcher_opt: Option<notify::RecommendedWatcher>,
auto_save_sender: Option<futures::channel::mpsc::Sender<AutoSaveMessage>>,
modifiers: Modifiers,
}

Expand Down Expand Up @@ -720,6 +728,25 @@ impl App {
])
}

// Send a message to the auto saver if enabled
fn update_auto_saver(&mut self, message: AutoSaveMessage) -> Command<Message> {
if let Some(mut sender) = self
.config
// Auto saving is enabled if a timeout is set
.auto_save_secs
.and_then(|_| self.auto_save_sender.clone())
{
Command::perform(async move { sender.send(message).await }, |res| {
if let Err(e) = res {
log::error!("failed to send message to auto saver: {e}");
}
message::none()
})
} else {
Command::none()
}
}

fn about(&self) -> Element<Message> {
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
let repository = "https://github.com/pop-os/cosmic-edit";
Expand Down Expand Up @@ -1123,6 +1150,11 @@ impl App {
.font_sizes
.iter()
.position(|font_size| font_size == &self.config.font_size);
let save_seconds = self
.config
.auto_save_secs
.map(|secs| secs.to_string())
.unwrap_or_default();
widget::settings::view_column(vec![
widget::settings::view_section(fl!("appearance"))
.add(
Expand Down Expand Up @@ -1173,6 +1205,16 @@ impl App {
.toggler(self.config.vim_bindings, Message::VimBindings),
)
.into(),
widget::settings::view_section(fl!("session"))
.add(
widget::settings::item::builder(fl!("auto-save-secs")).control(
widget::text_input(fl!("seconds"), save_seconds).on_input(|s| {
let secs = s.parse().ok();
Message::AutoSaveTimeout(secs)
}),
),
)
.into(),
])
.into()
}
Expand Down Expand Up @@ -1273,6 +1315,7 @@ impl Application for App {
project_search_value: String::new(),
project_search_result: None,
watcher_opt: None,
auto_save_sender: None,
modifiers: Modifiers::empty(),
};

Expand Down Expand Up @@ -1421,6 +1464,39 @@ impl Application for App {
self.config.app_theme = app_theme;
return self.save_config();
}
Message::AutoSaveSender(sender) => {
self.auto_save_sender = Some(sender);
}
Message::AutoSaveTimeout(timeout) => {
self.config.auto_save_secs = timeout;
if let Some(timeout) = timeout {
let entities: Vec<_> = self
.tab_model
.iter()
.filter_map(|entity| {
self.tab_model.data::<Tab>(entity).map(|tab| {
if let Tab::Editor(tab) = tab {
tab.changed().then_some(entity)
} else {
None
}
})
})
.flatten()
.collect();

// Set new timeout and register all modified tabs.
return Command::batch([
self.save_config(),
self.update_auto_saver(AutoSaveMessage::UpdateTimeout(timeout)),
self.update_auto_saver(AutoSaveMessage::RegisterBatch(entities)),
]);
}
return Command::batch([
self.save_config(),
self.update_auto_saver(AutoSaveMessage::CancelAll),
]);
}
Message::Config(config) => {
if config != self.config {
log::info!("update config");
Expand Down Expand Up @@ -1920,6 +1996,21 @@ impl Application for App {
if let Some(title) = title_opt {
self.tab_model.text_set(self.tab_model.active(), title);
}

// Remove saved tab from auto saver to avoid double saves
let entity = self.tab_model.active();
return self.update_auto_saver(AutoSaveMessage::Cancel(entity));
}
Message::SaveAny(entity) => {
// TODO: This variant and code should be updated to save backups instead of overwriting
// the open file
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
let title = tab.title();
if tab.path_opt.is_some() {
tab.save();
self.tab_model.text_set(entity, title);
}
}
}
Message::SaveAsDialog => {
if self.dialog_opt.is_none() {
Expand Down Expand Up @@ -2025,7 +2116,12 @@ impl Application for App {
let mut title = tab.title();
//TODO: better way of adding change indicator
title.push_str(" \u{2022}");
let has_path = tab.path_opt.is_some();
self.tab_model.text_set(entity, title);
// Register tab with the auto saver
if has_path {
return self.update_auto_saver(AutoSaveMessage::Register(entity));
}
}
}
Message::TabClose(entity) => {
Expand Down Expand Up @@ -2086,17 +2182,24 @@ impl Application for App {
entity,
))),
self.update_tab(),
self.update_auto_saver(AutoSaveMessage::Cancel(entity)),
]);
}

return self.update_tab();
return Command::batch([
self.update_tab(),
self.update_auto_saver(AutoSaveMessage::Cancel(entity)),
]);
}
Message::TabContextAction(entity, action) => {
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
// Close context menu
tab.context_menu = None;
// Run action's message
return self.update(action.message());
return Command::batch([
self.update(action.message()),
self.update_auto_saver(AutoSaveMessage::Cancel(entity)),
]);
}
}
Message::TabContextMenu(entity, position_opt) => {
Expand Down Expand Up @@ -2654,6 +2757,18 @@ impl Application for App {
Some(dialog) => dialog.subscription(),
None => subscription::Subscription::none(),
},
auto_save_subscription(
self.config
.auto_save_secs
// Autosave won't be triggered until the user enables it regardless of passing
// a timeout and starting the subscription.
.unwrap_or(NonZeroU64::new(1).unwrap()),
)
.map(|update| match update {
AutoSaveMessage::Ready(sender) => Message::AutoSaveSender(sender),
AutoSaveMessage::Save(entity) => Message::SaveAny(entity),
_ => unreachable!(),
}),
])
}
}
Loading