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