From b940ff72eec7add1b82a3c85dd6204fbb02ae903 Mon Sep 17 00:00:00 2001 From: Marco Herrn Date: Mon, 15 Apr 2019 17:59:03 +0200 Subject: [PATCH 1/5] Introduce new subcmd `edit` Allows editing the plugconf file(s) of the given repositories. Closes vim-volt/volt#230 There are still a few FIXMEs in the code. The most important one being: - Which error codes to use? Currently 15 and 20 are used, but the existing error codes are not documented. It is unclear whether to reuse an existing one or which to use for which purpose. The remaining ones are more about clean code and a different option --- CMDREF.md | 20 +++++ config/config.go | 12 +++ subcmd/edit.go | 218 +++++++++++++++++++++++++++++++++++++++++++++++ subcmd/help.go | 3 + 4 files changed, 253 insertions(+) create mode 100644 subcmd/edit.go diff --git a/CMDREF.md b/CMDREF.md index 508f2dea..17c4d460 100644 --- a/CMDREF.md +++ b/CMDREF.md @@ -33,6 +33,9 @@ Command This is shortcut of: volt profile rm -current {repository} [{repository2} ...] + edit [-e|--editor {editor}] {repository} [{repository2} ...] + Open the plugconf file(s) of one or more {repository} for editing. + profile set {name} Set profile name @@ -112,6 +115,23 @@ Description volt profile rm {current profile} {repository} [{repository2} ...] ``` +# volt edit + +``` +Usage + volt edit [-help] [-e|--editor {editor}] {repository} [{repository2} ...] + +Quick example + $ volt edit tyru/caw.vim # will open the plugconf file for tyru/caw.vim for editing + +Description + Open the plugconf file(s) of one or more {repository} for editing. + + If the -e option was given, use the given editor for editing those files (unless it cannot be found) + + It also calls "volt build" afterwards if modifications were made to the plugconf file(s). +``` + # volt enable ``` diff --git a/config/config.go b/config/config.go index fc8feedf..b390be7c 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type Config struct { Alias map[string][]string `toml:"alias"` Build configBuild `toml:"build"` Get configGet `toml:"get"` + Edit configEdit `toml:"edit"` } // configBuild is a config for 'volt build'. @@ -25,6 +26,11 @@ type configGet struct { FallbackGitCmd *bool `toml:"fallback_git_cmd"` } +// configEdit is a config for 'volt edit'. +type configEdit struct { + Editor string `toml:"editor"` +} + const ( // SymlinkBuilder creates symlinks when 'volt build'. SymlinkBuilder = "symlink" @@ -43,6 +49,9 @@ func initialConfigTOML() *Config { CreateSkeletonPlugconf: &trueValue, FallbackGitCmd: &falseValue, }, + Edit: configEdit{ + Editor: "", + }, } } @@ -76,6 +85,9 @@ func merge(cfg, initCfg *Config) { if cfg.Get.FallbackGitCmd == nil { cfg.Get.FallbackGitCmd = initCfg.Get.FallbackGitCmd } + if cfg.Edit.Editor == "" { + cfg.Edit.Editor = initCfg.Edit.Editor + } } func validate(cfg *Config) error { diff --git a/subcmd/edit.go b/subcmd/edit.go new file mode 100644 index 00000000..c21e4f48 --- /dev/null +++ b/subcmd/edit.go @@ -0,0 +1,218 @@ +package subcmd + +import ( + "errors" + "flag" + "fmt" + "os" + "os/exec" + + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" + "github.com/vim-volt/volt/logger" + "github.com/vim-volt/volt/pathutil" + "github.com/vim-volt/volt/subcmd/builder" +) + +func init() { + cmdMap["edit"] = &editCmd{} +} + +type editCmd struct { + helped bool + editor string +} + +func (cmd *editCmd) ProhibitRootExecution(args []string) bool { return true } + +func (cmd *editCmd) FlagSet() *flag.FlagSet { + fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + fs.SetOutput(os.Stdout) + fs.Usage = func() { + fmt.Print(` +Usage + volt edit [-help] [-e|--editor {editor}] {repository} [{repository2} ...] + +Quick example + $ volt edit tyru/caw.vim # will open the plugconf file for tyru/caw.vim for editing + +Description + Open the plugconf file(s) of one or more {repository} for editing. + + If the -e option was given, use the given editor for editing those files (unless it cannot be found) + + It also calls "volt build" afterwards if modifications were made to the plugconf file(s).` + "\n\n") + //fmt.Println("Options") + //fs.PrintDefaults() + fmt.Println() + cmd.helped = true + } + fs.StringVar(&cmd.editor, "editor", "", "Use the given editor for editing the plugconf files") + fs.StringVar(&cmd.editor, "e", "", "Use the given editor for editing the plugconf files") + return fs +} + +func (cmd *editCmd) Run(args []string) *Error { + reposPathList, err := cmd.parseArgs(args) + if err == ErrShowedHelp { + return nil + } + if err != nil { + return &Error{Code: 10, Msg: "Failed to parse args: " + err.Error()} + } + + hasChanges, err := cmd.doEdit(reposPathList) + if err != nil { + //FIXME: Which error code to use? + return &Error{Code: 15, Msg: "Failed to edit plugconf file: " + err.Error()} + } + + // Build opt dir + if hasChanges { + err = builder.Build(false) + if err != nil { + return &Error{Code: 12, Msg: "Could not build " + pathutil.VimVoltDir() + ": " + err.Error()} + } + } + + return nil +} + +func (cmd *editCmd) doEdit(reposPathList []pathutil.ReposPath) (bool, error) { + // Read lock.json + lockJSON, err := lockjson.Read() + if err != nil { + return false, err + } + + // Read config.toml + cfg, err := config.Read() + if err != nil { + return false, errors.New("could not read config.toml: " + err.Error()) + } + + viitor, err := cmd.identifyEditor(cfg) + if err != nil || viitor == "" { + //FIXME: Which error code to use? + return false, &Error{Code: 30, Msg: "No usable viitor found"} + } + + changeWasMade := false + //FIXME: Run single vim instance? Leads to problems if the configured + //editor does not support to open multiple files + for _, reposPath := range reposPathList { + + // Edit plugconf file + plugconfPath := reposPath.Plugconf() + + // Install a new template if none exists + if !pathutil.Exists(plugconfPath) { + getCmd := new(getCmd) + logger.Debugf("Installing new plugconf for '%s'.", reposPath) + getCmd.downloadPlugconf(reposPath) + } + + // Remember modification time before opening the editor + info, err := os.Stat(plugconfPath) + if err != nil { + return false, err + } + mTimeBefore := info.ModTime() + + // Call the editor with the plugconf file + vimCmd := exec.Command(viitor, plugconfPath) + vimCmd.Stdin = os.Stdin + vimCmd.Stdout = os.Stdout + if err = vimCmd.Run(); err != nil { + //FIXME: Don't abort immediately, but try to edit remaining files? + return false, err + } + + // Get modification time after closing the editor + info, err = os.Stat(plugconfPath) + if err != nil { + return false, err + } + mTimeAfter := info.ModTime() + + // A change was made if the modification time was updated + changeWasMade = changeWasMade || mTimeAfter.After(mTimeBefore) + + // Remove repository from lock.json + err = lockJSON.Repos.RemoveAllReposPath(reposPath) + err2 := lockJSON.Profiles.RemoveAllReposPath(reposPath) + if err == nil || err2 == nil { + // ignore? + } + } + + // Write to lock.json + if err = lockJSON.Write(); err != nil { + return changeWasMade, err + } + return changeWasMade, nil +} + +func (cmd *editCmd) parseArgs(args []string) (pathutil.ReposPathList, error) { + fs := cmd.FlagSet() + fs.Parse(args) + if cmd.helped { + return nil, ErrShowedHelp + } + + if len(fs.Args()) == 0 { + fs.Usage() + return nil, errors.New("repository was not given") + } + + // Normalize repos path + reposPathList := make(pathutil.ReposPathList, 0, len(fs.Args())) + for _, arg := range fs.Args() { + reposPath, err := pathutil.NormalizeRepos(arg) + if err != nil { + return nil, err + } + reposPathList = append(reposPathList, reposPath) + } + + return reposPathList, nil +} + +func (cmd *editCmd) identifyEditor(cfg *config.Config) (string, error) { + var editors []string + + // if an editor is specified as commandline argument, consider it + // as alternative + if cmd.editor != "" { + editors = append(editors, cmd.editor) + } + + // if an editor is configured in the config.toml, consider it as + // alternative + if cfg.Edit.Editor != "" { + editors = append(editors, cfg.Edit.Editor) + } + + // specifiy a fixed list of other alternatives + editors = append(editors, "$VISUAL", "nvim", "vim", "sensible-editor", "$EDITOR") + + for _, editor := range editors { + // resolve content of environment variables + var editorName string + if editor[0] == '$' { + editorName = os.Getenv(editor[1:]) + } else { + editorName = editor + } + + path, err := exec.LookPath(editorName) + if err != nil { + logger.Debug(editor + " not found in $PATH") + } else if path != "" { + logger.Debug("Using " + path + " as editor") + return editorName, nil + } + } + + return "", errors.New("No usable editor found") +} diff --git a/subcmd/help.go b/subcmd/help.go index 49bf679a..f86dceca 100644 --- a/subcmd/help.go +++ b/subcmd/help.go @@ -58,6 +58,9 @@ Command This is shortcut of: volt profile rm -current {repository} [{repository2} ...] + edit [-e|--editor {editor}] {repository} [{repository2} ...] + Open the plugconf file(s) of one or more {repository} for editing. + profile set {name} Set profile name From c7a498a0959000319698e709648f8ac846e0527a Mon Sep 17 00:00:00 2001 From: Marco Herrn Date: Sat, 20 Apr 2019 16:44:43 +0200 Subject: [PATCH 2/5] Changes to edit command as requested in Pull Request --- README.md | 7 +++++++ subcmd/edit.go | 35 +++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e43552c3..27885ab7 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,13 @@ create_skeleton_plugconf = true # installed, it tries to execute "git clone" or "git pull" as a fallback # * false: "volt get" or "volt get -u" won't try to execute fallback commands fallback_git_cmd = true + +[edit] +# If you ever wanted to use emacs to edit your vim plugin config, you can +# do so with the following. If not specified, volt will try to use +# vim/nvim, $VISUAL, sensible-editor, or $EDITOR in this order until a usable +# one is found. +editor = emacs ``` ## Features diff --git a/subcmd/edit.go b/subcmd/edit.go index c21e4f48..21a8d3f5 100644 --- a/subcmd/edit.go +++ b/subcmd/edit.go @@ -63,7 +63,6 @@ func (cmd *editCmd) Run(args []string) *Error { hasChanges, err := cmd.doEdit(reposPathList) if err != nil { - //FIXME: Which error code to use? return &Error{Code: 15, Msg: "Failed to edit plugconf file: " + err.Error()} } @@ -91,15 +90,12 @@ func (cmd *editCmd) doEdit(reposPathList []pathutil.ReposPath) (bool, error) { return false, errors.New("could not read config.toml: " + err.Error()) } - viitor, err := cmd.identifyEditor(cfg) - if err != nil || viitor == "" { - //FIXME: Which error code to use? - return false, &Error{Code: 30, Msg: "No usable viitor found"} + editor, err := cmd.identifyEditor(cfg) + if err != nil || editor == "" { + return false, &Error{Code: 30, Msg: "No usable editor found"} } changeWasMade := false - //FIXME: Run single vim instance? Leads to problems if the configured - //editor does not support to open multiple files for _, reposPath := range reposPathList { // Edit plugconf file @@ -120,12 +116,12 @@ func (cmd *editCmd) doEdit(reposPathList []pathutil.ReposPath) (bool, error) { mTimeBefore := info.ModTime() // Call the editor with the plugconf file - vimCmd := exec.Command(viitor, plugconfPath) - vimCmd.Stdin = os.Stdin - vimCmd.Stdout = os.Stdout - if err = vimCmd.Run(); err != nil { - //FIXME: Don't abort immediately, but try to edit remaining files? - return false, err + editorCmd := exec.Command(editor, plugconfPath) + editorCmd.Stdin = os.Stdin + editorCmd.Stdout = os.Stdout + if err = editorCmd.Run(); err != nil { + logger.Error("Error calling editor for '%s': %s", reposPath, err.Error) + continue } // Get modification time after closing the editor @@ -179,7 +175,7 @@ func (cmd *editCmd) parseArgs(args []string) (pathutil.ReposPathList, error) { } func (cmd *editCmd) identifyEditor(cfg *config.Config) (string, error) { - var editors []string + editors := make([]string, 4, 6) // if an editor is specified as commandline argument, consider it // as alternative @@ -193,8 +189,15 @@ func (cmd *editCmd) identifyEditor(cfg *config.Config) (string, error) { editors = append(editors, cfg.Edit.Editor) } + vimExecutable, err := pathutil.VimExecutable() + if err != nil { + logger.Debug("No vim executable found in $PATH") + } else { + editors = append(editors, vimExecutable) + } + // specifiy a fixed list of other alternatives - editors = append(editors, "$VISUAL", "nvim", "vim", "sensible-editor", "$EDITOR") + editors = append(editors, "$VISUAL", "sensible-editor", "$EDITOR") for _, editor := range editors { // resolve content of environment variables @@ -207,7 +210,7 @@ func (cmd *editCmd) identifyEditor(cfg *config.Config) (string, error) { path, err := exec.LookPath(editorName) if err != nil { - logger.Debug(editor + " not found in $PATH") + logger.Debug(editorName + " not found in $PATH") } else if path != "" { logger.Debug("Using " + path + " as editor") return editorName, nil From adc32892d6ed7a6cf0ae6be1b50c47debea5fe38 Mon Sep 17 00:00:00 2001 From: Marco Herrn Date: Tue, 23 Apr 2019 09:03:31 +0200 Subject: [PATCH 3/5] Respect new `edit` command in bash completion --- _contrib/completion/bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_contrib/completion/bash b/_contrib/completion/bash index e8d60763..2cc4693a 100644 --- a/_contrib/completion/bash +++ b/_contrib/completion/bash @@ -26,7 +26,7 @@ this_plug() { volt list -f "{{ range .Profiles }}{{ if eq \"$1\" .Name }}{{ range .ReposPath }}{{ println . }}{{ end }}{{ end }}{{ end }}" | sed -E 's@^(www\.)?github\.com/@@' | sort -u } -CMDS="get rm list enable disable profile build migrate self-upgrade version" +CMDS="get rm list enable disable edit profile build migrate self-upgrade version" PROFILE_CMDS="set show list new destroy rename add rm" MIGRATE_CMDS="lockjson plugconf/config-func" @@ -53,7 +53,7 @@ _volt() { elif [[ "${first}" == "profile" && "${last}" == "profile" ]] ; then COMPREPLY=( $(compgen -W "${PROFILE_CMDS}" -- ${cur}) ) - elif [[ "${first}" =~ ^(rm|disable)$ ]] ; then + elif [[ "${first}" =~ ^(rm|disable|edit)$ ]] ; then local profile=$(get_profile) plugs=$(get_plugs "$profile" "this") COMPREPLY=( $(compgen -W "${plugs}" -- ${cur}) ) From 4df6e6e6f3d0b1e879303b28fdea249f1b688e35 Mon Sep 17 00:00:00 2001 From: Takuya Fujiwara Date: Tue, 23 Apr 2019 12:29:15 +0200 Subject: [PATCH 4/5] Update README.md Co-Authored-By: hupfdule <36069345+hupfdule@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27885ab7..92314d8b 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ fallback_git_cmd = true # do so with the following. If not specified, volt will try to use # vim/nvim, $VISUAL, sensible-editor, or $EDITOR in this order until a usable # one is found. -editor = emacs +editor = "emacs" ``` ## Features From 502755cdcbf20f09e59d40787b42b86516653d87 Mon Sep 17 00:00:00 2001 From: Marco Herrn Date: Sat, 20 Apr 2019 16:44:43 +0200 Subject: [PATCH 5/5] Changes to edit command as requested in Pull Request --- subcmd/edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcmd/edit.go b/subcmd/edit.go index 21a8d3f5..85740065 100644 --- a/subcmd/edit.go +++ b/subcmd/edit.go @@ -175,7 +175,7 @@ func (cmd *editCmd) parseArgs(args []string) (pathutil.ReposPathList, error) { } func (cmd *editCmd) identifyEditor(cfg *config.Config) (string, error) { - editors := make([]string, 4, 6) + editors := make([]string, 0, 6) // if an editor is specified as commandline argument, consider it // as alternative