feat: browse GitHub GitLab and Gitea pull requests
This commit is contained in:
+93
-18
@@ -2594,6 +2594,7 @@ fn draw_diff(
|
||||
return;
|
||||
}
|
||||
if diff.phase != Phase::View {
|
||||
let pull_requests = diff.pull_request_remote.is_some();
|
||||
let panel = floating_panel(area);
|
||||
frame.render_widget(Clear, panel);
|
||||
let stage = if diff.phase == Phase::Before {
|
||||
@@ -2601,12 +2602,18 @@ fn draw_diff(
|
||||
} else {
|
||||
"After"
|
||||
};
|
||||
let title = format!(" Diff · Choose {stage} · {} ", diff.comparison_mode.label());
|
||||
let title = if pull_requests {
|
||||
" Pull requests ".into()
|
||||
} else {
|
||||
format!(" Diff · Choose {stage} · {} ", diff.comparison_mode.label())
|
||||
};
|
||||
let content = diff_panel(frame, panel, &title, "", "", true);
|
||||
let notice = diff.error.as_deref().or_else(|| {
|
||||
if diff.loading {
|
||||
Some(if diff.after.is_some() {
|
||||
"Loading comparison…"
|
||||
} else if pull_requests {
|
||||
"Loading pull requests…"
|
||||
} else {
|
||||
"Loading repository…"
|
||||
})
|
||||
@@ -2629,7 +2636,7 @@ fn draw_diff(
|
||||
Constraint::Length(u16::from(content.height >= 4)),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(u16::from(notice.is_some() && content.height >= 3)),
|
||||
Constraint::Length(u16::from(content.height >= 2)),
|
||||
Constraint::Length(u16::from(!pull_requests && content.height >= 2)),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(u16::from(content.height >= 7)),
|
||||
])
|
||||
@@ -2638,17 +2645,18 @@ fn draw_diff(
|
||||
.before
|
||||
.as_ref()
|
||||
.map_or("not selected", |revision| revision.label.as_str());
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(
|
||||
let stage_text = if pull_requests {
|
||||
"Select a pull request to compare its branch changes".into()
|
||||
} else {
|
||||
format!(
|
||||
"{} · Ctrl+b toggle · Before: {}",
|
||||
diff.comparison_mode.label(),
|
||||
display_text(before)
|
||||
)),
|
||||
stage_area,
|
||||
);
|
||||
if let Some(catalog) = &diff.catalog {
|
||||
let tabs: Vec<Span> = catalog
|
||||
.kind
|
||||
)
|
||||
};
|
||||
frame.render_widget(Paragraph::new(stage_text), stage_area);
|
||||
if pull_requests || diff.catalog.is_some() {
|
||||
let tabs: Vec<Span> = diff
|
||||
.categories()
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -2696,7 +2704,7 @@ fn draw_diff(
|
||||
);
|
||||
}
|
||||
let mut entries = diff.entries();
|
||||
if let Some(working) = entries.next() {
|
||||
if !pull_requests && let Some(working) = entries.next() {
|
||||
let label = format!(
|
||||
"{} {} · pinned",
|
||||
if diff.selected == 0 { ">" } else { " " },
|
||||
@@ -2721,6 +2729,7 @@ fn draw_diff(
|
||||
1
|
||||
};
|
||||
let count = usize::from(results.height) / row_height;
|
||||
let pinned = usize::from(!pull_requests);
|
||||
let mut key = std::collections::hash_map::DefaultHasher::new();
|
||||
(
|
||||
diff.phase as u8,
|
||||
@@ -2735,17 +2744,16 @@ fn draw_diff(
|
||||
let first = PickerViewport::follow(
|
||||
&mut panels.picker_scroll.revisions,
|
||||
key.finish(),
|
||||
diff.selected.saturating_sub(1),
|
||||
diff.selected.saturating_sub(pinned),
|
||||
usize::from(results.height),
|
||||
row_height,
|
||||
panels.picker_scroll.distance,
|
||||
)
|
||||
.saturating_add(1);
|
||||
);
|
||||
let mut lines = Vec::with_capacity(count * row_height);
|
||||
for (index, revision) in entries
|
||||
.enumerate()
|
||||
.map(|(index, revision)| (index + 1, revision))
|
||||
.skip(first - 1)
|
||||
.skip(first)
|
||||
.map(|(index, revision)| (index + pinned, revision))
|
||||
.take(count)
|
||||
{
|
||||
let style = if index == diff.selected {
|
||||
@@ -2783,13 +2791,23 @@ fn draw_diff(
|
||||
}
|
||||
}
|
||||
if lines.is_empty() && !diff.loading {
|
||||
frame.render_widget(Paragraph::new("No matching revisions"), results);
|
||||
let empty = if pull_requests {
|
||||
"No matching pull requests"
|
||||
} else {
|
||||
"No matching revisions"
|
||||
};
|
||||
frame.render_widget(Paragraph::new(empty), results);
|
||||
} else {
|
||||
frame.render_widget(Paragraph::new(lines), results);
|
||||
}
|
||||
let accept = if diff.error.is_some() {
|
||||
"retry"
|
||||
} else {
|
||||
"select"
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(
|
||||
"{navigation_hint} · Tab categories · type search · Enter select · Esc close · Ctrl+p actions"
|
||||
"{navigation_hint} · Tab categories · type search · Enter {accept} · Esc close · Ctrl+p actions"
|
||||
))
|
||||
.style(Style::default().fg(Color::DarkGray)),
|
||||
help,
|
||||
@@ -6719,6 +6737,63 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pull_request_picker_renders_all_rows_without_a_pinned_offset() {
|
||||
use crate::diff::{Catalog, Category, RepoKind, Revision, RevisionTarget};
|
||||
for (width, height) in [(100, 16), (44, 12)] {
|
||||
let mut diff = DiffState::pull_requests("origin".into());
|
||||
diff.take_request();
|
||||
let mut terminal =
|
||||
Terminal::new(ratatui::backend::TestBackend::new(width, height)).unwrap();
|
||||
let mut panels = WheelPanels::default();
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
assert!(screen_text(&terminal).contains("Loading pull requests"));
|
||||
diff.apply_result(1, Err("offline".into()));
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
assert!(screen_text(&terminal).contains("offline"));
|
||||
diff.handle(Input::Enter);
|
||||
diff.take_request();
|
||||
diff.apply_result(
|
||||
2,
|
||||
Ok(crate::diff::Outcome::Catalog(Catalog {
|
||||
kind: RepoKind::Git,
|
||||
entries: (0..20)
|
||||
.map(|index| Revision {
|
||||
target: RevisionTarget::PullRequest {
|
||||
remote: "origin".into(),
|
||||
base_ref: "refs/heads/main".into(),
|
||||
base_sha: "base".into(),
|
||||
head_ref: format!("refs/pull/{index}/head"),
|
||||
head_sha: format!("head-{index}"),
|
||||
},
|
||||
label: format!("#{index:02} Pull request"),
|
||||
description: "open · main ← topic".into(),
|
||||
category: Category::Open,
|
||||
})
|
||||
.collect(),
|
||||
notice: None,
|
||||
})),
|
||||
);
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("Pull requests"));
|
||||
assert!(text.contains("Open") && text.contains("Closed") && text.contains("All"));
|
||||
assert!(text.contains("> #00 Pull request"));
|
||||
assert!(!text.contains("pinned"));
|
||||
diff.handle(Input::End);
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("> #19 Pull request"));
|
||||
assert!(!text.contains("#00 Pull request"));
|
||||
diff.handle(Input::Home);
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
assert!(screen_text(&terminal).contains("> #00 Pull request"));
|
||||
diff.handle(Input::Char('\t'));
|
||||
render_diff(&mut terminal, &diff, &mut panels);
|
||||
assert!(screen_text(&terminal).contains("No matching pull requests"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_excerpts_render_original_lines_and_expand_without_losing_changes() {
|
||||
let before: Vec<_> = (0..80)
|
||||
|
||||
Reference in New Issue
Block a user