wmenu hacking notes =================== Goal ---- wmenu is a small Win32 dmenu-style picker. Keep it boring: one C file, native controls, stdin in, stdout out. Build ----- make Style ----- Follow https://suckless.org/coding_style/ where practical for Win32 C. Format the tracked C sources with: astyle --options=none --project=.astylerc wmenu.c test_stream.c arg.h AStyle covers indentation, braces, function layout, and most line breaks; review comments and long Win32 directives manually. Keep the existing C99 bool usage rather than changing program logic just to satisfy a style rule. Dependencies ------------ No external source dependencies. The byte-safe subsequence matcher lives in wmenu.c; ASCII matching retains fzy's smart-case behavior by default, and -i forces ASCII case-insensitive matching. Non-ASCII UTF-8 bytes compare literally. Program shape ------------- Argument parsing uses the suckless-style macros in arg.h. Keep flags short unless a Win32-ish name is already established, like -fn/-fs/-fw for fonts. Important sections in wmenu.c: - input/output conversion: widen(), text_encoded(), current_text() - stdin loading: read_stdin_thread() - row mapping/search: row_item(), refill_list(), search_step(), update_count() - selection/output: add_selection(), finish() - key modes: - handle_global_keys() - handle_emacs_keys() - handle_vi_keys() - Win32 plumbing: - InputProc() - ListProc() - WndProc() - main() Detachable modes ---------------- The key modes are intentionally isolated. To remove a mode: - Emacs keys: delete handle_emacs_keys() and its call from InputProc(). - Vi keys: delete handle_vi_keys() and the Ctrl+Alt+V toggle in handle_emacs_keys(). - Default Windows controls: pass -default-controls at runtime. Keep global keys tiny. Enter, Esc, Up/Down, and multi-select belong in handle_global_keys(). Editing policy belongs in a mode handler. Current key policy ------------------ Default mode is emacs-ish, but pragmatic: - Ctrl+A: first press moves to start; second press at start selects all - Ctrl+E: end - Ctrl+K: delete to end - Ctrl+Backspace: delete previous word - Ctrl+N / Ctrl+P: next/previous list item - Ctrl+D / Ctrl+U: page down/up list - Ctrl+W: cut selection - Ctrl+Alt+V: toggle vi mode - Esc: exit, except in vi insert mode where it returns to normal mode Use -default-controls for normal Windows edit behavior. Release checklist ----------------- 1. Run make, make test, and optionally make stress (about 155 MiB of synthetic rows; validates no-data ListBox navigation, full-file fiber search, and progress state). Run `powershell.exe -NoProfile -ExecutionPolicy Bypass -File test_pipe.ps1` to verify complete long rows appear before pipe EOF. Run `powershell.exe -NoProfile -ExecutionPolicy Bypass -File test_typing.ps1` to measure typing latency with 20,000 streamed rows and stdin still open. 2. Check: .\\wmenu.exe -help .\\wmenu.exe -version dir /b | .\\wmenu.exe 3. Check a slow pipe shows the bottom progress bar animating; search a loaded large file and check the same bar shows search progress. 4. Check Alt-Tab back focuses the input. 5. Check Esc exits from input and list focus; in vi insert mode it returns to normal mode instead. 6. Update the version string in wmenu.c. 7. Do not commit build outputs (*.obj, *.exe) unless intentionally packaging. HiDPI/layout ------------ Pixel constants in wmenu.c should go through `S(px)`, which scales from the system DPI. Do not add new raw layout numbers unless they are not pixels. Known simplifications --------------------- - Default input/output encoding is UTF-8. Use `-enc ansi` or `-enc oem` for legacy Windows pipes such as classic `cmd.exe` output. - The classic Win32 ListBox uses LBS_NODATA | LBS_OWNERDRAWFIXED. LB_SETCOUNT gives it the full row count; WM_DRAWITEM fetches only visible rows from items or the filtered row-index array. The ListBox handles scrolling/selection, but holds no copies of line text. Search runs in a Win32 fiber on the UI thread, yields after each SEARCH_BATCH, and resumes through WM_SEARCH_STEP. It never yields while holding items_lock. The reader remains a separate thread. It publishes item_count with InterlockedIncrement after inserting a row; UI count reads use loaded_count(), and item pointers are read under items_lock. Matching rows are indexed in source order (subsequence matching, not score-ranked). No silent row cap. LB_SETCOUNT is wrapped with redraw suppression: unchanged visible rows are not invalidated as the row count grows; only newly visible rows are, and those are painted immediately so queued stream messages cannot starve WM_PAINT. Restore the selection and top row after LB_SETCOUNT (Windows can otherwise jump to the end), and preserve pending selection paints when validating the viewport. The bottom bar shows search progress after stdin completes. A changed query resets the viewport to row zero; Ctrl+O/I history stores search text and source line IDs so it can restore locations across filters. Related edits coalesce, and the history is bounded. The existing loading timer picks up partial stdin batches when the pipe stays open. Search runs in 64-line slices while idle; queued keyboard/mouse input defers the next slice to a timer. - lnav inspiration: its logfile index tracks lines separately from the view (https://github.com/tstack/lnav/blob/master/src/logfile.hh, lf_index and read_line); its text view requests counts and rows from a text sub-source (https://github.com/tstack/lnav/blob/master/src/textview_curses.hh, text_line_count). We borrow the index/view separation, not lnav's log parsing or on-disk line buffers: piped input is still held in memory.