diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/App.kt b/src/main/kotlin/com/jetpackduba/gitnuro/App.kt index 70e6d4f..23c7bab 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/App.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/App.kt @@ -62,6 +62,9 @@ class App { @Inject lateinit var appGpgSigner: AppGpgSigner + @Inject + lateinit var appEnvInfo: AppEnvInfo + init { appComponent.inject(this) } @@ -73,6 +76,7 @@ class App { val dirToOpen = getDirToOpen(args) var defaultSelectedTabKey = 0 + appEnvInfo.isFlatpak = args.contains("--flatpak") // TODO Test this appStateManager.loadRepositoriesTabs() try { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/AppEnvInfo.kt b/src/main/kotlin/com/jetpackduba/gitnuro/AppEnvInfo.kt new file mode 100644 index 0000000..19aa2dd --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/AppEnvInfo.kt @@ -0,0 +1,9 @@ +package com.jetpackduba.gitnuro + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppEnvInfo @Inject constructor() { + var isFlatpak = false +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/di/AppComponent.kt b/src/main/kotlin/com/jetpackduba/gitnuro/di/AppComponent.kt index a7f7988..8a8fe87 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/di/AppComponent.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/di/AppComponent.kt @@ -1,6 +1,7 @@ package com.jetpackduba.gitnuro.di import com.jetpackduba.gitnuro.App +import com.jetpackduba.gitnuro.AppEnvInfo import com.jetpackduba.gitnuro.AppStateManager import com.jetpackduba.gitnuro.credentials.CredentialsStateManager import com.jetpackduba.gitnuro.di.modules.AppModule @@ -22,4 +23,6 @@ interface AppComponent { fun credentialsStateManager(): CredentialsStateManager fun appPreferences(): AppSettings + + fun appEnvInfo(): AppEnvInfo } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/di/TabComponent.kt b/src/main/kotlin/com/jetpackduba/gitnuro/di/TabComponent.kt index bc124b0..43db155 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/di/TabComponent.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/di/TabComponent.kt @@ -2,6 +2,7 @@ package com.jetpackduba.gitnuro.di import com.jetpackduba.gitnuro.di.modules.NetworkModule import com.jetpackduba.gitnuro.di.modules.TabModule +import com.jetpackduba.gitnuro.di.modules.TerminalModule import com.jetpackduba.gitnuro.ui.components.TabInformation import dagger.Component @@ -10,6 +11,7 @@ import dagger.Component modules = [ NetworkModule::class, TabModule::class, + TerminalModule::class, ], dependencies = [ AppComponent::class diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/TerminalModule.kt b/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/TerminalModule.kt new file mode 100644 index 0000000..17dfa4f --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/TerminalModule.kt @@ -0,0 +1,32 @@ +package com.jetpackduba.gitnuro.di.modules + +import com.jetpackduba.gitnuro.AppEnvInfo +import com.jetpackduba.gitnuro.extensions.OS +import com.jetpackduba.gitnuro.extensions.getCurrentOs +import com.jetpackduba.gitnuro.terminal.* +import dagger.Module +import dagger.Provides +import javax.inject.Provider + +@Module +class TerminalModule { + @Provides + fun provideTerminalProvider( + linuxTerminalProvider: Provider, + windowsTerminalProvider: Provider, + macTerminalProvider: Provider, + flatpakTerminalProvider: Provider, + appEnvInfo: AppEnvInfo, + ): ITerminalProvider { + + if (appEnvInfo.isFlatpak) + return flatpakTerminalProvider.get() + + return when (getCurrentOs()) { + OS.LINUX -> linuxTerminalProvider.get() + OS.WINDOWS -> windowsTerminalProvider.get() + OS.MAC -> macTerminalProvider.get() + OS.UNKNOWN -> throw NotImplementedError("Unknown operating system") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/extensions/Shell.kt b/src/main/kotlin/com/jetpackduba/gitnuro/extensions/Shell.kt index 069de3a..0ad878a 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/extensions/Shell.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/extensions/Shell.kt @@ -1,6 +1,7 @@ package com.jetpackduba.gitnuro.extensions import com.jetpackduba.gitnuro.logging.printLog +import java.io.File import java.io.IOException import java.util.* @@ -25,6 +26,14 @@ fun runCommand(command: String): String? { } } +fun runCommandInPath(command: String, path: String) { + val processBuilder = ProcessBuilder(command).apply { + directory(File(path)) + } + + processBuilder.start() +} + fun runCommandWithoutResult(command: String, args: String, file: String): Boolean { val parts: Array = prepareCommand(command, args, file) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/FlatpakTerminalProvider.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/FlatpakTerminalProvider.kt new file mode 100644 index 0000000..7c42046 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/FlatpakTerminalProvider.kt @@ -0,0 +1,27 @@ +package com.jetpackduba.gitnuro.terminal + +import com.jetpackduba.gitnuro.extensions.runCommand +import com.jetpackduba.gitnuro.extensions.runCommandInPath +import javax.inject.Inject + +private const val FLATPAK_PREFIX = "/usr/bin/flatpak-spawn --host --env=TERM=xterm-256color" + +// TODO Test in flatpak +class FlatpakTerminalProvider @Inject constructor( + private val linuxTerminalProvider: LinuxTerminalProvider, +) : ITerminalProvider { + + override fun getTerminalEmulators(): List { + return linuxTerminalProvider.getTerminalEmulators() + } + + override fun isTerminalInstalled(terminalEmulator: TerminalEmulator): Boolean { + val checkTerminalInstalled = runCommand("$FLATPAK_PREFIX which ${terminalEmulator.path} 2>/dev/null") + + return !checkTerminalInstalled.isNullOrEmpty() + } + + override fun startTerminal(terminalEmulator: TerminalEmulator, repositoryPath: String) { + runCommandInPath("$FLATPAK_PREFIX ${terminalEmulator.path}", repositoryPath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/ITerminalProvider.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/ITerminalProvider.kt new file mode 100644 index 0000000..a05ad70 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/ITerminalProvider.kt @@ -0,0 +1,9 @@ +package com.jetpackduba.gitnuro.terminal + +interface ITerminalProvider { + fun getTerminalEmulators(): List + + fun isTerminalInstalled(terminalEmulator: TerminalEmulator): Boolean + + fun startTerminal(terminalEmulator: TerminalEmulator, repositoryPath: String) +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/LinuxTerminalProvider.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/LinuxTerminalProvider.kt new file mode 100644 index 0000000..545aa21 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/LinuxTerminalProvider.kt @@ -0,0 +1,27 @@ +package com.jetpackduba.gitnuro.terminal + +import com.jetpackduba.gitnuro.extensions.runCommand +import com.jetpackduba.gitnuro.extensions.runCommandInPath +import javax.inject.Inject + +class LinuxTerminalProvider @Inject constructor() : ITerminalProvider { + override fun getTerminalEmulators(): List { + return listOf( + TerminalEmulator("Gnome Terminal", "gnome-terminal"), + TerminalEmulator("KDE Terminal", "kde-terminal"), + TerminalEmulator("XFCE Terminal", "xfce4-terminal"), + TerminalEmulator("Mate Terminal", "mate-terminal"), + TerminalEmulator("LXQT Terminal", "qterminal"), + ) + } + + override fun isTerminalInstalled(terminalEmulator: TerminalEmulator): Boolean { + val checkTerminalInstalled = runCommand("which ${terminalEmulator.path} 2>/dev/null") + + return !checkTerminalInstalled.isNullOrEmpty() + } + + override fun startTerminal(terminalEmulator: TerminalEmulator, repositoryPath: String) { + runCommandInPath(terminalEmulator.path, repositoryPath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/MacTerminalProvider.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/MacTerminalProvider.kt new file mode 100644 index 0000000..5cdf668 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/MacTerminalProvider.kt @@ -0,0 +1,24 @@ +package com.jetpackduba.gitnuro.terminal + +import com.jetpackduba.gitnuro.extensions.runCommand +import com.jetpackduba.gitnuro.extensions.runCommandInPath +import javax.inject.Inject + +// TODO Test this on MacOS +class MacTerminalProvider @Inject constructor() : ITerminalProvider { + override fun getTerminalEmulators(): List { + return listOf( + TerminalEmulator("MacOS Terminal", "Terminal") + ) + } + + override fun isTerminalInstalled(terminalEmulator: TerminalEmulator): Boolean { + val checkTerminalInstalled = runCommand("which ${terminalEmulator.path} 2>/dev/null") + + return !checkTerminalInstalled.isNullOrEmpty() + } + + override fun startTerminal(terminalEmulator: TerminalEmulator, repositoryPath: String) { + runCommandInPath("open -a ${terminalEmulator.path}", repositoryPath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/OpenRepositoryInTerminalUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/OpenRepositoryInTerminalUseCase.kt new file mode 100644 index 0000000..54237ae --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/OpenRepositoryInTerminalUseCase.kt @@ -0,0 +1,22 @@ +package com.jetpackduba.gitnuro.terminal + +import com.jetpackduba.gitnuro.extensions.runCommandInPath +import javax.inject.Inject + +// For flatpak: https://github.com/flathub/com.visualstudio.code#use-host-shell-in-the-integrated-terminal + +class OpenRepositoryInTerminalUseCase @Inject constructor( + private val terminalProvider: ITerminalProvider +) { + operator fun invoke(path: String) { + val terminalEmulators = terminalProvider.getTerminalEmulators() + + for (terminal in terminalEmulators) { + val isTerminalEmulatorInstalled = terminalProvider.isTerminalInstalled(terminal) + if (isTerminalEmulatorInstalled) { + runCommandInPath(terminal.path, path) + break + } + } + } +} diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/TerminalEmulator.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/TerminalEmulator.kt new file mode 100644 index 0000000..f834ee2 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/TerminalEmulator.kt @@ -0,0 +1,3 @@ +package com.jetpackduba.gitnuro.terminal + +data class TerminalEmulator(val name: String, val path: String) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/terminal/WindowsTerminalProvider.kt b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/WindowsTerminalProvider.kt new file mode 100644 index 0000000..cc13b84 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/terminal/WindowsTerminalProvider.kt @@ -0,0 +1,22 @@ +package com.jetpackduba.gitnuro.terminal + +import com.jetpackduba.gitnuro.extensions.runCommandInPath +import javax.inject.Inject + +// TODO Test this on windows +class WindowsTerminalProvider @Inject constructor() : ITerminalProvider { + override fun getTerminalEmulators(): List { + return listOf( + TerminalEmulator("Powershell", "powershell"), + ) + } + + override fun isTerminalInstalled(terminalEmulator: TerminalEmulator): Boolean { + // TODO how do we know if it's installed? We must check the output when trying to start an app that doesn't exist + return true + } + + override fun startTerminal(terminalEmulator: TerminalEmulator, repositoryPath: String) { + runCommandInPath("start ${terminalEmulator.path}", repositoryPath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt index 2b97917..dd9a460 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt @@ -127,12 +127,12 @@ fun Menu( Spacer(modifier = Modifier.weight(1f)) -// MenuButton( -// modifier = Modifier.padding(end = 4.dp), -// title = "Terminal", -// icon = painterResource("terminal.svg"), -// onClick = onQuickActions, -// ) + MenuButton( + modifier = Modifier.padding(end = 4.dp), + title = "Terminal", + icon = painterResource(AppIcons.TERMINAL), + onClick = { menuViewModel.openTerminal() }, + ) MenuButton( modifier = Modifier.padding(end = 4.dp), diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt index 2d377ea..ad500d2 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt @@ -10,6 +10,7 @@ import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase import com.jetpackduba.gitnuro.preferences.AppSettings +import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase import javax.inject.Inject class MenuViewModel @Inject constructor( @@ -20,6 +21,7 @@ class MenuViewModel @Inject constructor( private val popLastStashUseCase: PopLastStashUseCase, private val stashChangesUseCase: StashChangesUseCase, private val stageUntrackedFileUseCase: StageUntrackedFileUseCase, + private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase, private val settings: AppSettings, ) { val isPullWithRebaseDefault = settings.pullRebaseFlow @@ -58,4 +60,10 @@ class MenuViewModel @Inject constructor( ) { git -> popLastStashUseCase(git) } + + fun openTerminal() = tabState.runOperation( + refreshType = RefreshType.NONE + ) { git -> + openRepositoryInTerminalUseCase(git.repository.directory.parent) + } } \ No newline at end of file