github.com/abhinav/git-pr@v0.6.1-0.20171029234004-54218d68c11b/editor/vi_like.go (about)

     1  package editor
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  )
    10  
    11  // ErrFileUnsaved is returned if the user exited a vi-like editor without
    12  // saving the file.
    13  var ErrFileUnsaved = errors.New("file was not saved")
    14  
    15  const _viLikeplaceholder = "#### git-stack placeholder ####"
    16  
    17  // ViLike is an editor backed by vi/vim/neovim/etc.
    18  //
    19  // If the user exits the editor without saving the file, no changes are
    20  // recorded.
    21  type ViLike struct {
    22  	name string
    23  	path string
    24  }
    25  
    26  // NewViLike builds a new vi-like editor.
    27  func NewViLike(name string) (*ViLike, error) {
    28  	path, err := exec.LookPath(name)
    29  	if err != nil {
    30  		return nil, fmt.Errorf("could not resolve editor %q: %v", name, err)
    31  	}
    32  
    33  	return &ViLike{name: name, path: path}, nil
    34  }
    35  
    36  // Name of the editor.
    37  func (vi *ViLike) Name() string {
    38  	return vi.name
    39  }
    40  
    41  // EditString asks the user to edit the given string inside a vi-like editor.
    42  // ErrFileUnsaved is returned if the user exits the editor without saving the
    43  // file.
    44  func (vi *ViLike) EditString(in string) (string, error) {
    45  	sourceFile, err := tempFileWithContents(in)
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  	defer os.Remove(sourceFile)
    50  
    51  	destFile, err := tempFileWithContents(_viLikeplaceholder)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	defer os.Remove(destFile)
    56  
    57  	// To detect this, we create a file with some placeholder contents, load
    58  	// it up in vim and replace the placeholder contents with the actual
    59  	// contents we want to edit. If the user doesn't save it, the placeholder
    60  	// will be retained.
    61  	cmd := exec.Command(
    62  		vi.path,
    63  		"-c", "%d", // delete placeholder
    64  		"-c", "0read "+sourceFile, // read the source file
    65  		"-c", "$d", // delete trailing newline
    66  		"-c", "set ft=gitcommit | 0", // set filetype and go to start of file
    67  		destFile)
    68  	cmd.Stdout = os.Stdout
    69  	cmd.Stdin = os.Stdin
    70  	cmd.Stderr = os.Stderr
    71  	if err := cmd.Run(); err != nil {
    72  		return "", fmt.Errorf("editor %q failed: %v", vi.name, err)
    73  	}
    74  
    75  	contents, err := ioutil.ReadFile(destFile)
    76  	if err != nil {
    77  		return "", fmt.Errorf("could not read temporary file: %v", err)
    78  	}
    79  
    80  	out := string(contents)
    81  	if out == _viLikeplaceholder {
    82  		return "", ErrFileUnsaved
    83  	}
    84  
    85  	return out, nil
    86  }