Skip to content

Commit 2afeede

Browse files
committed
docs: add display engine API contract (inputs/outputs)
Document the exact interface between Emacs C core and the display engine: what data flows in (buffer text, text properties, overlays, faces, window state, buffer-local/global vars) and what must flow back (window_end, cursor position, matrix, layout query functions, callbacks during layout). This is the contract the Rust replacement must satisfy.
1 parent 5324256 commit 2afeede

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

‎docs/rust-display-engine.md‎

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,193 @@ Additionally, **fontification runs DURING layout**. `jit-lock` calls `fontificat
6767
**Category E — Hooks that expect synchronous state:**
6868
`pre-redisplay-function` (runs INSIDE `redisplay_internal()`), `fontification-functions` (runs DURING iterator scanning), `window-scroll-functions`, `post-command-hook`
6969

70+
## Display Engine API Contract
71+
72+
The display engine sits between the Emacs C core and the rendering backend. Understanding exactly what flows in each direction is critical — the Rust replacement must satisfy this contract.
73+
74+
```
75+
EMACS C CORE PROVIDES DISPLAY ENGINE PROVIDES BACK
76+
═══════════════════ ═══════════════════════════
77+
78+
Buffer text (gap buffer) ──────┐ ┌──── w->window_end_pos/vpos/valid
79+
Text properties (face, │ │ w->cursor (hpos, vpos, x, y)
80+
display, invisible, │ │ w->phys_cursor (+ width/height)
81+
fontified, composition) ─────┤ │ w->mode_line_height
82+
Overlays (face, before/after │ │ /header_line_height
83+
string, invisible, │ ┌────┴──┐ /tab_line_height
84+
priority, window) ───────────┼───►│DISPLAY│───►w->current_matrix (glyph rows)
85+
Face cache (colors, fonts, │ │ENGINE │ windows_or_buffers_changed
86+
decorations) ────────────────┤ └────┬──┘ f->cursor_type_changed
87+
Window state (start, hscroll, │ │
88+
pointm, dimensions) ─────────┤ ├──── pos-visible-in-window-p()
89+
Buffer-local vars (truncate, │ │ window-end()
90+
word-wrap, tab-width, │ │ vertical-motion()
91+
bidi, line-spacing) ─────────┤ │ move-to-column()
92+
Global vars (scroll-margin, │ │ compute-motion()
93+
scroll-step, display- │ │ posn-at-point/x-y()
94+
line-numbers) ───────────────┘ │ current-column()
95+
│ line-pixel-height()
96+
fontification-functions ◄────────────────┘ format-mode-line()
97+
(callback DURING layout) (Lisp query functions)
98+
```
99+
100+
### Inputs: What Emacs C Core Provides to the Display Engine
101+
102+
#### Buffer Data
103+
104+
| Input | How accessed | Purpose |
105+
|-------|-------------|---------|
106+
| Buffer text | `BYTE_POS_ADDR`, `FETCH_MULTIBYTE_CHAR` | Raw characters to display |
107+
| Narrowing bounds | `BEGV`, `ZV` | Visible region of buffer |
108+
| Point position | `PT` | Where cursor goes |
109+
| Multibyte flag | `BVAR(buf, enable_multibyte_characters)` | Character encoding mode |
110+
111+
#### Text Properties (at each position)
112+
113+
| Property | Purpose |
114+
|----------|---------|
115+
| `face` | Text styling (colors, bold, italic, underline, etc.) |
116+
| `display` | Override rendering (images, strings, spaces, margins, fringes) |
117+
| `invisible` | Hide text or show ellipsis |
118+
| `fontified` | Triggers lazy fontification via `fontification-functions` |
119+
| `composition` | Ligatures, combining characters, emoji sequences |
120+
| `mouse-face` | Hover highlighting |
121+
| `line-height` | Override line height |
122+
| `raise` | Vertical offset |
123+
124+
#### Overlays (at each position)
125+
126+
| Property | Purpose |
127+
|----------|---------|
128+
| `face` | Overlay face (merged with text property face, priority-ordered) |
129+
| `display` | Display overrides |
130+
| `before-string` / `after-string` | Inserted strings with their own properties |
131+
| `invisible` | Hide overlay region |
132+
| `priority` | Stacking order for face merging and string ordering |
133+
| `window` | Per-window overlay filtering |
134+
135+
#### Face Data
136+
137+
| Input | Purpose |
138+
|-------|---------|
139+
| `face_at_buffer_position()` | Merged face at position (text prop + all overlays) |
140+
| `FACE_FROM_ID(f, id)` | Resolve face ID to colors, font, decorations |
141+
| `face_for_char()` | Font fallback for specific character (fontset system) |
142+
| `merge_faces()` | Combine control-char face with base face |
143+
144+
#### Window State
145+
146+
| Field | Purpose |
147+
|-------|---------|
148+
| `w->start` | First visible buffer position (marker) |
149+
| `w->pointm` | Window's copy of point (for non-selected windows) |
150+
| `w->hscroll`, `w->min_hscroll` | Horizontal scroll offset |
151+
| `WINDOW_PIXEL_WIDTH/HEIGHT(w)` | Window dimensions in pixels |
152+
| `w->contents` | Which buffer is displayed |
153+
| Display table | `window_display_table(w)` — character display overrides |
154+
155+
#### Buffer-Local Variables
156+
157+
| Variable | Purpose |
158+
|----------|---------|
159+
| `truncate-lines` | Truncate long lines vs wrap |
160+
| `word-wrap` | Word wrapping mode |
161+
| `tab-width` | Tab character width in columns |
162+
| `line-spacing` / `extra-line-spacing` | Extra vertical space between lines |
163+
| `selective-display` | Hide lines by indentation level |
164+
| `ctl-arrow` | Display control chars as `^X` vs `\NNN` |
165+
| `bidi-display-reordering` | Enable bidirectional text reordering |
166+
| `bidi-paragraph-direction` | Force paragraph direction (left-to-right / right-to-left) |
167+
168+
#### Global Variables
169+
170+
| Variable | Purpose |
171+
|----------|---------|
172+
| `scroll-margin` | Lines to keep visible around point |
173+
| `scroll-conservatively` | How aggressively to scroll |
174+
| `scroll-step` | Lines to scroll at a time |
175+
| `hscroll-margin` / `hscroll-step` | Horizontal scroll parameters |
176+
| `truncate-partial-width-windows` | Truncate in narrow windows |
177+
| `display-line-numbers` | Line number display mode (absolute/relative/visual) |
178+
| `display-line-numbers-width` | Width of line number column |
179+
| `nobreak-char-display` | Display non-breaking spaces/hyphens |
180+
| `auto-composition-mode` | Enable automatic character composition |
181+
| `maximum-scroll-margin` | Cap on scroll margin |
182+
183+
### Outputs: What the Display Engine Must Provide Back
184+
185+
**This is the contract the Rust replacement must satisfy.**
186+
187+
#### Window Fields (set during redisplay, read by Emacs)
188+
189+
| Field | Type | Who reads it | Purpose |
190+
|-------|------|-------------|---------|
191+
| `w->window_end_pos` | `ptrdiff_t` | `window-end` (window.c) | Buffer position of last visible char (as `Z - end_charpos`) |
192+
| `w->window_end_vpos` | `int` | window.c | Matrix row number of last visible char |
193+
| `w->window_end_valid` | `bool` | `window-end`, `window-line-height` | Guard: are end fields valid? Many functions check this first. |
194+
| `w->cursor` | `{x, y, hpos, vpos}` | Cursor drawing, movement commands | Intended cursor position in matrix coordinates |
195+
| `w->phys_cursor` | `{x, y, hpos, vpos}` | neomacsterm.c, window.c | Actual cursor pixel position (window-relative) |
196+
| `w->phys_cursor_type` | `enum` | window.c | Current cursor style: box(0), bar(1), hbar(2), hollow(3) |
197+
| `w->phys_cursor_width` | `int` | neomacsterm.c, window.c | Cursor width in pixels |
198+
| `w->phys_cursor_height` | `int` | neomacsterm.c, window.c | Cursor height in pixels |
199+
| `w->phys_cursor_ascent` | `int` | window.c | Cursor ascent in pixels |
200+
| `w->phys_cursor_on_p` | `bool` | window.c, dispnew.c | Is cursor currently being displayed? |
201+
| `w->mode_line_height` | `int` | `window-mode-line-height`, layout | Mode-line pixel height (-1 if unknown) |
202+
| `w->header_line_height` | `int` | `window-header-line-height` | Header-line pixel height (-1 if unknown) |
203+
| `w->tab_line_height` | `int` | `window-tab-line-height` | Tab-line pixel height (-1 if unknown) |
204+
| `w->last_cursor_vpos` | `int` | xdisp.c | Previous frame's cursor vpos (detects cursor movement) |
205+
206+
#### Frame Fields
207+
208+
| Field | Purpose |
209+
|-------|---------|
210+
| `f->cursor_type_changed` | Signals cursor needs redraw (set true on change, cleared after draw) |
211+
| `f->garbaged` | Cleared after full redraw |
212+
| `f->updated_p` | Frame was updated this redisplay cycle |
213+
214+
#### Global Variables
215+
216+
| Variable | Purpose |
217+
|----------|---------|
218+
| `windows_or_buffers_changed` | Dirty flag: 0 = all cached display data is fresh. Non-zero = needs redisplay. |
219+
| `update_mode_lines` | Mode-line dirty flag |
220+
| `redisplaying_p` | Re-entrancy guard: true while redisplay is running |
221+
222+
#### Current Matrix (`w->current_matrix`)
223+
224+
The glyph matrix is read by code OUTSIDE the display engine:
225+
226+
| Consumer | What it reads | Why |
227+
|----------|--------------|-----|
228+
| `window-line-height` (window.c) | `MATRIX_ROW()`, row height/ascent | Return line dimensions to Lisp |
229+
| `window-cursor-info` (window.c) | Row at `phys_cursor.vpos` → glyph | Get glyph under cursor for width/height |
230+
| `pos-visible-in-window-p` (window.c) | Calls `pos_visible_p()` which runs iterator | Check if position is visible |
231+
| neomacsterm.c | Entire matrix: all rows, all glyphs, all areas | Extract for GPU rendering |
232+
233+
#### Layout Query Functions (must be implemented by the display engine)
234+
235+
| Lisp Function | What it computes | How it works |
236+
|---------------|-----------------|--------------|
237+
| `pos-visible-in-window-p` | Is buffer position visible? | Runs display iterator from `w->start` |
238+
| `window-end` | Last visible buffer position | Reads `window_end_pos` or recomputes via iterator |
239+
| `vertical-motion` | Move N visual lines | Runs iterator with wrapping/truncation |
240+
| `move-to-column` | Move to column N | Scans line handling tabs, display props, wide chars |
241+
| `compute-motion` | Position after hypothetical motion | Full layout computation (7 parameters) |
242+
| `posn-at-point` | Pixel position of point | Calls `pos-visible-in-window-p` internally |
243+
| `posn-at-x-y` | Buffer position at pixel coords | Queries glyph matrix rows and glyphs |
244+
| `current-column` | Column number at point | Scans from line start |
245+
| `line-pixel-height` | Pixel height of current line | Runs iterator for one line |
246+
| `format-mode-line` | Rendered mode-line text | Evaluates Lisp format specs, returns text with properties |
247+
248+
#### Callbacks During Layout
249+
250+
| Callback | When | Direction |
251+
|----------|------|-----------|
252+
| `fontification-functions` | Iterator reaches unfontified text | Display engine → Lisp (pauses layout, calls Lisp, resumes) |
253+
| `pre-redisplay-function` | Start of `redisplay_internal()` | Display engine → Lisp |
254+
| `window-scroll-functions` | After window scroll | Display engine → Lisp |
255+
| `redisplay_interface` hooks | After layout, during drawing | Display engine → rendering backend |
256+
70257
## What We Keep vs Replace
71258

72259
### Keep (Emacs C/Lisp)

0 commit comments

Comments
 (0)