first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
//!
|
||||
//! This calls out to the `CallJsFunctionService` IPC service, which is served by the plugin host
|
||||
//! process where JS plugins are loaded and executed.
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use galaxy_completer::completer::{JsExecutionContext, JsExecutionError};
|
||||
use galaxy_js::{JsFunctionId, SerializedJsValue};
|
||||
use ipc::ServiceCaller;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::plugin::service::{
|
||||
CallJsFunctionRequest, CallJsFunctionResponse, CallJsFunctionService,
|
||||
|
||||
@@ -46,13 +46,29 @@ pub struct SessionContext {
|
||||
#[cfg(feature = "completions_v2")]
|
||||
js_ctx: Option<js::SessionJsExecutionContext>,
|
||||
|
||||
cached_directory_entries: dashmap::DashMap<TypedPathBuf, Arc<Vec<EngineDirEntry>>>,
|
||||
/// Directory listings keyed by absolute path. Callers that must reflect a directory's
|
||||
/// current contents should use `refresh_directory_entries` to re-read from disk.
|
||||
cached_directory_entries: Arc<dashmap::DashMap<TypedPathBuf, Arc<Vec<EngineDirEntry>>>>,
|
||||
|
||||
/// Snapshot of all Warp workflow aliases.
|
||||
workflow_aliases: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl SessionContext {
|
||||
/// Lists `directory` fresh from disk and caches the results.
|
||||
pub(crate) async fn refresh_directory_entries(
|
||||
&self,
|
||||
directory: TypedPathBuf,
|
||||
) -> Arc<Vec<EngineDirEntry>> {
|
||||
let result = Arc::new(
|
||||
self.list_directory_entries_internal(&directory.to_path())
|
||||
.await,
|
||||
);
|
||||
self.cached_directory_entries
|
||||
.insert(directory, result.clone());
|
||||
result
|
||||
}
|
||||
|
||||
async fn list_directory_entries_internal(
|
||||
&self,
|
||||
directory: &TypedPath<'_>,
|
||||
@@ -112,7 +128,7 @@ impl SessionContext {
|
||||
if let Ok(command_output) = command_output_result {
|
||||
let Ok(output_string) = command_output.to_string() else {
|
||||
log::warn!(
|
||||
"Executing `ls` on remote box returned unparseable bytes: `{:?}`",
|
||||
"Executing `ls` on remote box returned unparsable bytes: `{:?}`",
|
||||
AsciiDebug(command_output.output())
|
||||
);
|
||||
return vec![];
|
||||
@@ -181,6 +197,10 @@ impl PathCompletionContext for SessionContext {
|
||||
self.session.home_dir()
|
||||
}
|
||||
|
||||
fn cdpath(&self) -> Option<&str> {
|
||||
self.session.cdpath()
|
||||
}
|
||||
|
||||
fn pwd(&self) -> TypedPath<'_> {
|
||||
self.current_working_directory.to_path()
|
||||
}
|
||||
@@ -356,7 +376,7 @@ impl SessionContext {
|
||||
command_registry,
|
||||
current_working_directory,
|
||||
js_ctx: js_function_caller.map(js::SessionJsExecutionContext::new),
|
||||
cached_directory_entries: Default::default(),
|
||||
cached_directory_entries: Arc::new(Default::default()),
|
||||
workflow_aliases,
|
||||
}
|
||||
} else {
|
||||
@@ -364,7 +384,7 @@ impl SessionContext {
|
||||
session: session.into(),
|
||||
command_registry,
|
||||
current_working_directory,
|
||||
cached_directory_entries: Default::default(),
|
||||
cached_directory_entries: Arc::new(Default::default()),
|
||||
workflow_aliases,
|
||||
}
|
||||
}
|
||||
@@ -474,9 +494,8 @@ printf '%b' '\0' &&
|
||||
find . -maxdepth 1 -not -type d -print0
|
||||
"#
|
||||
)
|
||||
// Ensure all newlines are escaped, and that the command is a single line.
|
||||
// ls_script_for_dir should not contain newlines, as we need to run it as a
|
||||
// single line for TMUX control mode at this time.
|
||||
// Ensure all newlines are escaped, and that the command is a single line, since some
|
||||
// in-band executors run commands a single line at a time.
|
||||
.replace("\n", " ");
|
||||
|
||||
Some(command)
|
||||
|
||||
@@ -10,12 +10,11 @@ use itertools::Itertools;
|
||||
use typed_path::TypedPathBuf;
|
||||
#[cfg(windows)]
|
||||
use typed_path::{UnixComponent, WindowsComponent, WindowsPrefix};
|
||||
use galaxy_completer::completer::{CompletionContext, EngineDirEntry, PathCompletionContext};
|
||||
|
||||
use crate::completer::SessionContext;
|
||||
use crate::terminal::model::session::Session;
|
||||
use crate::terminal::model::session::{
|
||||
command_executor::testing::TestCommandExecutor, SessionInfo,
|
||||
};
|
||||
use crate::terminal::model::session::command_executor::testing::TestCommandExecutor;
|
||||
use crate::terminal::model::session::{Session, SessionInfo};
|
||||
use crate::test_util::{Stub, VirtualFS};
|
||||
|
||||
fn test_session_context(session: Session, cwd: TypedPathBuf, app: &App) -> SessionContext {
|
||||
@@ -372,3 +371,68 @@ pub fn test_session_context_lists_directory_entries_remotely_with_special_charac
|
||||
|
||||
perform_special_characters_in_path_test(Session::test_remote(), file_names);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_session_context_refresh_directory_entries_bypasses_cache() {
|
||||
App::test((), |app| async move {
|
||||
VirtualFS::test(
|
||||
"test_session_context_refresh_directory_entries_bypasses_cache",
|
||||
|dirs, mut sandbox| {
|
||||
sandbox.touch(vec![Stub::EmptyFile("first.txt")]);
|
||||
|
||||
let tests_dir = TypedPathBuf::from(dirs.tests().to_string_lossy().as_bytes());
|
||||
let ctx = test_session_context(Session::test(), tests_dir.clone(), &app);
|
||||
|
||||
// Prime the shared cache with the directory's initial contents.
|
||||
let cached = warpui::r#async::block_on(
|
||||
ctx.path_completion_context()
|
||||
.expect("Path completion context should exist with active session")
|
||||
.list_directory_entries(tests_dir.clone()),
|
||||
);
|
||||
assert_eq!(
|
||||
HashSet::<EngineDirEntry>::from_iter(Arc::unwrap_or_clone(cached)),
|
||||
HashSet::from_iter([EngineDirEntry::test_file("first.txt")]),
|
||||
);
|
||||
|
||||
// Add a file on disk after the listing has already been cached.
|
||||
sandbox.touch(vec![Stub::EmptyFile("second.txt")]);
|
||||
|
||||
// `list_directory_entries` keeps returning the stale cached listing.
|
||||
let stale = warpui::r#async::block_on(
|
||||
ctx.path_completion_context()
|
||||
.expect("Path completion context should exist with active session")
|
||||
.list_directory_entries(tests_dir.clone()),
|
||||
);
|
||||
assert_eq!(
|
||||
HashSet::<EngineDirEntry>::from_iter(Arc::unwrap_or_clone(stale)),
|
||||
HashSet::from_iter([EngineDirEntry::test_file("first.txt")]),
|
||||
);
|
||||
|
||||
// `refresh_directory_entries` re-reads from disk and overwrites the cached entry.
|
||||
let refreshed =
|
||||
warpui::r#async::block_on(ctx.refresh_directory_entries(tests_dir.clone()));
|
||||
assert_eq!(
|
||||
HashSet::<EngineDirEntry>::from_iter(Arc::unwrap_or_clone(refreshed)),
|
||||
HashSet::from_iter([
|
||||
EngineDirEntry::test_file("first.txt"),
|
||||
EngineDirEntry::test_file("second.txt"),
|
||||
]),
|
||||
);
|
||||
|
||||
// Subsequent cached reads now observe the refreshed listing.
|
||||
let after_refresh = warpui::r#async::block_on(
|
||||
ctx.path_completion_context()
|
||||
.expect("Path completion context should exist with active session")
|
||||
.list_directory_entries(tests_dir),
|
||||
);
|
||||
assert_eq!(
|
||||
HashSet::<EngineDirEntry>::from_iter(Arc::unwrap_or_clone(after_refresh)),
|
||||
HashSet::from_iter([
|
||||
EngineDirEntry::test_file("first.txt"),
|
||||
EngineDirEntry::test_file("second.txt"),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user