# TODO — porting gaps that block 100% C-faithful ports
This file tracks port gaps where a function in `src/ported/` cannot
yet be faithfully ported because it depends on a C primitive that
hasn't been ported. Each entry blocks at least one downstream file
from being marked as 100% line-by-line in `docs/PORT_CHECKLIST.md`.
When an item is fixed: port the dependency, fix every blocked
function, then strike through (or delete) the entry here.
---
## Lexer-context machinery (`Src/lex.c`)
C functions that drive standalone lexer walks over a buffer —
`zcontext_save()`, `zcontext_restore()`, `ctxtlex()`, `inpush()`,
`strinbeg()`, `strinend()` — are not yet ported with their
side-effect-driven token-stream API. zshrs lowers the lexer through
fusevm bytecode and does not currently expose a "tokenise this
string" entry point.
**Blocks:**
- `zle/textobjects.rs::selectargument` — the `select-in-shell-word`
argument-N selector at `Src/Zle/textobjects.c:212`. Body uses
`ctxtlex()` over an inpush'd line buffer to find argument
boundaries respecting quoting and expansion. Current Rust port
is a whitespace-split approximation that handles only the simple
no-quote case.
---
## `virangeflag` file-global (`Src/Zle/zle_vi.c:36`)
Cross-compilation-unit int set during vi-operator-pending
evaluation. Used by `selectword` (textobjects.c:196) and several
zle_move / zle_word fns to skip the trim-cursor adjustment when
the widget is being invoked as part of a vi range op.
**Blocks:**
- `zle/textobjects.rs::selectword` — the cursor-adjustment arm at
`c:196-203` reads `virangeflag` to decide whether to set
`region_active = 1` (emacs-mode default) or `DECCS()` (vi-cmd
mode). Current Rust port treats it as constant-false.
**Fix path:** PORT_PLAN Phase 3 bucket-2 wave (Arc<RwLock<i32>>
or AtomicI32 file-static; needs paramdef wiring since it's also
exposed via the param table).
---
## Rust-only-types-still-present in src/ported/
These files have one or more `pub struct` / `pub enum` whose name
doesn't appear in the matching C source. They violate strict-rule 1
("zero Rust-only structs/enums") and need rewrite before being
ticked DONE in PORT_CHECKLIST.md:
- `modules/zselect.rs` — DONE. `SelectMode`/`ZselectOptions`/
`SelectResult` already deleted; `bin_zselect` rewritten as a
single fn doing inline flag parsing + select() call, mirroring
`Src/Modules/zselect.c:65-200`. Zero Rust-only types. Tests:
6/6 (test-threads=1; pattern/zselect share file-static globals
like the C source).
- `modules/langinfo.rs` — DONE. Earlier `LangInfoItem` already
deleted; liitem already returns raw `Option<libc::nl_item>`.
Tests pass: 5/5.
- `modules/ksh93.rs` — DONE. Earlier `Ksh93Params`/`NamerefOptions`
already deleted; matchgetfn signature updated to take
`&ShellExecutor` so the param-table reads happen through
`exec.arrays`/`exec.variables` rather than `std::env::var` (zsh
shell arrays aren't env vars).
- `modules/stat.rs` — DONE (commit `4ae9cff069`).
- `modules/mapfile.rs` — DONE (commit `a2d70f4776`).
- `modules/hlgroup.rs` — PARTIAL (commit `a4d3925a6a`); blocked
on prompt.c match_highlight + zattrescape ports — see above.
- `modules/zprof.rs` — DONE (commit `92be6c235d`); Profiler bag-of-
globals dissolved into module-level CALLS/NCALLS/ARCS/NARCS/
STACK/ZPROF_MODULE statics matching C file-statics.
---
## prompt.c match_highlight + zattrescape — Rust-only signatures
`Src/prompt.c:2031 match_highlight()` and `Src/prompt.c:257
zattrescape()` are present in `src/ported/prompt.rs` but with
Rust-only signatures and Rust-only `TextAttrs` shape:
- `match_highlight(spec: &str) -> (TextAttrs, TextAttrs)` —
C signature is `int match_highlight(const char *teststr,
zattr *on_var, zattr *setmask, int *layer)` returning the
number of bytes consumed and writing to out-args.
- `zattrescape(attrs: &TextAttrs) -> String` — C signature is
`char *zattrescape(zattr atr, int *len)` returning a newly-
allocated `\033[...m` escape stream (the `len` out-arg holds
the byte length). Current Rust port returns `%`-prefixed
prompt syntax, not ANSI escapes.
Both are downstream of an unported `zattr` integer type and the
`Color`/`TextAttrs` Rust-only abstractions in prompt.rs.
**Blocks:**
- `modules/hlgroup.rs::convertattr` — c:46-47 calls
`match_highlight(attrstr, &atr, NULL, NULL)` then
`zattrescape(atr, sgr ? NULL : &len)`. Rust port currently
inlines the highlight + colour parsing because the prompt.rs
ports don't speak the C protocol. Move to a real chain when
the prompt.rs ports are made C-faithful.
- `modules/hlgroup.rs::getgroup` and `::scangroup` — both
read the `$.zle.hlgroups` (`GROUPVAR`) hashtable through
`getvalue()` + the magic-assoc dispatch. Pending the real
Param/HashTable port, both bodies are the C "no entry"
branch (None / empty Vec).
**Fix path:** rewrite prompt.rs's `match_highlight` and
`zattrescape` from `Src/prompt.c:257`/`:2031` line-by-line.
Replace `TextAttrs` with the C `zattr` integer bitmask. Then
hlgroup.rs::convertattr collapses to the 3-call chain in c:42-77.
---
## Param/locallevel/HashNode machinery (`Src/params.c` + `Src/zsh.h`)
zsh's parameter table is a custom linked-hashtable with per-entry
`gsu_*` callback dispatch and `level`/`old` chaining for function-
local scoping. zshrs's `ShellExecutor` stores params in plain
`HashMap`s, missing the entire scope/level/dispatch substrate.
**Blocks:**
- `modules/param_private.rs` — bin_private's `makeprivate`
promotion + rejection logic (c:140-178), all 12 GSU callbacks
(`pps_*`/`ppi_*`/`ppf_*`/`ppa_*`/`pph_*`),
`is_private`/`scopeprivate`/`wrap_private`/`getprivatenode`/
`printprivatenode` — every fn that needs `Param.level` or the
`gsu_closure` chaining is a no-op stub.
**Fix path:** port `struct param`, `struct gsu_scalar`/`gsu_integer`/
`gsu_float`/`gsu_array`/`gsu_hash` from zsh.h, port the dispatch in
`getvalue`/`assignsparam`/`setiparam` to consult the per-param GSU,
add `level`/`old` chaining and the `locallevel++`/`locallevel--`
hooks at function entry/exit. Substantial; touches params.rs +
exec.rs + every magic-assoc consumer.
---
## Module-loader signatures (need `&mut ShellExecutor`)
Every C module's `setup_()` / `features_()` / `enables_()` / `boot_()`
/ `cleanup_()` / `finish_()` takes a `Module m` arg and reads/writes
shell-wide state (param table, fdtable, function-wrapper list,
emulation mode). zshrs's free-fn signatures `pub fn boot_() -> i32`
have no access to that state.
Files where this gap means boot_/cleanup_/etc. is a partial port:
- `modules/newuser.rs::boot_` — needs `EMULATION(EMULATE_ZSH)` check
+ `source(buf)` for the newuser-install-script probe (newuser.c:67-103).
- `modules/ksh93.rs::cleanup_` — needs `deletewrapper(m, wrapper)` +
paramtab walk to clear `PM_NAMEREF` flags (ksh93.c:265-281).
- `modules/example.rs::boot_` — needs `addwrapper(m, wrapper)`
(example.c:222-228).
- (More to come as the audit progresses.)
**Fix path:** introduce a `&mut ShellExecutor` parameter on the module-
loader signatures, threading it through the dispatcher. This is
project-wide and should land in one commit.
---
## Note: this file is the only place to mention port gaps
If a function in `src/ported/` is not 100% line-by-line, it
must NOT be ticked in `docs/PORT_CHECKLIST.md`. Add the gap
here, link from the file's checklist entry, and keep moving.
┌─────────────────────────────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────────┐
│ Item │ File │ Notes │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ get_unambig_pos ainfo-based output is single-position; C's posl is │ complete.rs c:1447 │ Needs cline_str posl tracking (was │
│ multi-position │ │ reverted) │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ instmatch Cmatch threading │ compresult.rs c:578 │ Signature divergence from C │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ do_single ipre/pre/ppre/brace re-insertion │ compresult.rs c:963 │ Signature divergence │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ hasbrpsfx test-insert + comparison │ compresult.rs c:685 │ Currently just brace-char check │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ valid_match menu-cursor walker │ compresult.rs c:1210 │ Currently prefix/suffix-only │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ build_pos_string colon-joined position list │ compresult.rs c:489 │ Currently "n/total" menu-status │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ copyprevshellword shell-tokenizer │ zle_misc.rs c:1108 │ Needs ctxtlex replay │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ set_comp_sep lex replay body │ compcore.rs │ ~400 lines needing ctxtlex │
│ │ c:1490-1893 │ │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ sep_comp_string lex replay body │ compctl.rs │ Same lex replay │
│ │ c:2840-2873 │ │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ getcurcmd lex over zlemetaline │ zle_tricky.rs c:2934 │ Same │
├─────────────────────────────────────────────────────────────────────┼──────────────────────┼────────────────────────────────────────┤
│ doexpandhist real history-! expansion │ zle_tricky.rs c:2854 │ Needs hist_expand exposed as a fn │
└─────────────────────────────────────────────────────────────────────┴──────────────────────┴───────────────────────────────────────