github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/surveyext/editor.go (about) 1 package surveyext 2 3 // This file extends survey.Editor to give it more flexible behavior. For more context, read 4 // https://github.com/cli/cli/issues/70 5 // To see what we extended, search through for EXTENDED comments. 6 7 import ( 8 "os" 9 "path/filepath" 10 "runtime" 11 12 "github.com/AlecAivazis/survey/v2" 13 "github.com/AlecAivazis/survey/v2/terminal" 14 ) 15 16 var ( 17 bom = []byte{0xef, 0xbb, 0xbf} 18 defaultEditor = "nano" // EXTENDED to switch from vim as a default editor 19 ) 20 21 func init() { 22 if runtime.GOOS == "windows" { 23 defaultEditor = "notepad" 24 } else if g := os.Getenv("GIT_EDITOR"); g != "" { 25 defaultEditor = g 26 } else if v := os.Getenv("VISUAL"); v != "" { 27 defaultEditor = v 28 } else if e := os.Getenv("EDITOR"); e != "" { 29 defaultEditor = e 30 } 31 } 32 33 // EXTENDED to enable different prompting behavior 34 type GhEditor struct { 35 *survey.Editor 36 EditorCommand string 37 BlankAllowed bool 38 39 lookPath func(string) ([]string, []string, error) 40 } 41 42 func (e *GhEditor) editorCommand() string { 43 if e.EditorCommand == "" { 44 return defaultEditor 45 } 46 47 return e.EditorCommand 48 } 49 50 // EXTENDED to change prompt text 51 var EditorQuestionTemplate = ` 52 {{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} 53 {{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} 54 {{- color "default+hb"}}{{ .Message }} {{color "reset"}} 55 {{- if .ShowAnswer}} 56 {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} 57 {{- else }} 58 {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}} 59 {{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} 60 {{- color "cyan"}}[(e) to launch {{ .EditorCommand }}{{- if .BlankAllowed }}, enter to skip{{ end }}] {{color "reset"}} 61 {{- end}}` 62 63 // EXTENDED to pass editor name (to use in prompt) 64 type EditorTemplateData struct { 65 survey.Editor 66 EditorCommand string 67 BlankAllowed bool 68 Answer string 69 ShowAnswer bool 70 ShowHelp bool 71 Config *survey.PromptConfig 72 } 73 74 // EXTENDED to augment prompt text and keypress handling 75 func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (interface{}, error) { 76 err := e.Render( 77 EditorQuestionTemplate, 78 // EXTENDED to support printing editor in prompt and BlankAllowed 79 EditorTemplateData{ 80 Editor: *e.Editor, 81 BlankAllowed: e.BlankAllowed, 82 EditorCommand: filepath.Base(e.editorCommand()), 83 Config: config, 84 }, 85 ) 86 if err != nil { 87 return "", err 88 } 89 90 // start reading runes from the standard in 91 rr := e.NewRuneReader() 92 _ = rr.SetTermMode() 93 defer func() { _ = rr.RestoreTermMode() }() 94 95 cursor := e.NewCursor() 96 cursor.Hide() 97 defer cursor.Show() 98 99 for { 100 // EXTENDED to handle the e to edit / enter to skip behavior + BlankAllowed 101 r, _, err := rr.ReadRune() 102 if err != nil { 103 return "", err 104 } 105 if r == 'e' { 106 break 107 } 108 if r == '\r' || r == '\n' { 109 if e.BlankAllowed { 110 return initialValue, nil 111 } else { 112 continue 113 } 114 } 115 if r == terminal.KeyInterrupt { 116 return "", terminal.InterruptErr 117 } 118 if r == terminal.KeyEndTransmission { 119 break 120 } 121 if string(r) == config.HelpInput && e.Help != "" { 122 err = e.Render( 123 EditorQuestionTemplate, 124 EditorTemplateData{ 125 // EXTENDED to support printing editor in prompt, BlankAllowed 126 Editor: *e.Editor, 127 BlankAllowed: e.BlankAllowed, 128 EditorCommand: filepath.Base(e.editorCommand()), 129 ShowHelp: true, 130 Config: config, 131 }, 132 ) 133 if err != nil { 134 return "", err 135 } 136 } 137 continue 138 } 139 140 stdio := e.Stdio() 141 lookPath := e.lookPath 142 if lookPath == nil { 143 lookPath = defaultLookPath 144 } 145 text, err := edit(e.editorCommand(), e.FileName, initialValue, stdio.In, stdio.Out, stdio.Err, cursor, lookPath) 146 if err != nil { 147 return "", err 148 } 149 150 // check length, return default value on empty 151 if len(text) == 0 && !e.AppendDefault { 152 return e.Default, nil 153 } 154 155 return text, nil 156 } 157 158 // EXTENDED This is straight copypasta from survey to get our overridden prompt called.; 159 func (e *GhEditor) Prompt(config *survey.PromptConfig) (interface{}, error) { 160 initialValue := "" 161 if e.Default != "" && e.AppendDefault { 162 initialValue = e.Default 163 } 164 return e.prompt(initialValue, config) 165 } 166 167 func DefaultEditorName() string { 168 return filepath.Base(defaultEditor) 169 }