Align tests with runtime sandbox and polish terminal help
- Run level solution scenarios through GitRepositoryRuntime with a real filesystem sandbox and git binary so evidence logs match app behavior. - Materialize synthetic remotes as local bare repositories and configure upstream tracking for pull/push levels. - Route mobile-hostile Git interactions through shared sandbox semantics for clone, patch add, interactive rebase, squash merge, submodule, stash, revert, and related flows. - Relax affected level validators to inspect observable runtime state instead of fallback-only staged/index details. - Show .git in ls output for native and fallback helper commands. - Make tab completion resolve filenames and directories relative to the current working directory. - Integrate help controls into the callout overlay and reposition the prompt callout above the terminal prompt. - Add regression coverage for .git listing and subfolder completion.
This commit is contained in:
@@ -629,22 +629,34 @@ fun GitHugApp() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileCompletionCandidates(repo: RepoState): List<String> {
|
||||
internal fun fileCompletionCandidates(repo: RepoState): List<String> {
|
||||
val prefix = repo.currentDirPrefix()
|
||||
return repo.files
|
||||
.filterNot { it.deleted }
|
||||
.map { it.name }
|
||||
.filter { it.startsWith(prefix) }
|
||||
.map { it.removePrefix(prefix) }
|
||||
.filter { it.isNotBlank() }
|
||||
}
|
||||
|
||||
private fun directoryCompletionCandidates(repo: RepoState): List<String> {
|
||||
internal fun directoryCompletionCandidates(repo: RepoState): List<String> {
|
||||
val prefix = repo.currentDirPrefix()
|
||||
return repo.files
|
||||
.filterNot { it.deleted }
|
||||
.map { it.name }
|
||||
.filter { it.startsWith(prefix) }
|
||||
.map { it.removePrefix(prefix) }
|
||||
.flatMap { file ->
|
||||
val parts = file.name.split('/').dropLast(1)
|
||||
val parts = file.split('/').dropLast(1)
|
||||
parts.indices.map { index -> parts.take(index + 1).joinToString("/") + "/" }
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun RepoState.currentDirPrefix(): String {
|
||||
return if (currentDir == ".") "" else currentDir.trimEnd('/') + "/"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HelpCalloutOverlay(
|
||||
showHelpOnStart: Boolean,
|
||||
@@ -661,37 +673,42 @@ private fun HelpCalloutOverlay(
|
||||
text = "Read the exercise description, then solve it by entering commands below.",
|
||||
modifier = Modifier.align(Alignment.TopStart),
|
||||
)
|
||||
HelpBubble(
|
||||
text = "Tap the prompt to enter a command.",
|
||||
modifier = Modifier.align(Alignment.BottomStart),
|
||||
)
|
||||
Surface(
|
||||
HelpBubbleWithControls(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.fillMaxWidth(),
|
||||
color = PanelPrimary.copy(alpha = 0.97f),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
shadowElevation = 8.dp,
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(bottom = 54.dp),
|
||||
text = "Tap the prompt to enter a command.",
|
||||
showHelpOnStart = showHelpOnStart,
|
||||
onShowHelpOnStartChange = onShowHelpOnStartChange,
|
||||
onOk = onOk,
|
||||
onClose = onClose,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HelpBubbleWithControls(
|
||||
text: String,
|
||||
showHelpOnStart: Boolean,
|
||||
onShowHelpOnStartChange: (Boolean) -> Unit,
|
||||
onOk: () -> Unit,
|
||||
onClose: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bubbleColor = Color(0xFFFFF1A8)
|
||||
Column(modifier = modifier.fillMaxWidth(0.9f)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(bubbleColor, RoundedCornerShape(18.dp))
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Help",
|
||||
color = TextPrimary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp,
|
||||
)
|
||||
TextButton(onClick = onClose) {
|
||||
Text("Close", color = Accent)
|
||||
}
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = text,
|
||||
color = Color(0xFF161000),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
@@ -700,24 +717,44 @@ private fun HelpCalloutOverlay(
|
||||
checked = showHelpOnStart,
|
||||
onCheckedChange = onShowHelpOnStartChange,
|
||||
colors = CheckboxDefaults.colors(
|
||||
checkedColor = Accent,
|
||||
uncheckedColor = TextSecondary,
|
||||
checkmarkColor = AppBackground,
|
||||
checkedColor = Color(0xFF161000),
|
||||
uncheckedColor = Color(0xFF6E5A00),
|
||||
checkmarkColor = bubbleColor,
|
||||
),
|
||||
)
|
||||
Text("Show help on start", color = TextPrimary)
|
||||
Text("Show help on start", color = Color(0xFF161000))
|
||||
}
|
||||
Button(
|
||||
onClick = onOk,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Accent,
|
||||
contentColor = AppBackground,
|
||||
),
|
||||
) {
|
||||
Text("OK")
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
onClick = onOk,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFF161000),
|
||||
contentColor = bubbleColor,
|
||||
),
|
||||
) {
|
||||
Text("OK")
|
||||
}
|
||||
TextButton(onClick = onClose) {
|
||||
Text("Close", color = Color(0xFF161000))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.padding(start = 28.dp)
|
||||
.size(width = 26.dp, height = 13.dp),
|
||||
) {
|
||||
drawPath(
|
||||
path = Path().apply {
|
||||
moveTo(0f, 0f)
|
||||
lineTo(size.width, 0f)
|
||||
lineTo(size.width * 0.25f, size.height)
|
||||
close()
|
||||
},
|
||||
color = bubbleColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user