Skip to content

Commit a6ecb66

Browse files
committed
✨ Add file history panel to Explore mode with compact companion bar
Replace the verbose companion session panel with a navigable file history view that shows git log entries for the selected file. The right panel now displays commit history with hash, message, author, time, and +/- stats. Key changes: - Add FileLogEntry type for commit metadata with additions/deletions - Load git log asynchronously when file selection changes - Support global commit log toggle (L key) for repo-wide history - Implement keyboard navigation (j/k, g/G, Ctrl+d/u) in history panel - Add 'y' to copy commit hash, Enter to view commit details - Create compact companion status bar showing branch, staged/unstaged counts, and session duration in a single line - Add scrollbar for large commit histories
1 parent 6c42718 commit a6ecb66

File tree

16 files changed

+1062
-320
lines changed

16 files changed

+1062
-320
lines changed

src/companion/branch_memory.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ impl BranchMemory {
9595

9696
/// Check if this is a returning visit (visited before more than 5 minutes ago)
9797
pub fn is_returning_visit(&self) -> bool {
98-
self.session_count > 1
99-
&& self.time_since_last_visit() > chrono::Duration::minutes(5)
98+
self.session_count > 1 && self.time_since_last_visit() > chrono::Duration::minutes(5)
10099
}
101100

102101
/// Generate a welcome message if returning
@@ -114,6 +113,9 @@ impl BranchMemory {
114113
format!("{} minutes ago", duration.num_minutes())
115114
};
116115

117-
Some(format!("Welcome back to {}! Last here {}", self.branch_name, time_str))
116+
Some(format!(
117+
"Welcome back to {}! Last here {}",
118+
self.branch_name, time_str
119+
))
118120
}
119121
}

src/companion/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ impl CompanionService {
5757
Some(w)
5858
}
5959
Err(e) => {
60-
tracing::warn!("Failed to start file watcher: {}. Companion will run without live updates.", e);
60+
tracing::warn!(
61+
"Failed to start file watcher: {}. Companion will run without live updates.",
62+
e
63+
);
6164
None
6265
}
6366
};

src/companion/storage.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ impl CompanionStorage {
2525
let branches_dir = repo_dir.join("branches");
2626

2727
// Ensure directories exist
28-
fs::create_dir_all(&branches_dir)
29-
.with_context(|| format!("Failed to create companion directory: {}", branches_dir.display()))?;
28+
fs::create_dir_all(&branches_dir).with_context(|| {
29+
format!(
30+
"Failed to create companion directory: {}",
31+
branches_dir.display()
32+
)
33+
})?;
3034

3135
Ok(Self {
3236
repo_dir,
@@ -52,8 +56,7 @@ impl CompanionStorage {
5256

5357
/// Sanitize branch name for filesystem
5458
fn sanitize_branch_name(branch: &str) -> String {
55-
branch
56-
.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], "_")
59+
branch.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], "_")
5760
}
5861

5962
/// Get session file path
@@ -104,8 +107,13 @@ impl CompanionStorage {
104107
drop(file);
105108

106109
// Atomic rename
107-
fs::rename(&temp_path, path)
108-
.with_context(|| format!("Failed to rename {} to {}", temp_path.display(), path.display()))?;
110+
fs::rename(&temp_path, path).with_context(|| {
111+
format!(
112+
"Failed to rename {} to {}",
113+
temp_path.display(),
114+
path.display()
115+
)
116+
})?;
109117

110118
Ok(())
111119
}
@@ -134,9 +142,10 @@ impl CompanionStorage {
134142
let entry = entry?;
135143
let path = entry.path();
136144
if path.extension().is_some_and(|e| e == "json")
137-
&& let Some(stem) = path.file_stem() {
138-
branches.push(stem.to_string_lossy().to_string());
139-
}
145+
&& let Some(stem) = path.file_stem()
146+
{
147+
branches.push(stem.to_string_lossy().to_string());
148+
}
140149
}
141150
}
142151

src/companion/watcher.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use anyhow::{Context, Result};
77
use ignore::gitignore::{Gitignore, GitignoreBuilder};
88
use notify::{RecommendedWatcher, RecursiveMode};
9-
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, RecommendedCache};
9+
use notify_debouncer_full::{DebounceEventResult, Debouncer, RecommendedCache, new_debouncer};
1010
use std::path::{Path, PathBuf};
1111
use std::sync::Arc;
1212
use std::time::Duration;
@@ -92,7 +92,9 @@ impl FileWatcherService {
9292
// Fallback: just ignore .git
9393
let mut fallback = GitignoreBuilder::new(repo_path);
9494
let _ = fallback.add_line(None, ".git/");
95-
fallback.build().expect("Failed to build fallback gitignore")
95+
fallback
96+
.build()
97+
.expect("Failed to build fallback gitignore")
9698
}))
9799
}
98100

@@ -133,7 +135,9 @@ impl FileWatcherService {
133135

134136
let companion_event = match event.kind {
135137
EventKind::Create(_) => Some(CompanionEvent::FileCreated(path.clone())),
136-
EventKind::Modify(_) => Some(CompanionEvent::FileModified(path.clone())),
138+
EventKind::Modify(_) => {
139+
Some(CompanionEvent::FileModified(path.clone()))
140+
}
137141
EventKind::Remove(_) => Some(CompanionEvent::FileDeleted(path.clone())),
138142
_ => None,
139143
};

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
pub mod agents;
1717
pub mod changelog;
18-
pub mod companion;
1918
pub mod cli;
2019
pub mod commands;
2120
pub mod common;
21+
pub mod companion;
2222
pub mod config;
2323
pub mod context;
2424
pub mod git;

0 commit comments

Comments
 (0)