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:
Joe Tretter
2026-05-09 15:41:55 -05:00
parent 0acf8c9ab0
commit f49e8a96b1
9 changed files with 708 additions and 120 deletions

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
@@ -31,6 +32,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
data class GitMessageEditorState(
val invocation: GitEditorInvocation,
@@ -46,64 +48,75 @@ fun GitMessageEditorDialog(
) {
val horizontalScroll = rememberScrollState()
val verticalScroll = rememberScrollState()
val dialogScroll = rememberScrollState()
Dialog(onDismissRequest = onClose) {
Surface(
Dialog(
onDismissRequest = onClose,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.72f)
.imePadding(),
color = PanelPrimary,
shape = RoundedCornerShape(8.dp),
.fillMaxSize()
.imePadding()
.verticalScroll(dialogScroll)
.padding(12.dp),
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
Surface(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 320.dp, max = 620.dp),
color = PanelPrimary,
shape = RoundedCornerShape(8.dp),
) {
Text(
text = state.invocation.title,
color = TextPrimary,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
EditorButton(label = "Close", onClick = onClose)
EditorButton(label = "Save", enabled = state.content.isNotBlank(), onClick = onSave)
}
Text(
text = state.invocation.command,
color = TextSecondary,
fontFamily = FontFamily.Monospace,
fontSize = 13.sp,
)
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.heightIn(min = 220.dp)
.background(TerminalBackground, RoundedCornerShape(6.dp))
.padding(10.dp)
.horizontalScroll(horizontalScroll)
.verticalScroll(verticalScroll),
) {
BasicTextField(
value = state.content,
onValueChange = onContentChange,
modifier = Modifier.sizeIn(minWidth = 1000.dp, minHeight = 700.dp),
textStyle = TextStyle(
color = TextPrimary,
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
),
cursorBrush = SolidColor(Accent),
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Ascii,
),
Text(
text = state.invocation.title,
color = TextPrimary,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
EditorButton(label = "Close", onClick = onClose)
EditorButton(label = "Save", enabled = state.content.isNotBlank(), onClick = onSave)
}
Text(
text = state.invocation.command,
color = TextSecondary,
fontFamily = FontFamily.Monospace,
fontSize = 13.sp,
)
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.heightIn(min = 220.dp)
.background(TerminalBackground, RoundedCornerShape(6.dp))
.padding(10.dp)
.horizontalScroll(horizontalScroll)
.verticalScroll(verticalScroll),
) {
BasicTextField(
value = state.content,
onValueChange = onContentChange,
modifier = Modifier.sizeIn(minWidth = 1000.dp, minHeight = 700.dp),
textStyle = TextStyle(
color = TextPrimary,
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
),
cursorBrush = SolidColor(Accent),
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Ascii,
),
)
}
}
}
}