Auto-commit after successful build: update app gameplay/UI, improve build setup
Changed files:\napp/build.gradle.kts app/src/main/java/solutions/tretter/githugandroid/GitHelpCommands.kt app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt app/src/main/java/solutions/tretter/githugandroid/ManPageDialog.kt app/src/test/java/solutions/tretter/githugandroid/GitHelpCommandsTest.kt
This commit is contained in:
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId = "solutions.tretter.githugandroid"
|
applicationId = "solutions.tretter.githugandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 98
|
versionCode = 99
|
||||||
versionName = "0.1.97"
|
versionName = "0.1.98"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
data class GitHelpInvocation(
|
||||||
|
val topic: String,
|
||||||
|
val command: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun parseGitHelpInvocation(command: String): GitHelpInvocation? {
|
||||||
|
val tokens = GitSandboxEngine.tokenizeCommand(command)
|
||||||
|
if (tokens.size < 3 || tokens[0] != "git" || tokens[1] != "help") return null
|
||||||
|
val topic = tokens[2].takeIf { it.matches(Regex("[A-Za-z0-9_-]+")) } ?: return null
|
||||||
|
return GitHelpInvocation(topic = topic, command = command)
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ fun GitHugApp() {
|
|||||||
var historyDraft by remember { mutableStateOf("") }
|
var historyDraft by remember { mutableStateOf("") }
|
||||||
var hasRestoredProgress by remember { mutableStateOf(false) }
|
var hasRestoredProgress by remember { mutableStateOf(false) }
|
||||||
var editorState by remember { mutableStateOf<TextEditorState?>(null) }
|
var editorState by remember { mutableStateOf<TextEditorState?>(null) }
|
||||||
|
var manPageState by remember { mutableStateOf<ManPageState?>(null) }
|
||||||
val currentLevel = levels[currentLevelIndex]
|
val currentLevel = levels[currentLevelIndex]
|
||||||
|
|
||||||
LaunchedEffect(persistedPaneLayout) {
|
LaunchedEffect(persistedPaneLayout) {
|
||||||
@@ -168,6 +169,7 @@ fun GitHugApp() {
|
|||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
editorState = null
|
editorState = null
|
||||||
|
manPageState = null
|
||||||
applyRecommendedPaneWeights(persist = false)
|
applyRecommendedPaneWeights(persist = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +186,7 @@ fun GitHugApp() {
|
|||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
editorState = null
|
editorState = null
|
||||||
|
manPageState = null
|
||||||
paneLayout = paneLayout.copy(
|
paneLayout = paneLayout.copy(
|
||||||
weights = paneLayout.weights + recommendedPaneWeights(
|
weights = paneLayout.weights + recommendedPaneWeights(
|
||||||
heights = recommendedPaneHeights(
|
heights = recommendedPaneHeights(
|
||||||
@@ -323,6 +326,17 @@ fun GitHugApp() {
|
|||||||
applyRecommendedPaneWeights(persist = false)
|
applyRecommendedPaneWeights(persist = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openManPage(invocation: GitHelpInvocation) {
|
||||||
|
val content = runtime.gitManPage(currentLevel, repo, invocation.topic)
|
||||||
|
output = buildList {
|
||||||
|
addAll(output)
|
||||||
|
add("$ ${invocation.command}")
|
||||||
|
add("Opened git help viewer for ${invocation.topic}")
|
||||||
|
}
|
||||||
|
manPageState = ManPageState(topic = invocation.topic, content = content)
|
||||||
|
applyRecommendedPaneWeights(persist = false)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveEditor() {
|
fun saveEditor() {
|
||||||
val state = editorState ?: return
|
val state = editorState ?: return
|
||||||
val targetPath = state.saveAsPath.trim()
|
val targetPath = state.saveAsPath.trim()
|
||||||
@@ -352,6 +366,12 @@ fun GitHugApp() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val helpInvocation = parseGitHelpInvocation(raw)
|
||||||
|
if (helpInvocation != null) {
|
||||||
|
openManPage(helpInvocation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
||||||
applyCommandResult(raw, newRepo, lines, echoCommand = true)
|
applyCommandResult(raw, newRepo, lines, echoCommand = true)
|
||||||
}
|
}
|
||||||
@@ -460,5 +480,12 @@ fun GitHugApp() {
|
|||||||
onSave = { saveEditor() },
|
onSave = { saveEditor() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manPageState?.let { state ->
|
||||||
|
ManPageDialog(
|
||||||
|
state = state,
|
||||||
|
onClose = { manPageState = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,9 +85,26 @@ class GitRepositoryRuntime(private val context: Context) {
|
|||||||
add(" selected ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}")
|
add(" selected ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}")
|
||||||
add(" helper commands: ls, pwd, cat, touch, mkdir, rm, echo")
|
add(" helper commands: ls, pwd, cat, touch, mkdir, rm, echo")
|
||||||
add(" visual editors: vi, nano, emacs, ed, ex")
|
add(" visual editors: vi, nano, emacs, ed, ex")
|
||||||
|
add(" git help <command>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun gitManPage(level: Level, currentRepo: RepoState, topic: String): String {
|
||||||
|
val nativeGit = nativeGitBinary()
|
||||||
|
if (nativeGit == null) {
|
||||||
|
return fallbackManPage(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sandboxRoot = sandboxDir(level).canonicalFile
|
||||||
|
val workingDir = File(sandboxRoot, currentRepo.currentDir).canonicalFile
|
||||||
|
.takeIf { it.path.startsWith(sandboxRoot.path) } ?: sandboxRoot
|
||||||
|
val result = runGit(nativeGit, workingDir, listOf(topic, "-h"))
|
||||||
|
return result.outputLines
|
||||||
|
.filterNot { it.contains("no man viewer", ignoreCase = true) }
|
||||||
|
.joinToString("\n")
|
||||||
|
.ifBlank { fallbackManPage(topic) }
|
||||||
|
}
|
||||||
|
|
||||||
fun readEditorFile(level: Level, currentRepo: RepoState, path: String): Pair<String, List<String>> {
|
fun readEditorFile(level: Level, currentRepo: RepoState, path: String): Pair<String, List<String>> {
|
||||||
val nativeGit = nativeGitBinary()
|
val nativeGit = nativeGitBinary()
|
||||||
if (nativeGit == null) {
|
if (nativeGit == null) {
|
||||||
@@ -198,6 +215,80 @@ class GitRepositoryRuntime(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fallbackManPage(topic: String): String {
|
||||||
|
val body = when (topic) {
|
||||||
|
"tag" -> """
|
||||||
|
NAME
|
||||||
|
git-tag - Create, list, delete or verify a tag object
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
git tag <tagname>
|
||||||
|
git tag -d <tagname>
|
||||||
|
git tag
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Tags name specific points in history. In GitHug Android,
|
||||||
|
tag levels usually expect you to create or reference a tag
|
||||||
|
by name.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
git tag new_tag
|
||||||
|
git tag v1.2
|
||||||
|
""".trimIndent()
|
||||||
|
"branch" -> """
|
||||||
|
NAME
|
||||||
|
git-branch - List, create, or delete branches
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
git branch <branchname>
|
||||||
|
git branch -d <branchname>
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
git branch test_code
|
||||||
|
git branch -d delete_me
|
||||||
|
""".trimIndent()
|
||||||
|
"checkout" -> """
|
||||||
|
NAME
|
||||||
|
git-checkout - Switch branches or restore files
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
git checkout <branch>
|
||||||
|
git checkout -b <branch>
|
||||||
|
git checkout -- <file>
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
git checkout -b my_branch
|
||||||
|
git checkout -- config.rb
|
||||||
|
""".trimIndent()
|
||||||
|
"add" -> """
|
||||||
|
NAME
|
||||||
|
git-add - Add file contents to the index
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
git add <path>
|
||||||
|
git add .
|
||||||
|
git add -p <path>
|
||||||
|
""".trimIndent()
|
||||||
|
"commit" -> """
|
||||||
|
NAME
|
||||||
|
git-commit - Record changes to the repository
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
git commit -m <message>
|
||||||
|
git commit --amend
|
||||||
|
""".trimIndent()
|
||||||
|
else -> """
|
||||||
|
NAME
|
||||||
|
git-$topic
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
No bundled fallback manpage is available for git $topic.
|
||||||
|
Try the command suggestions or use git help for another command.
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
private fun materializeNativeGitState(nativeGit: File, sandbox: File, desired: RepoState) {
|
private fun materializeNativeGitState(nativeGit: File, sandbox: File, desired: RepoState) {
|
||||||
desired.config.forEach { (key, value) ->
|
desired.config.forEach { (key, value) ->
|
||||||
runGit(nativeGit, sandbox, listOf("config", key, value))
|
runGit(nativeGit, sandbox, listOf("config", key, value))
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
|
||||||
|
data class ManPageState(
|
||||||
|
val topic: String,
|
||||||
|
val content: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ManPageDialog(
|
||||||
|
state: ManPageState,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
Dialog(onDismissRequest = onClose) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(0.86f),
|
||||||
|
color = PanelPrimary,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Git Manpage",
|
||||||
|
color = TextPrimary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClose,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Accent,
|
||||||
|
contentColor = AppBackground,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text("Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "git ${state.topic}",
|
||||||
|
color = TextPrimary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = state.content,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.background(TerminalBackground, RoundedCornerShape(6.dp))
|
||||||
|
.padding(10.dp)
|
||||||
|
.verticalScroll(scrollState),
|
||||||
|
color = TextPrimary,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GitHelpCommandsTest {
|
||||||
|
@Test
|
||||||
|
fun parsesGitHelpCommand() {
|
||||||
|
val invocation = parseGitHelpInvocation("git help tag")
|
||||||
|
|
||||||
|
assertEquals("tag", invocation?.topic)
|
||||||
|
assertEquals("git help tag", invocation?.command)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ignoresNonHelpCommands() {
|
||||||
|
assertNull(parseGitHelpInvocation("git tag -h"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rejectsUnsafeHelpTopic() {
|
||||||
|
assertNull(parseGitHelpInvocation("git help ../config"))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user