Detailed changes
  
  
    
    @@ -3,10 +3,10 @@ package bugcmd
 import (
 	"github.com/spf13/cobra"
 
+	buginput "github.com/MichaelMure/git-bug/commands/bug/input"
 	"github.com/MichaelMure/git-bug/commands/bug/select"
 	"github.com/MichaelMure/git-bug/commands/completion"
 	"github.com/MichaelMure/git-bug/commands/execenv"
-	"github.com/MichaelMure/git-bug/commands/input"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
@@ -50,7 +50,7 @@ func runBugCommentNew(env *execenv.Env, opts bugCommentNewOptions, args []string
 	}
 
 	if opts.messageFile != "" && opts.message == "" {
-		opts.message, err = input.BugCommentFileInput(opts.messageFile)
+		opts.message, err = buginput.BugCommentFileInput(opts.messageFile)
 		if err != nil {
 			return err
 		}
@@ -61,8 +61,8 @@ func runBugCommentNew(env *execenv.Env, opts bugCommentNewOptions, args []string
 			env.Err.Println("No message given. Use -m or -F option to specify a message. Aborting.")
 			return nil
 		}
-		opts.message, err = input.BugCommentEditorInput(env.Backend, "")
-		if err == input.ErrEmptyMessage {
+		opts.message, err = buginput.BugCommentEditorInput(env.Backend, "")
+		if err == buginput.ErrEmptyMessage {
 			env.Err.Println("Empty message, aborting.")
 			return nil
 		}
  
  
  
    
    @@ -3,8 +3,8 @@ package bugcmd
 import (
 	"github.com/spf13/cobra"
 
+	buginput "github.com/MichaelMure/git-bug/commands/bug/input"
 	"github.com/MichaelMure/git-bug/commands/execenv"
-	"github.com/MichaelMure/git-bug/commands/input"
 )
 
 type bugCommentEditOptions struct {
@@ -47,7 +47,7 @@ func runBugCommentEdit(env *execenv.Env, opts bugCommentEditOptions, args []stri
 	}
 
 	if opts.messageFile != "" && opts.message == "" {
-		opts.message, err = input.BugCommentFileInput(opts.messageFile)
+		opts.message, err = buginput.BugCommentFileInput(opts.messageFile)
 		if err != nil {
 			return err
 		}
@@ -58,8 +58,8 @@ func runBugCommentEdit(env *execenv.Env, opts bugCommentEditOptions, args []stri
 			env.Err.Println("No message given. Use -m or -F option to specify a message. Aborting.")
 			return nil
 		}
-		opts.message, err = input.BugCommentEditorInput(env.Backend, "")
-		if err == input.ErrEmptyMessage {
+		opts.message, err = buginput.BugCommentEditorInput(env.Backend, "")
+		if err == buginput.ErrEmptyMessage {
 			env.Err.Println("Empty message, aborting.")
 			return nil
 		}
  
  
  
    
    @@ -3,8 +3,8 @@ package bugcmd
 import (
 	"github.com/spf13/cobra"
 
+	buginput "github.com/MichaelMure/git-bug/commands/bug/input"
 	"github.com/MichaelMure/git-bug/commands/execenv"
-	"github.com/MichaelMure/git-bug/commands/input"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
@@ -45,16 +45,16 @@ func newBugNewCommand() *cobra.Command {
 func runBugNew(env *execenv.Env, opts bugNewOptions) error {
 	var err error
 	if opts.messageFile != "" && opts.message == "" {
-		opts.title, opts.message, err = input.BugCreateFileInput(opts.messageFile)
+		opts.title, opts.message, err = buginput.BugCreateFileInput(opts.messageFile)
 		if err != nil {
 			return err
 		}
 	}
 
 	if !opts.nonInteractive && opts.messageFile == "" && (opts.message == "" || opts.title == "") {
-		opts.title, opts.message, err = input.BugCreateEditorInput(env.Backend, opts.title, opts.message)
+		opts.title, opts.message, err = buginput.BugCreateEditorInput(env.Backend, opts.title, opts.message)
 
-		if err == input.ErrEmptyTitle {
+		if err == buginput.ErrEmptyTitle {
 			env.Out.Println("Empty title, aborting.")
 			return nil
 		}
  
  
  
    
    @@ -3,10 +3,10 @@ package bugcmd
 import (
 	"github.com/spf13/cobra"
 
+	buginput "github.com/MichaelMure/git-bug/commands/bug/input"
 	"github.com/MichaelMure/git-bug/commands/bug/select"
 	"github.com/MichaelMure/git-bug/commands/completion"
 	"github.com/MichaelMure/git-bug/commands/execenv"
-	"github.com/MichaelMure/git-bug/commands/input"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
@@ -53,8 +53,8 @@ func runBugTitleEdit(env *execenv.Env, opts bugTitleEditOptions, args []string)
 			env.Err.Println("No title given. Use -m or -F option to specify a title. Aborting.")
 			return nil
 		}
-		opts.title, err = input.BugTitleEditorInput(env.Repo, snap.Title)
-		if err == input.ErrEmptyTitle {
+		opts.title, err = buginput.BugTitleEditorInput(env.Repo, snap.Title)
+		if err == buginput.ErrEmptyTitle {
 			env.Out.Println("Empty title, aborting.")
 			return nil
 		}
  
  
  
    
    @@ -0,0 +1,230 @@
+package buginput
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+
+	"github.com/pkg/errors"
+
+	"github.com/MichaelMure/git-bug/commands/input"
+	"github.com/MichaelMure/git-bug/repository"
+)
+
+const messageFilename = "BUG_MESSAGE_EDITMSG"
+
+// ErrEmptyMessage is returned when the required message has not been entered
+var ErrEmptyMessage = errors.New("empty message")
+
+// ErrEmptyTitle is returned when the required title has not been entered
+var ErrEmptyTitle = errors.New("empty title")
+
+const bugTitleCommentTemplate = `%s%s
+
+# Please enter the title and comment message. The first non-empty line will be
+# used as the title. Lines starting with '#' will be ignored.
+# An empty title aborts the operation.
+`
+
+// BugCreateEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract title
+// and message.
+func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) {
+	if preMessage != "" {
+		preMessage = "\n\n" + preMessage
+	}
+
+	template := fmt.Sprintf(bugTitleCommentTemplate, preTitle, preMessage)
+
+	raw, err := input.LaunchEditorWithTemplate(repo, messageFilename, template)
+	if err != nil {
+		return "", "", err
+	}
+
+	return processCreate(raw)
+}
+
+// BugCreateFileInput read from either from a file or from the standard input
+// and extract a title and a message
+func BugCreateFileInput(fileName string) (string, string, error) {
+	raw, err := input.FromFile(fileName)
+	if err != nil {
+		return "", "", err
+	}
+
+	return processCreate(raw)
+}
+
+func processCreate(raw string) (string, string, error) {
+	lines := strings.Split(raw, "\n")
+
+	var title string
+	var buffer bytes.Buffer
+	for _, line := range lines {
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+
+		if title == "" {
+			trimmed := strings.TrimSpace(line)
+			if trimmed != "" {
+				title = trimmed
+			}
+			continue
+		}
+
+		buffer.WriteString(line)
+		buffer.WriteString("\n")
+	}
+
+	if title == "" {
+		return "", "", ErrEmptyTitle
+	}
+
+	message := strings.TrimSpace(buffer.String())
+
+	return title, message, nil
+}
+
+const bugCommentTemplate = `%s
+
+# Please enter the comment message. Lines starting with '#' will be ignored,
+# and an empty message aborts the operation.
+`
+
+// BugCommentEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a comment.
+func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) {
+	template := fmt.Sprintf(bugCommentTemplate, preMessage)
+
+	raw, err := input.LaunchEditorWithTemplate(repo, messageFilename, template)
+	if err != nil {
+		return "", err
+	}
+
+	return processComment(raw)
+}
+
+// BugCommentFileInput read from either from a file or from the standard input
+// and extract a message
+func BugCommentFileInput(fileName string) (string, error) {
+	raw, err := input.FromFile(fileName)
+	if err != nil {
+		return "", err
+	}
+
+	return processComment(raw)
+}
+
+func processComment(raw string) (string, error) {
+	lines := strings.Split(raw, "\n")
+
+	var buffer bytes.Buffer
+	for _, line := range lines {
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+		buffer.WriteString(line)
+		buffer.WriteString("\n")
+	}
+
+	message := strings.TrimSpace(buffer.String())
+
+	if message == "" {
+		return "", ErrEmptyMessage
+	}
+
+	return message, nil
+}
+
+const bugTitleTemplate = `%s
+
+# Please enter the new title. Only one line will used.
+# Lines starting with '#' will be ignored, and an empty title aborts the operation.
+`
+
+// BugTitleEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a title.
+func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) {
+	template := fmt.Sprintf(bugTitleTemplate, preTitle)
+
+	raw, err := input.LaunchEditorWithTemplate(repo, messageFilename, template)
+	if err != nil {
+		return "", err
+	}
+
+	lines := strings.Split(raw, "\n")
+
+	var title string
+	for _, line := range lines {
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+		trimmed := strings.TrimSpace(line)
+		if trimmed == "" {
+			continue
+		}
+		title = trimmed
+		break
+	}
+
+	if title == "" {
+		return "", ErrEmptyTitle
+	}
+
+	return title, nil
+}
+
+const queryTemplate = `%s
+
+# Please edit the bug query.
+# Lines starting with '#' will be ignored, and an empty query aborts the operation.
+#
+# Example: status:open author:"rené descartes" sort:edit
+#
+# Valid filters are:
+#
+# - status:open, status:closed
+# - author:<query>
+# - title:<title>
+# - label:<label>
+# - no:label
+#
+# Sorting
+#
+# - sort:id, sort:id-desc, sort:id-asc
+# - sort:creation, sort:creation-desc, sort:creation-asc
+# - sort:edit, sort:edit-desc, sort:edit-asc
+#
+# Notes
+# 
+# - queries are case insensitive.
+# - you can combine as many qualifiers as you want.
+# - you can use double quotes for multi-word search terms (ex: author:"René Descartes")
+`
+
+// QueryEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a query.
+func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) {
+	template := fmt.Sprintf(queryTemplate, preQuery)
+
+	raw, err := input.LaunchEditorWithTemplate(repo, messageFilename, template)
+	if err != nil {
+		return "", err
+	}
+
+	lines := strings.Split(raw, "\n")
+
+	for _, line := range lines {
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+		trimmed := strings.TrimSpace(line)
+		if trimmed == "" {
+			continue
+		}
+		return trimmed, nil
+	}
+
+	return "", nil
+}
  
  
  
    
    @@ -12,245 +12,24 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"strings"
 
 	"github.com/go-git/go-billy/v5/util"
-	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/repository"
 )
 
-const messageFilename = "BUG_MESSAGE_EDITMSG"
-
-// ErrEmptyMessage is returned when the required message has not been entered
-var ErrEmptyMessage = errors.New("empty message")
-
-// ErrEmptyTitle is returned when the required title has not been entered
-var ErrEmptyTitle = errors.New("empty title")
-
-const bugTitleCommentTemplate = `%s%s
-
-# Please enter the title and comment message. The first non-empty line will be
-# used as the title. Lines starting with '#' will be ignored.
-# An empty title aborts the operation.
-`
-
-// BugCreateEditorInput will open the default editor in the terminal with a
-// template for the user to fill. The file is then processed to extract title
-// and message.
-func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) {
-	if preMessage != "" {
-		preMessage = "\n\n" + preMessage
-	}
-
-	template := fmt.Sprintf(bugTitleCommentTemplate, preTitle, preMessage)
-
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
-
-	if err != nil {
-		return "", "", err
-	}
-
-	return processCreate(raw)
-}
-
-// BugCreateFileInput read from either from a file or from the standard input
-// and extract a title and a message
-func BugCreateFileInput(fileName string) (string, string, error) {
-	raw, err := fromFile(fileName)
-	if err != nil {
-		return "", "", err
-	}
-
-	return processCreate(raw)
-}
-
-func processCreate(raw string) (string, string, error) {
-	lines := strings.Split(raw, "\n")
-
-	var title string
-	var buffer bytes.Buffer
-	for _, line := range lines {
-		if strings.HasPrefix(line, "#") {
-			continue
-		}
-
-		if title == "" {
-			trimmed := strings.TrimSpace(line)
-			if trimmed != "" {
-				title = trimmed
-			}
-			continue
-		}
-
-		buffer.WriteString(line)
-		buffer.WriteString("\n")
-	}
-
-	if title == "" {
-		return "", "", ErrEmptyTitle
-	}
-
-	message := strings.TrimSpace(buffer.String())
-
-	return title, message, nil
-}
-
-const bugCommentTemplate = `%s
-
-# Please enter the comment message. Lines starting with '#' will be ignored,
-# and an empty message aborts the operation.
-`
-
-// BugCommentEditorInput will open the default editor in the terminal with a
-// template for the user to fill. The file is then processed to extract a comment.
-func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) {
-	template := fmt.Sprintf(bugCommentTemplate, preMessage)
-
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
-	if err != nil {
-		return "", err
-	}
-
-	return processComment(raw)
-}
-
-// BugCommentFileInput read from either from a file or from the standard input
-// and extract a message
-func BugCommentFileInput(fileName string) (string, error) {
-	raw, err := fromFile(fileName)
-	if err != nil {
-		return "", err
-	}
-
-	return processComment(raw)
-}
-
-func processComment(raw string) (string, error) {
-	lines := strings.Split(raw, "\n")
-
-	var buffer bytes.Buffer
-	for _, line := range lines {
-		if strings.HasPrefix(line, "#") {
-			continue
-		}
-		buffer.WriteString(line)
-		buffer.WriteString("\n")
-	}
-
-	message := strings.TrimSpace(buffer.String())
-
-	if message == "" {
-		return "", ErrEmptyMessage
-	}
-
-	return message, nil
-}
-
-const bugTitleTemplate = `%s
-
-# Please enter the new title. Only one line will used.
-# Lines starting with '#' will be ignored, and an empty title aborts the operation.
-`
-
-// BugTitleEditorInput will open the default editor in the terminal with a
-// template for the user to fill. The file is then processed to extract a title.
-func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) {
-	template := fmt.Sprintf(bugTitleTemplate, preTitle)
-
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
-	if err != nil {
-		return "", err
-	}
-
-	lines := strings.Split(raw, "\n")
-
-	var title string
-	for _, line := range lines {
-		if strings.HasPrefix(line, "#") {
-			continue
-		}
-		trimmed := strings.TrimSpace(line)
-		if trimmed == "" {
-			continue
-		}
-		title = trimmed
-		break
-	}
-
-	if title == "" {
-		return "", ErrEmptyTitle
-	}
-
-	return title, nil
-}
-
-const queryTemplate = `%s
-
-# Please edit the bug query.
-# Lines starting with '#' will be ignored, and an empty query aborts the operation.
-#
-# Example: status:open author:"rené descartes" sort:edit
-#
-# Valid filters are:
-#
-# - status:open, status:closed
-# - author:<query>
-# - title:<title>
-# - label:<label>
-# - no:label
-#
-# Sorting
-#
-# - sort:id, sort:id-desc, sort:id-asc
-# - sort:creation, sort:creation-desc, sort:creation-asc
-# - sort:edit, sort:edit-desc, sort:edit-asc
-#
-# Notes
-# 
-# - queries are case insensitive.
-# - you can combine as many qualifiers as you want.
-# - you can use double quotes for multi-word search terms (ex: author:"René Descartes")
-`
-
-// QueryEditorInput will open the default editor in the terminal with a
-// template for the user to fill. The file is then processed to extract a query.
-func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) {
-	template := fmt.Sprintf(queryTemplate, preQuery)
-
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
-	if err != nil {
-		return "", err
-	}
-
-	lines := strings.Split(raw, "\n")
-
-	for _, line := range lines {
-		if strings.HasPrefix(line, "#") {
-			continue
-		}
-		trimmed := strings.TrimSpace(line)
-		if trimmed == "" {
-			continue
-		}
-		return trimmed, nil
-	}
-
-	return "", nil
-}
-
-// launchEditorWithTemplate will launch an editor as launchEditor do, but with a
+// LaunchEditorWithTemplate will launch an editor as LaunchEditor do, but with a
 // provided template.
-func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) {
+func LaunchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) {
 	err := util.WriteFile(repo.LocalStorage(), fileName, []byte(template), 0644)
 	if err != nil {
 		return "", err
 	}
 
-	return launchEditor(repo, fileName)
+	return LaunchEditor(repo, fileName)
 }
 
-// launchEditor launches the default editor configured for the given repo. This
+// LaunchEditor launches the default editor configured for the given repo. This
 // method blocks until the editor command has returned.
 //
 // The specified filename should be a temporary file and provided as a relative path
@@ -259,7 +38,7 @@ func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string
 //
 // This method returns the text that was read from the temporary file, or
 // an error if any step in the process failed.
-func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) {
+func LaunchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) {
 	defer repo.LocalStorage().Remove(fileName)
 
 	editor, err := repo.GetCoreEditor()
@@ -302,11 +81,11 @@ func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, e
 	return string(output), err
 }
 
