mirror of https://github.com/helix-editor/helix
Merge 0df5f5f446
into 4281228da3
commit
36754e35ef
|
@ -7,6 +7,7 @@
|
|||
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
|
||||
- [`[editor.file-picker]` Section](#editorfile-picker-section)
|
||||
- [`[editor.auto-pairs]` Section](#editorauto-pairs-section)
|
||||
- [`[editor.auto-reload]` Section](#editorauto-reload-section)
|
||||
- [`[editor.auto-save]` Section](#editorauto-save-section)
|
||||
- [`[editor.search]` Section](#editorsearch-section)
|
||||
- [`[editor.whitespace]` Section](#editorwhitespace-section)
|
||||
|
@ -265,6 +266,16 @@ name = "rust"
|
|||
'<' = '>'
|
||||
```
|
||||
|
||||
### `[editor.auto-reload]` Section
|
||||
|
||||
Controls auto reloading of externally modified files.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `focus-gained` | Enable automatic reloading of externally modified files when Helix is focused. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
|
||||
| `periodic.enable` | Enable periodic auto reloading of externally modified files | `false` |
|
||||
| `periodic.interval` | Time interval in milliseconds between auto reload checks | `3000` |
|
||||
|
||||
### `[editor.auto-save]` Section
|
||||
|
||||
Control auto save behavior.
|
||||
|
|
|
@ -1406,7 +1406,11 @@ fn reload(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
|
||||
pub fn reload_all(
|
||||
cx: &mut compositor::Context,
|
||||
_args: Args,
|
||||
event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
if event != PromptEvent::Validate {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use helix_event::AsyncHook;
|
|||
|
||||
use crate::config::Config;
|
||||
use crate::events;
|
||||
use crate::handlers::auto_reload::AutoReloadHandler;
|
||||
use crate::handlers::auto_save::AutoSaveHandler;
|
||||
use crate::handlers::signature_help::SignatureHelpHandler;
|
||||
|
||||
|
@ -12,6 +13,7 @@ pub use helix_view::handlers::{word_index, Handlers};
|
|||
|
||||
use self::document_colors::DocumentColorsHandler;
|
||||
|
||||
pub(super) mod auto_reload;
|
||||
mod auto_save;
|
||||
pub mod completion;
|
||||
mod diagnostics;
|
||||
|
@ -25,6 +27,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
|
|||
let event_tx = completion::CompletionHandler::new(config).spawn();
|
||||
let signature_hints = SignatureHelpHandler::new().spawn();
|
||||
let auto_save = AutoSaveHandler::new().spawn();
|
||||
let auto_reload = AutoReloadHandler::new().spawn();
|
||||
let document_colors = DocumentColorsHandler::default().spawn();
|
||||
let word_index = word_index::Handler::spawn();
|
||||
|
||||
|
@ -32,6 +35,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
|
|||
completions: helix_view::handlers::completion::CompletionHandler::new(event_tx),
|
||||
signature_hints,
|
||||
auto_save,
|
||||
auto_reload,
|
||||
document_colors,
|
||||
word_index,
|
||||
};
|
||||
|
@ -40,6 +44,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
|
|||
completion::register_hooks(&handlers);
|
||||
signature_help::register_hooks(&handlers);
|
||||
auto_save::register_hooks(&handlers);
|
||||
auto_reload::register_hooks(&handlers);
|
||||
diagnostics::register_hooks(&handlers);
|
||||
snippet::register_hooks(&handlers);
|
||||
document_colors::register_hooks(&handlers);
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{self, AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use helix_core::command_line::Args;
|
||||
use helix_event::{register_hook, send_blocking};
|
||||
use helix_view::document::Mode;
|
||||
use helix_view::events::DocumentDidOpen;
|
||||
use helix_view::handlers::{AutoReloadEvent, Handlers};
|
||||
use helix_view::{Document, Editor};
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::compositor::Compositor;
|
||||
use crate::events::OnModeSwitch;
|
||||
use crate::job;
|
||||
use crate::ui::{Prompt, PromptEvent};
|
||||
use crate::{commands, ui};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AutoReloadHandler {
|
||||
reload_pending: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AutoReloadHandler {
|
||||
pub fn new() -> AutoReloadHandler {
|
||||
AutoReloadHandler {
|
||||
reload_pending: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl helix_event::AsyncHook for AutoReloadHandler {
|
||||
type Event = AutoReloadEvent;
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: Self::Event,
|
||||
existing_debounce: Option<Instant>,
|
||||
) -> Option<Instant> {
|
||||
match event {
|
||||
Self::Event::CheckForChanges { after } => {
|
||||
Some(Instant::now() + Duration::from_millis(after))
|
||||
}
|
||||
Self::Event::LeftInsertMode | Self::Event::EditorFocused => {
|
||||
if existing_debounce.is_some() {
|
||||
// If the event happened more recently than the debounce, let the
|
||||
// debounce run down before checking for changes.
|
||||
existing_debounce
|
||||
} else {
|
||||
// Otherwise if there is a reload pending, check immediately.
|
||||
if self.reload_pending.load(Ordering::Relaxed) {
|
||||
self.finish_debounce();
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_debounce(&mut self) {
|
||||
let reload_pending = self.reload_pending.clone();
|
||||
job::dispatch_blocking(move |editor, compositor| {
|
||||
if editor.mode() == Mode::Insert {
|
||||
// Avoid reloading while in insert mode since this mixes up
|
||||
// the modification indicator and prevents future saves.
|
||||
reload_pending.store(true, atomic::Ordering::Relaxed);
|
||||
} else {
|
||||
prompt_to_reload_if_needed(editor, compositor);
|
||||
reload_pending.store(false, atomic::Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests a reload if any documents have been modified externally.
|
||||
fn prompt_to_reload_if_needed(editor: &mut Editor, compositor: &mut Compositor) {
|
||||
// If there are no externally modified documents, we can do nothing.
|
||||
if count_externally_modified_documents(editor.documents()) == 0 {
|
||||
// Reset the debounce timer to allow for the next check.
|
||||
let config = editor.config.load();
|
||||
if config.auto_reload.periodic.enable {
|
||||
let interval = config.auto_reload.periodic.interval;
|
||||
send_blocking(
|
||||
&editor.handlers.auto_reload,
|
||||
AutoReloadEvent::CheckForChanges { after: interval },
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let prompt = Prompt::new(
|
||||
Cow::Borrowed("Some files have been modified externally, press Enter to reload them."),
|
||||
None,
|
||||
ui::completers::none,
|
||||
|cx, _, event| {
|
||||
if event == PromptEvent::Update {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) =
|
||||
commands::typed::reload_all(cx, Args::default(), PromptEvent::Validate)
|
||||
{
|
||||
cx.editor
|
||||
.set_error(format!("Failed to reload document: {err}"));
|
||||
} else {
|
||||
cx.editor.set_status("Reloaded modified documents");
|
||||
}
|
||||
|
||||
// Reset the debounce timer to allow for the next check.
|
||||
let config = cx.editor.config.load();
|
||||
if config.auto_reload.periodic.enable {
|
||||
let interval = config.auto_reload.periodic.interval;
|
||||
send_blocking(
|
||||
&cx.editor.handlers.auto_reload,
|
||||
AutoReloadEvent::CheckForChanges { after: interval },
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Show the prompt to the user.
|
||||
compositor.push(Box::new(prompt));
|
||||
}
|
||||
|
||||
pub fn count_externally_modified_documents<'a>(docs: impl Iterator<Item = &'a Document>) -> usize {
|
||||
docs // Filter out documents that have unsaved changes.
|
||||
.filter(|doc| !doc.is_modified())
|
||||
// Get the documents that have been modified externally.
|
||||
.filter(|doc| {
|
||||
let last_saved_time = doc.get_last_saved_time();
|
||||
let Some(path) = doc.path() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if the file has been modified externally
|
||||
if let Ok(metadata) = fs::metadata(path) {
|
||||
if let Ok(modified_time) = metadata.modified() {
|
||||
if modified_time > last_saved_time {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
pub(super) fn register_hooks(handlers: &Handlers) {
|
||||
let tx = handlers.auto_reload.clone();
|
||||
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
|
||||
let config = event.editor.config.load();
|
||||
if config.auto_reload.periodic.enable {
|
||||
let interval = config.auto_reload.periodic.interval;
|
||||
send_blocking(&tx, AutoReloadEvent::CheckForChanges { after: interval });
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let tx = handlers.auto_reload.clone();
|
||||
register_hook!(move |event: &mut OnModeSwitch<'_, '_>| {
|
||||
if event.old_mode == Mode::Insert {
|
||||
send_blocking(&tx, AutoReloadEvent::LeftInsertMode)
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -1468,6 +1468,24 @@ impl Component for EditorView {
|
|||
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
|
||||
Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
|
||||
Event::FocusGained => {
|
||||
if context.editor.config().auto_reload.focus_gained {
|
||||
if crate::handlers::auto_reload::count_externally_modified_documents(
|
||||
context.editor.documents(),
|
||||
) > 0
|
||||
{
|
||||
if let Err(e) = commands::typed::reload_all(
|
||||
context,
|
||||
helix_core::command_line::Args::default(),
|
||||
super::PromptEvent::Validate,
|
||||
) {
|
||||
context.editor.set_error(format!("{}", e));
|
||||
} else {
|
||||
context
|
||||
.editor
|
||||
.set_status("Reloaded files due to external changes");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.terminal_focused = true;
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
|
|
|
@ -1200,6 +1200,11 @@ impl Document {
|
|||
};
|
||||
}
|
||||
|
||||
/// Return the last saved time of the document.
|
||||
pub fn get_last_saved_time(&self) -> SystemTime {
|
||||
self.last_saved_time
|
||||
}
|
||||
|
||||
// Detect if the file is readonly and change the readonly field if necessary (unix only)
|
||||
pub fn detect_readonly(&mut self) {
|
||||
// Allows setting the flag for files the user cannot modify, like root files
|
||||
|
|
|
@ -63,6 +63,7 @@ use arc_swap::{
|
|||
};
|
||||
|
||||
pub const DEFAULT_AUTO_SAVE_DELAY: u64 = 3000;
|
||||
pub const DEFAULT_AUTO_RELOAD_INTERVAL: u64 = 3000;
|
||||
|
||||
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
|
@ -290,6 +291,10 @@ pub struct Config {
|
|||
/// Time delay defaults to false with 3000ms delay. Focus lost defaults to false.
|
||||
#[serde(deserialize_with = "deserialize_auto_save")]
|
||||
pub auto_save: AutoSave,
|
||||
/// Automatic reload of the modified documents on a periodic time interval and/or when the editor gains focus.
|
||||
/// Time interval defaults to false with 3000ms delay. Focus gained defaults to false.
|
||||
#[serde(deserialize_with = "deserialize_auto_reload")]
|
||||
pub auto_reload: AutoReload,
|
||||
/// Set a global text_width
|
||||
pub text_width: usize,
|
||||
/// Time in milliseconds since last keypress before idle timers trigger.
|
||||
|
@ -886,6 +891,52 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct AutoReload {
|
||||
/// Whether to check for file changes when the editor is focused. Defaults to false.
|
||||
#[serde(default)]
|
||||
pub focus_gained: bool,
|
||||
/// Autosave periodically at some interval. Defaults to disabled.
|
||||
#[serde(default)]
|
||||
pub periodic: AutoReloadPeriodic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AutoReloadPeriodic {
|
||||
#[serde(default)]
|
||||
/// Enable auto reload periodically. Defaults to false.
|
||||
pub enable: bool,
|
||||
#[serde(default = "default_auto_reload_interval")]
|
||||
/// Time interval in milliseconds. Defaults to [DEFAULT_AUTO_RELOAD_INTERVAL].
|
||||
pub interval: u64,
|
||||
}
|
||||
|
||||
pub fn default_auto_reload_interval() -> u64 {
|
||||
DEFAULT_AUTO_RELOAD_INTERVAL
|
||||
}
|
||||
|
||||
fn deserialize_auto_reload<'de, D>(deserializer: D) -> Result<AutoReload, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(untagged, deny_unknown_fields, rename_all = "kebab-case")]
|
||||
enum AutoReloadToml {
|
||||
FocusGained(bool),
|
||||
AutoReload(AutoReload),
|
||||
}
|
||||
|
||||
match AutoReloadToml::deserialize(deserializer)? {
|
||||
AutoReloadToml::FocusGained(focus_gained) => Ok(AutoReload {
|
||||
focus_gained,
|
||||
..Default::default()
|
||||
}),
|
||||
AutoReloadToml::AutoReload(auto_reload) => Ok(auto_reload),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct WhitespaceCharacters {
|
||||
|
@ -1016,6 +1067,7 @@ impl Default for Config {
|
|||
auto_format: true,
|
||||
default_yank_register: '"',
|
||||
auto_save: AutoSave::default(),
|
||||
auto_reload: AutoReload::default(),
|
||||
idle_timeout: Duration::from_millis(250),
|
||||
completion_timeout: Duration::from_millis(250),
|
||||
preview_completion_insert: true,
|
||||
|
|
|
@ -17,11 +17,19 @@ pub enum AutoSaveEvent {
|
|||
LeftInsertMode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AutoReloadEvent {
|
||||
CheckForChanges { after: u64 },
|
||||
EditorFocused,
|
||||
LeftInsertMode,
|
||||
}
|
||||
|
||||
pub struct Handlers {
|
||||
// only public because most of the actual implementation is in helix-term right now :/
|
||||
pub completions: CompletionHandler,
|
||||
pub signature_hints: Sender<lsp::SignatureHelpEvent>,
|
||||
pub auto_save: Sender<AutoSaveEvent>,
|
||||
pub auto_reload: Sender<AutoReloadEvent>,
|
||||
pub document_colors: Sender<lsp::DocumentColorsEvent>,
|
||||
pub word_index: word_index::Handler,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue