Skip to content

Commit aadbe65

Browse files
committed
#303 Open File in Folder
- Add function to context menu for unstaged, staged and commited files - Add File extention function to open a folder
1 parent 2fd0907 commit aadbe65

File tree

8 files changed

+101
-5
lines changed

8 files changed

+101
-5
lines changed

src/main/kotlin/com/jetpackduba/gitnuro/extensions/FileExtensions.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.jetpackduba.gitnuro.extensions
22

3+
import kotlinx.io.IOException
4+
import java.awt.Desktop
35
import java.io.File
6+
import java.util.*
47

58
fun File.openDirectory(dirName: String): File {
69
val newDir = File(this, dirName)
@@ -10,4 +13,43 @@ fun File.openDirectory(dirName: String): File {
1013
}
1114

1215
return newDir
16+
}
17+
18+
fun File.openFileInFolder() {
19+
20+
if (!exists() || !isDirectory) {
21+
println("Folder with path $path does not exist or is not a folder")
22+
return
23+
}
24+
25+
try {
26+
if (Desktop.isDesktopSupported()) {
27+
val desktop = Desktop.getDesktop()
28+
if (desktop.isSupported(Desktop.Action.OPEN)) {
29+
desktop.open(this)
30+
return
31+
}
32+
}
33+
} catch (e: Exception) {
34+
println("Desktop API failed: ${e.message}")
35+
}
36+
37+
// Fallback
38+
val os = System.getProperty("os.name").lowercase(Locale.getDefault())
39+
val command = when {
40+
os.contains("linux") -> listOf("xdg-open", absolutePath)
41+
os.contains("mac") -> listOf("open", absolutePath)
42+
os.contains("windows") -> listOf("explorer", absolutePath)
43+
else -> null
44+
}
45+
46+
if (command != null) {
47+
try {
48+
ProcessBuilder(command).start()
49+
} catch (ex: IOException) {
50+
println("Failed to open file explorer: ${ex.message}")
51+
}
52+
} else {
53+
println("Unsupported OS: $os")
54+
}
1355
}

src/main/kotlin/com/jetpackduba/gitnuro/git/workspace/Status.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import androidx.compose.ui.graphics.Color
55
import androidx.compose.ui.graphics.vector.ImageVector
66
import com.jetpackduba.gitnuro.extensions.icon
77
import com.jetpackduba.gitnuro.extensions.iconColor
8+
import kotlin.io.path.Path
9+
import kotlin.io.path.pathString
810

