Skip to content
Open
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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A fast terminal diff viewer and code review TUI, written in Rust.
Review `git diff`, commits, branches, or GitHub PRs side-by-side without leaving your terminal. Ships as a single static Rust binary and stays snappy on multi-thousand-line diffs.

- Side-by-side diff viewer with tree-sitter syntax highlighting
- Review GitHub Pull Requests with `lumen diff --pr 123`
- Review GitHub and Azure DevOps Pull Requests with `lumen diff --pr 123`
- Annotate selections, hunks, or whole files
- Watch mode and stacked-commit review
- Optional AI commit messages and change explanations (10+ providers)
Expand Down Expand Up @@ -52,6 +52,8 @@ Before you begin, ensure you have:
1. `git` installed on your system
2. [fzf](https://github.com/junegunn/fzf) (optional) - Required for `lumen explain --list` command
3. [mdcat](https://github.com/swsnr/mdcat) (optional) - Required for pretty output formatting
4. [GitHub CLI (`gh`)](https://cli.github.com/) (optional) - Required for reviewing GitHub Pull Requests
5. [Azure CLI (`az`)](https://learn.microsoft.com/cli/azure/) (optional) - Required for reviewing Azure DevOps Pull Requests (sign in with `az login`, or set `AZURE_DEVOPS_EXT_PAT`)

### Installation

Expand Down Expand Up @@ -86,9 +88,10 @@ lumen diff HEAD~1
# View changes between branches
lumen diff main..feature/A

# View changes in a GitHub Pull Request
# View changes in a Pull Request (GitHub or Azure DevOps)
lumen diff --pr 123 # (--pr is optional)
lumen diff https://github.com/owner/repo/pull/123
lumen diff https://dev.azure.com/org/project/_git/repo/pullrequest/123

# Open the PR associated with the current branch
lumen diff --detect-pr
Expand Down
37 changes: 12 additions & 25 deletions src/command/diff/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ fn open_tui_writer() -> io::Result<Box<dyn Write + Send>> {

use super::annotation::{AnnotationEditor, AnnotationEditorResult};
use super::coordinates::{extract_selected_text, PanelLayout};
use super::git::{
get_current_branch, load_file_diffs, load_pr_file_diffs, load_single_commit_diffs,
};
use super::git::{get_current_branch, load_file_diffs, load_single_commit_diffs};
use super::highlight;
use super::pr_provider::{load_pr_file_diffs, pr_file_web_url};
use super::render::{
render_diff, render_empty_state, truncate_path, FilePickerItem, KeyBind, KeyBindSection, Modal,
ModalContent, ModalFileStatus, ModalResult,
Expand Down Expand Up @@ -281,8 +280,9 @@ pub fn run_app_stacked(
run_app_internal(options, None, file_diffs, Some(commits), backend)
}

/// Sync viewed files from GitHub to local state
fn sync_viewed_files_from_github(pr_info: &PrInfo, state: &mut AppState) {
/// Sync per-file viewed state from the hosting provider into local state.
/// No-op for providers without viewed-file support (e.g. Azure DevOps).
fn sync_viewed_files_from_provider(pr_info: &PrInfo, state: &mut AppState) {
if let Ok(viewed_paths) = fetch_viewed_files(pr_info) {
state.viewed_files.clear();
for (idx, diff) in state.file_diffs.iter().enumerate() {
Expand Down Expand Up @@ -329,14 +329,14 @@ fn run_app_internal(
state.init_stacked_mode(commits);
}

// Load viewed files from GitHub on startup in PR mode (before TUI starts)
// Load viewed files from the provider on startup in PR mode (before TUI starts)
if let Some(ref pr) = pr_info {
let mut spinner = Spinner::new(
spinners::Dots,
format!("Syncing viewed status for {} files", state.file_diffs.len()),
Color::Cyan,
);
sync_viewed_files_from_github(pr, &mut state);
sync_viewed_files_from_provider(pr, &mut state);
let viewed_count = state.viewed_files.len();
spinner.success(&format!("{} files marked as viewed", viewed_count));
}
Expand Down Expand Up @@ -389,7 +389,7 @@ fn run_app_internal(

if state.needs_reload {
let file_diffs = if let Some(ref pr) = pr_info {
// In PR mode, reload from GitHub
// In PR mode, reload from the hosting provider
match load_pr_file_diffs(pr) {
Ok(diffs) => diffs,
Err(e) => {
Expand All @@ -407,7 +407,7 @@ fn run_app_internal(

// Re-sync viewed files from GitHub in PR mode
if let Some(ref pr) = pr_info {
sync_viewed_files_from_github(pr, &mut state);
sync_viewed_files_from_provider(pr, &mut state);
}
}

Expand Down Expand Up @@ -1960,14 +1960,9 @@ fn run_app_internal(
if let Some(ref pr) = pr_info {
if !state.file_diffs.is_empty() {
let filename = &state.file_diffs[state.current_file].filename;
let file_url = format!(
"https://github.com/{}/{}/pull/{}/files#diff-{}",
pr.repo_owner,
pr.repo_name,
pr.number,
generate_file_anchor(filename)
);
let _ = open_url(&file_url);
if let Some(file_url) = pr_file_web_url(pr, filename) {
let _ = open_url(&file_url);
}
}
}
}
Expand Down Expand Up @@ -2236,11 +2231,3 @@ fn open_url(url: &str) -> io::Result<()> {
}
Ok(())
}

fn generate_file_anchor(filename: &str) -> String {
use sha2::{Digest, Sha256};

let mut hasher = Sha256::new();
hasher.update(filename.as_bytes());
format!("{:x}", hasher.finalize())
}
Loading