quinjet 0.0.52

A fast, live, keyboard-first Git source-control interface for the terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
use std::collections::{HashMap, HashSet};
use std::num::NonZeroU16;
use std::ops::Range;
use std::path::Path;

use ratatui::Frame;
use ratatui::buffer::{Buffer, CellDiffOption};
use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget, Wrap};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::app::{
    App, ChangeRow, ChangeSection, CheckListRow, ContentFileHit, ContentReviewHit, ContentStepHit,
    DiffLayout, Focus, HelpHit, LinkHit, Modal, ModalAction, OpenTarget, PaletteCommand,
    PrActionItem, PrMenuItem, ProjectOpenMode, ProjectRow, PullRequestContentLink,
    PullRequestContentRow, PullRequestSection, PullRequestTreeEntry, RepositoryTabAction,
    RepositoryTabHit, ScmAction, ScmActionHit, ScmMenuItem, SideBySideRow, SidebarHit,
    SidebarHitArea, ToastLevel, UiGeometry, View,
};
use crate::convert::cells;
use crate::date_time::format_relative_timestamp;
use crate::file_icons;
#[cfg(test)]
use crate::git::diff::PullRequestDetails;
use crate::git::diff::{DiffDocument, DiffLine, DiffLineKind, HighlightSpan};
use crate::git::github::{
    CheckLogLine, CheckLogSeverity, CheckStep, ConversationEntry, ConversationKind,
    GitHubRepository, PullRequest, PullRequestCheckStatus, PullRequestFileStatus,
};
#[cfg(test)]
use crate::git::github::{PullRequestCheck, PullRequestFile, RecentPullRequest};
use crate::git::status::{Change, ChangeArea, ChangeStatus};
use crate::git::{Branch, HistoryBranch, ProjectGroup, Stash};
use crate::ssh::SshContext;
use crate::theme::{AppearanceChoice, Theme, ThemeName};

