diff --git a/src/main/composeResources/values/strings.xml b/src/main/composeResources/values/strings.xml
index 1fd3bd64..80b78833 100644
--- a/src/main/composeResources/values/strings.xml
+++ b/src/main/composeResources/values/strings.xml
@@ -37,6 +37,8 @@
Pop the last stash
Terminal
Open a terminal in the repository's path
+ Editor
+ Open a repository in external editor
Actions
Additional actions
Settings
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/editor/OpenRepositoryInEditorUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/editor/OpenRepositoryInEditorUseCase.kt
new file mode 100644
index 00000000..a0443ef3
--- /dev/null
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/editor/OpenRepositoryInEditorUseCase.kt
@@ -0,0 +1,34 @@
+package com.jetpackduba.gitnuro.editor
+
+import com.jetpackduba.gitnuro.managers.ErrorsManager
+import com.jetpackduba.gitnuro.managers.IShellManager
+import com.jetpackduba.gitnuro.models.errorNotification
+import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
+import javax.inject.Inject
+
+/**
+ * Use-case for opening the repository directory with an external editor.
+ */
+class OpenRepositoryInEditorUseCase @Inject constructor(
+ private val settings: AppSettingsRepository,
+ private val shellManager: IShellManager,
+ private val errorsManager: ErrorsManager
+) {
+ /**
+ * Opens the given path in the configured external editor.
+ * @param path The path to open. Should be a directory.
+ */
+ suspend operator fun invoke(path: String) {
+ val editor = settings.editor;
+ val isSet = editor.isNotEmpty();
+
+ if (!isSet) {
+ errorsManager.emitNotification(
+ errorNotification("No editor configured")
+ )
+ return
+ }
+
+ shellManager.runCommandInPath(listOf(editor, path), path)
+ }
+}
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt b/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt
index ad9feba0..3d158ca6 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt
@@ -97,6 +97,11 @@ enum class KeybindingOption {
* Used to open the settings screen
*/
SETTINGS,
+
+ /**
+ * Used to open the current repository in an external editor
+ */
+ OPEN_EDITOR
}
@@ -156,6 +161,9 @@ private fun baseKeybindings() = mapOf(
KeybindingOption.SETTINGS to listOf(
Keybinding(key = Key.S, control = true, alt = true),
),
+ KeybindingOption.OPEN_EDITOR to listOf(
+ Keybinding(key = Key.A, control = true, shift = true),
+ ),
)
private fun linuxKeybindings(): Map> = baseKeybindings()
@@ -179,6 +187,7 @@ private fun macKeybindings(): Map> {
KeybindingOption.CHANGE_CURRENT_TAB_RIGHT,
KeybindingOption.CHANGE_CURRENT_TAB_LEFT,
KeybindingOption.SETTINGS,
+ KeybindingOption.OPEN_EDITOR,
)
for (key in keysToReplaceControlWithCommand) {
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/repositories/AppSettingsRepository.kt b/src/main/kotlin/com/jetpackduba/gitnuro/repositories/AppSettingsRepository.kt
index 3820ae2d..17155a44 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/repositories/AppSettingsRepository.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/repositories/AppSettingsRepository.kt
@@ -61,6 +61,8 @@ private const val PREF_GIT_PUSH_WITH_LEASE = "gitPushWithLease"
private const val PREF_VERIFY_SSL = "verifySsl"
+private const val PREF_EDITOR = "editor"
+
private const val DEFAULT_SWAP_UNCOMMITTED_CHANGES = false
private const val DEFAULT_SHOW_CHANGES_AS_TREE = false
private const val DEFAULT_CACHE_CREDENTIALS_IN_MEMORY = true
@@ -91,6 +93,9 @@ class AppSettingsRepository @Inject constructor() {
private val _verifySslFlow = MutableStateFlow(cacheCredentialsInMemory)
val verifySslFlow = _verifySslFlow.asStateFlow()
+ private val _editorFlow = MutableStateFlow(editor)
+ val editorFlow = _editorFlow.asStateFlow()
+
private val _defaultCloneDirFlow = MutableStateFlow(defaultCloneDir)
val defaultCloneDirFlow = _defaultCloneDirFlow.asStateFlow()
@@ -241,6 +246,15 @@ class AppSettingsRepository @Inject constructor() {
_verifySslFlow.value = value
}
+ var editor: String
+ get() {
+ return preferences.get(PREF_EDITOR, "")
+ }
+ set(value) {
+ preferences.put(PREF_EDITOR, value)
+ _editorFlow.value = value
+ }
+
var scaleUi: Float
get() {
return preferences.getFloat(PREF_UI_SCALE, DEFAULT_UI_SCALE)
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt
index 2a7d9996..f4b237c2 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt
@@ -174,6 +174,15 @@ fun Menu(
keybinding = null,
)
+ MenuButton(
+ modifier = Modifier.padding(end = 4.dp),
+ title = stringResource(Res.string.menu_editor),
+ icon = painterResource(Res.drawable.edit),
+ onClick = { menuViewModel.openEditor() },
+ tooltip = stringResource(Res.string.menu_editor_tooltip),
+ keybinding = KeybindingOption.OPEN_EDITOR.keyBinding,
+ )
+
MenuButton(
modifier = Modifier.padding(end = 4.dp),
title = stringResource(Res.string.menu_actions),
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt
index 1d7bf5b9..174dbc8b 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt
@@ -94,6 +94,7 @@ fun RepositoryOpenPage(
showQuickActionsDialog = false
when (it) {
QuickActionType.OPEN_DIR_IN_FILE_MANAGER -> repositoryOpenViewModel.openFolderInFileExplorer()
+ QuickActionType.OPEN_DIR_IN_EDITOR -> repositoryOpenViewModel.openFolderInEditor()
QuickActionType.CLONE -> onShowCloneDialog()
QuickActionType.REFRESH -> repositoryOpenViewModel.refreshAll()
QuickActionType.SIGN_OFF -> showSignOffDialog = true
@@ -165,6 +166,11 @@ fun RepositoryOpenPage(
true
}
+ it.matchesBinding(KeybindingOption.OPEN_EDITOR) -> {
+ repositoryOpenViewModel.openFolderInEditor()
+ true
+ }
+
else -> false
}
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/QuickActionsDialog.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/QuickActionsDialog.kt
index 62f45e7f..d1c03525 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/QuickActionsDialog.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/QuickActionsDialog.kt
@@ -36,6 +36,7 @@ fun QuickActionsDialog(
val items = remember {
listOf(
QuickAction(Res.drawable.code, "Open repository in file manager", QuickActionType.OPEN_DIR_IN_FILE_MANAGER),
+ QuickAction(Res.drawable.code, "Open repository in editor", QuickActionType.OPEN_DIR_IN_EDITOR),
QuickAction(Res.drawable.download, "Clone new repository", QuickActionType.CLONE),
QuickAction(Res.drawable.refresh, "Refresh repository data", QuickActionType.REFRESH),
QuickAction(Res.drawable.sign, "Signoff config", QuickActionType.SIGN_OFF),
@@ -128,6 +129,7 @@ data class QuickAction(val icon: DrawableResource, val title: String, val type:
enum class QuickActionType {
OPEN_DIR_IN_FILE_MANAGER,
+ OPEN_DIR_IN_EDITOR,
CLONE,
REFRESH,
SIGN_OFF
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/settings/SettingsDialog.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/settings/SettingsDialog.kt
index 6ee0b9a1..aabe95c1 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/settings/SettingsDialog.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/settings/SettingsDialog.kt
@@ -73,6 +73,7 @@ val settings = listOf(
SettingsEntry.Section("Tools"),
SettingsEntry.Entry(Res.drawable.terminal, "Terminal") { Terminal(it) },
+ SettingsEntry.Entry(Res.drawable.edit, "Editor") { Editor(it) },
SettingsEntry.Entry(Res.drawable.info, "Logs") { Logs(it) },
)
@@ -357,6 +358,20 @@ private fun Security(settingsViewModel: SettingsViewModel) {
)
}
+@Composable
+fun Editor(settingsViewModel: SettingsViewModel) {
+ val editor by settingsViewModel.editorFlow.collectAsState()
+
+ SettingTextInput(
+ title = "External editor path",
+ subtitle = "Configure an external editor to open the repository with",
+ value = editor,
+ onValueChanged = { value ->
+ settingsViewModel.editor = value
+ },
+ )
+}
+
@Composable
fun Terminal(settingsViewModel: SettingsViewModel) {
var commitsLimit by remember { mutableStateOf(settingsViewModel.terminalPath) }
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt
index 2f99cce5..49f64946 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt
@@ -1,6 +1,7 @@
package com.jetpackduba.gitnuro.viewmodels
import com.jetpackduba.gitnuro.TaskType
+import com.jetpackduba.gitnuro.editor.OpenRepositoryInEditorUseCase
import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.remote_operations.FetchAllRemotesUseCase
@@ -23,6 +24,7 @@ interface IGlobalMenuActionsViewModel {
fun stash(): Job
fun popStash(): Job
fun openTerminal(): Job
+ fun openEditor(): Job
}
class GlobalMenuActionsViewModel @Inject constructor(
@@ -33,6 +35,7 @@ class GlobalMenuActionsViewModel @Inject constructor(
private val popLastStashUseCase: PopLastStashUseCase,
private val stashChangesUseCase: StashChangesUseCase,
private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase,
+ private val openRepositoryInEditorUseCase: OpenRepositoryInEditorUseCase,
) : IGlobalMenuActionsViewModel {
override fun pull(pullType: PullType) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
@@ -100,4 +103,10 @@ class GlobalMenuActionsViewModel @Inject constructor(
) { git ->
openRepositoryInTerminalUseCase(git.repository.workTree.absolutePath)
}
+
+ override fun openEditor() = tabState.runOperation(
+ refreshType = RefreshType.NONE
+ ) { git ->
+ openRepositoryInEditorUseCase(git.repository.workTree.absolutePath)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt
index 5733a468..e6c42057 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt
@@ -2,6 +2,7 @@ package com.jetpackduba.gitnuro.viewmodels
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
import com.jetpackduba.gitnuro.TaskType
+import com.jetpackduba.gitnuro.editor.OpenRepositoryInEditorUseCase
import com.jetpackduba.gitnuro.exceptions.codeToMessage
import com.jetpackduba.gitnuro.git.*
import com.jetpackduba.gitnuro.git.branches.CreateBranchUseCase
@@ -12,10 +13,12 @@ import com.jetpackduba.gitnuro.logging.printDebug
import com.jetpackduba.gitnuro.logging.printLog
import com.jetpackduba.gitnuro.managers.AppStateManager
import com.jetpackduba.gitnuro.managers.ErrorsManager
+import com.jetpackduba.gitnuro.managers.IShellManager
import com.jetpackduba.gitnuro.managers.newErrorNow
import com.jetpackduba.gitnuro.models.AuthorInfoSimple
import com.jetpackduba.gitnuro.models.errorNotification
import com.jetpackduba.gitnuro.models.positiveNotification
+import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase
import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase
import com.jetpackduba.gitnuro.system.PickerType
@@ -62,6 +65,7 @@ class RepositoryOpenViewModel @Inject constructor(
private val stashChangesUseCase: StashChangesUseCase,
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
private val openFilePickerUseCase: OpenFilePickerUseCase,
+ private val openInEditorUseCase: OpenRepositoryInEditorUseCase,
private val openUrlInBrowserUseCase: OpenUrlInBrowserUseCase,
private val tabsManager: TabsManager,
private val tabScope: CoroutineScope,
@@ -352,6 +356,13 @@ class RepositoryOpenViewModel @Inject constructor(
Desktop.getDesktop().open(git.repository.workTree)
}
+ fun openFolderInEditor() = tabState.runOperation(
+ showError = true,
+ refreshType = RefreshType.NONE,
+ ) { git ->
+ openInEditorUseCase(git.repository.workTree.path)
+ }
+
fun openUrlInBrowser(url: String) {
openUrlInBrowserUseCase(url)
}
diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/SettingsViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/SettingsViewModel.kt
index cf0da061..e6dc0042 100644
--- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/SettingsViewModel.kt
+++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/SettingsViewModel.kt
@@ -43,6 +43,7 @@ class SettingsViewModel @Inject constructor(
val swapUncommittedChangesFlow = appSettingsRepository.swapUncommittedChangesFlow
val cacheCredentialsInMemoryFlow = appSettingsRepository.cacheCredentialsInMemoryFlow
val verifySslFlow = appSettingsRepository.verifySslFlow
+ val editorFlow = appSettingsRepository.editorFlow
val terminalPathFlow = appSettingsRepository.terminalPathFlow
val avatarProviderFlow = appSettingsRepository.avatarProviderTypeFlow
val dateFormatFlow = appSettingsRepository.dateTimeFormatFlow
@@ -90,6 +91,12 @@ class SettingsViewModel @Inject constructor(
appSettingsRepository.verifySsl = value
}
+ var editor: String
+ get() = appSettingsRepository.editor
+ set(value) {
+ appSettingsRepository.editor = value
+ }
+
var pullRebase: Boolean
get() = appSettingsRepository.pullRebase
set(value) {