1
16
17 package editor
18
19 import (
20 "fmt"
21 "io"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "runtime"
26 "strings"
27
28 "github.com/sirupsen/logrus"
29 )
30
31 const (
32
33
34
35
36 defaultEditor = "vi"
37 defaultShell = "/bin/bash"
38 windowsEditor = "notepad"
39 windowsShell = "cmd"
40 )
41
42
43 type Editor struct {
44 Args []string
45 Shell bool
46 }
47
48
49
50
51
52
53 func NewDefaultEditor(envs []string) Editor {
54 args, shell := defaultEnvEditor(envs)
55 return Editor{
56 Args: args,
57 Shell: shell,
58 }
59 }
60
61 func defaultEnvShell() []string {
62 shell := os.Getenv("SHELL")
63 if shell == "" {
64 shell = platformize(defaultShell, windowsShell)
65 }
66 flag := "-c"
67 if shell == windowsShell {
68 flag = "/C"
69 }
70 return []string{shell, flag}
71 }
72
73 func defaultEnvEditor(envs []string) ([]string, bool) {
74 var editor string
75 for _, env := range envs {
76 if len(env) > 0 {
77 editor = os.Getenv(env)
78 }
79 if len(editor) > 0 {
80 break
81 }
82 }
83 if editor == "" {
84 editor = platformize(defaultEditor, windowsEditor)
85 }
86 if !strings.Contains(editor, " ") {
87 return []string{editor}, false
88 }
89 if !strings.ContainsAny(editor, "\"'\\") {
90 return strings.Split(editor, " "), false
91 }
92
93 shell := defaultEnvShell()
94 return append(shell, editor), true
95 }
96
97 func (e Editor) args(path string) []string {
98 args := make([]string, len(e.Args))
99 copy(args, e.Args)
100 if e.Shell {
101 last := args[len(args)-1]
102 args[len(args)-1] = fmt.Sprintf("%s %q", last, path)
103 } else {
104 args = append(args, path)
105 }
106 return args
107 }
108
109
110
111 func (e Editor) Launch(path string) error {
112 if len(e.Args) == 0 {
113 return fmt.Errorf("no editor defined, can't open %s", path)
114 }
115 abs, err := filepath.Abs(path)
116 if err != nil {
117 return err
118 }
119 args := e.args(abs)
120
121 cmd := exec.Command(args[0], args[1:]...)
122 cmd.Stdout = os.Stdout
123 cmd.Stderr = os.Stderr
124 cmd.Stdin = os.Stdin
125 logrus.Infof("Opening file with editor %v", args)
126 if err := (TTY{In: os.Stdin, TryDev: true}).Safe(cmd.Run); err != nil {
127 if err, ok := err.(*exec.Error); ok {
128 if err.Err == exec.ErrNotFound {
129 return fmt.Errorf("unable to launch the editor %q", strings.Join(e.Args, " "))
130 }
131 }
132 return fmt.Errorf("there was a problem with the editor %q", strings.Join(e.Args, " "))
133 }
134 return nil
135 }
136
137
138
139
140
141 func (e Editor) LaunchTempFile(prefix, suffix string, r io.Reader) (bytes []byte, path string, err error) {
142 f, err := os.CreateTemp("", fmt.Sprintf("%s*%s", prefix, suffix))
143 if err != nil {
144 return nil, "", err
145 }
146 defer f.Close()
147 path = f.Name()
148 if _, err := io.Copy(f, r); err != nil {
149 os.Remove(path)
150 return nil, path, err
151 }
152
153 f.Close()
154 if err := e.Launch(path); err != nil {
155 return nil, path, err
156 }
157 bytes, err = os.ReadFile(path)
158 return bytes, path, err
159 }
160
161 func platformize(linux, windows string) string {
162 if runtime.GOOS == "windows" {
163 return windows
164 }
165 return linux
166 }
167
View as plain text