diff --git a/internal/zenutil/osa_generated.go b/internal/zenutil/osa_generated.go index 7258dd8..45022b0 100644 --- a/internal/zenutil/osa_generated.go +++ b/internal/zenutil/osa_generated.go @@ -36,6 +36,12 @@ app.activate() var res=app.{{.Operation}}({{json .Options}}) if(Array.isArray(res)){res.join({{json .Separator}})}else{res.toString()} {{- end}} +{{define "list" -}} +var app=Application.currentApplication() +app.includeStandardAdditions=true +var res=app.chooseFromList({{json .Items}},{{json .Options}}) +res.join({{json .Separator}}) +{{- end}} {{define "notify" -}} var app=Application.currentApplication() app.includeStandardAdditions=true diff --git a/internal/zenutil/osascripts/list.gojs b/internal/zenutil/osascripts/list.gojs new file mode 100644 index 0000000..060c48f --- /dev/null +++ b/internal/zenutil/osascripts/list.gojs @@ -0,0 +1,5 @@ +var app = Application.currentApplication() +app.includeStandardAdditions = true + +var res = app.chooseFromList({{json .Items}}, {{json .Options}}) +res.join({{json .Separator}}) \ No newline at end of file diff --git a/internal/zenutil/run_darwin.go b/internal/zenutil/run_darwin.go index bd7f392..29ef823 100644 --- a/internal/zenutil/run_darwin.go +++ b/internal/zenutil/run_darwin.go @@ -86,6 +86,24 @@ type DialogOptions struct { Timeout int `json:"givingUpAfter,omitempty"` } +// List is internal. +type List struct { + Items []string + Separator string + Options ListOptions +} + +// ListOptions is internal. +type ListOptions struct { + Title *string `json:"withTitle,omitempty"` + Prompt *string `json:"withPrompt,omitempty"` + OK *string `json:"okButtonName,omitempty"` + Cancel *string `json:"cancelButtonName,omitempty"` + Default []string `json:"defaultItems,omitempty"` + Multiple bool `json:"multipleSelectionsAllowed,omitempty"` + Empty bool `json:"emptySelectionAllowed,omitempty"` +} + // Notify is internal. type Notify struct { Text string diff --git a/list.go b/list.go new file mode 100644 index 0000000..c4dc9b3 --- /dev/null +++ b/list.go @@ -0,0 +1,45 @@ +package zenity + +// List displays the list dialog. +// +// Returns false on cancel, or ErrExtraButton. +// +// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton, +// Icon, DefaultItems, DisallowEmpty. +func List(text string, items []string, options ...Option) (string, bool, error) { + return list(text, items, applyOptions(options)) +} + +// ListItems displays the list dialog. +// +// Returns false on cancel, or ErrExtraButton. +func ListItems(text string, items ...string) (string, bool, error) { + return List(text, items) +} + +// ListMultiple displays the list dialog, allowing multiple rows to be selected. +// +// Returns a nil slice on cancel, or ErrExtraButton. +// +// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton, +// Icon, DefaultItems, DisallowEmpty. +func ListMultiple(text string, items []string, options ...Option) ([]string, error) { + return listMultiple(text, items, applyOptions(options)) +} + +// ListMultiple displays the list dialog, allowing multiple rows to be selected. +// +// Returns a nil slice on cancel, or ErrExtraButton. +func ListMultipleItems(text string, items ...string) ([]string, error) { + return ListMultiple(text, items) +} + +// DefaultItems returns an Option to set the items to initally select. +func DefaultItems(items ...string) Option { + return funcOption(func(o *options) { o.defaultItems = items }) +} + +// DisallowEmpty returns an Option to not allow zero rows to be selected (Windows and macOS only). +func DisallowEmpty() Option { + return funcOption(func(o *options) { o.disallowEmpty = true }) +} diff --git a/list_darwin.go b/list_darwin.go new file mode 100644 index 0000000..2f77698 --- /dev/null +++ b/list_darwin.go @@ -0,0 +1,55 @@ +package zenity + +import ( + "bytes" + "os/exec" + "strings" + + "github.com/ncruces/zenity/internal/zenutil" +) + +func list(text string, items []string, opts options) (string, bool, error) { + var data zenutil.List + data.Items = items + data.Options.Prompt = &text + data.Options.Title = opts.title + data.Options.OK = opts.okLabel + data.Options.Cancel = opts.cancelLabel + data.Options.Default = opts.defaultItems + data.Options.Empty = !opts.disallowEmpty + + out, err := zenutil.Run(opts.ctx, "list", data) + if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 { + return "", false, nil + } + if err != nil { + return "", false, err + } + return string(bytes.TrimSuffix(out, []byte{'\n'})), true, nil +} + +func listMultiple(text string, items []string, opts options) ([]string, error) { + var data zenutil.List + data.Items = items + data.Options.Prompt = &text + data.Options.Title = opts.title + data.Options.OK = opts.okLabel + data.Options.Cancel = opts.cancelLabel + data.Options.Default = opts.defaultItems + data.Options.Empty = !opts.disallowEmpty + data.Options.Multiple = true + data.Separator = zenutil.Separator + + out, err := zenutil.Run(opts.ctx, "list", data) + if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 { + return nil, nil + } + if err != nil { + return nil, err + } + out = bytes.TrimSuffix(out, []byte{'\n'}) + if len(out) == 0 { + return nil, nil + } + return strings.Split(string(out), zenutil.Separator), nil +} diff --git a/list_test.go b/list_test.go new file mode 100644 index 0000000..4967020 --- /dev/null +++ b/list_test.go @@ -0,0 +1,66 @@ +package zenity_test + +import ( + "context" + "errors" + "os" + "testing" + "time" + + "github.com/ncruces/zenity" +) + +func ExampleList() { + zenity.List( + "Select items from the list below:", + []string{"apples", "oranges", "bananas", "strawberries"}, + zenity.Title("Select items from the list"), + zenity.DisallowEmpty(), + ) + // Output: +} + +func ExampleListItems() { + zenity.ListItems( + "Select items from the list below:", + "apples", "oranges", "bananas", "strawberries") + // Output: +} + +func ExampleListMultiple() { + zenity.ListMultiple( + "Select items from the list below:", + []string{"apples", "oranges", "bananas", "strawberries"}, + zenity.Title("Select items from the list"), + zenity.DefaultItems("apples", "bananas"), + ) + // Output: +} + +func ExampleListMultipleItems() { + zenity.ListMultipleItems( + "Select items from the list below:", + "apples", "oranges", "bananas", "strawberries") + // Output: +} + +func TestListTimeout(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second/10) + + _, _, err := zenity.List("", nil, zenity.Context(ctx)) + if !os.IsTimeout(err) { + t.Error("did not timeout:", err) + } + + cancel() +} + +func TestListCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, _, err := zenity.List("", nil, zenity.Context(ctx)) + if !errors.Is(err, context.Canceled) { + t.Error("was not canceled:", err) + } +} diff --git a/list_unix.go b/list_unix.go new file mode 100644 index 0000000..3172cec --- /dev/null +++ b/list_unix.go @@ -0,0 +1,11 @@ +// +build !windows,!darwin + +package zenity + +func list(text string, items []string, opts options) (string, bool, error) { + return "", false, nil +} + +func listMultiple(text string, items []string, opts options) ([]string, error) { + return nil, nil +} diff --git a/list_windows.go b/list_windows.go new file mode 100644 index 0000000..b5a6ac7 --- /dev/null +++ b/list_windows.go @@ -0,0 +1,9 @@ +package zenity + +func list(text string, items []string, opts options) (string, bool, error) { + return "", false, nil +} + +func listMultiple(text string, items []string, opts options) ([]string, error) { + return nil, nil +} diff --git a/zenity.go b/zenity.go index 0162829..872c57a 100644 --- a/zenity.go +++ b/zenity.go @@ -41,6 +41,10 @@ type options struct { ellipsize bool defaultCancel bool + // List options + disallowEmpty bool + defaultItems []string + // File selection options directory bool confirmOverwrite bool