allow adding a package

This commit is contained in:
Jesse Duffield 2020-04-12 11:47:16 +10:00
parent 774e098d47
commit 02f0624909
10 changed files with 178 additions and 103 deletions

View File

@ -418,5 +418,9 @@ func FileExists(filename string) bool {
if os.IsNotExist(err) {
return false
}
if err != nil {
// should actually do something here
return false
}
return !info.IsDir()
}

View File

@ -22,7 +22,6 @@ type AppConfig struct {
UserConfig *viper.Viper
UserConfigDir string
AppState *AppState
IsNewPackage bool
}
// AppConfigurer interface allows individual app config structs to inherit Fields
@ -40,8 +39,6 @@ type AppConfigurer interface {
WriteToUserConfig(string, interface{}) error
SaveAppState() error
LoadAppState() error
SetIsNewPackage(bool)
GetIsNewPackage() bool
}
// NewAppConfig makes a new app config
@ -65,7 +62,6 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
UserConfig: userConfig,
UserConfigDir: filepath.Dir(userConfigPath),
AppState: &AppState{},
IsNewPackage: false,
}
if err := appConfig.LoadAppState(); err != nil {
@ -75,16 +71,6 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
return appConfig, nil
}
// GetIsNewPackage returns known repo boolean
func (c *AppConfig) GetIsNewPackage() bool {
return c.IsNewPackage
}
// SetIsNewPackage set if the current repo is known
func (c *AppConfig) SetIsNewPackage(isNew bool) {
c.IsNewPackage = isNew
}
// GetDebug returns debug flag
func (c *AppConfig) GetDebug() bool {
return c.Debug

View File

@ -15,11 +15,24 @@ import (
"github.com/jesseduffield/lazynpm/pkg/theme"
)
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
func (gui *Gui) wrappedConfirmationFunction(function func() error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
if function != nil {
if err := function(g, v); err != nil {
if err := function(); err != nil {
return err
}
}
return gui.closeConfirmationPrompt(g, returnFocusOnClose)
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(function func(string) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
if function != nil {
if err := function(v.Buffer()); err != nil {
return err
}
}
@ -101,44 +114,68 @@ func (gui *Gui) onNewPopupPanel() {
}
}
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, editable bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
type createPopupPanelOpts struct {
hasLoader bool
returnFocusOnClose bool
editable bool
prompt string
handleConfirm func() error
handleConfirmPrompt func(string) error
handleClose func() error
}
func (gui *Gui) createPopupPanel(currentView *gocui.View, title string, opts createPopupPanelOpts) error {
gui.onNewPopupPanel()
g.Update(func(g *gocui.Gui) error {
gui.g.Update(func(g *gocui.Gui) error {
// delete the existing confirmation panel if it exists
if view, _ := g.View("confirmation"); view != nil {
if err := gui.closeConfirmationPrompt(g, true); err != nil {
gui.Log.Error(err)
}
}
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, opts.prompt, opts.hasLoader)
if err != nil {
return err
}
confirmationView.Editable = editable
if editable {
confirmationView.Editable = opts.editable
if opts.editable {
confirmationView.EditGotoToEndOfLine()
}
gui.renderString("confirmation", prompt)
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
gui.renderString("confirmation", opts.prompt)
return gui.setKeyBindings(opts)
})
return nil
}
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
return gui.createPopupPanel(g, currentView, "", prompt, true, true, false, nil, nil)
return gui.createPopupPanel(currentView, "", createPopupPanelOpts{
prompt: prompt,
hasLoader: true,
returnFocusOnClose: true,
})
}
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
func (gui *Gui) createConfirmationPanel(currentView *gocui.View, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
return gui.createPopupPanel(gui.g, currentView, title, prompt, false, returnFocusOnClose, false, handleConfirm, handleClose)
func (gui *Gui) createConfirmationPanel(currentView *gocui.View, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func() error) error {
return gui.createPopupPanel(currentView, title, createPopupPanelOpts{
prompt: prompt,
returnFocusOnClose: returnFocusOnClose,
handleConfirm: handleConfirm,
handleClose: handleClose,
})
}
func (gui *Gui) createPromptPanel(currentView *gocui.View, title string, initialContent string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
return gui.createPopupPanel(gui.g, currentView, title, initialContent, false, true, true, handleConfirm, nil)
func (gui *Gui) createPromptPanel(currentView *gocui.View, title string, initialContent string, handleConfirm func(string) error) error {
return gui.createPopupPanel(currentView, title, createPopupPanelOpts{
prompt: initialContent,
returnFocusOnClose: true,
editable: true,
handleConfirmPrompt: handleConfirm,
})
}
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) error {
func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
actions := gui.Tr.TemplateLocalize(
"CloseConfirm",
Teml{
@ -148,27 +185,27 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
)
gui.renderString("options", actions)
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
return err
if opts.handleConfirmPrompt != nil {
if err := gui.g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedPromptConfirmationFunction(opts.handleConfirmPrompt, opts.returnFocusOnClose)); err != nil {
return err
}
} else {
if err := gui.g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(opts.handleConfirm, opts.returnFocusOnClose)); err != nil {
return err
}
}
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
return gui.g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(opts.handleClose, opts.returnFocusOnClose))
}
// createSpecificErrorPanel allows you to create an error popup, specifying the
// view to be focused when the user closes the popup, and a boolean specifying
// whether we will log the error. If the message may include a user password,
// this function is to be used over the more generic createErrorPanel, with
// willLog set to false
func (gui *Gui) createSpecificErrorPanel(message string, nextView *gocui.View, willLog bool) error {
if willLog {
go func() {
// when reporting is switched on this log call sometimes introduces
// a delay on the error panel popping up. Here I'm adding a second wait
// so that the error is logged while the user is reading the error message
time.Sleep(time.Second)
gui.Log.Error(message)
}()
}
func (gui *Gui) createErrorPanel(message string) error {
go func() {
// when reporting is switched on this log call sometimes introduces
// a delay on the error panel popping up. Here I'm adding a second wait
// so that the error is logged while the user is reading the error message
time.Sleep(time.Second)
gui.Log.Error(message)
}()
colorFunction := color.New(color.FgRed).SprintFunc()
coloredMessage := colorFunction(strings.TrimSpace(message))
@ -176,11 +213,8 @@ func (gui *Gui) createSpecificErrorPanel(message string, nextView *gocui.View, w
return err
}
return gui.createConfirmationPanel(nextView, true, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
}
func (gui *Gui) createErrorPanel(message string) error {
return gui.createSpecificErrorPanel(message, gui.g.CurrentView(), true)
// TODO: see if returning to the current view is bad in the case of popup views
return gui.createConfirmationPanel(gui.g.CurrentView(), true, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
}
func (gui *Gui) surfaceError(err error) error {

View File

@ -334,44 +334,6 @@ func (gui *Gui) updateRecentPackagesList() error {
return errors.New("Must open lazynpm in an npm package")
}
func (gui *Gui) sendPackageToTop(path string) error {
// in case we're not already there, chdir to path
if err := os.Chdir(path); err != nil {
return err
}
recentPackages := gui.Config.GetAppState().RecentPackages
isNew, recentPackages := newRecentPackagesList(recentPackages, path)
gui.Config.SetIsNewPackage(isNew)
gui.Config.GetAppState().RecentPackages = recentPackages
return gui.Config.SaveAppState()
}
func (gui *Gui) removePackage(path string) error {
recentPackages := gui.Config.GetAppState().RecentPackages
index, ok := utils.StringIndex(recentPackages, path)
if !ok {
return nil
}
recentPackages = append(recentPackages[:index], recentPackages[index+1:]...)
gui.Config.GetAppState().RecentPackages = recentPackages
return gui.Config.SaveAppState()
}
// newRecentPackagesList returns a new repo list with a new entry but only when it doesn't exist yet
func newRecentPackagesList(recentPackages []string, currentPackage string) (bool, []string) {
isNew := true
newPackages := []string{currentPackage}
for _, pkg := range recentPackages {
if pkg != currentPackage {
newPackages = append(newPackages, pkg)
} else {
isNew = false
}
}
return isNew, newPackages
}
func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
gui.waitForIntro.Add(len(tasks))
done := make(chan struct{})
@ -391,7 +353,7 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
}
func (gui *Gui) showShamelessSelfPromotionMessage(done chan struct{}) error {
onConfirm := func(g *gocui.Gui, v *gocui.View) error {
onConfirm := func() error {
done <- struct{}{}
return gui.Config.WriteToUserConfig("startupPopupVersion", StartupPopupVersion)
}
@ -400,10 +362,10 @@ func (gui *Gui) showShamelessSelfPromotionMessage(done chan struct{}) error {
}
func (gui *Gui) promptAnonymousReporting(done chan struct{}) error {
return gui.createConfirmationPanel(nil, true, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(nil, true, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func() error {
done <- struct{}{}
return gui.Config.WriteToUserConfig("reporting", "on")
}, func(g *gocui.Gui, v *gocui.View) error {
}, func() error {
done <- struct{}{}
return gui.Config.WriteToUserConfig("reporting", "off")
})

View File

@ -440,6 +440,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleCheckoutPackage),
},
{
ViewName: "packages",
Key: gui.getKey("universal.new"),
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleAddPackage),
},
{
ViewName: "packages",
Key: gui.getKey("packages.link"),

View File

@ -2,6 +2,8 @@ package gui
import (
"fmt"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/go-errors/errors"
@ -222,8 +224,22 @@ func (gui *Gui) handleRemovePackage() error {
return gui.createErrorPanel("Cannot remove current package")
}
return gui.createConfirmationPanel(gui.getPackagesView(), true, "Remove package", "Do you want to remove this package from the list? It won't actually be removed from the filesystem, but as far as lazynpm is concerned it'll be as good as dead. You won't have to worry about it no more.", func(*gocui.Gui, *gocui.View) error {
return gui.createConfirmationPanel(gui.getPackagesView(), true, "Remove package", "Do you want to remove this package from the list? It won't actually be removed from the filesystem, but as far as lazynpm is concerned it'll be as good as dead. You won't have to worry about it no more.", func() error {
return gui.removePackage(selectedPkg.Path)
},
nil)
}
func (gui *Gui) handleAddPackage() error {
return gui.createPromptPanel(gui.getPackagesView(), "Add package path to add", "", func(input string) error {
configPath := input
if !strings.HasSuffix(configPath, "package.json") {
configPath = filepath.Join(configPath, "package.json")
}
if !commands.FileExists(configPath) {
return gui.createErrorPanel(fmt.Sprintf("%s not found", configPath))
}
return gui.addPackage(strings.TrimSuffix(input, "package.json"))
})
}

View File

@ -40,7 +40,7 @@ func (gui *Gui) quit(v *gocui.View) error {
}
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
return gui.createConfirmationPanel(v, true, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(v, true, "", gui.Tr.SLocalize("ConfirmQuit"), func() error {
return gocui.ErrQuit
}, nil)
}

View File

@ -0,0 +1,69 @@
package gui
import (
"os"
"github.com/jesseduffield/lazynpm/pkg/utils"
)
func (gui *Gui) mutateRecentPackages(f func([]string) ([]string, bool)) error {
recentPackages := gui.Config.GetAppState().RecentPackages
recentPackages, changed := f(recentPackages)
if !changed {
return nil
}
gui.Config.GetAppState().RecentPackages = recentPackages
return gui.Config.SaveAppState()
}
func (gui *Gui) sendPackageToTop(path string) error {
// in case we're not already there, chdir to path
if err := os.Chdir(path); err != nil {
return err
}
return gui.mutateRecentPackages(func(recentPackages []string) ([]string, bool) {
updatedRecentPackages := newRecentPackagesList(recentPackages, path)
// just unconditionally saying we updated it even if we didn't
return updatedRecentPackages, true
})
}
func (gui *Gui) removePackage(path string) error {
return gui.mutateRecentPackages(func(recentPackages []string) ([]string, bool) {
index, ok := utils.StringIndex(recentPackages, path)
if !ok {
// not removing it if it's already been removed
return nil, false
}
updatedRecentPackages := append(recentPackages[:index], recentPackages[index+1:]...)
return updatedRecentPackages, true
})
}
func (gui *Gui) addPackage(path string) error {
return gui.mutateRecentPackages(func(recentPackages []string) ([]string, bool) {
_, ok := utils.StringIndex(recentPackages, path)
if ok {
// not adding it if it's already present
return nil, false
}
updatedRecentPackages := append(recentPackages, path)
return updatedRecentPackages, true
})
}
// newRecentPackagesList returns a new repo list with a new entry but only when it doesn't exist yet
// if it already exists, it will be moved to the start of the array
func newRecentPackagesList(recentPackages []string, currentPackage string) []string {
newPackages := []string{currentPackage}
for _, pkg := range recentPackages {
if pkg != currentPackage {
newPackages = append(newPackages, pkg)
}
}
return newPackages
}

View File

@ -2,7 +2,6 @@ package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazynpm/pkg/commands"
@ -38,10 +37,9 @@ func (gui *Gui) handleScriptSelect(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleRunScript() error {
script := gui.getSelectedScript()
return gui.createPromptPanel(gui.getScriptsView(), "run script", fmt.Sprintf("npm run %s ", script.Name), func(g *gocui.Gui, v *gocui.View) error {
cmdStr := strings.TrimSpace(v.Buffer())
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
if err := gui.newPtyTask("main", cmd, cmdStr); err != nil {
return gui.createPromptPanel(gui.getScriptsView(), "run script", fmt.Sprintf("npm run %s ", script.Name), func(input string) error {
cmd := gui.OSCommand.ExecutableFromString(input)
if err := gui.newPtyTask("main", cmd, input); err != nil {
gui.Log.Error(err)
}
return nil
@ -51,7 +49,7 @@ func (gui *Gui) handleRunScript() error {
func (gui *Gui) handleRemoveScript() error {
script := gui.getSelectedScript()
return gui.createConfirmationPanel(gui.getScriptsView(), true, "Remove script", fmt.Sprintf("are you sure you want to remove script `%s`?", script.Name), func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(gui.getScriptsView(), true, "Remove script", fmt.Sprintf("are you sure you want to remove script `%s`?", script.Name), func() error {
return gui.surfaceError(
gui.NpmManager.RemoveScript(script.Name, gui.currentPackage().ConfigPath()),
)

View File

@ -6,7 +6,7 @@ func (gui *Gui) showUpdatePrompt(newVersion string) error {
title := "New version available!"
message := "Download latest version? (enter/esc)"
currentView := gui.g.CurrentView()
return gui.createConfirmationPanel(currentView, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(currentView, true, title, message, func() error {
gui.startUpdating(newVersion)
return nil
}, nil)
@ -57,7 +57,7 @@ func (gui *Gui) onUpdateFinish(err error) error {
func (gui *Gui) createUpdateQuitConfirmation(g *gocui.Gui, v *gocui.View) error {
title := "Currently Updating"
message := "An update is in progress. Are you sure you want to quit?"
return gui.createConfirmationPanel(v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(v, true, title, message, func() error {
return gocui.ErrQuit
}, nil)
}