diff --git a/README.md b/README.md index d2b5a40..3661d01 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ So I made it possible to register snippets with description and search them easi # TOC - [Main features](#main-features) +- [Parameters] (#parameters) - [Examples](#examples) - [Register the previous command easily](#register-the-previous-command-easily) - [bash](#bash-prev-function) @@ -70,12 +71,27 @@ So I made it possible to register snippets with description and search them easi `pet` has the following features. - Register your command snippets easily. -- Use variables in snippets. +- Use variables (with one or several default values) in snippets. - Search snippets interactively. - Run snippets directly. - Edit snippets easily (config is just a TOML file). - Sync snippets via Gist or GitLab Snippets automatically. +# Parameters +There are `` ways of entering parameters. + +They can contain default values: Hello `` +defined by the equal sign. + +They can even contain `` where the default value would be \spaces & = signs\>. + +Default values just can't \. + +They can also contain multiple default values: +Hello `` + +The values in this case would be :Hello \John\_\|\|\_Sam\_\|\|\_Jane Doe = special #chars\_\|\> + # Examples Some examples are shown below. diff --git a/dialog/params.go b/dialog/params.go index 603c49a..b89ae31 100644 --- a/dialog/params.go +++ b/dialog/params.go @@ -18,11 +18,11 @@ var ( // This matches most encountered patterns // Skips match if there is a whitespace at the end ex. // Ignores <, > characters since they're used to match the pattern - patternRegex = `<([^<>]*[^\s])>` + parameterStringRegex = `<([^<>]*[^\s])>` ) func insertParams(command string, filledInParams map[string]string) string { - r := regexp.MustCompile(patternRegex) + r := regexp.MustCompile(parameterStringRegex) matches := r.FindAllStringSubmatch(command, -1) if len(matches) == 0 { @@ -48,7 +48,7 @@ func insertParams(command string, filledInParams map[string]string) string { // SearchForParams returns variables from a command func SearchForParams(command string) [][2]string { - r := regexp.MustCompile(patternRegex) + r := regexp.MustCompile(parameterStringRegex) params := r.FindAllStringSubmatch(command, -1) if len(params) == 0 { diff --git a/dialog/params_test.go b/dialog/params_test.go index 56edd86..e99544b 100644 --- a/dialog/params_test.go +++ b/dialog/params_test.go @@ -211,6 +211,21 @@ func TestSearchForParams_EqualsInDefaultValueIgnored(t *testing.T) { } } +func TestSearchForParams_MultipleDefaultValuesDoNotBreakFunction(t *testing.T) { + command := "echo \" , \"" + want := [][2]string{ + {"param", "|_Hello_||_Hello world_||_How are you?_|"}, + {"second", "Hello"}, + {"third", ""}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + func TestInsertParams(t *testing.T) { command := " hello" diff --git a/dialog/view.go b/dialog/view.go index 6ff602e..ff75f11 100644 --- a/dialog/view.go +++ b/dialog/view.go @@ -3,6 +3,7 @@ package dialog import ( "fmt" "log" + "regexp" "github.com/awesome-gocui/gocui" ) @@ -10,25 +11,94 @@ import ( var ( layoutStep = 3 curView = -1 + + // This is for matching multiple default values in parameters + parameterMultipleValueRegex = `(\|_.*?_\|)` ) -func generateView(g *gocui.Gui, desc string, fill string, coords []int, editable bool) error { - if StringInSlice(desc, views) { +// createView sets up a new view with the given parameters. +func createView(g *gocui.Gui, name string, coords []int, editable bool) (*gocui.View, error) { + if StringInSlice(name, views) { + return nil, nil + } + + v, err := g.SetView(name, coords[0], coords[1], coords[2], coords[3], 0) + if err != nil && err != gocui.ErrUnknownView { + return nil, err + } + + v.Title = name + v.Wrap = true + v.Autoscroll = true + v.Editable = editable + + views = append(views, name) + + return v, nil +} + +func generateSingleParameterView(g *gocui.Gui, name string, defaultParam string, coords []int, editable bool) error { + view, err := createView(g, name, coords, editable) + if err != nil { + return err + } + + g.SetKeybinding(view.Name(), gocui.KeyCtrlK, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + v.Clear() return nil + }) + + fmt.Fprint(view, defaultParam) + return nil +} + +func generateMultipleParameterView(g *gocui.Gui, name string, defaultParams []string, coords []int, editable bool) error { + view, err := createView(g, name, coords, editable) + if err != nil { + return err + } + + currentOpt := 0 + maxOpt := len(defaultParams) + + fmt.Fprint(view, defaultParams[currentOpt]) + + viewTitle := name + // Adjust view title to hint the user about the available + // options if there are more than one + if maxOpt > 1 { + viewTitle = name + " (UP/DOWN => Select default value)" } - if v, err := g.SetView(desc, coords[0], coords[1], coords[2], coords[3], 0); err != nil { - if err != gocui.ErrUnknownView { - return err + + view.Title = viewTitle + + g.SetKeybinding(view.Name(), gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + if maxOpt == 0 { + return nil } - fmt.Fprint(v, fill) - } - view, _ := g.View(desc) - view.Title = desc - view.Wrap = true - view.Autoscroll = true - view.Editable = editable + next := currentOpt + 1 + if next >= maxOpt { + next = 0 + } + v.Clear() + fmt.Fprint(v, defaultParams[next]) + currentOpt = next + return nil + }) - views = append(views, desc) + g.SetKeybinding(view.Name(), gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + if maxOpt == 0 { + return nil + } + prev := currentOpt - 1 + if prev < 0 { + prev = maxOpt - 1 + } + v.Clear() + fmt.Fprint(v, defaultParams[prev]) + currentOpt = prev + return nil + }) return nil } @@ -51,20 +121,45 @@ func GenerateParamsLayout(params [][2]string, command string) { leftX := (maxX / 2) - (maxX / 3) rightX := (maxX / 2) + (maxX / 3) - generateView(g, "Command(TAB => Select next, ENTER => Execute command):", + generateSingleParameterView(g, "Command(TAB => Select next, ENTER => Execute command):", command, []int{leftX, maxY / 10, rightX, maxY/10 + 5}, false) idx := 0 // Create a view for each param for _, pair := range params { // Unpack parameter key and value - k, v := pair[0], pair[1] - generateView(g, k, v, - []int{leftX, - (maxY / 4) + (idx+1)*layoutStep, - rightX, - (maxY / 4) + 2 + (idx+1)*layoutStep}, - true) + parameterKey, parameterValue := pair[0], pair[1] + + // Check value for multiple defaults + r := regexp.MustCompile(parameterMultipleValueRegex) + matches := r.FindAllStringSubmatch(parameterValue, -1) + + if len(matches) > 0 { + // Extract the default values and generate multiple params view + parameters := []string{} + for _, p := range matches { + _, matchedGroup := p[0], p[1] + // Remove the separators + matchedGroup = matchedGroup[2 : len(matchedGroup)-2] + parameters = append(parameters, matchedGroup) + } + generateMultipleParameterView( + g, parameterKey, parameters, []int{ + leftX, + (maxY / 4) + (idx+1)*layoutStep, + rightX, + (maxY / 4) + 2 + (idx+1)*layoutStep}, + true) + } else { + // Generate single param view using the single value + generateSingleParameterView(g, parameterKey, parameterValue, + []int{ + leftX, + (maxY / 4) + (idx+1)*layoutStep, + rightX, + (maxY / 4) + 2 + (idx+1)*layoutStep}, + true) + } idx++ }