-// fromFile loads and returns the contents of a given file. If - is passed
+// FromFile loads and returns the contents of a given file. If - is passed
 // through, much like git, it will read from stdin. This can be piped data,
 // unless there is a tty in which case the user will be prompted to enter a
 // message.
-func fromFile(fileName string) (string, error) {
+func FromFile(fileName string) (string, error) {
 	if fileName == "-" {
 		stat, err := os.Stdin.Stat()
 		if err != nil {
  
  
  
    
    @@ -8,7 +8,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/commands/input"
+	buginput "github.com/MichaelMure/git-bug/commands/bug/input"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/query"
 	"github.com/MichaelMure/git-bug/util/text"
@@ -187,14 +187,14 @@ func newBugWithEditor(repo *cache.RepoCache) error {
 	ui.g.Close()
 	ui.g = nil
 
-	title, message, err := input.BugCreateEditorInput(ui.cache, "", "")
+	title, message, err := buginput.BugCreateEditorInput(ui.cache, "", "")
 
-	if err != nil && err != input.ErrEmptyTitle {
+	if err != nil && err != buginput.ErrEmptyTitle {
 		return err
 	}
 
 	var b *cache.BugCache
-	if err == input.ErrEmptyTitle {
+	if err == buginput.ErrEmptyTitle {
 		ui.msgPopup.Activate(msgPopupErrorTitle, "Empty title, aborting.")
 		initGui(nil)
 
@@ -230,13 +230,11 @@ func addCommentWithEditor(bug *cache.BugCache) error {
 	ui.g.Close()
 	ui.g = nil
 
-	message, err := input.BugCommentEditorInput(ui.cache, "")
-
-	if err != nil && err != input.ErrEmptyMessage {
+	message, err := buginput.BugCommentEditorInput(ui.cache, "")
+	if err != nil && err != buginput.ErrEmptyMessage {
 		return err
 	}
-
-	if err == input.ErrEmptyMessage {
+	if err == buginput.ErrEmptyMessage {
 		ui.msgPopup.Activate(msgPopupErrorTitle, "Empty message, aborting.")
 	} else {
 		_, _, err := bug.AddComment(text.Cleanup(message))
@@ -263,12 +261,12 @@ func editCommentWithEditor(bug *cache.BugCache, target entity.CombinedId, preMes
 	ui.g.Close()
 	ui.g = nil
 
-	message, err := input.BugCommentEditorInput(ui.cache, preMessage)
-	if err != nil && err != input.ErrEmptyMessage {
+	message, err := buginput.BugCommentEditorInput(ui.cache, preMessage)
+	if err != nil && err != buginput.ErrEmptyMessage {
 		return err
 	}
 
-	if err == input.ErrEmptyMessage {
+	if err == buginput.ErrEmptyMessage {
 		// TODO: Allow comments to be deleted?
 		ui.msgPopup.Activate(msgPopupErrorTitle, "Empty message, aborting.")
 	} else if message == preMessage {
@@ -300,13 +298,13 @@ func setTitleWithEditor(bug *cache.BugCache) error {
 
 	snap := bug.Snapshot()
 
-	title, err := input.BugTitleEditorInput(ui.cache, snap.Title)
+	title, err := buginput.BugTitleEditorInput(ui.cache, snap.Title)
 
-	if err != nil && err != input.ErrEmptyTitle {
+	if err != nil && err != buginput.ErrEmptyTitle {
 		return err
 	}
 
-	if err == input.ErrEmptyTitle {
+	if err == buginput.ErrEmptyTitle {
 		ui.msgPopup.Activate(msgPopupErrorTitle, "Empty title, aborting.")
 	} else if title == snap.Title {
 		ui.msgPopup.Activate(msgPopupErrorTitle, "No change, aborting.")
@@ -335,7 +333,7 @@ func editQueryWithEditor(bt *bugTable) error {
 	ui.g.Close()
 	ui.g = nil
 
-	queryStr, err := input.QueryEditorInput(bt.repo, bt.queryStr)
+	queryStr, err := buginput.QueryEditorInput(bt.repo, bt.queryStr)
 
 	if err != nil {
 		return err