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  }