1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Picking a folder or a markdown file to open.
//!
//! Shared by the initial-launch picker in `main.rs` (shown when there is no
//! meaningful path to open — see `main::needs_folder_picker`) and the
//! "Open…" command / Cmd+O in `browser.rs`, so the two entry points cannot
//! answer "what can be picked" differently.
//!
//! macOS gets one dialog that returns either. rfd 0.17.2 already builds
//! exactly that panel for macOS — an `NSOpenPanel` with `canChooseFiles` and
//! `canChooseDirectories` both `true`, the same configuration Xcode's own
//! Open dialog uses — as `FileDialog::pick_file_or_folder`
//! (`rfd::backend::macos::file_dialog::panel_ffi::build_pick_file_or_folder`),
//! so this calls straight into it instead of re-deriving the same AppKit
//! calls by hand. That also sidesteps having to reason about main-thread
//! dispatch ourselves: rfd's synchronous pickers already funnel through
//! `dispatch2::run_on_main`, safe to call from any thread, which is why this
//! function makes no thread-affinity promise of its own — `main.rs` calls it
//! before the event loop exists, `browser.rs` from a spawned background
//! thread, and both already work today with the plain `pick_folder()` this
//! replaces.
//!
//! Windows and Linux have no native mixed picker, so there a small two-button
//! prompt asks which kind of dialog to open first, then rfd's separate
//! `pick_folder`/`pick_file` runs exactly as it always has.
//!
//! That prompt is plain `MessageButtons::YesNo`, not `OkCancelCustom` — rfd
//! only renders custom button *labels* through `TaskDialogIndirect`, gated
//! behind its `common-controls-v6` Cargo feature, and that function is
//! exported only by the side-by-side, manifest-activated v6 `comctl32.dll`.
//! A Rust binary embeds no such manifest by default, so the statically
//! linked import resolves against the older `comctl32.dll` every Windows
//! ships for compatibility — which does not export `TaskDialogIndirect` at
//! all. That is not a graceful degrade to plain buttons; it is
//! `STATUS_ENTRYPOINT_NOT_FOUND` at process launch, for every invocation of
//! the binary that reaches this code in its static call graph, including one
//! that never shows the dialog — confirmed the hard way, as two Windows CI
//! legs failing every `cli_integration.rs` subprocess test with that exact
//! NTSTATUS the first time this feature was enabled. `YesNo` has called
//! `MessageBoxW` unconditionally since Windows 1.0, so it has no comctl32
//! version to get wrong.
use PathBuf;
/// Ask the user to choose a markdown file or a folder to open.
///
/// `markdown_extensions` limits which files are enabled/selectable in the
/// dialog; pass `Config::default().markdown_extensions` where no repository
/// config has been loaded yet (the very first launch, before `Config::read`
/// has run).
/// Which kind of dialog to show next, on platforms with no mixed picker.
/// Asks whether to open a folder or a file. `None` means the user cancelled.
/// Pure match behind [`prompt_open_kind`], split out so it is testable
/// without a real dialog.