diff --git a/kittens/choose_files/main.go b/kittens/choose_files/main.go index 23b8f93e2..32ef31535 100644 --- a/kittens/choose_files/main.go +++ b/kittens/choose_files/main.go @@ -471,6 +471,7 @@ func (h *Handler) set_state_from_config(_ *Config, opts *Options) (err error) { h.state.filter_map = nil h.state.current_filter = "" if len(opts.FileFilter) > 0 { + opts.FileFilter = utils.Uniq(opts.FileFilter) has_all_files := false fmap := make(map[string][]Filter) seen := utils.NewSet[string](len(opts.FileFilter)) @@ -509,7 +510,7 @@ func (h *Handler) set_state_from_config(_ *Config, opts *Options) (err error) { var default_cwd string func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { - write_output := func(selections []string, interrupted bool) { + write_output := func(selections []string, interrupted bool, current_filter string) { payload := make(map[string]any) if err != nil { if opts.WriteOutputTo != "" { @@ -538,6 +539,9 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if opts.WriteOutputTo != "" { if opts.OutputFormat == "json" { payload["paths"] = selections + if current_filter != "" { + payload["current_filter"] = current_filter + } b, _ := json.MarshalIndent(payload, "", " ") m = string(b) } @@ -603,22 +607,22 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { } err = lp.Run() if err != nil { - write_output(nil, false) + write_output(nil, false, "") return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() - write_output(nil, true) + write_output(nil, true, "") return 1, nil } rc = lp.ExitCode() switch rc { case 0: - write_output(handler.state.selections, false) + write_output(handler.state.selections, false, handler.state.current_filter) default: - write_output(nil, true) + write_output(nil, true, "") } return } diff --git a/kittens/desktop_ui/portal.go b/kittens/desktop_ui/portal.go index 3c353fcff..a59563a98 100644 --- a/kittens/desktop_ui/portal.go +++ b/kittens/desktop_ui/portal.go @@ -630,12 +630,51 @@ func (self *Portal) ReadAll(namespaces []string) (ReadAllType, *dbus.Error) { } type vmap map[string]dbus.Variant +type Filter_expression struct { + Ftype uint32 + Val string +} +type Filter struct { + Name string + Expressions []Filter_expression +} + +func (f Filter) Equal(o Filter) bool { + return f.Name == o.Name && slices.Equal(f.Expressions, o.Expressions) +} + type ChooseFilesData struct { Title string Mode string Cwd string SuggestedSaveFileName, SuggestedSaveFilePath string Handle dbus.ObjectPath + Filters []Filter +} + +func (c *ChooseFilesData) set_filters(options vmap) { + if v, found := options["filters"]; found { + v.Store(&c.Filters) + } + if v, found := options["current_filter"]; found { + var x Filter + if err := v.Store(&x); err == nil { + idx := slices.IndexFunc(c.Filters, func(q Filter) bool { return x.Equal(q) }) + if idx > -1 { + c.Filters = slices.Delete(c.Filters, idx, idx+1) + } + c.Filters = slices.Insert(c.Filters, 0, x) + } + } +} + +func get_matching_filter(name string, all_filters []Filter) (dbus.Variant, bool) { + for _, x := range all_filters { + if x.Name == name { + return dbus.MakeVariant(x), true + } + } + return dbus.Variant{}, false } func var_to_bool_or_false(v dbus.Variant) bool { @@ -666,9 +705,10 @@ func (self *Portal) Cleanup() { } type ChooserResponse struct { - Paths []string `json:"paths"` - Error string `json:"error"` - Interrupted bool `json:"interrupted"` + Paths []string `json:"paths"` + Error string `json:"error"` + Interrupted bool `json:"interrupted"` + Current_filter string `json:"current_filter"` } func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, result_dict vmap) { @@ -753,6 +793,11 @@ func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, resu if cfd.Title != "" { args = append(args, "--title", cfd.Title) } + for _, fs := range cfd.Filters { + for _, exp := range fs.Expressions { + args = append(args, "--file-filter", fmt.Sprintf("%s:%s:%s", utils.IfElse(exp.Ftype == 0, "glob", "mime"), exp.Val, fs.Name)) + } + } args = append(args, "--write-pid-to", pid_path) args = append(args, utils.IfElse(cfd.Cwd == "", "~", cfd.Cwd)) cmd := exec.Command(utils.KittyExe(), args...) @@ -801,6 +846,11 @@ func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, resu return prefix + u.EscapedPath() }, result.Paths) result_dict = vmap{"uris": dbus.MakeVariant(uris)} + if result.Current_filter != "" { + if v, found := get_matching_filter(result.Current_filter, cfd.Filters); found { + result_dict["current_filter"] = v + } + } return } @@ -817,6 +867,7 @@ func (options vmap) get_bytearray(name string) string { func (self *Portal) OpenFile(handle dbus.ObjectPath, app_id string, parent_window string, title string, options vmap) (uint32, vmap, *dbus.Error) { cfd := ChooseFilesData{Title: title, Cwd: options.get_bytearray("current_folder"), Handle: handle} + cfd.set_filters(options) dir_only := false if v, found := options["directory"]; found && var_to_bool_or_false(v) { dir_only = true @@ -840,6 +891,7 @@ func (self *Portal) SaveFile(handle dbus.ObjectPath, app_id string, parent_windo SuggestedSaveFileName: options.get_bytearray("current_name"), SuggestedSaveFilePath: options.get_bytearray("current_file")} multiple := false + cfd.set_filters(options) if v, found := options["multiple"]; found && var_to_bool_or_false(v) { multiple = true } diff --git a/tools/utils/misc.go b/tools/utils/misc.go index 5e3268ae7..281a5b5bf 100644 --- a/tools/utils/misc.go +++ b/tools/utils/misc.go @@ -158,6 +158,18 @@ func StableSort[T any](s []T, cmp func(a, b T) int) []T { return s } +func Uniq[T comparable](s []T) []T { + seen := NewSet[T](len(s)) + ans := make([]T, 0, len(s)) + for _, x := range s { + if !seen.Has(x) { + seen.Add(x) + ans = append(ans, x) + } + } + return ans +} + func sort_with_key[T any, C constraints.Ordered](stable bool, s []T, key func(a T) C) []T { type t struct { key C