2023-12-12 21:30:33 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
)
|
|
|
|
|
|
|
|
var projectOpenCmd = &cobra.Command{
|
|
|
|
Use: "open [fuzzy alias search]",
|
|
|
|
Short: "Open project in your IDE",
|
|
|
|
Aliases: []string{"goto", "cd"},
|
2023-12-20 16:23:11 +00:00
|
|
|
Args: cobra.OnlyValidArgs,
|
2023-12-12 21:30:33 +00:00
|
|
|
ValidArgsFunction: validAliasesFunc,
|
|
|
|
Long: projOpenCmdLong,
|
|
|
|
Run: projectOpenCmdRun,
|
|
|
|
}
|
|
|
|
|
|
|
|
var knownEditors = []string{
|
|
|
|
"vim",
|
|
|
|
"emacs",
|
|
|
|
"code",
|
|
|
|
"codium",
|
|
|
|
"/Applications/GoLand.app/Contents/MacOS/goland",
|
|
|
|
}
|
|
|
|
|
|
|
|
var entrypointFiles = []string{
|
|
|
|
"README.md",
|
|
|
|
"README",
|
|
|
|
"main.go",
|
|
|
|
"main.py",
|
|
|
|
"app.py",
|
|
|
|
"index.php",
|
|
|
|
"server.php",
|
|
|
|
"index.js",
|
|
|
|
"app.js",
|
|
|
|
"server.js",
|
|
|
|
"app.ts",
|
|
|
|
"main.c",
|
|
|
|
"main.cpp",
|
|
|
|
}
|
|
|
|
|
|
|
|
func projectOpenCmdRun(cmd *cobra.Command, args []string) {
|
|
|
|
// Find an editor
|
|
|
|
editor := findEditor()
|
|
|
|
if editor == "" {
|
|
|
|
plog.Fatal("No usable editor found")
|
|
|
|
}
|
|
|
|
|
2024-01-17 15:06:20 +00:00
|
|
|
remotes := viper.GetStringSlice("remote")
|
2024-01-14 15:33:15 +00:00
|
|
|
fzfOpts := &fzfProjectOpts{
|
|
|
|
Ctx: cmd.Context(),
|
|
|
|
Search: searchStringFromArgs(args),
|
2024-01-17 15:06:20 +00:00
|
|
|
Remotes: remotes,
|
2024-01-14 15:33:15 +00:00
|
|
|
}
|
|
|
|
project := fzfCwdOrSearchProjectAliases(fzfOpts)
|
2023-12-12 21:30:33 +00:00
|
|
|
if project == nil {
|
2023-12-29 15:24:12 +00:00
|
|
|
plog.Fatal("No project to open, nothing to do")
|
2023-12-12 21:30:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the project
|
2024-01-15 21:02:15 +00:00
|
|
|
path := projectCache.GetProjectPath(project)
|
2023-12-12 21:30:33 +00:00
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
plog.Fatal("Unable to open project", plog.Args("error", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the project with the editor
|
|
|
|
file := getEntrypointFile(path)
|
|
|
|
openEditor(editor, path+"/"+file)
|
|
|
|
}
|
|
|
|
|
|
|
|
func openEditor(editor string, path string) {
|
|
|
|
// Compile arguments
|
|
|
|
args := make([]string, 0, 1)
|
|
|
|
if conf.Editor.OpenFlags != "" {
|
|
|
|
args = append(args, conf.Editor.OpenFlags)
|
|
|
|
}
|
|
|
|
args = append(args, path)
|
|
|
|
|
|
|
|
// Launch editor
|
|
|
|
cmd := exec.CommandContext(rootCmd.Context(), editor, args...)
|
|
|
|
cmd.Dir = filepath.Dir(path)
|
2023-12-13 18:28:07 +00:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
2023-12-12 21:30:33 +00:00
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
plog.Error("Failed to open project", plog.Args(
|
|
|
|
"error", err.Error(),
|
|
|
|
"command", cmd.String(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getEntrypointFile(projectPath string) string {
|
|
|
|
if err := os.Chdir(projectPath); err != nil {
|
|
|
|
return ""
|
2023-12-29 13:55:06 +00:00
|
|
|
} else if conf.Editor.OpenDirectory {
|
2023-12-29 14:22:19 +00:00
|
|
|
return "."
|
2023-12-12 21:30:33 +00:00
|
|
|
}
|
2023-12-29 13:55:06 +00:00
|
|
|
|
|
|
|
// Search list of well-known main files
|
2023-12-12 21:30:33 +00:00
|
|
|
for _, f := range entrypointFiles {
|
|
|
|
if _, err := os.Stat(f); err == nil {
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
}
|
2023-12-29 13:55:06 +00:00
|
|
|
|
2023-12-12 21:30:33 +00:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func findEditor() string {
|
|
|
|
var editor string
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// First try configured editor
|
|
|
|
if conf.Editor.Binary != "" {
|
|
|
|
editor, err = getEditor(conf.Editor.Binary)
|
|
|
|
if err != nil || editor == "" {
|
|
|
|
plog.Error("Configured editor is not usable, finding others", plog.Args(
|
|
|
|
"error", err,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then try to find a known editor
|
|
|
|
if editor == "" || err != nil {
|
|
|
|
conf.Editor.OpenFlags = ""
|
|
|
|
for _, e := range knownEditors {
|
|
|
|
path, err := getEditor(e)
|
|
|
|
if err == nil && path != "" {
|
|
|
|
editor = path
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plog.Debug("Editor found for open", plog.Args(
|
|
|
|
"editor", editor,
|
|
|
|
"command", editor+" "+conf.Editor.OpenFlags,
|
|
|
|
))
|
|
|
|
|
|
|
|
return editor
|
|
|
|
}
|
|
|
|
|
|
|
|
func getEditor(editor string) (string, error) {
|
|
|
|
path, err := getEditorPath(editor)
|
|
|
|
if path != "" && err == nil {
|
|
|
|
if !isEditorExecutable(path) {
|
|
|
|
err = errors.New("Editor is not executable")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getEditorPath(editor string) (string, error) {
|
|
|
|
// Check path first
|
|
|
|
if editor[0] != '/' {
|
|
|
|
editor, _ = exec.LookPath(editor)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolvePath(editor)
|
|
|
|
}
|
|
|
|
|
|
|
|
func isEditorExecutable(editor string) bool {
|
|
|
|
var canExec bool
|
|
|
|
|
|
|
|
stat, err := os.Stat(editor)
|
|
|
|
|
|
|
|
if err == nil && (stat.Mode()&0444 != 0 && stat.Mode()&0111 != 0) {
|
|
|
|
canExec = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return canExec
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
projectCmd.AddCommand(projectOpenCmd)
|
|
|
|
projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)")
|
|
|
|
projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary")
|
|
|
|
projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)")
|
|
|
|
viper.BindPFlag("editor.displayName", projectOpenCmd.Flag("displayName"))
|
|
|
|
viper.BindPFlag("editor.binary", projectOpenCmd.Flag("binary"))
|
|
|
|
viper.BindPFlag("editor.openFlags", projectOpenCmd.Flag("openFlags"))
|
|
|
|
}
|