Add app-native interactive Git dialogs
- Open an interactive staging dialog for `git add -i`, `git stage -i`, and `git add --interactive`, allowing paths to be selected before staging. - Open an interactive rebase dialog for `git rebase -i` / `--interactive`, with pick, squash, drop, commit-message editing, and reorder controls. - Keep the existing synthetic runtime behavior available for direct engine/test calls, while making app-entered interactive commands show real UI instead of auto-staging. - Center manpage search navigation using actual text-layout highlight bounds instead of approximate line and column math. - Make editor dialogs use a full-screen IME-padded outer scroll container so controls remain reachable when the keyboard pans or resizes the dialog.
This commit is contained in:
@@ -70,6 +70,8 @@ fun GitHugApp() {
|
||||
var hasRestoredProgress by remember { mutableStateOf(false) }
|
||||
var editorState by remember { mutableStateOf<TextEditorState?>(null) }
|
||||
var gitMessageEditorState by remember { mutableStateOf<GitMessageEditorState?>(null) }
|
||||
var interactiveAddState by remember { mutableStateOf<InteractiveAddState?>(null) }
|
||||
var interactiveRebaseState by remember { mutableStateOf<InteractiveRebaseState?>(null) }
|
||||
var manPageState by remember { mutableStateOf<ManPageState?>(null) }
|
||||
var solvedCelebrationTitle by remember { mutableStateOf<String?>(null) }
|
||||
var showTerminalInputHint by remember { mutableStateOf(true) }
|
||||
@@ -209,6 +211,8 @@ fun GitHugApp() {
|
||||
historyDraft = ""
|
||||
editorState = null
|
||||
gitMessageEditorState = null
|
||||
interactiveAddState = null
|
||||
interactiveRebaseState = null
|
||||
manPageState = null
|
||||
applyRecommendedPaneWeights(persist = false)
|
||||
}
|
||||
@@ -227,6 +231,8 @@ fun GitHugApp() {
|
||||
historyDraft = ""
|
||||
editorState = null
|
||||
gitMessageEditorState = null
|
||||
interactiveAddState = null
|
||||
interactiveRebaseState = null
|
||||
manPageState = null
|
||||
paneLayout = paneLayout.copy(
|
||||
weights = paneLayout.weights + recommendedPaneWeights(
|
||||
@@ -418,6 +424,59 @@ fun GitHugApp() {
|
||||
applyCommandResult(state.invocation.command, newRepo, lines, echoCommand = false)
|
||||
}
|
||||
|
||||
fun openInteractiveAdd(invocation: InteractiveAddInvocation) {
|
||||
output = buildList {
|
||||
addAll(output)
|
||||
add("$ ${invocation.command}")
|
||||
add("Opened interactive add")
|
||||
}
|
||||
interactiveAddState = InteractiveAddState(
|
||||
invocation = invocation,
|
||||
files = repo.files,
|
||||
)
|
||||
applyRecommendedPaneWeights(persist = false)
|
||||
}
|
||||
|
||||
fun stageInteractiveSelection(selectedFiles: Set<String>) {
|
||||
val state = interactiveAddState ?: return
|
||||
val updatedRepo = repo.copy(
|
||||
files = repo.files.map { file ->
|
||||
if (file.name in selectedFiles) file.copy(staged = true) else file
|
||||
},
|
||||
)
|
||||
interactiveAddState = null
|
||||
val lines = if (selectedFiles.isEmpty()) {
|
||||
listOf("No changes staged")
|
||||
} else {
|
||||
listOf("staged ${selectedFiles.size} path(s)") + selectedFiles.sorted().map { " $it" }
|
||||
}
|
||||
applyCommandResult(state.invocation.command, updatedRepo, lines, echoCommand = false)
|
||||
}
|
||||
|
||||
fun openInteractiveRebase(invocation: InteractiveRebaseInvocation) {
|
||||
val count = invocation.commitCount ?: repo.commits.size
|
||||
val commits = repo.commits.takeLast(count.coerceAtLeast(0))
|
||||
output = buildList {
|
||||
addAll(output)
|
||||
add("$ ${invocation.command}")
|
||||
add("Opened interactive rebase")
|
||||
}
|
||||
interactiveRebaseState = InteractiveRebaseState(invocation = invocation, commits = commits)
|
||||
applyRecommendedPaneWeights(persist = false)
|
||||
}
|
||||
|
||||
fun applyInteractiveRebaseSelection(drafts: List<RebaseCommitDraft>) {
|
||||
val state = interactiveRebaseState ?: return
|
||||
interactiveRebaseState = null
|
||||
val newRepo = applyInteractiveRebase(repo, drafts)
|
||||
applyCommandResult(
|
||||
raw = state.invocation.command,
|
||||
newRepo = newRepo,
|
||||
lines = listOf("Successfully rebased and updated ${repo.headBranch}."),
|
||||
echoCommand = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun runCommand() {
|
||||
val submittedText = commandInput.text
|
||||
val raw = submittedText.trim()
|
||||
@@ -453,6 +512,18 @@ fun GitHugApp() {
|
||||
return
|
||||
}
|
||||
|
||||
val interactiveAddInvocation = parseInteractiveAddInvocation(raw)
|
||||
if (interactiveAddInvocation != null) {
|
||||
openInteractiveAdd(interactiveAddInvocation)
|
||||
return
|
||||
}
|
||||
|
||||
val interactiveRebaseInvocation = parseInteractiveRebaseInvocation(raw)
|
||||
if (interactiveRebaseInvocation != null) {
|
||||
openInteractiveRebase(interactiveRebaseInvocation)
|
||||
return
|
||||
}
|
||||
|
||||
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
||||
applyCommandResult(raw, newRepo, lines, echoCommand = true)
|
||||
}
|
||||
@@ -615,6 +686,28 @@ fun GitHugApp() {
|
||||
)
|
||||
}
|
||||
|
||||
interactiveAddState?.let { state ->
|
||||
InteractiveAddDialog(
|
||||
state = state,
|
||||
onClose = {
|
||||
output = output + "Interactive add closed without staging"
|
||||
interactiveAddState = null
|
||||
},
|
||||
onStage = { selectedFiles -> stageInteractiveSelection(selectedFiles) },
|
||||
)
|
||||
}
|
||||
|
||||
interactiveRebaseState?.let { state ->
|
||||
InteractiveRebaseDialog(
|
||||
state = state,
|
||||
onClose = {
|
||||
output = output + "Interactive rebase closed without applying"
|
||||
interactiveRebaseState = null
|
||||
},
|
||||
onApply = { drafts -> applyInteractiveRebaseSelection(drafts) },
|
||||
)
|
||||
}
|
||||
|
||||
manPageState?.let { state ->
|
||||
ManPageDialog(
|
||||
state = state,
|
||||
|
||||
Reference in New Issue
Block a user