9-
data class StatusEntry(val filePath: String, val statusType: StatusType) {
11+
data class StatusEntry(
12+
val filePath: String,
13+
val statusType: StatusType
14+
) {
1015
val icon: ImageVector
1116
get() = statusType.icon
1217

src/main/kotlin/com/jetpackduba/gitnuro/ui/CommitChanges.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ fun CommitChanges(
8787
commitChangesStatus = commitChangesStatus,
8888
onBlame = onBlame,
8989
onHistory = onHistory,
90+
onOpenFileInFolder = { commitChangesViewModel.openFileInFolder(it) },
9091
showSearch = showSearch,
9192
showAsTree = showAsTree,
9293
changesListScroll = changesListScroll,
@@ -121,6 +122,7 @@ private fun CommitChangesView(
121122
searchFilter: TextFieldValue,
122123
onBlame: (String) -> Unit,
123124
onHistory: (String) -> Unit,
125+
onOpenFileInFolder: (String) -> Unit,
124126
onDiffSelected: (DiffEntry) -> Unit,
125127
onSearchFilterToggled: (Boolean) -> Unit,
126128
onSearchFocused: () -> Unit,
@@ -167,6 +169,7 @@ private fun CommitChangesView(
167169
diffEntry,
168170
onBlame = { onBlame(diffEntry.filePath) },
169171
onHistory = { onHistory(diffEntry.filePath) },
172+
onOpenFileInFolder = { onOpenFileInFolder(diffEntry.parentDirectoryPath) },
170173
)
171174
}
172175
)
@@ -183,6 +186,7 @@ private fun CommitChangesView(
183186
diffEntry,
184187
onBlame = { onBlame(diffEntry.filePath) },
185188
onHistory = { onHistory(diffEntry.filePath) },
189+
onOpenFileInFolder = { onOpenFileInFolder(diffEntry.parentDirectoryPath) },
186190
)
187191
},
188192
onDirectoryClicked = onDirectoryClicked,

src/main/kotlin/com/jetpackduba/gitnuro/ui/UncommitedChanges.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ fun UncommittedChanges(
165165
onHistoryFile = onHistoryFile,
166166
onReset = { statusViewModel.resetStaged(it) },
167167
onDelete = { statusViewModel.deleteFile(it) },
168+
onOpenFileInFolder = { statusViewModel.openFileInFolder(it) },
168169
onAllAction = { statusViewModel.unstageAll() },
169170
onAlternateShowAsTree = { statusViewModel.alternateShowAsTree() },
170171
onTreeDirectoryClicked = { statusViewModel.stagedTreeDirectoryClicked(it) },
@@ -189,6 +190,7 @@ fun UncommittedChanges(
189190
onHistoryFile = onHistoryFile,
190191
onReset = { statusViewModel.resetUnstaged(it) },
191192
onDelete = { statusViewModel.deleteFile(it) },
193+
onOpenFileInFolder = { statusViewModel.openFileInFolder(it) },
192194
onAllAction = { statusViewModel.stageAll() },
193195
onAlternateShowAsTree = { statusViewModel.alternateShowAsTree() },
194196
onTreeDirectoryClicked = { statusViewModel.stagedTreeDirectoryClicked(it) },
@@ -359,6 +361,7 @@ fun ColumnScope.StagedView(
359361
onHistoryFile: (String) -> Unit,
360362
onReset: (StatusEntry) -> Unit,
361363
onDelete: (StatusEntry) -> Unit,
364+
onOpenFileInFolder: (String?) -> Unit,
362365
onAllAction: () -> Unit,
363366
onAlternateShowAsTree: () -> Unit,
364367
onTreeDirectoryClicked: (String) -> Unit,
@@ -393,6 +396,7 @@ fun ColumnScope.StagedView(
393396
onHistoryFile = onHistoryFile,
394397
onReset = onReset,
395398
onDelete = onDelete,
399+
onOpenFileInFolder = onOpenFileInFolder,
396400
onAllAction = onAllAction,
397401
onAlternateShowAsTree = onAlternateShowAsTree,
398402
onTreeDirectoryClicked = onTreeDirectoryClicked,
@@ -431,6 +435,7 @@ fun ColumnScope.UnstagedView(
431435
onHistoryFile: (String) -> Unit,
432436
onReset: (StatusEntry) -> Unit,
433437
onDelete: (StatusEntry) -> Unit,
438+
onOpenFileInFolder: (String?) -> Unit,
434439
onAllAction: () -> Unit,
435440
onAlternateShowAsTree: () -> Unit,
436441
onTreeDirectoryClicked: (String) -> Unit,
@@ -465,6 +470,7 @@ fun ColumnScope.UnstagedView(
465470
onHistoryFile = onHistoryFile,
466471
onReset = onReset,
467472
onDelete = onDelete,
473+
onOpenFileInFolder = onOpenFileInFolder,
468474
onAllAction = onAllAction,
469475
onAlternateShowAsTree = onAlternateShowAsTree,
470476
onTreeDirectoryClicked = onTreeDirectoryClicked,
@@ -512,6 +518,7 @@ fun ColumnScope.NeutralView(
512518
onHistoryFile: (String) -> Unit,
513519
onReset: (StatusEntry) -> Unit,
514520
onDelete: (StatusEntry) -> Unit,
521+
onOpenFileInFolder: (String?) -> Unit,
515522
onAllAction: () -> Unit,
516523
onAlternateShowAsTree: () -> Unit,
517524
onTreeDirectoryClicked: (String) -> Unit,
@@ -548,6 +555,7 @@ fun ColumnScope.NeutralView(
548555
onHistory = { onHistoryFile(statusEntry.filePath) },
549556
onReset = { onReset(statusEntry) },
550557
onDelete = { onDelete(statusEntry) },
558+
onOpenFileInFolder = { onOpenFileInFolder(statusEntry.parentDirectoryPath) },
551559
)
552560
},
553561
onAllAction = onAllAction,
@@ -588,6 +596,7 @@ fun ColumnScope.NeutralView(
588596
onHistory = { onHistoryFile(statusEntry.filePath) },
589597
onReset = { onReset(statusEntry) },
590598
onDelete = { onDelete(statusEntry) },
599+
onOpenFileInFolder = { onOpenFileInFolder(statusEntry.parentDirectoryPath) },
591600
)
592601
},
593602
onAllAction = onAllAction,
@@ -1079,8 +1088,6 @@ fun EntriesHeader(
10791088
)
10801089
}
10811090

1082-
1083-
10841091
if (showSearch) {
10851092
SearchTextField(
10861093
searchFilter = searchFilter,
@@ -1097,7 +1104,6 @@ fun EntriesHeader(
10971104
requestFocus = false
10981105
}
10991106
}
1100-
11011107
}
11021108
}
11031109

src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/CommitedChangesEntriesContextMenu.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro.ui.context_menu
33
import androidx.compose.foundation.ExperimentalFoundationApi
44
import com.jetpackduba.gitnuro.generated.resources.Res
55
import com.jetpackduba.gitnuro.generated.resources.blame
6+
import com.jetpackduba.gitnuro.generated.resources.folder_open
67
import com.jetpackduba.gitnuro.generated.resources.history
78
import org.eclipse.jgit.diff.DiffEntry
89
import org.jetbrains.compose.resources.painterResource
@@ -12,6 +13,7 @@ fun committedChangesEntriesContextMenuItems(
1213
diffEntry: DiffEntry,
1314
onBlame: () -> Unit,
1415
onHistory: () -> Unit,
16+
onOpenFileInFolder: () -> Unit,
1517
): List<ContextMenuElement> {
1618
return mutableListOf<ContextMenuElement>().apply {
1719
if (diffEntry.changeType != DiffEntry.ChangeType.ADD ||
@@ -31,6 +33,14 @@ fun committedChangesEntriesContextMenuItems(
3133
onClick = onHistory,
3234
)
3335
)
36+
37+
add(
38+
ContextMenuElement.ContextTextEntry(
39+
label = "Open file in folder",
40+
icon = { painterResource(Res.drawable.folder_open) },
41+
onClick = onOpenFileInFolder,
42+
)
43+
)
3444
}
3545
}
3646
}

src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/StatusEntriesContextMenu.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ fun statusEntriesContextMenuItems(
1212
onDelete: () -> Unit = {},
1313
onBlame: () -> Unit,
1414
onHistory: () -> Unit,
15+
onOpenFileInFolder: () -> Unit,
1516
): List<ContextMenuElement> {
1617
return mutableListOf<ContextMenuElement>().apply {
1718
if (statusEntry.statusType != StatusType.ADDED) {
@@ -54,6 +55,14 @@ fun statusEntriesContextMenuItems(
5455
)
5556
)
5657
}
58+
59+
add(
60+
ContextMenuElement.ContextTextEntry(
61+
label = "Open file in folder",
62+
icon = { painterResource(Res.drawable.folder_open) },
63+
onClick = onOpenFileInFolder,
64+
)
65+
)
5766
}
5867
}
5968

src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CommitChangesViewModel.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.jetpackduba.gitnuro.extensions.delayedStateChange
77
import com.jetpackduba.gitnuro.extensions.filePath
88
import com.jetpackduba.gitnuro.extensions.fullData
99
import com.jetpackduba.gitnuro.extensions.lowercaseContains
10+
import com.jetpackduba.gitnuro.extensions.openFileInFolder
1011
import com.jetpackduba.gitnuro.git.CloseableView
1112
import com.jetpackduba.gitnuro.git.RefreshType
1213
import com.jetpackduba.gitnuro.git.TabState
@@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.*
1920
import kotlinx.coroutines.launch
2021
import org.eclipse.jgit.diff.DiffEntry
2122
import org.eclipse.jgit.revwalk.RevCommit
23+
import java.io.File
2224
import javax.inject.Inject
2325

2426
private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 300L
@@ -159,6 +161,12 @@ class CommitChangesViewModel @Inject constructor(
159161
appSettingsRepository.showChangesAsTree = !appSettingsRepository.showChangesAsTree
160162
}
161163

164+
fun openFileInFolder(folderPath: String?) = tabState.runOperation(
165+
refreshType = RefreshType.UNCOMMITTED_CHANGES,
166+
) {
167+
folderPath?.let { File(it).openFileInFolder() }
168+
}
169+
162170
fun onDirectoryClicked(directoryPath: String) {
163171
val contractedDirectories = treeContractedDirectories.value
164172

src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/StatusViewModel.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ import kotlinx.coroutines.Dispatchers
2828
import kotlinx.coroutines.flow.*
2929
import kotlinx.coroutines.launch
3030
import kotlinx.coroutines.withContext
31+
import kotlinx.io.IOException
3132
import org.eclipse.jgit.api.Git
3233
import org.eclipse.jgit.lib.PersonIdent
3334
import org.eclipse.jgit.lib.RepositoryState
35+
import java.awt.Desktop
3436
import java.io.File
37+
import java.util.*
3538
import javax.inject.Inject
3639

3740
private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 500L
@@ -128,7 +131,10 @@ class StatusViewModel @Inject constructor(
128131
stageStateFiltered.unstaged,
129132
contractedDirectories
130133
) { it.filePath },
131-
filteredStaged = entriesToTreeEntry(stageStateFiltered.filteredStaged, contractedDirectories) { it.filePath },
134+
filteredStaged = entriesToTreeEntry(
135+
stageStateFiltered.filteredStaged,
136+
contractedDirectories
137+
) { it.filePath },
132138
filteredUnstaged = entriesToTreeEntry(
133139
stageStateFiltered.filteredUnstaged,
134140
contractedDirectories
@@ -509,6 +515,12 @@ class StatusViewModel @Inject constructor(
509515
fileToDelete.deleteRecursively()
510516
}
511517

518+
fun openFileInFolder(folderPath: String?) = tabState.runOperation(
519+
refreshType = RefreshType.UNCOMMITTED_CHANGES,
520+
) {
521+
folderPath?.let { File(it).openFileInFolder() }
522+
}
523+
512524
fun updateCommitMessage(message: String) {
513525
savedCommitMessage = savedCommitMessage.copy(message = message)
514526
persistMessage()

0 commit comments

Comments
 (0)