const DETAIL_LABEL_WIDTH: usize = 12;
const MAX_INTRALINE_SOURCE_BYTES: usize = 32 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum HelpRow {
    Section(&'static str),
    Shortcut {
        keys: &'static str,
        description: &'static str,
    },
    Spacer,
}

pub(crate) const HELP_ROWS: &[HelpRow] = &[
    HelpRow::Section("Navigation"),
    HelpRow::Shortcut {
        keys: "j / k, ↑ / ↓",
        description: "Move selection or scroll preview",
    },
    HelpRow::Shortcut {
        keys: "Drag in preview",
        description: "Select and copy text inside one diff pane",
    },
    HelpRow::Shortcut {
        keys: "Double-click divider",
        description: "Restore that pane's default size",
    },
    HelpRow::Shortcut {
        keys: "PgUp / PgDn",
        description: "Move by a page",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+D / Ctrl+U",
        description: "Scroll the preview by half a page",
    },
    HelpRow::Shortcut {
        keys: "gg / Home",
        description: "Jump to the first item",
    },
    HelpRow::Shortcut {
        keys: "G / End",
        description: "Jump to the last item",
    },
    HelpRow::Shortcut {
        keys: "Tab",
        description: "Switch focus between sidebar and preview",
    },
    HelpRow::Shortcut {
        keys: "Enter",
        description: "Toggle sidebar / preview focus",
    },
    HelpRow::Shortcut {
        keys: "h / l, ← / →, swipe",
        description: "Scroll preview horizontally",
    },
    HelpRow::Shortcut {
        keys: "[ / ]",
        description: "Previous / next diff hunk",
    },
    HelpRow::Shortcut {
        keys: "e / E",
        description: "Collapse / expand multi-file diffs",
    },
    HelpRow::Shortcut {
        keys: "t / T",
        description: "Toggle expanded vs compact diff context",
    },
    HelpRow::Shortcut {
        keys: "Space in preview",
        description: "Toggle a file in a multi-file preview",
    },
    HelpRow::Shortcut {
        keys: "z",
        description: "Hide / show sidebar",
    },
    HelpRow::Shortcut {
        keys: "m",
        description: "Toggle mouse capture",
    },
    HelpRow::Shortcut {
        keys: "1 / 2 / 3",
        description: "Changes / commit history / pull requests",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+Tab / Ctrl+Shift+Tab",
        description: "Next / previous project tab",
    },
    HelpRow::Shortcut {
        keys: "Option/Alt+1..9",
        description: "Activate a project tab by position",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+W",
        description: "Close the active project tab",
    },
    HelpRow::Shortcut {
        keys: "Right-click project tab",
        description: "Open, close, or close other project tabs",
    },
    HelpRow::Shortcut {
        keys: "N",
        description: "Open a project or worktree in a new tab",
    },
    HelpRow::Shortcut {
        keys: "/",
        description: "Filter the active list",
    },
    HelpRow::Shortcut {
        keys: "Shift+O",
        description: "Open the selected branch, commit, pull request, or check",
    },
    HelpRow::Shortcut {
        keys: "Esc",
        description: "Clear filter, close modal, or return focus",
    },
    HelpRow::Spacer,
    HelpRow::Section("Changes"),
    HelpRow::Shortcut {
        keys: "s / Space",
        description: "Stage or unstage the selected file",
    },
    HelpRow::Shortcut {
        keys: "u",
        description: "Unstage the selected file",
    },
    HelpRow::Shortcut {
        keys: "[+] / [−]",
        description: "Click an individual file or group action",
    },
    HelpRow::Shortcut {
        keys: "Space / ← / →",
        description: "Collapse or expand the selected Changes group",
    },
    HelpRow::Shortcut {
        keys: "a / U",
        description: "Stage all / unstage all",
    },
    HelpRow::Shortcut {
        keys: "x",
        description: "Revert the selected change, or every checked file (asks first)",
    },
    HelpRow::Shortcut {
        keys: "X",
        description: "Remove the selected file, or every checked file (asks first)",
    },
    HelpRow::Shortcut {
        keys: "c",
        description: "Commit staged changes, or stash when files are checked",
    },
    HelpRow::Shortcut {
        keys: "*",
        description: "Check / uncheck the selected file; a group header checkbox does its whole group",
    },
    HelpRow::Shortcut {
        keys: "S",
        description: "View and manage stashes",
    },
    HelpRow::Shortcut {
        keys: "d",
        description: "Compare current branch with another branch",
    },
    HelpRow::Shortcut {
        keys: "b / B",
        description: "Branch picker / checkout branch picker",
    },
    HelpRow::Shortcut {
        keys: "w",
        description: "Switch the current tab to another project or worktree",
    },
    HelpRow::Spacer,
    HelpRow::Section("Commits"),
    HelpRow::Shortcut {
        keys: "b",
        description: "View another local or remote branch (no checkout)",
    },
    HelpRow::Shortcut {
        keys: "C / R",
        description: "Cherry-pick / revert selected commit",
    },
    HelpRow::Shortcut {
        keys: "n",
        description: "Create branch at selected commit",
    },
    HelpRow::Spacer,
    HelpRow::Section("Pull Requests"),
    HelpRow::Shortcut {
        keys: "3",
        description: "Open the on-demand PR view (no automatic fetch)",
    },
    HelpRow::Shortcut {
        keys: "/",
        description: "Focus the numeric PR field; Enter opens it",
    },
    HelpRow::Shortcut {
        keys: "o",
        description: "Discover or choose the base repository",
    },
    HelpRow::Shortcut {
        keys: "Shift+P / Shift+F",
        description: "The PR and its checks / all changed files",
    },
    HelpRow::Shortcut {
        keys: "j / k",
        description: "Select the conversation, a check, a file, or a folder",
    },
    HelpRow::Shortcut {
        keys: "j / k in a file",
        description: "Select a reviewable diff line",
    },
    HelpRow::Shortcut {
        keys: "c / C",
        description: "Add a line comment / file comment to a pending review",
    },
    HelpRow::Shortcut {
        keys: "a / x",
        description: "Reply to / resolve the selected review thread",
    },
    HelpRow::Shortcut {
        keys: "Click review thread",
        description: "Reply, copy, open, edit, delete, or change thread state",
    },
    HelpRow::Shortcut {
        keys: "Shift+V",
        description: "Submit a comment, approval, or change request",
    },
    HelpRow::Shortcut {
        keys: "Space",
        description: "Collapse / expand the selected folder, or open a recent PR",
    },
    HelpRow::Shortcut {
        keys: "r",
        description: "Refetch this PR now, bypassing the cache",
    },
    HelpRow::Shortcut {
        keys: "primary CTA / ▶",
        description: "Merge, close, reopen, or open in browser (after confirm)",
    },
    HelpRow::Spacer,
    HelpRow::Section("Check Logs"),
    HelpRow::Shortcut {
        keys: "j / k in the list",
        description: "Select a check to read its run log",
    },
    HelpRow::Shortcut {
        keys: "Tab, then j / k",
        description: "Move through that run's steps",
    },
    HelpRow::Shortcut {
        keys: "[ / ]",
        description: "Previous / next step",
    },
    HelpRow::Shortcut {
        keys: "Space",
        description: "Fold or unfold the selected step",
    },
    HelpRow::Shortcut {
        keys: "e / E",
        description: "Fold or unfold every step",
    },
    HelpRow::Shortcut {
        keys: "PgUp / PgDn, wheel",
        description: "Scroll the output of an unfolded step",
    },
    HelpRow::Shortcut {
        keys: "h / l, ← / →",
        description: "Read a log line past the right edge",
    },
    HelpRow::Spacer,
    HelpRow::Section("Branches"),
    HelpRow::Shortcut {
        keys: "↑ / ↓",
        description: "Move through matching branches",
    },
    HelpRow::Shortcut {
        keys: "Enter",
        description: "Check out the selected branch",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+N",
        description: "Create a new branch",
    },
    HelpRow::Shortcut {
        keys: "F2 / Ctrl+R",
        description: "Rename the selected branch",
    },
    HelpRow::Shortcut {
        keys: "Delete",
        description: "Delete the selected branch (asks first)",
    },
    HelpRow::Spacer,
    HelpRow::Section("Stashes"),
    HelpRow::Shortcut {
        keys: "↑ / ↓",
        description: "Move through matching stashes",
    },
    HelpRow::Shortcut {
        keys: "Enter",
        description: "Preview the selected stash",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+N",
        description: "Stash working tree changes",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+U",
        description: "Stash including untracked files",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+S",
        description: "Stash only staged changes",
    },
    HelpRow::Shortcut {
        keys: "Alt+A",
        description: "Apply the selected stash",
    },
    HelpRow::Shortcut {
        keys: "Alt+P",
        description: "Pop the selected stash",
    },
    HelpRow::Shortcut {
        keys: "Delete",
        description: "Drop the selected stash (asks first)",
    },
    HelpRow::Shortcut {
        keys: "Ctrl+Delete",
        description: "Clear every stash (asks first)",
    },
    HelpRow::Spacer,
    HelpRow::Section("Conflict"),
    HelpRow::Shortcut {
        keys: "o",
        description: "Keep our version",
    },
    HelpRow::Shortcut {
        keys: "t",
        description: "Keep their version",
    },
    HelpRow::Shortcut {
        keys: "s / Enter",
        description: "Stage the resolved file",
    },
    HelpRow::Spacer,
    HelpRow::Section("Repository"),
    HelpRow::Shortcut {
        keys: "r / Ctrl+R",
        description: "Refresh",
    },
    HelpRow::Shortcut {
        keys: "f / l / p / y",
        description: "Fetch / pull / push / sync",
    },
    HelpRow::Shortcut {
        keys: "v",
        description: "Toggle unified / side-by-side diff",
    },
    HelpRow::Shortcut {
        keys: ": / Ctrl+P",
        description: "Open command palette",
    },
    HelpRow::Shortcut {
        keys: "?",
        description: "Show this help",
    },
    HelpRow::Shortcut {
        keys: "q",
        description: "Quit",
    },
];

mod content;
mod diff_render;
mod feedback;
mod help;
mod layout;
mod modal_branches;
mod modal_choices;
mod modal_conflict;
mod modal_list;
pub(crate) mod modal_ssh;
mod modal_stashes;
mod modals;
pub(crate) mod pickers;
mod project_picker;
mod prose;
mod pull_request_checks;
mod pull_request_conversation;
mod pull_request_details;
mod pull_request_files;
mod pull_request_overview;
mod pull_request_review;
mod repository_tabs;
mod side_by_side;
mod sidebar_changes;
mod sidebar_history;
mod sidebar_pull_requests;
mod style;
mod unified_diff;

#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use content::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use diff_render::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use feedback::*;
pub(crate) use help::{draw_help, help_shortcut_count};
#[cfg(test)]
pub(crate) use help::{help_display_index, help_rows, help_shortcut_index_at};
pub(crate) use layout::draw;
#[cfg(test)]
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use layout::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use modal_branches::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use modal_choices::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use modal_conflict::*;
pub(crate) use modal_list::ModalList;
use modal_ssh::draw_ssh_project_modal;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use modal_stashes::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use modals::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pickers::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use project_picker::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use prose::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_checks::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_conversation::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_details::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_files::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_overview::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use pull_request_review::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use repository_tabs::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use side_by_side::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use sidebar_changes::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use sidebar_history::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use sidebar_pull_requests::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use style::*;
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use unified_diff::*;

#[cfg(test)]
#[expect(unused_results, reason = "test helpers discard unused return values")]
mod tests;