Skip to content

Commit 206065e

Browse files
committed
feat(ui): add discard dialog for file changes
1 parent 7c2139c commit 206065e

13 files changed

Lines changed: 329 additions & 12 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "stage-tui",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"private": false,
55
"description": "Minimalist TUI Git client",
66
"homepage": "https://github.com/jenslys/stage",

src/app.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CommitHistoryDialog } from "./ui/components/commit-history-dialog"
66
import { useGitTuiController } from "./hooks/use-git-tui-controller"
77
import { CommitDialog } from "./ui/components/commit-dialog"
88
import { DiffWorkspace } from "./ui/components/diff-workspace"
9+
import { DiscardDialog } from "./ui/components/discard-dialog"
910
import { FooterBar } from "./ui/components/footer-bar"
1011
import { MergeConflictDialog } from "./ui/components/merge-conflict-dialog"
1112
import { ShortcutsDialog } from "./ui/components/shortcuts-dialog"
@@ -24,6 +25,7 @@ type ActiveScreen =
2425
| "history"
2526
| "shortcuts"
2627
| "sync"
28+
| "discard"
2729
| "merge-conflict"
2830

2931
export function App({ config }: AppProps) {
@@ -33,17 +35,19 @@ export function App({ config }: AppProps) {
3335
const controller = useGitTuiController(renderer, config)
3436
const activeScreen: ActiveScreen = controller.mergeConflictDialogOpen
3537
? "merge-conflict"
36-
: controller.syncDialogOpen
37-
? "sync"
38-
: controller.branchDialogOpen
39-
? "branch"
40-
: controller.commitDialogOpen
41-
? "commit"
42-
: controller.historyDialogOpen
43-
? "history"
44-
: controller.shortcutsDialogOpen
45-
? "shortcuts"
46-
: "main"
38+
: controller.discardDialogOpen
39+
? "discard"
40+
: controller.syncDialogOpen
41+
? "sync"
42+
: controller.branchDialogOpen
43+
? "branch"
44+
: controller.commitDialogOpen
45+
? "commit"
46+
: controller.historyDialogOpen
47+
? "history"
48+
: controller.shortcutsDialogOpen
49+
? "shortcuts"
50+
: "main"
4751
const footerHint = resolveFooterHint({
4852
activeScreen,
4953
branchDialogMode: controller.branchDialogMode,
@@ -252,6 +256,29 @@ export function App({ config }: AppProps) {
252256
/>
253257
) : null}
254258

259+
{activeScreen === "discard" ? (
260+
<DiscardDialog
261+
open={true}
262+
focus={controller.focus}
263+
terminalWidth={terminalWidth}
264+
terminalHeight={terminalHeight}
265+
path={controller.discardPath ?? ""}
266+
options={controller.discardOptions}
267+
optionIndex={controller.discardOptionIndex}
268+
onOptionClick={(index) => {
269+
controller.setDiscardSelection(index)
270+
}}
271+
onOptionScroll={(direction) => {
272+
if (direction === "up") {
273+
controller.moveDiscardSelectionUp()
274+
} else {
275+
controller.moveDiscardSelectionDown()
276+
}
277+
}}
278+
theme={theme}
279+
/>
280+
) : null}
281+
255282
{activeScreen === "merge-conflict" ? (
256283
<MergeConflictDialog
257284
open={true}
@@ -314,6 +341,7 @@ function resolveFooterHint({
314341
if (activeScreen === "commit") return "enter commit esc cancel"
315342
if (activeScreen === "shortcuts") return "esc close"
316343
if (activeScreen === "sync") return "enter choose esc back"
344+
if (activeScreen === "discard") return "enter choose esc back"
317345
if (activeScreen === "merge-conflict") return "enter open or finish tab next panel esc back"
318346
if (activeScreen === "history") {
319347
if (historyMode === "action") return "enter choose esc back"

src/git/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
commitChanges,
2424
completeMergeCommit,
2525
createAndCheckoutBranch,
26+
discardFileChanges,
2627
deleteLocalBranch,
2728
deleteRemoteBranch,
2829
fetchRepo,
@@ -130,6 +131,10 @@ export class GitClient {
130131
await markConflictResolved(path, this.runGit)
131132
}
132133

134+
async discardFileChanges(path: string): Promise<void> {
135+
await discardFileChanges(path, this.runGit)
136+
}
137+
133138
async workingTreeFileHasConflictMarkers(path: string): Promise<boolean> {
134139
const normalizedPath = path.trim()
135140
if (!normalizedPath) {

src/git/write-ops.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,25 @@ export async function markConflictResolved(path: string, runGit: RunGit): Promis
6969
await runGit(["add", "--", normalizedPath])
7070
}
7171

72+
export async function discardFileChanges(path: string, runGit: RunGit): Promise<void> {
73+
const normalizedPath = path.trim()
74+
if (!normalizedPath) {
75+
throw new Error("File path is required.")
76+
}
77+
78+
const trackedCheck = await runGit(["ls-files", "--error-unmatch", "--", normalizedPath], {
79+
expectedCodes: [0, 1, 128],
80+
})
81+
const isTracked = trackedCheck.code === 0
82+
83+
if (isTracked) {
84+
await runGit(["restore", "--staged", "--worktree", "--", normalizedPath])
85+
return
86+
}
87+
88+
await runGit(["clean", "-f", "--", normalizedPath])
89+
}
90+
7291
export async function completeMergeCommit(runGit: RunGit): Promise<void> {
7392
const mergeInProgress = await isMergeInProgress(runGit)
7493
if (!mergeInProgress) {

src/hooks/git-tui-keyboard/handle-dialog-keys.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function handleDialogKeys({
2121
renderer,
2222
commitDialogOpen,
2323
syncDialogOpen,
24+
discardDialogOpen,
2425
mergeConflictDialogOpen,
2526
branchDialogOpen,
2627
branchDialogMode,
@@ -29,13 +30,15 @@ export function handleDialogKeys({
2930
shortcutsDialogOpen,
3031
closeShortcutsDialog,
3132
closeSyncDialog,
33+
closeDiscardDialog,
3234
closeMergeConflictDialog,
3335
openShortcutsDialog,
3436
focus,
3537
mergeConflictFileCount,
3638
setFocus,
3739
submitHistoryAction,
3840
submitSyncAction,
41+
submitDiscardAction,
3942
submitMergeConflictAction,
4043
openSelectedMergeConflictFileInEditor,
4144
submitHistoryCommitSelection,
@@ -59,6 +62,8 @@ export function handleDialogKeys({
5962
moveSyncSelectionDown,
6063
moveMergeConflictFileUp,
6164
moveMergeConflictFileDown,
65+
moveDiscardSelectionUp,
66+
moveDiscardSelectionDown,
6267
moveMergeConflictActionUp,
6368
moveMergeConflictActionDown,
6469
commitChanges,
@@ -82,6 +87,7 @@ export function handleDialogKeys({
8287
if (
8388
!commitDialogOpen &&
8489
!syncDialogOpen &&
90+
!discardDialogOpen &&
8591
!mergeConflictDialogOpen &&
8692
!branchDialogOpen &&
8793
!historyDialogOpen &&
@@ -127,6 +133,27 @@ export function handleDialogKeys({
127133
return true
128134
}
129135

136+
if (discardDialogOpen && flags.isEnter) {
137+
key.preventDefault()
138+
key.stopPropagation()
139+
void submitDiscardAction()
140+
return true
141+
}
142+
143+
if (discardDialogOpen && focus === "discard-dialog-list" && key.name === "up") {
144+
key.preventDefault()
145+
key.stopPropagation()
146+
moveDiscardSelectionUp()
147+
return true
148+
}
149+
150+
if (discardDialogOpen && focus === "discard-dialog-list" && key.name === "down") {
151+
key.preventDefault()
152+
key.stopPropagation()
153+
moveDiscardSelectionDown()
154+
return true
155+
}
156+
130157
if (mergeConflictDialogOpen && flags.isEnter && focus === "merge-conflict-files") {
131158
key.preventDefault()
132159
key.stopPropagation()
@@ -310,6 +337,11 @@ export function handleDialogKeys({
310337
return true
311338
}
312339

340+
if (discardDialogOpen) {
341+
closeDiscardDialog()
342+
return true
343+
}
344+
313345
if (historyDialogOpen) {
314346
if (historyDialogMode === "action") {
315347
backToHistoryCommitList()
@@ -353,6 +385,11 @@ export function handleDialogKeys({
353385
return true
354386
}
355387

388+
if (discardDialogOpen) {
389+
setFocus("discard-dialog-list")
390+
return true
391+
}
392+
356393
if (branchDialogOpen) {
357394
setFocus(branchDialogMode === "create" ? "branch-create" : "branch-dialog-list")
358395
return true

src/hooks/git-tui-keyboard/handle-main-keys.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function handleMainKeys({
2323
moveToPreviousFile,
2424
moveToNextFile,
2525
openCommitDialog,
26+
openDiscardDialog,
2627
openSelectedFileInEditor,
2728
openBranchDialog,
2829
openHistoryDialog,
@@ -102,6 +103,19 @@ export function handleMainKeys({
102103
return true
103104
}
104105

106+
if (
107+
!isDialogOpen &&
108+
flags.isPlainShortcutKey &&
109+
focus === "files" &&
110+
fileCount > 0 &&
111+
(key.name === "delete" || key.name === "backspace")
112+
) {
113+
key.preventDefault()
114+
key.stopPropagation()
115+
openDiscardDialog()
116+
return true
117+
}
118+
105119
if (!isDialogOpen && key.ctrl && key.name === "r") {
106120
key.preventDefault()
107121
key.stopPropagation()

src/hooks/git-tui-keyboard/key-flags.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function resolveKeyboardFlags(
1313
dialogs: {
1414
commitDialogOpen: boolean
1515
syncDialogOpen: boolean
16+
discardDialogOpen: boolean
1617
mergeConflictDialogOpen: boolean
1718
branchDialogOpen: boolean
1819
historyDialogOpen: boolean
@@ -32,6 +33,7 @@ export function resolveKeyboardFlags(
3233
const isDialogOpen =
3334
dialogs.commitDialogOpen ||
3435
dialogs.syncDialogOpen ||
36+
dialogs.discardDialogOpen ||
3537
dialogs.mergeConflictDialogOpen ||
3638
dialogs.branchDialogOpen ||
3739
dialogs.historyDialogOpen ||

src/hooks/git-tui-keyboard/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type UseGitTuiKeyboardParams = {
1212
renderer: RendererClipboard
1313
commitDialogOpen: boolean
1414
syncDialogOpen: boolean
15+
discardDialogOpen: boolean
1516
mergeConflictDialogOpen: boolean
1617
branchDialogOpen: boolean
1718
branchDialogMode: BranchDialogMode
@@ -28,6 +29,7 @@ export type UseGitTuiKeyboardParams = {
2829
openBranchDialog: () => void
2930
closeBranchDialog: () => void
3031
closeSyncDialog: () => void
32+
closeDiscardDialog: () => void
3133
closeMergeConflictDialog: () => void
3234
showBranchDialogList: () => void
3335
submitBranchSelection: () => Promise<void>
@@ -47,6 +49,7 @@ export type UseGitTuiKeyboardParams = {
4749
submitHistoryCommitSelection: () => Promise<void>
4850
submitHistoryAction: () => Promise<void>
4951
submitSyncAction: () => Promise<void>
52+
submitDiscardAction: () => Promise<void>
5053
submitMergeConflictAction: () => Promise<void>
5154
openSelectedMergeConflictFileInEditor: () => Promise<void>
5255
moveCommitSelectionUp: () => void
@@ -57,11 +60,14 @@ export type UseGitTuiKeyboardParams = {
5760
moveHistoryActionDown: () => void
5861
moveMergeConflictFileUp: () => void
5962
moveMergeConflictFileDown: () => void
63+
moveDiscardSelectionUp: () => void
64+
moveDiscardSelectionDown: () => void
6065
moveMergeConflictActionUp: () => void
6166
moveMergeConflictActionDown: () => void
6267
commitChanges: () => Promise<void>
6368
createBranchAndCheckout: () => Promise<void>
6469
openCommitDialog: () => void
70+
openDiscardDialog: () => void
6571
openSelectedFileInEditor: () => Promise<void>
6672
openShortcutsDialog: () => void
6773
closeShortcutsDialog: () => void

0 commit comments

Comments
 (0)