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