Skip to content
Merged
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
77 changes: 77 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions otty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ thiserror = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
shell-words = "1.1.0"
notify = "8.2.0"

[package.metadata.generate-rpm]
maintainer = "Ilya Shvyryalkin <ilyashvy@gmail.com>"
Expand Down
32 changes: 32 additions & 0 deletions otty/src/events/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ fn handle_effect(app: &mut App, effect: ExplorerEffect) -> Task<AppEvent> {
}
})
},
ExplorerEffect::ReloadDirectoryRequested { directory } => {
load_directory_async(directory.clone(), move |nodes| {
ExplorerIntent::DirectoryReloaded {
directory: directory.clone(),
nodes,
}
})
},
ExplorerEffect::OpenFileTerminalTab { file_path } => {
open_file_terminal_tab(app, file_path)
},
Expand Down Expand Up @@ -100,3 +108,27 @@ fn open_file_terminal_tab(app: &mut App, file_path: PathBuf) -> Task<AppEvent> {
},
)))
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::handle_effect;
use crate::app::App;
use crate::widgets::explorer::ExplorerEffect;

#[test]
fn given_reload_directory_requested_when_handled_then_load_task_is_emitted()
{
let (mut app, _) = App::new();

let task = handle_effect(
&mut app,
ExplorerEffect::ReloadDirectoryRequested {
directory: PathBuf::from("/tmp/project"),
},
);

assert_eq!(task.units(), 1);
}
}
61 changes: 60 additions & 1 deletion otty/src/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ pub(crate) fn context_menu_guard(event: &AppEvent) -> MenuGuard {
| crate::widgets::settings::SettingsIntent::SaveFailed(_),
),
) => Allow,
AppEvent::Explorer(
crate::widgets::explorer::ExplorerEvent::Effect(_),
)
| AppEvent::Explorer(
crate::widgets::explorer::ExplorerEvent::Intent(
crate::widgets::explorer::ExplorerIntent::RootLoaded { .. }
| crate::widgets::explorer::ExplorerIntent::FolderLoaded {
..
}
| crate::widgets::explorer::ExplorerIntent::DirectoryChanged {
..
}
| crate::widgets::explorer::ExplorerIntent::DirectoryReloaded {
..
}
| crate::widgets::explorer::ExplorerIntent::LoadFailed { .. },
),
) => Allow,
AppEvent::Explorer(
crate::widgets::explorer::ExplorerEvent::Intent(
crate::widgets::explorer::ExplorerIntent::SyncRoot { .. },
Expand Down Expand Up @@ -178,8 +196,22 @@ pub(crate) fn inline_edit_guard(event: &AppEvent) -> bool {
),
) => false,
AppEvent::Explorer(
crate::widgets::explorer::ExplorerEvent::Effect(_),
)
| AppEvent::Explorer(
crate::widgets::explorer::ExplorerEvent::Intent(
crate::widgets::explorer::ExplorerIntent::SyncRoot { .. },
crate::widgets::explorer::ExplorerIntent::SyncRoot { .. }
| crate::widgets::explorer::ExplorerIntent::RootLoaded { .. }
| crate::widgets::explorer::ExplorerIntent::FolderLoaded {
..
}
| crate::widgets::explorer::ExplorerIntent::DirectoryChanged {
..
}
| crate::widgets::explorer::ExplorerIntent::DirectoryReloaded {
..
}
| crate::widgets::explorer::ExplorerIntent::LoadFailed { .. },
),
) => false,
AppEvent::SyncTerminalGridSizes => false,
Expand All @@ -193,8 +225,11 @@ pub(crate) fn inline_edit_guard(event: &AppEvent) -> bool {

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::{MenuGuard, context_menu_guard, inline_edit_guard};
use crate::events::AppEvent;
use crate::widgets::explorer::{ExplorerEvent, ExplorerIntent};
use crate::widgets::sidebar::{SidebarEvent, SidebarIntent};

#[test]
Expand All @@ -211,4 +246,28 @@ mod tests {
{
assert!(!inline_edit_guard(&AppEvent::SyncTerminalGridSizes));
}

#[test]
fn given_explorer_directory_change_when_context_menu_guard_runs_then_event_is_allowed()
{
let guard = context_menu_guard(&AppEvent::Explorer(
ExplorerEvent::Intent(ExplorerIntent::DirectoryChanged {
directory: PathBuf::from("/tmp/project"),
}),
));

assert!(matches!(guard, MenuGuard::Allow));
}

#[test]
fn given_explorer_directory_change_when_inline_edit_guard_runs_then_edit_is_not_cancelled()
{
let event = AppEvent::Explorer(ExplorerEvent::Intent(
ExplorerIntent::DirectoryChanged {
directory: PathBuf::from("/tmp/project"),
},
));

assert!(!inline_edit_guard(&event));
}
}
2 changes: 2 additions & 0 deletions otty/src/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub(super) fn subscription(app: &App) -> Subscription<AppEvent> {
}
}

subs.push(app.widgets.explorer.subscription().map(AppEvent::Explorer));

// Quick launch tick for launch indicators and auto-persist
if app.widgets.quick_launch.has_active_launches()
|| app.widgets.quick_launch.state_is_dirty()
Expand Down
9 changes: 9 additions & 0 deletions otty/src/widgets/explorer/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ pub(crate) enum ExplorerIntent {
path: TreePath,
nodes: Vec<FileNode>,
},
/// A watched directory emitted a filesystem tree change.
DirectoryChanged { directory: PathBuf },
/// Watched directory contents reloaded successfully.
DirectoryReloaded {
directory: PathBuf,
nodes: Vec<FileNode>,
},
/// A directory load operation failed.
LoadFailed { message: String },
}
Expand All @@ -29,6 +36,8 @@ pub(crate) enum ExplorerEffect {
LoadRootRequested { root: PathBuf },
/// Request asynchronous loading of a folder's children.
LoadFolderRequested { path: TreePath, directory: PathBuf },
/// Request asynchronous reloading of a watched directory.
ReloadDirectoryRequested { directory: PathBuf },
/// Request opening a file in a command terminal tab.
OpenFileTerminalTab { file_path: PathBuf },
}
Expand Down
41 changes: 40 additions & 1 deletion otty/src/widgets/explorer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ pub(crate) mod services;
pub(crate) mod state;
pub(crate) mod types;
pub(crate) mod view;
mod watcher;

