: nply/wmenu

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 
132 
133 
134 
135 
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.