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/EditorCommands.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/TextEditorDialog.kt app/src/test/java/solutions/tretter/githugandroid/EditorCommandsTest.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 = 96
|
versionCode = 97
|
||||||
versionName = "0.1.95"
|
versionName = "0.1.96"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
private val VisualEditorCommands = setOf("vi", "nano", "emacs", "ed", "ex")
|
||||||
|
|
||||||
|
data class VisualEditorInvocation(
|
||||||
|
val editor: String,
|
||||||
|
val path: String?,
|
||||||
|
val command: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun parseVisualEditorInvocation(command: String): VisualEditorInvocation? {
|
||||||
|
val tokens = GitSandboxEngine.tokenizeCommand(command)
|
||||||
|
val editor = tokens.firstOrNull()?.takeIf { it in VisualEditorCommands } ?: return null
|
||||||
|
return VisualEditorInvocation(
|
||||||
|
editor = editor,
|
||||||
|
path = tokens.drop(1).firstOrNull { !it.startsWith("-") },
|
||||||
|
command = command,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ fun GitHugApp() {
|
|||||||
var historyIndex by remember { mutableStateOf(-1) }
|
var historyIndex by remember { mutableStateOf(-1) }
|
||||||
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) }
|
||||||
val currentLevel = levels[currentLevelIndex]
|
val currentLevel = levels[currentLevelIndex]
|
||||||
|
|
||||||
LaunchedEffect(persistedPaneLayout) {
|
LaunchedEffect(persistedPaneLayout) {
|
||||||
@@ -166,6 +167,7 @@ fun GitHugApp() {
|
|||||||
visibleHint = null
|
visibleHint = null
|
||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
|
editorState = null
|
||||||
applyRecommendedPaneWeights(persist = false)
|
applyRecommendedPaneWeights(persist = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +183,7 @@ fun GitHugApp() {
|
|||||||
visibleHint = null
|
visibleHint = null
|
||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
|
editorState = null
|
||||||
paneLayout = paneLayout.copy(
|
paneLayout = paneLayout.copy(
|
||||||
weights = paneLayout.weights + recommendedPaneWeights(
|
weights = paneLayout.weights + recommendedPaneWeights(
|
||||||
heights = recommendedPaneHeights(
|
heights = recommendedPaneHeights(
|
||||||
@@ -247,6 +250,87 @@ fun GitHugApp() {
|
|||||||
commandInput = TextFieldValue(newText, selection = TextRange(newCursor))
|
commandInput = TextFieldValue(newText, selection = TextRange(newCursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) {
|
||||||
|
val levelForResult = currentLevel
|
||||||
|
val solvedAfterCommand = levelForResult.validator(newRepo, raw)
|
||||||
|
val wasAlreadyCompleted = currentLevel.id in completedLevels
|
||||||
|
AppLog.d(
|
||||||
|
"GitHugApp",
|
||||||
|
"Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
|
||||||
|
)
|
||||||
|
val newOutput = buildList {
|
||||||
|
addAll(output)
|
||||||
|
if (echoCommand) {
|
||||||
|
add("$ $raw")
|
||||||
|
}
|
||||||
|
addAll(lines)
|
||||||
|
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
||||||
|
add("✔ Level solved: ${levelForResult.title}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
||||||
|
val updatedCompletedLevels = completedLevels + levelForResult.id
|
||||||
|
completedLevels = updatedCompletedLevels
|
||||||
|
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
|
||||||
|
if (nextLevelIndex >= 0) {
|
||||||
|
val nextLevelId = levels[nextLevelIndex].id
|
||||||
|
AppLog.d(
|
||||||
|
"GitHugApp",
|
||||||
|
"Level solved ${levelForResult.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId",
|
||||||
|
)
|
||||||
|
scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) }
|
||||||
|
loadLevel(nextLevelIndex)
|
||||||
|
} else {
|
||||||
|
AppLog.d("GitHugApp", "All levels completed")
|
||||||
|
scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
|
||||||
|
repo = newRepo
|
||||||
|
output = listOf("🏁 All Githug levels completed.")
|
||||||
|
clearCommandInput()
|
||||||
|
suppressedImeEcho = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AppLog.d("GitHugApp", "Staying on level=${levelForResult.id}")
|
||||||
|
repo = newRepo
|
||||||
|
output = newOutput
|
||||||
|
applyRecommendedPaneWeights(persist = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openEditor(invocation: VisualEditorInvocation) {
|
||||||
|
val path = invocation.path.orEmpty()
|
||||||
|
val (content, lines) = if (path.isBlank()) {
|
||||||
|
"" to emptyList()
|
||||||
|
} else {
|
||||||
|
runtime.readEditorFile(currentLevel, repo, path)
|
||||||
|
}
|
||||||
|
output = buildList {
|
||||||
|
addAll(output)
|
||||||
|
add("$ ${invocation.command}")
|
||||||
|
addAll(lines)
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
add("Opened ${invocation.editor} editor${if (path.isBlank()) "" else " for $path"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
editorState = TextEditorState(
|
||||||
|
editor = invocation.editor,
|
||||||
|
originalCommand = invocation.command,
|
||||||
|
path = path,
|
||||||
|
content = content,
|
||||||
|
saveAsPath = path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
applyRecommendedPaneWeights(persist = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveEditor(targetPath: String) {
|
||||||
|
val state = editorState ?: return
|
||||||
|
if (targetPath.isBlank()) return
|
||||||
|
val (newRepo, lines) = runtime.writeEditorFile(currentLevel, repo, targetPath, state.content)
|
||||||
|
editorState = null
|
||||||
|
applyCommandResult(state.originalCommand, newRepo, lines, echoCommand = false)
|
||||||
|
}
|
||||||
|
|
||||||
fun runCommand() {
|
fun runCommand() {
|
||||||
val submittedText = commandInput.text
|
val submittedText = commandInput.text
|
||||||
val raw = submittedText.trim()
|
val raw = submittedText.trim()
|
||||||
@@ -261,47 +345,14 @@ fun GitHugApp() {
|
|||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
|
|
||||||
|
val editorInvocation = parseVisualEditorInvocation(raw)
|
||||||
|
if (editorInvocation != null) {
|
||||||
|
openEditor(editorInvocation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
||||||
val solvedAfterCommand = currentLevel.validator(newRepo, raw)
|
applyCommandResult(raw, newRepo, lines, echoCommand = true)
|
||||||
val wasAlreadyCompleted = currentLevel.id in completedLevels
|
|
||||||
AppLog.d(
|
|
||||||
"GitHugApp",
|
|
||||||
"Command='$raw' level=${currentLevel.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
|
|
||||||
)
|
|
||||||
val newOutput = buildList {
|
|
||||||
addAll(output)
|
|
||||||
add("$ $raw")
|
|
||||||
addAll(lines)
|
|
||||||
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
|
||||||
add("✔ Level solved: ${currentLevel.title}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
|
||||||
val updatedCompletedLevels = completedLevels + currentLevel.id
|
|
||||||
completedLevels = updatedCompletedLevels
|
|
||||||
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
|
|
||||||
if (nextLevelIndex >= 0) {
|
|
||||||
val nextLevelId = levels[nextLevelIndex].id
|
|
||||||
AppLog.d(
|
|
||||||
"GitHugApp",
|
|
||||||
"Level solved ${currentLevel.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId",
|
|
||||||
)
|
|
||||||
scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) }
|
|
||||||
loadLevel(nextLevelIndex)
|
|
||||||
} else {
|
|
||||||
AppLog.d("GitHugApp", "All levels completed")
|
|
||||||
scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
|
|
||||||
repo = newRepo
|
|
||||||
output = listOf("🏁 All Githug levels completed.")
|
|
||||||
clearCommandInput()
|
|
||||||
suppressedImeEcho = null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AppLog.d("GitHugApp", "Staying on level=${currentLevel.id}")
|
|
||||||
repo = newRepo
|
|
||||||
output = newOutput
|
|
||||||
applyRecommendedPaneWeights(persist = false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showCommandHelp() {
|
fun showCommandHelp() {
|
||||||
@@ -395,5 +446,25 @@ fun GitHugApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editorState?.let { state ->
|
||||||
|
TextEditorDialog(
|
||||||
|
state = state,
|
||||||
|
onContentChange = { editorState = state.copy(content = it) },
|
||||||
|
onSaveAsPathChange = { editorState = state.copy(saveAsPath = it) },
|
||||||
|
onCancel = {
|
||||||
|
output = output + "Editor closed without saving"
|
||||||
|
editorState = null
|
||||||
|
},
|
||||||
|
onSave = { saveEditor(state.path) },
|
||||||
|
onSaveAs = {
|
||||||
|
val targetPath = state.saveAsPath.trim()
|
||||||
|
if (targetPath.isNotEmpty()) {
|
||||||
|
editorState = state.copy(path = targetPath, saveAsPath = targetPath)
|
||||||
|
saveEditor(targetPath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,9 +84,59 @@ class GitRepositoryRuntime(private val context: Context) {
|
|||||||
add(" binary path: nativeLibraryDir/libgit.so")
|
add(" binary path: nativeLibraryDir/libgit.so")
|
||||||
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun readEditorFile(level: Level, currentRepo: RepoState, path: String): Pair<String, List<String>> {
|
||||||
|
val nativeGit = nativeGitBinary()
|
||||||
|
if (nativeGit == null) {
|
||||||
|
return currentRepo.files.find { it.name == path }?.content.orEmpty() to emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val sandboxRoot = sandboxDir(level).canonicalFile
|
||||||
|
val workingDir = File(sandboxRoot, currentRepo.currentDir).canonicalFile
|
||||||
|
.takeIf { it.path.startsWith(sandboxRoot.path) } ?: sandboxRoot
|
||||||
|
val file = File(workingDir, path).canonicalFile
|
||||||
|
if (!file.path.startsWith(sandboxRoot.path)) {
|
||||||
|
return "" to listOf("editor: $path: Permission denied")
|
||||||
|
}
|
||||||
|
if (file.exists() && file.isDirectory) {
|
||||||
|
return "" to listOf("editor: $path: Is a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (file.exists()) file.readText() to emptyList() else "" to emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeEditorFile(level: Level, currentRepo: RepoState, path: String, content: String): Pair<RepoState, List<String>> {
|
||||||
|
val nativeGit = nativeGitBinary()
|
||||||
|
if (nativeGit == null) {
|
||||||
|
val updatedFiles = currentRepo.files.toMutableList()
|
||||||
|
val index = updatedFiles.indexOfFirst { it.name == path }
|
||||||
|
if (index == -1) {
|
||||||
|
updatedFiles += GitFile(name = path, content = content)
|
||||||
|
} else {
|
||||||
|
updatedFiles[index] = updatedFiles[index].copy(content = content)
|
||||||
|
}
|
||||||
|
return currentRepo.copy(files = updatedFiles) to listOf("Saved $path")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sandboxRoot = sandboxDir(level).canonicalFile
|
||||||
|
val workingDir = File(sandboxRoot, currentRepo.currentDir).canonicalFile
|
||||||
|
.takeIf { it.path.startsWith(sandboxRoot.path) } ?: sandboxRoot
|
||||||
|
val file = File(workingDir, path).canonicalFile
|
||||||
|
if (!file.path.startsWith(sandboxRoot.path)) {
|
||||||
|
return currentRepo to listOf("editor: $path: Permission denied")
|
||||||
|
}
|
||||||
|
if (file.exists() && file.isDirectory) {
|
||||||
|
return currentRepo to listOf("editor: $path: Is a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
file.parentFile?.mkdirs()
|
||||||
|
file.writeText(content)
|
||||||
|
return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to listOf("Saved $path")
|
||||||
|
}
|
||||||
|
|
||||||
private fun executeHelperCommand(sandboxRoot: File, workingDir: File, currentRepo: RepoState, tokens: List<String>): Pair<RepoState, List<String>> {
|
private fun executeHelperCommand(sandboxRoot: File, workingDir: File, currentRepo: RepoState, tokens: List<String>): Pair<RepoState, List<String>> {
|
||||||
return when (tokens.first()) {
|
return when (tokens.first()) {
|
||||||
"ls" -> currentRepo to workingDir.listFiles()
|
"ls" -> currentRepo to workingDir.listFiles()
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
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.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
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 TextEditorState(
|
||||||
|
val editor: String,
|
||||||
|
val originalCommand: String,
|
||||||
|
val path: String,
|
||||||
|
val content: String,
|
||||||
|
val saveAsPath: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextEditorDialog(
|
||||||
|
state: TextEditorState,
|
||||||
|
onContentChange: (String) -> Unit,
|
||||||
|
onSaveAsPathChange: (String) -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
onSave: () -> Unit,
|
||||||
|
onSaveAs: () -> Unit,
|
||||||
|
) {
|
||||||
|
Dialog(onDismissRequest = onCancel) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(0.86f),
|
||||||
|
color = PanelPrimary,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
EditorButton(label = "Cancel", onClick = onCancel)
|
||||||
|
EditorButton(label = "Save", enabled = state.path.isNotBlank(), onClick = onSave)
|
||||||
|
EditorButton(label = "Save As", enabled = state.saveAsPath.isNotBlank(), onClick = onSaveAs)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "${state.editor} ${state.path.ifBlank { "<new file>" }}",
|
||||||
|
color = TextPrimary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.saveAsPath,
|
||||||
|
onValueChange = onSaveAsPathChange,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
label = { Text("Save As") },
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = state.content,
|
||||||
|
onValueChange = onContentChange,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.heightIn(min = 260.dp)
|
||||||
|
.background(TerminalBackground, RoundedCornerShape(6.dp))
|
||||||
|
.padding(10.dp),
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = TextPrimary,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(Accent),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EditorButton(
|
||||||
|
label: String,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Accent,
|
||||||
|
contentColor = AppBackground,
|
||||||
|
disabledContainerColor = PanelTertiary,
|
||||||
|
disabledContentColor = TextMuted,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class EditorCommandsTest {
|
||||||
|
@Test
|
||||||
|
fun parsesSupportedVisualEditorCommandWithPath() {
|
||||||
|
val invocation = parseVisualEditorInvocation("vi .gitignore")
|
||||||
|
|
||||||
|
assertEquals("vi", invocation?.editor)
|
||||||
|
assertEquals(".gitignore", invocation?.path)
|
||||||
|
assertEquals("vi .gitignore", invocation?.command)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ignoresEditorOptionsWhenChoosingPath() {
|
||||||
|
val invocation = parseVisualEditorInvocation("nano -w README")
|
||||||
|
|
||||||
|
assertEquals("nano", invocation?.editor)
|
||||||
|
assertEquals("README", invocation?.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ignoresNonEditorCommands() {
|
||||||
|
assertNull(parseVisualEditorInvocation("git status"))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user