pub(crate) use event::{ExplorerEffect, ExplorerEvent, ExplorerIntent};
use iced::Task;
use iced::{Subscription, Task};
pub(crate) use reducer::ExplorerCtx;
use state::ExplorerState;

Expand Down Expand Up @@ -36,6 +37,11 @@ impl ExplorerWidget {
reducer::reduce(&mut self.state, event, ctx)
}

/// Return active filesystem watcher subscription for loaded directories.
pub(crate) fn subscription(&self) -> Subscription<ExplorerEvent> {
watcher::subscription(self.state.watched_directories())
}

/// Return a tree view model for the sidebar panel.
pub(crate) fn vm(&self) -> model::ExplorerTreeViewModel<'_> {
model::ExplorerTreeViewModel {
Expand Down Expand Up @@ -72,3 +78,36 @@ impl ExplorerWidget {
&self.state
}
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::{ExplorerCtx, ExplorerIntent, ExplorerWidget};

#[test]
fn given_empty_explorer_when_subscription_requested_then_no_units_are_registered()
{
let widget = ExplorerWidget::new();

assert_eq!(widget.subscription().units(), 0);
}

#[test]
fn given_rooted_explorer_when_subscription_requested_then_watcher_unit_is_registered()
{
let mut widget = ExplorerWidget::new();
let ctx = ExplorerCtx {
active_shell_cwd: None,
};

let _task = widget.reduce(
ExplorerIntent::SyncRoot {
cwd: PathBuf::from("/tmp/project"),
},
&ctx,
);

assert_eq!(widget.subscription().units(), 1);
}
}
Loading
Loading