Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+750
View File
@@ -0,0 +1,750 @@
#[cfg(feature = "local_fs")]
use indexmap::IndexSet;
#[cfg(feature = "local_fs")]
use repo_metadata::repositories::DetectedRepositories;
use std::collections::HashMap;
#[cfg(feature = "local_fs")]
use std::collections::HashSet;
use std::path::{Path, PathBuf};
#[cfg(feature = "local_fs")]
use warpui::{AppContext, SingletonEntity as _};
use warpui::{Entity, EntityId, ModelContext};
use warpui::{ModelHandle, ViewHandle};
#[cfg(feature = "local_fs")]
use crate::code::file_tree::FileTreeView;
use crate::code_review::comments::{
AttachedReviewComment, PendingImportedReviewComment, ReviewCommentBatch,
};
use crate::code_review::{
code_review_view::CodeReviewView,
diff_state::{DiffMode, DiffStateModel},
};
use crate::workspace::view::global_search::view::GlobalSearchView;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkingDirectory {
pub path: PathBuf,
pub terminal_id: Option<EntityId>,
}
/// Events emitted when the set of working directories changes
#[derive(Clone, Debug)]
pub enum WorkingDirectoriesEvent {
/// The set of working directories has changed for a specific pane group.
DirectoriesChanged {
/// The PaneGroup whose directories changed
pane_group_id: EntityId,
/// All active directories (deduplicated) in most to least recently added order.
directories: Vec<WorkingDirectory>,
},
/// The set of repositories has changed for a specific pane group.
RepositoriesChanged {
/// The PaneGroup whose repositories changed
pane_group_id: EntityId,
/// All active repository roots (deduplicated) in most to least recently added order.
repositories: Vec<PathBuf>,
},
/// The focused repository changed for a specific pane group.
/// This fires when the user focuses a different pane or CDs within the focused pane.
FocusedRepoChanged {
/// The PaneGroup whose focused repo changed
pane_group_id: EntityId,
/// All active repository-terminal ID pairs (deduplicated)
repository_terminal_map: HashMap<PathBuf, EntityId>,
/// The repository path of the focused terminal, if any
focused_repo: Option<PathBuf>,
},
}
#[derive(Default)]
#[cfg(feature = "local_fs")]
/// Workspace model that tracks working directories across all pane groups.
/// Emits events when the set of directories changes for any pane group.
pub struct WorkingDirectoriesModel {
/// Per-pane-group tracking of active directories as a deduplicated, ordered set.
///
/// IMPORTANT: This stores the *display roots* for the left panel (file tree / global search),
/// not the raw working directories reported by each pane.
///
/// Concretely, for each pane group's active paths we store:
/// - the detected repository root when the path belongs to a repo
/// - otherwise, the normalized path itself
///
/// IndexSet maintains insertion order - most recently added directories appear later.
pane_groups: HashMap<EntityId, IndexSet<PathBuf>>,
/// Per-pane-group tracking of active repository roots as a deduplicated, ordered set.
/// IndexSet maintains insertion order - most recently added repositories appear later.
repository_roots: HashMap<EntityId, IndexSet<PathBuf>>,
/// Per-pane-group mapping from root paths to a matching terminal view ID.
/// This allows looking up which terminal is associated with each root path.
/// Note, a single root path can be associated with multiple terminals.
/// we're just storing an arbitrary terminal ID for each root path.
directory_to_terminal: HashMap<EntityId, HashMap<PathBuf, EntityId>>,
/// Global mapping from repository root paths to their DiffStateModel.
/// Since git state is inherently tied to a repository (not a pane group),
/// this is stored globally and shared across all pane groups viewing the same repo.
diff_state_models: HashMap<PathBuf, ModelHandle<DiffStateModel>>,
/// Global mapping from repository root paths to their CommentBatch.
/// Like the DiffStateModel mapping, comments are inherently tied to git diffs
/// and are shared across all pane groups viewing the same repo.
comment_models: HashMap<PathBuf, ModelHandle<ReviewCommentBatch>>,
/// Per-pane-group mapping from repository root paths to their CodeReviewView.
/// This allows reusing code review views across multiple requests for the same repo.
code_review_views: HashMap<EntityId, HashMap<PathBuf, ViewHandle<CodeReviewView>>>,
/// Per-pane-group tracking of the focused repository root path.
focused_repo: HashMap<EntityId, Option<PathBuf>>,
global_search_views: HashMap<EntityId, ViewHandle<GlobalSearchView>>,
file_tree_views: HashMap<EntityId, ViewHandle<FileTreeView>>,
}
#[derive(Default)]
#[cfg(not(feature = "local_fs"))]
/// Does nothing without a local file system
pub struct WorkingDirectoriesModel {}
/// Index Sets are ordered by insertion order. This function updates an index set to match a new set of items.
#[cfg(feature = "local_fs")]
pub fn update_index_set(
index_set: &mut IndexSet<PathBuf>,
new_items: impl IntoIterator<Item = PathBuf>,
) {
let new_items: Vec<PathBuf> = new_items.into_iter().collect();
index_set.retain(|item| new_items.iter().any(|new_item| new_item == item));
for item in new_items {
index_set.insert(item);
}
}
#[cfg(feature = "local_fs")]
impl WorkingDirectoriesModel {
pub fn new() -> Self {
Self::default()
}
/// Get the unique directories for a specific pane group in insertion order (oldest first).
fn least_recent_directories_for_pane_group(
&self,
pane_group_id: EntityId,
) -> Option<&IndexSet<PathBuf>> {
self.pane_groups.get(&pane_group_id)
}
/// Get the unique directories for a specific pane group in most to least recently added order.
pub fn most_recent_directories_for_pane_group(
&self,
pane_group_id: EntityId,
) -> Option<impl Iterator<Item = WorkingDirectory> + '_> {
self.least_recent_directories_for_pane_group(pane_group_id)
.map(move |dirs| {
dirs.iter().rev().map(move |path| WorkingDirectory {
path: path.clone(),
terminal_id: self.get_terminal_id_for_root_path(pane_group_id, path),
})
})
}
/// Get the unique repository roots for a specific pane group in insertion order (oldest first).
fn least_recent_repositories_for_pane_group(
&self,
pane_group_id: EntityId,
) -> Option<&IndexSet<PathBuf>> {
self.repository_roots.get(&pane_group_id)
}
/// Get the unique repository roots for a specific pane group in most to least recently added order.
pub fn most_recent_repositories_for_pane_group(
&self,
pane_group_id: EntityId,
) -> Option<impl Iterator<Item = PathBuf> + '_> {
self.least_recent_repositories_for_pane_group(pane_group_id)
.map(|repos| repos.iter().rev().cloned())
}
/// Get the terminal view ID associated with a specific root path in a pane group.
pub fn get_terminal_id_for_root_path(
&self,
pane_group_id: EntityId,
root_path: &Path,
) -> Option<EntityId> {
self.directory_to_terminal
.get(&pane_group_id)
.and_then(|roots| roots.get(root_path).copied())
}
/// Get or create a DiffStateModel for a specific repository.
/// If the model doesn't exist, it will be created.
pub fn get_or_create_diff_state_model(
&mut self,
repo_path: PathBuf,
ctx: &mut ModelContext<Self>,
) -> Option<ModelHandle<DiffStateModel>> {
if let Some(model) = self.diff_state_models.get(&repo_path) {
return Some(model.clone());
}
// Create new DiffStateModel for this repo
let diff_state_model =
ctx.add_model(|ctx| DiffStateModel::new(Some(repo_path.display().to_string()), ctx));
self.diff_state_models
.insert(repo_path.clone(), diff_state_model.clone());
Some(diff_state_model)
}
/// DiffStateModels are shared across tabs. When you delete repos from one tab,
/// we should check if its still in use in any tab. If not, stop its watcher and delete it.
fn drop_unused_diff_state_models(
&mut self,
removed_repos: impl Iterator<Item = PathBuf>,
ctx: &mut ModelContext<Self>,
) {
for repo_path in removed_repos {
if self
.repository_roots
.values()
.all(|tab| !tab.contains(&repo_path))
{
if let Some(model) = self.diff_state_models.remove(&repo_path) {
model.update(ctx, |model, ctx| {
model.stop_active_watcher(ctx);
});
}
}
}
}
/// Get or create a ReviewCommentBatch for a specific repository.
/// If the model doesn't exist, it will be created.
pub fn get_or_create_code_review_comments(
&mut self,
repo_path: &Path,
ctx: &mut ModelContext<Self>,
) -> Option<ModelHandle<ReviewCommentBatch>> {
if let Some(existing) = self.comment_models.get(repo_path) {
return Some(existing.clone());
}
let model = ctx.add_model(|_ctx| ReviewCommentBatch::default());
self.comment_models
.insert(repo_path.to_path_buf(), model.clone());
Some(model)
}
/// Store a CodeReviewView for a specific repository in a pane group.
pub fn store_code_review_view(
&mut self,
pane_group_id: EntityId,
repo_path: PathBuf,
view: ViewHandle<CodeReviewView>,
) {
let pane_group_views = self.code_review_views.entry(pane_group_id).or_default();
pane_group_views.insert(repo_path, view);
// Remove any inactive code reviews here. This allows these to be garbage collected.
self.remove_inactive_code_reviews(pane_group_id);
}
/// Remove any code review view state that is not active in any of the terminal views that belong to this pane group.
fn remove_inactive_code_reviews(&mut self, pane_group_id: EntityId) {
let Some(code_review_views) = self.code_review_views.get_mut(&pane_group_id) else {
return;
};
let Some(terminal_mapping) = self.directory_to_terminal.get(&pane_group_id) else {
return;
};
code_review_views.retain(|path, _| terminal_mapping.contains_key(path));
}
/// Get an existing CodeReviewView for a specific repository in a pane group.
/// Returns None if no view exists for this combination.
pub fn get_code_review_view(
&self,
pane_group_id: EntityId,
repo_path: &Path,
) -> Option<ViewHandle<CodeReviewView>> {
self.code_review_views
.get(&pane_group_id)
.and_then(|pane_group_views| pane_group_views.get(repo_path))
.cloned()
}
pub fn store_global_search_view(
&mut self,
pane_group_id: EntityId,
view: ViewHandle<GlobalSearchView>,
) {
self.global_search_views.insert(pane_group_id, view);
}
pub fn get_global_search_view(
&self,
pane_group_id: EntityId,
) -> Option<ViewHandle<GlobalSearchView>> {
self.global_search_views.get(&pane_group_id).cloned()
}
pub fn store_file_tree_view(
&mut self,
pane_group_id: EntityId,
view: ViewHandle<FileTreeView>,
) {
self.file_tree_views.insert(pane_group_id, view);
}
pub fn get_file_tree_view(&self, pane_group_id: EntityId) -> Option<ViewHandle<FileTreeView>> {
self.file_tree_views.get(&pane_group_id).cloned()
}
/// Permanently removes all state associated with a pane group.
/// This should be called when a tab is closed (pane group is destroyed),
/// as opposed to handle_empty_pane_group which is called when working directories
/// become empty but the pane group still exists (e.g., settings page).
pub fn remove_pane_group(&mut self, pane_group_id: EntityId, ctx: &mut ModelContext<Self>) {
// Clean up directories, terminals, and repos (emits events for subscribers)
self.handle_empty_pane_group(pane_group_id, ctx);
// Clean up views that should persist in handle_empty_pane_group e.g. there's only a settings pane in the pane group
// but need to be removed when the pane group is destroyed
self.global_search_views.remove(&pane_group_id);
self.file_tree_views.remove(&pane_group_id);
self.code_review_views.remove(&pane_group_id);
self.focused_repo.remove(&pane_group_id);
}
fn handle_empty_pane_group(&mut self, pane_group_id: EntityId, ctx: &mut ModelContext<Self>) {
let did_remove_dirs = self.pane_groups.remove(&pane_group_id).is_some();
let did_remove_terminals = self.directory_to_terminal.remove(&pane_group_id).is_some();
let removed_repos = self.repository_roots.remove(&pane_group_id);
let did_remove_repos = removed_repos.is_some();
if let Some(removed_repos) = removed_repos {
self.drop_unused_diff_state_models(removed_repos.into_iter(), ctx);
}
if did_remove_dirs {
ctx.emit(WorkingDirectoriesEvent::DirectoriesChanged {
pane_group_id,
directories: vec![],
});
}
if did_remove_repos {
ctx.emit(WorkingDirectoriesEvent::RepositoriesChanged {
pane_group_id,
repositories: vec![],
});
}
if did_remove_terminals {
ctx.emit(WorkingDirectoriesEvent::FocusedRepoChanged {
pane_group_id,
repository_terminal_map: HashMap::new(),
focused_repo: None,
});
}
}
/// If `focused_terminal_id` is provided, the repo_to_terminal map will prioritize
pub fn refresh_working_directories_for_pane_group(
&mut self,
pane_group_id: EntityId,
terminal_cwds: Vec<(EntityId, String)>,
local_paths: Vec<(EntityId, String)>,
focused_terminal_id: Option<EntityId>,
ctx: &mut ModelContext<Self>,
) {
if terminal_cwds.is_empty() && local_paths.is_empty() {
self.handle_empty_pane_group(pane_group_id, ctx);
return;
}
let old_directories: Vec<WorkingDirectory> = self
.least_recent_directories_for_pane_group(pane_group_id)
.map(|dirs| {
dirs.iter()
.map(|dir| WorkingDirectory {
path: dir.clone(),
terminal_id: self.get_terminal_id_for_root_path(pane_group_id, dir),
})
.collect()
})
.unwrap_or_default();
let old_repos: Vec<PathBuf> = self
.least_recent_repositories_for_pane_group(pane_group_id)
.map(|repos| repos.iter().cloned().collect())
.unwrap_or_default();
let old_focused_repo: Option<PathBuf> =
self.focused_repo.get(&pane_group_id).cloned().flatten();
// Resolve a path to its detected repository root, or keep the path as-is if no repo is found.
let root_for_path = |path: PathBuf| {
DetectedRepositories::as_ref(ctx)
.get_root_for_path(&path)
.unwrap_or(path)
};
let root_for_raw_path = |raw_path: &str| normalize_cwd(raw_path).map(root_for_path);
// Collapse working directories to their nearest repository root (when detected).
let mut file_path_ancestors: HashSet<PathBuf> = terminal_cwds
.iter()
.filter_map(|(_, cwd)| root_for_raw_path(cwd))
.collect();
let local_cwds: Vec<(EntityId, String)> = local_paths
.into_iter()
.filter_map(|(view_id, path)| {
let path_buf = PathBuf::from(&path);
let resolved_path = self
.get_repo_root_for_path(&path_buf, ctx)
.or_else(|| path_buf.parent().map(|p| p.to_path_buf()))?;
if file_path_ancestors.insert(resolved_path.clone()) {
Some((view_id, resolved_path.display().to_string()))
} else {
None
}
})
.collect();
// FYI we have the 3 entity types terminal, code, and notebook below but we're merging them in a way that we only care about the actual paths
// Be careful to not mix the entity IDs if we end up using them in the future!!!
//
// NOTE: We intentionally collapse paths to their repo root when possible, so this is a
// "working roots" list rather than raw per-pane working directories.
let new_root_paths: Vec<PathBuf> = terminal_cwds
.iter()
.chain(local_cwds.iter())
.filter_map(|(_, cwd)| root_for_raw_path(cwd))
.collect();
// Get or create the IndexSet for this pane group
// (IndexSet maintains insertion order and auto-deduplicates)
let pane_group_roots = self.pane_groups.entry(pane_group_id).or_default();
update_index_set(pane_group_roots, new_root_paths.clone());
// Build repo roots and their terminal associations
// First pass: collect all repo roots and build initial mapping
let new_repo_roots: Vec<PathBuf> = self
.pane_groups
.get(&pane_group_id)
.into_iter()
.flat_map(|dirs| dirs.iter())
.filter_map(|dir| self.get_repo_root_for_path(dir, ctx))
.collect();
let mut new_roots: HashSet<PathBuf> = HashSet::from_iter(new_repo_roots.iter().cloned());
new_roots.extend(new_root_paths.iter().cloned());
// Build mapping from directories to their terminal IDs
let mut new_root_to_terminal: HashMap<PathBuf, EntityId> = terminal_cwds
.iter()
.filter_map(|(terminal_id, cwd)| root_for_raw_path(cwd).map(|p| (p, *terminal_id)))
.collect();
new_root_to_terminal.retain(|cwd, _terminal_id| new_roots.contains(cwd));
// Second pass: if we have a focused terminal, ensure its repo maps to it
// This ensures the dropdown selects the correct repo when a pane is focused or CD'd
let mut focused_repo: Option<PathBuf> = None;
if let Some(focused_id) = focused_terminal_id {
let mut repos_to_insert = Vec::new();
for (dir, terminal_id) in &new_root_to_terminal {
if *terminal_id == focused_id {
if let Some(repo_root) = self.get_repo_root_for_path(dir, ctx) {
repos_to_insert.push((repo_root.clone(), focused_id));
focused_repo = Some(repo_root);
}
}
}
for (repo_root, focused_id) in repos_to_insert {
new_root_to_terminal.insert(repo_root, focused_id);
}
}
// Get or create the IndexSet for repository roots
// (IndexSet maintains insertion order and auto-deduplicates)
let pane_group_repos = self.repository_roots.entry(pane_group_id).or_default();
update_index_set(pane_group_repos, new_repo_roots);
// Update the repo to terminal mapping
self.directory_to_terminal
.insert(pane_group_id, new_root_to_terminal);
let new_directories: Vec<WorkingDirectory> = self
.pane_groups
.get(&pane_group_id)
.map(|dirs| {
dirs.iter()
.map(|dir| WorkingDirectory {
path: dir.clone(),
terminal_id: self.get_terminal_id_for_root_path(pane_group_id, dir),
})
.collect()
})
.unwrap_or_default();
let new_deduplicated_repos: Vec<PathBuf> = self
.repository_roots
.get(&pane_group_id)
.map(|repos| repos.iter().cloned().collect())
.unwrap_or_default();
if old_directories != new_directories {
self.emit_directories_changed(pane_group_id, ctx);
}
if old_repos != new_deduplicated_repos {
self.drop_unused_diff_state_models(
old_repos
.into_iter()
.filter(|repo| !new_deduplicated_repos.contains(repo)),
ctx,
);
self.emit_repositories_changed(pane_group_id, ctx);
}
if old_focused_repo != focused_repo {
self.focused_repo
.insert(pane_group_id, focused_repo.clone());
self.emit_focused_repo_changed(pane_group_id, focused_repo, ctx);
}
}
/// Get the repository root for a given path.
fn get_repo_root_for_path(&self, path: &Path, ctx: &AppContext) -> Option<PathBuf> {
DetectedRepositories::as_ref(ctx).get_root_for_path(path)
}
/// Emit a DirectoriesChanged event with the current state for a specific pane group.
/// Directories are returned in most recent first order for use in the UI.
fn emit_directories_changed(&mut self, pane_group_id: EntityId, ctx: &mut ModelContext<Self>) {
ctx.emit(WorkingDirectoriesEvent::DirectoriesChanged {
pane_group_id,
directories: self
.most_recent_directories_for_pane_group(pane_group_id)
.map(|iter| iter.collect())
.unwrap_or_default(),
});
}
/// Emit a RepositoriesChanged event with the current state for a specific pane group.
/// Repositories are returned in most recent first order for use in the UI.
fn emit_repositories_changed(&mut self, pane_group_id: EntityId, ctx: &mut ModelContext<Self>) {
ctx.emit(WorkingDirectoriesEvent::RepositoriesChanged {
pane_group_id,
repositories: self
.most_recent_repositories_for_pane_group(pane_group_id)
.map(|iter| iter.collect())
.unwrap_or_default(),
});
}
fn emit_focused_repo_changed(
&mut self,
pane_group_id: EntityId,
focused_repo: Option<PathBuf>,
ctx: &mut ModelContext<Self>,
) {
ctx.emit(WorkingDirectoriesEvent::FocusedRepoChanged {
pane_group_id,
repository_terminal_map: self
.directory_to_terminal
.get(&pane_group_id)
.cloned()
.unwrap_or_default(),
focused_repo,
});
}
pub(crate) fn insert_code_review_comments(
&mut self,
pane_group_id: EntityId,
repo_path: &Path,
comments: &Vec<PendingImportedReviewComment>,
diff_mode: &DiffMode,
ctx: &mut ModelContext<Self>,
) {
if let Some(code_review_view) = self.get_code_review_view(pane_group_id, repo_path) {
code_review_view.update(ctx, |code_review_view, ctx| {
code_review_view.set_diff_base(diff_mode.to_owned(), ctx);
code_review_view.expand_comment_list(ctx);
})
} else {
log::error!(
"WorkingDirectoriesModel did not find CodeReviewView for repo path {:?}",
repo_path
);
}
if let Some(comment_batch) = self.get_or_create_code_review_comments(repo_path, ctx) {
let comments = comments.to_owned();
comment_batch.update(ctx, |comment_batch, ctx| {
comment_batch.add_pending_imported_comments(comments, diff_mode.to_owned(), ctx);
})
}
}
/// Inserts pre-flattened (already attached) review comments into the comment batch for the
/// given repository, creating the batch if needed. Unlike `insert_code_review_comments`, these
/// comments have already been thread-flattened and converted to `AttachedReviewComment`, so
/// they are ready to be repositioned onto diff editors immediately.
pub(crate) fn upsert_flattened_code_review_comments(
&mut self,
repo_path: &Path,
comments: Vec<AttachedReviewComment>,
ctx: &mut ModelContext<Self>,
) {
if let Some(comment_batch) = self.get_or_create_code_review_comments(repo_path, ctx) {
comment_batch.update(ctx, |comment_batch, ctx| {
comment_batch.upsert_imported_comments(comments, ctx);
});
}
}
}
#[cfg(not(feature = "local_fs"))]
impl WorkingDirectoriesModel {
pub fn new() -> Self {
Self::default()
}
/// Get the unique directories for a specific pane group in most to least recently added order.
pub fn most_recent_directories_for_pane_group(
&self,
_pane_group_id: EntityId,
) -> Option<impl Iterator<Item = WorkingDirectory> + '_> {
Option::<std::iter::Empty<WorkingDirectory>>::None
}
/// Get the unique repository roots for a specific pane group in most to least recently added order.
pub fn most_recent_repositories_for_pane_group(
&self,
_pane_group_id: EntityId,
) -> Option<impl Iterator<Item = PathBuf> + '_> {
Option::<std::iter::Empty<PathBuf>>::None
}
/// Get the terminal view ID associated with a specific repository in a pane group.
pub fn get_terminal_id_for_root_path(
&self,
_pane_group_id: EntityId,
_root_path: &Path,
) -> Option<EntityId> {
None
}
pub fn refresh_working_directories_for_pane_group(
&mut self,
_pane_group_id: EntityId,
_terminal_cwds: Vec<(EntityId, String)>,
_local_paths: Vec<(EntityId, String)>,
_focused_terminal_id: Option<EntityId>,
_ctx: &mut ModelContext<Self>,
) {
}
pub fn get_or_create_diff_state_model(
&mut self,
_repo_path: PathBuf,
_ctx: &mut ModelContext<Self>,
) -> Option<ModelHandle<DiffStateModel>> {
None
}
pub fn get_or_create_code_review_comments(
&mut self,
_repo_path: &Path,
_ctx: &mut ModelContext<Self>,
) -> Option<ModelHandle<ReviewCommentBatch>> {
None
}
pub fn store_code_review_view(
&mut self,
_pane_group_id: EntityId,
_repo_path: PathBuf,
_view: ViewHandle<CodeReviewView>,
) {
}
pub fn get_code_review_view(
&self,
_pane_group_id: EntityId,
_repo_path: &Path,
) -> Option<ViewHandle<CodeReviewView>> {
None
}
pub fn store_global_search_view(
&mut self,
_pane_group_id: EntityId,
_view: ViewHandle<GlobalSearchView>,
) {
}
pub fn get_global_search_view(
&self,
_pane_group_id: EntityId,
) -> Option<ViewHandle<GlobalSearchView>> {
None
}
pub fn store_file_tree_view(
&mut self,
_pane_group_id: EntityId,
_view: ViewHandle<crate::code::file_tree::FileTreeView>,
) {
}
pub fn get_file_tree_view(
&self,
_pane_group_id: EntityId,
) -> Option<ViewHandle<crate::code::file_tree::FileTreeView>> {
None
}
pub fn remove_pane_group(&mut self, _pane_group_id: EntityId, _ctx: &mut ModelContext<Self>) {}
pub(crate) fn insert_code_review_comments(
&mut self,
_pane_group_id: EntityId,
_repo_path: &Path,
_comments: &Vec<PendingImportedReviewComment>,
_diff_mode: &DiffMode,
_ctx: &mut ModelContext<Self>,
) {
}
pub(crate) fn upsert_flattened_code_review_comments(
&mut self,
_repo_path: &Path,
_comments: Vec<AttachedReviewComment>,
_ctx: &mut ModelContext<Self>,
) {
}
}
impl Entity for WorkingDirectoriesModel {
type Event = WorkingDirectoriesEvent;
}
/// Normalize a CWD path string to a canonical PathBuf
///
/// This function attempts to canonicalize (resolve symlinks, make absolute)
///
/// Returns None if the path is empty, invalid, or cannot be canonicalized.
/// Canonicalization failure may indicate remote paths or non-existent directories,
/// which could be supported in the future.
#[cfg(feature = "local_fs")]
fn normalize_cwd(raw_cwd: &str) -> Option<PathBuf> {
if raw_cwd.is_empty() {
return None;
}
let path = PathBuf::from(raw_cwd.to_string());
// Use dunce::canonicalize to avoid Windows extended-length path prefix (\\?\)
// which would cause path comparison mismatches with CanonicalizedPath.
dunce::canonicalize(&path).ok()
}
#[cfg(test)]
#[path = "working_directories_tests.rs"]
mod tests;