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

Add entering normal mode as autosave trigger #6563

Closed
wants to merge 6 commits into from
Closed
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
4 changes: 3 additions & 1 deletion book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
| `auto-format` | Enable automatic formatting on save | `true` |
| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
| `auto-save` | Enable automatic saving on certain triggers, such as `"unfocused"` (when the terminal looses focus)[^1] or `"normal-mode"` (when entering normal mode) | `[]` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` |
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
Expand All @@ -63,6 +63,8 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set | `80` |
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |

[^1]: Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal.

### `[editor.statusline]` Section

Allows configuring the statusline at the bottom of the editor.
Expand Down
10 changes: 8 additions & 2 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use helix_core::{
};
use helix_view::{
document::{Mode, SavePoint, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
editor::{AutoSaveTrigger, CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Expand Down Expand Up @@ -1346,7 +1346,13 @@ impl Component for EditorView {
Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
Event::FocusGained => EventResult::Ignored(None),
Event::FocusLost => {
if context.editor.config().auto_save {
// Trigger autosave if configured
if context
.editor
.config()
.auto_save
.contains(&AutoSaveTrigger::Unfocused)
{
if let Err(e) = commands::typed::write_all_impl(context, false, false) {
context.editor.set_error(format!("{}", e));
}
Expand Down
73 changes: 71 additions & 2 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,55 @@ impl Default for FilePickerConfig {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AutoSaveTrigger {
/// Auto save when the terminal window looses focus
Unfocused,
/// Auto save when the editor (re-)enters normal mode.
NormalMode,
}

/// Parse the value of the `auto-save` key.
/// Can be either a boolean or a list of [AutoSaveTrigger]s.
fn deserialize_autosave_compat<'de, D>(deserializer: D) -> Result<Vec<AutoSaveTrigger>, D::Error>
where
D: Deserializer<'de>,
{
struct AutoSaveVisitor;

impl<'de> serde::de::Visitor<'de> for AutoSaveVisitor {
type Value = Vec<AutoSaveTrigger>;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a list of autosave triggers or a boolean")
}

/// New configuration: a list of AutoSaveTriggers.
fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
where
S: serde::de::SeqAccess<'de>,
{
Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
}

/// Old configuration:
/// if false => no trigger
/// if true => Only the Unfocused trigger.
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match value {
false => Ok(Vec::new()),
true => Ok(vec![AutoSaveTrigger::Unfocused]),
}
}
}

deserializer.deserialize_any(AutoSaveVisitor)
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
Expand Down Expand Up @@ -241,7 +290,8 @@ pub struct Config {
/// Automatic formatting on save. Defaults to true.
pub auto_format: bool,
/// Automatic save on focus lost. Defaults to false.
pub auto_save: bool,
#[serde(deserialize_with = "deserialize_autosave_compat")]
pub auto_save: Vec<AutoSaveTrigger>,
/// Set a global text_width
pub text_width: usize,
/// Time in milliseconds since last keypress before idle timers trigger.
Expand Down Expand Up @@ -732,7 +782,7 @@ impl Default for Config {
auto_pairs: AutoPairConfig::default(),
auto_completion: true,
auto_format: true,
auto_save: false,
auto_save: Vec::new(),
idle_timeout: Duration::from_millis(400),
completion_trigger_len: 2,
auto_info: true,
Expand Down Expand Up @@ -1658,6 +1708,25 @@ impl Editor {
doc.set_selection(view.id, selection);
doc.restore_cursor = false;
}

// Trigger autosave if configured
if self
.config()
.auto_save
.contains(&AutoSaveTrigger::NormalMode)
{
if self.save::<PathBuf>(doc!(self).id, None, false).is_ok() {
// TODO: make 'modified' icon in statusline disappear.
}

// Silently ignore errors.
// Common sources of errors include:
// - No file path set on focused document
// - Partent directory doesn't exist.
//
// In either case, autosave freaking out is probably not
// desired.
}
}

pub fn current_stack_frame(&self) -> Option<&StackFrame> {
Expand Down