github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/surveyext/editor_manual.go (about) 1 package surveyext 2 3 import ( 4 "bytes" 5 "io" 6 "os" 7 "os/exec" 8 "runtime" 9 10 "github.com/cli/safeexec" 11 shellquote "github.com/kballard/go-shellquote" 12 ) 13 14 type showable interface { 15 Show() error 16 } 17 18 func Edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (string, error) { 19 return edit(editorCommand, fn, initialValue, stdin, stdout, stderr, nil, defaultLookPath) 20 } 21 22 func defaultLookPath(name string) ([]string, []string, error) { 23 exe, err := safeexec.LookPath(name) 24 if err != nil { 25 return nil, nil, err 26 } 27 return []string{exe}, nil, nil 28 } 29 30 func needsBom() bool { 31 // The reason why we do this is because notepad.exe on Windows determines the 32 // encoding of an "empty" text file by the locale, for example, GBK in China, 33 // while golang string only handles utf8 well. However, a text file with utf8 34 // BOM header is not considered "empty" on Windows, and the encoding will then 35 // be determined utf8 by notepad.exe, instead of GBK or other encodings. 36 37 // This could be enhanced in the future by doing this only when a non-utf8 38 // locale is in use, and possibly doing that for any OS, not just windows. 39 40 return runtime.GOOS == "windows" 41 } 42 43 func edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Writer, stderr io.Writer, cursor showable, lookPath func(string) ([]string, []string, error)) (string, error) { 44 // prepare the temp file 45 pattern := fn 46 if pattern == "" { 47 pattern = "survey*.txt" 48 } 49 f, err := os.CreateTemp("", pattern) 50 if err != nil { 51 return "", err 52 } 53 defer os.Remove(f.Name()) 54 55 // write utf8 BOM header if necessary for the current platform and/or locale 56 if needsBom() { 57 if _, err := f.Write(bom); err != nil { 58 return "", err 59 } 60 } 61 62 // write initial value 63 if _, err := f.WriteString(initialValue); err != nil { 64 return "", err 65 } 66 67 // close the fd to prevent the editor unable to save file 68 if err := f.Close(); err != nil { 69 return "", err 70 } 71 72 if editorCommand == "" { 73 editorCommand = defaultEditor 74 } 75 args, err := shellquote.Split(editorCommand) 76 if err != nil { 77 return "", err 78 } 79 args = append(args, f.Name()) 80 81 editorExe, env, err := lookPath(args[0]) 82 if err != nil { 83 return "", err 84 } 85 args = append(editorExe, args[1:]...) 86 87 cmd := exec.Command(args[0], args[1:]...) 88 cmd.Env = env 89 cmd.Stdin = stdin 90 cmd.Stdout = stdout 91 cmd.Stderr = stderr 92 93 if cursor != nil { 94 _ = cursor.Show() 95 } 96 97 // open the editor 98 if err := cmd.Run(); err != nil { 99 return "", err 100 } 101 102 // raw is a BOM-unstripped UTF8 byte slice 103 raw, err := os.ReadFile(f.Name()) 104 if err != nil { 105 return "", err 106 } 107 108 // strip BOM header 109 return string(bytes.TrimPrefix(raw, bom)), nil 110 }