Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multiple parameter support via two character separators #290

Merged
merged 4 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 `<n_ways>` ways of entering parameters.

They can contain default values: Hello `<subject=world>`
defined by the equal sign.

They can even contain `<content=spaces & = signs>` where the default value would be \<content=<mark>spaces & = signs</mark>\>.

Default values just can't \<end with spaces \>.

They can also contain multiple default values:
Hello `<subject=|_John_||_Sam_||_Jane Doe = special #chars_|>`

The values in this case would be :Hello \<subject=\|\_<mark>John</mark>\_\|\|\_<mark>Sam</mark>\_\|\|\_<mark>Jane Doe = special #chars</mark>\_\|\>

# Examples
Some examples are shown below.

Expand Down
6 changes: 3 additions & 3 deletions dialog/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ var (
// This matches most encountered patterns
// Skips match if there is a whitespace at the end ex. <param='my >
// 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 {
Expand All @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions dialog/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,21 @@ func TestSearchForParams_EqualsInDefaultValueIgnored(t *testing.T) {
}
}

func TestSearchForParams_MultipleDefaultValuesDoNotBreakFunction(t *testing.T) {
command := "echo \"<param=|_Hello_||_Hello world_||_How are you?_|> <second=Hello>, <third>\""
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 := "<a=1> <a> <b> hello"

Expand Down
137 changes: 116 additions & 21 deletions dialog/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,102 @@ package dialog
import (
"fmt"
"log"
"regexp"

"github.com/awesome-gocui/gocui"
)

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
}
Expand All @@ -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++
}

Expand Down