[WIP] Live Testing and Error Fixes: FreeBSD 15#3
Conversation
- Add vms.termcap.c (fetched from upstream tcsh-org/tcsh); compiles to an empty translation unit on BSD/Linux, but is required by Makefile.in - Add dch-template.in (proper Debian changelog template, adapted from upstream tcsh with mcsh branding) - configure.ac: remove erroneous '##0' strip from PACKAGE_PATCHLEVEL derivation; for version x.y.0 this produced an empty macro, causing compile errors in tc.vers.c - configure.ac: fix TCSH_BASELINE_VERSION AC_DEFINE_UNQUOTED to expand the literal version string rather than the macro name - Commit generated autoconf outputs (configure, aclocal.m4, m4/, acaux/, config.h.in, nls/catgen) so the repo is buildable without requiring autoconf/automake to be installed - Add alacritty.toml (sets mcsh as the terminal shell for testing) 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
The version string format included '[tcsh baseline N.N.N]'; the square brackets are glob metacharacters. set1() in sh.set.c always globs the value vector unless VAR_NOGLOB is set, so on startup fix_version() was calling setcopy(STRversion, ..., VAR_READWRITE) with a string containing '[...]', tglob() flagged it as a pattern, globall() found no matching files, and the shell immediately died with 'No match'. Upstream tcsh's version string never contains glob metacharacters, so this regression was introduced when mcsh added the '[tcsh baseline ...]' annotation. Replace the bracketed annotation with the brace-free form 'tcsh-baseline:N.N.N', consistent with the colon-separated option flags that follow it. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
- sh.sem.c: restore upstream Dfix() call for all commands including doif/dowhile; mcsh was skipping dollar substitution for expression builtins, breaking variable comparisons like `if ($x > 3)`. Also move bifunc assignment to correct location. - sh.exp.c: restore expr() to upstream's simple `return exp0(vp, 0)`; mcsh had replaced it with a complex copy-and-echo version causing incorrect token advancement. Remove double-expansion path in exp6() that caused Dfix1/globone to run on already-processed tokens. - sh.func.c: fix search() TC_IF nesting level to only increment for `if...then` constructs, not bare `if cmd` forms, matching upstream behaviour. - sh.set.c: guard getn() against empty string (ignored expression arms return STRNULL for short-circuit evaluation), returning 0 instead of ERR_BADNUM. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Add ghost-text autosuggestion sourced from command history: - ed.h: add GhostBuf[INBUFSIZE] global for the predicted suffix - ed.chared.c: add predict_from_history() which walks Histlist forward looking for an entry whose prefix matches the current InputBuf; populates GhostBuf with the untyped suffix. Add e_predict_accept() command which, when the cursor is at end-of-line and GhostBuf is non-empty, splices the ghost into InputBuf and clears it; otherwise falls through to e_charfwd. - ed.defns.c: register e_predict_accept as F_PREDICT_ACCEPT (120), bump F_NUM_FNS to 121, add FuncNames entry predict-accept. - ed.decls.h: declare e_predict_accept and predict_from_history. - ed.inputl.c: call predict_from_history() after F_INSERT / F_PREDICT_ACCEPT returns CC_NORM; clear GhostBuf for all other commands. - ed.refresh.c: after cursor is repositioned at end of Refresh(), write ghost chars with ANSI dim, reset, then move cursor back so the visible cursor stays at the insertion point. - ed.screen.c: bind right-arrow to F_PREDICT_ACCEPT by default; falls through to charfwd when no ghost is active so existing behaviour is preserved. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Two bugs fixed: - Delayed display: e_insert used RefPlusOne() (fast one-char path) which bypassed Refresh() entirely, so ghost text appeared one keystroke late. Fix: call predict_from_history() in e_insert() before RefPlusOne/Refresh, and extract DrawGhost() as a static helper called by both Refresh() and RefPlusOne() so ghost renders on the fast path too. - Ghost persisted through backspace/movement: GhostBuf was only cleared inside CC_NORM after Refresh() had already fired, so the old ghost was briefly visible. Fix: clear GhostBuf immediately after LastCmd is set (before the switch/Refresh) for any command that is not F_INSERT or F_PREDICT_ACCEPT. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 33 minutes and 20 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (16)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a predictive autocomplete feature based on command history and updates various build and configuration files. Feedback highlights several critical issues: the shell's expansion logic was altered in a way that breaks built-ins and lazy evaluation, and the simplification of expression handling removed necessary echoing for verbose mode. Additionally, the new history prediction logic may cause input latency due to full history scans, the ghost text rendering fails to account for wide characters, and a hardcoded path in the Alacritty configuration limits portability.
There was a problem hiding this comment.
Actionable comments posted: 22
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
ed.chared.c (1)
1471-1488:⚠️ Potential issue | 🟡 MinorPrediction updates miss digit self-inserts.
Digits are dispatched through
F_DIGIT/e_digit(), note_insert(), so numeric prefixes do not recompute predictions and are cleared by the input loop. Move the prediction update into a shared insert path or mirror it in the non-argument branch ofe_digit().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ed.chared.c` around lines 1471 - 1488, The prediction update is missing for digit self-inserts because digits go through F_DIGIT/e_digit() rather than e_insert(), so ensure predict_from_history() runs for numeric inserts: either move the predict_from_history() call into the common insert path used by c_insert()/e_insert() (so all insertors call it), or add a matching predict_from_history() call in the non-argument branch of e_digit() (the branch that performs the single-char insert and updates Cursor), and ensure numeric prefixes are preserved/cleared consistently after prediction is updated.sh.exp.c (1)
580-590:⚠️ Potential issue | 🔴 CriticalKeep
$/quote expansion lazy insideexp6().This fallback now sends operands directly to
globone(). Expression parsing relies onTEXP_IGNOREfor short-circuited operands, so$/quoted operands must be expanded here only when the operand is actually evaluated; otherwise they either expand too early viaDfix(t)or fail to expand for directexpr()callers.🐛 Proposed fix: restore operand-local expansion
- Char *cp; + Char *cp, *dp, *ep; @@ cp = *(*vp)++; + if ((ignore & TEXP_NOGLOB) == 0) { + for (dp = cp; *dp; dp++) { + if (cmap(*dp, _DOL | QUOTES)) { + if (ignore & TEXP_IGNORE) + return (Strsave(STRNULL)); + cp = Dfix1(cp); + cleanup_push(cp, xfree); + ep = globone(cp, G_ERROR); + cleanup_until(cp); + return (ep); + } + } + } `#ifdef` convex🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sh.exp.c` around lines 580 - 590, The fallback in exp6 currently sends raw operands directly to globone, causing `$`/quote expansion to happen too early or not at all; restore operand-local (deferred) expansion by not calling globone here. In exp6 (using symbols cp, vp, FILETESTS/FILEVALS, filetest, globone, TEXP_NOGLOB), keep the existing filetest branch, but for the normal fallback return an unexpanded operand (e.g., Strsave(cp) or the prior operand-local representation) instead of calling globone; move any Dfix()/globone expansion into the actual expr evaluation path (where expr() checks TEXP_IGNORE/TEXP_NOGLOB) so `$` and quoted values are expanded only when the operand is truly evaluated.configure.ac (1)
64-65:⚠️ Potential issue | 🟡 MinorNormalize
PACKAGE_PATCHLEVELbefore emitting it as a C integer.
PATCHLEVELis substituted unquoted inpatchlevel.h.in; values like08or09become invalid C integer literals. Restore normalization or reject leading-zero patch components beforeAC_SUBST.Suggested fix
PACKAGE_PATCHLEVEL="${PACKAGE_VERSION##*.}" +PACKAGE_PATCHLEVEL="$(printf '%s\n' "$PACKAGE_PATCHLEVEL" | sed 's/^0*//; s/^$/0/')" AC_SUBST(PACKAGE_PATCHLEVEL)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@configure.ac` around lines 64 - 65, PACKAGE_PATCHLEVEL is emitted without normalization which allows values like "08" or "09" to become invalid C integer literals in patchlevel.h.in; update the configure.ac logic around PACKAGE_PATCHLEVEL (the code that computes PACKAGE_PATCHLEVEL and calls AC_SUBST(PACKAGE_PATCHLEVEL)) to either normalize the value by stripping any leading zeros from the extracted patch component or validate and abort with a clear error if a leading-zero patch component is detected, then AC_SUBST the normalized/validated value so patchlevel.h.in always receives a valid C integer literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@acaux/install-sh`:
- Around line 162-168: The case patterns that sanitize names only match
single-character names (e.g., the clause "case $dst_arg in -* | [=\(\)!])") so
names beginning with those characters like "=foo" or "(tmp" are not caught;
update each such case (the one setting dst_arg and the two other similar case
blocks) to use the "starts with" form by appending a glob star to the
character-class alternative (replace "[-* | [=\(\)!])" style branches with "-* |
[=\(\)!]*" so names that start with -, =, (, ), or ! are prefixed with ./).
In `@alacritty.toml`:
- Line 5: The program path is hardcoded to a user-specific absolute path
("program = \"/home/orpheus497/Projects/mcsh/mcsh\""), which breaks portability;
change the Alacritty config to invoke the installed binary by name so the shell
resolves it via PATH (e.g., set the program value to "mcsh" or use an
environment-aware wrapper), ensuring the launcher uses the mcsh executable from
PATH rather than a fixed home directory.
- Line 2: The import line in alacritty.toml that references
"~/.cache/wal/colors-alacritty.toml" is ephemeral (pywal-generated) and causes
nondeterministic styling; comment out or remove that import and instead add
instructions to use a local override file (e.g., a developer-maintained optional
override) so environment-specific colors live in an untracked local file; update
the configuration to import only the local override and document that developers
may create the optional override rather than depending on the pywal path.
In `@configure.ac`:
- Around line 27-29: The AC_DEFINE_UNQUOTED call for TCSH_BASELINE_VERSION
currently hardcodes "6.24.13" causing duplication with
TCSH_VERSION/TCSH_BASELINE_VERS; update the
AC_DEFINE_UNQUOTED([TCSH_BASELINE_VERSION], ...) invocation to use the existing
variable (expand TCSH_BASELINE_VERS or TCSH_VERSION) instead of the literal
string so the baseline value is single-sourced and config.h/patchlevel.h stay in
sync (locate the AC_DEFINE_UNQUOTED call and replace the hardcoded value with
the appropriate variable expansion).
In `@dch-template.in`:
- Line 4: Replace the placeholder line "* Please look in the Fixes file for an
actual changelog" in dch-template.in with a concise, self-contained changelog
entry (summary of changes, impact, and version/release info) so debian/changelog
contains meaningful release notes; you may optionally keep a short “see Fixes
for details” note but must ensure the main entry (the modified template entry)
includes the essential release notes directly.
- Line 1: The template hardcodes the distribution as "unstable" in
dch-template.in; change that to a safe placeholder or token so release tooling
sets it explicitly — e.g., replace the literal "unstable" with "UNRELEASED" or a
substitution variable like `@DISTRIBUTION`@ in the dch template line
(`@PACKAGE_TARNAME`@ (`@PACKAGE_VERSION`@) <distribution>;) and ensure your release
pipeline substitutes `@DISTRIBUTION`@ (or your chosen token) with the intended
suite during packaging.
In `@ed.chared.c`:
- Around line 3900-3905: The code appends characters from GhostBuf to the input
buffer (using GhostBuf, LastChar, Cursor, InputLim) but never restores the NUL
terminator, violating the string invariant; after the copy loop in the block
that checks GhostBuf[0] and Cursor == LastChar, ensure you write the terminator
back into the buffer (set *LastChar = '\0') and update Cursor/LastChar as
appropriate, taking care to only write the NUL if LastChar < InputLim (or
otherwise ensure you stay within buffer bounds) so the input string is properly
terminated and no stale data can leak.
- Around line 3881-3891: The loop over Histlist entries skips records whose
hp->histline is still NULL and copies a trailing newline into GhostBuf; fix by
ensuring each hp's histline is materialized before use (populate hp->histline if
NULL) rather than unconditionally continuing, and change the copy loop that
builds GhostBuf from hl to stop at end-of-string or at the first newline
character (e.g., '\n' and/or '\r') and respect INBUFSIZE bounds so no trailing
newline is copied and ghost text is built consistently (references: Histlist,
hp->histline, hl, InputBuf, inputlen, GhostBuf, INBUFSIZE, predict-accept).
In `@ed.defns.c`:
- Around line 1766-1770: The new command entry for predict-accept currently
reuses the NLS catalog id CSAVS(3, 122) (same as newline-and-hold); change the
CSAVS call in the f->desc assignment for the predict-accept entry to a unique,
unused catalog id (e.g. CSAVS(3, <new_id>)) so localized builds don't show the
wrong help text—update the f->desc for the entry where f->name ==
"predict-accept" and f->func == F_PREDICT_ACCEPT to reference that new id and
ensure the corresponding message is added to the NLS source.
In `@ed.inputl.c`:
- Around line 191-193: GhostBuf is being cleared for non-insert/predict-accept
commands but no redraw is forced, so previously rendered ghost text can remain
visible; update the code that clears GhostBuf (the block checking cmdnum vs
F_INSERT and F_PREDICT_ACCEPT) to also trigger a command-line repaint when the
command handling will return CC_NORM, CC_ARGHACK, or CC_ERROR so the cleared
GhostBuf is actually removed from the screen—i.e., after setting GhostBuf[0] =
'\0', call the existing repaint/redraw routine used for the input line (or set
the flag that causes a redraw) so the display is refreshed whenever those return
codes are expected.
In `@ed.refresh.c`:
- Around line 333-368: DrawGhost currently emits raw terminal sequences and
writes suggestion text via putpure from GhostBuf, so Display/Vdisplay are
unaware of the overlay and later refreshes leave stale tails and break on
non-ANSI, wrapping or wide characters; change DrawGhost to update the virtual
display model instead of direct terminal writes (or at minimum erase the
previous ghost overlay via the same virtual-display API before drawing a new
one): locate DrawGhost, GhostBuf, putpure and the refresh code paths
(Display/Vdisplay/Refresh/RefPlusOne) and either (1) push the ghost text into
the same buffer/structure Display/Vdisplay uses so subsequent Refresh() will
render and clear it correctly (handling character widths and wrapping), or (2)
add tracking of previous ghost length/positions and call the virtual-display
erase/move APIs to remove the previous overlay before emitting the new ghost,
removing raw ESC[...D usage and ensuring wide-char/terminal-capability-correct
positioning.
In `@m4/lib-prefix.m4`:
- Around line 151-154: The comment block in AC_LIB_PREPARE_MULTILIB contains a
typo: the third line starts with "dn;" instead of the M4 comment prefix "dnl",
so the text gets emitted into configure; correct that token to "dnl" (i.e.,
replace the "dn;" with "dnl") so the entire comment is properly discarded by M4
and not leaked into the generated shell script.
In `@m4/po.m4`:
- Around line 426-434: The failure handler in the generated rule (inside the
heredoc appended to "$ac_file.tmp") deletes "$(srcdir)/$frobbedlang.msg"
although the target is "$frobbedlang/$(DOMAIN).resources.dll"; update the
error-cleanup to remove the partial DLL (e.g. rm -f
"$(srcdir)/$frobbedlang/$(DOMAIN).resources.dll" or rm -f
"$frobbedlang/$(DOMAIN).resources.dll" with proper quoting) instead of the .msg
file so the rule cleans up the actual failed target (refer to the heredoc block
that writes the rule using frobbedlang, $(DOMAIN), $(MSGFMT), and $(srcdir)).
- Around line 33-35: Update the exported GETTEXT_MACRO_VERSION value in m4/po.m4
from 0.22 to 0.23 so it matches the file header; locate the
AC_SUBST([GETTEXT_MACRO_VERSION], [0.22]) line and change the literal to 0.23 to
ensure consistency.
In `@sh.func.c`:
- Around line 349-353: The boolean coercion is lost by assigning expr(&v) (which
returns tcsh_number_t) into an int variable i, risking truncation; change
handling so truthiness is preserved—either declare i with type tcsh_number_t (or
a compatible wider type) and then set i = noexec ? 1 : expr(&v), or keep i as
int but assign i = noexec ? 1 : (expr(&v) != 0); update the code around
variables i and the call expr(&v) (in doif()/sh.func.c) accordingly so branch
decisions rely only on nonzero-vs-zero rather than raw truncated values.
In `@sh.sem.c`:
- Around line 83-84: Restore the previous gating so expression-style builtins
are not pre-expanded by Dfix(t) before short-circuit operators run: stop calling
the full argument expansion for builtins that are expression evaluators when
handling doif, dotest, dolet, doexit, and dowhile so that exp6() can evaluate
operands lazily and exp0()/exp1() can apply TEXP_IGNORE to the RHS; in practice,
update Dfix (and the place that sets bifunc) to detect the expression-builtin
case (the same condition that was present before the change) and skip the eager
expansion for those builtins, leaving evaluation to exp6()/exp0()/exp1() as
before.
In `@sh.set.c`:
- Around line 604-605: Make getn strict: remove the unconditional "if (*cp ==
'\0') return 0;" behavior in getn so empty input is treated as a parse-failure
(return a failure indicator or propagate an error state) rather than silently
yielding 0; then update the callers that intentionally allow ignored-expression
arms (e.g., the set handling for "history" and "killring" and the getstatus()
expression-evaluation path) to detect an empty/ignored arm and apply the
fallback-to-0 only there. Ensure you reference getn for the change and update
the set history=/set killring= handlers and the
getstatus()/expression-evaluation code path to implement the 0 default where
appropriate.
In `@vms.termcap.c`:
- Around line 153-156: The scan for the first ':' in functions like tgetflag and
tgetstr currently advances past the end of capab when the entry is malformed;
change the loop that searches for ':' (e.g., the while (*++cp != ':') loop) to
stop on either ':' or '\0' and, if '\0' is reached, return the appropriate error
value (tgetflag should return -1, tgetstr should return NULL). Apply the same
guarded check to the other similar loops mentioned (the scans at the other two
locations) so they won't read past the string terminator.
- Around line 108-123: The code currently uses sscanf(...,"%[^|]",tmp) and
strncmp(name,tmp,len) which allows prefix matches (e.g., "vt" matching "vt100");
change both sscanf calls to use a scanset that stops at '|' or ':' (e.g.,
"%[^|:]") and replace the strncmp check with an exact string comparison (e.g.,
strcmp(name, tmp) == 0) so lengths must match before returning success; update
the two matching sites that reference tmp/ptr/name/len (the
sscanf(ptr,...)/sscanf(bp,...) blocks) to use the new scanset and strcmp,
leaving the fclose(fp) and return(1) behavior intact.
- Around line 95-98: The loop that locates continuations uses cp =
&bp[strlen(bp) - 2] and then calls fgets(cp, 1024, fp), which can overflow the
1024-byte bp buffer; change the logic to (1) verify strlen(bp) >= 2 before
computing cp, (2) compute remaining = sizeof bp - strlen(bp) - 1 (or otherwise
track the actual buffer capacity) and pass that remaining size to fgets, (3)
check fgets() return for NULL and handle truncated lines by either failing with
an error or safely terminating the buffer, and (4) preserve the original
continuation-detection behavior (checking the two trailing backslashes) while
preventing writes past the end of bp.
- Around line 287-315: The tgoto implementation writes into static char ret[24]
without bounds checks and emits only two digits for %d; fix by replacing direct
writes through rp with bounded appends that respect ret's capacity (use
snprintf/strncat/strlcat or an internal safe_append helper) and when handling %d
or '+' format the full decimal representation of numval (using snprintf into a
temp buffer then append) so multi-digit coordinates like 100 are emitted
correctly; update handling of cases '%' '+' 'i' and 'd' to use the safe append
function and ensure rp/remaining length are tracked when consuming cm,
referencing symbols ret, rp, incr, argno, numval, cm, destline, destcol, and the
tgoto routine.
- Around line 230-264: The backslash-escape handling in the switch on *++cp
incorrectly parses octals and skips writing unknown escapes; update the '\\'
case logic (the switch that examines *++cp using cp, area, i) so octal parsing
only consumes at most three octal digits (0-7) and builds i accordingly, and
ensure the default/unknown-escape path writes the current character into **area
and advances area (instead of only advancing cp), preserving existing handling
for recognized escapes like 'E','n','r','t','b','f','^','\\'.
---
Outside diff comments:
In `@configure.ac`:
- Around line 64-65: PACKAGE_PATCHLEVEL is emitted without normalization which
allows values like "08" or "09" to become invalid C integer literals in
patchlevel.h.in; update the configure.ac logic around PACKAGE_PATCHLEVEL (the
code that computes PACKAGE_PATCHLEVEL and calls AC_SUBST(PACKAGE_PATCHLEVEL)) to
either normalize the value by stripping any leading zeros from the extracted
patch component or validate and abort with a clear error if a leading-zero patch
component is detected, then AC_SUBST the normalized/validated value so
patchlevel.h.in always receives a valid C integer literal.
In `@ed.chared.c`:
- Around line 1471-1488: The prediction update is missing for digit self-inserts
because digits go through F_DIGIT/e_digit() rather than e_insert(), so ensure
predict_from_history() runs for numeric inserts: either move the
predict_from_history() call into the common insert path used by
c_insert()/e_insert() (so all insertors call it), or add a matching
predict_from_history() call in the non-argument branch of e_digit() (the branch
that performs the single-char insert and updates Cursor), and ensure numeric
prefixes are preserved/cleared consistently after prediction is updated.
In `@sh.exp.c`:
- Around line 580-590: The fallback in exp6 currently sends raw operands
directly to globone, causing `$`/quote expansion to happen too early or not at
all; restore operand-local (deferred) expansion by not calling globone here. In
exp6 (using symbols cp, vp, FILETESTS/FILEVALS, filetest, globone, TEXP_NOGLOB),
keep the existing filetest branch, but for the normal fallback return an
unexpanded operand (e.g., Strsave(cp) or the prior operand-local representation)
instead of calling globone; move any Dfix()/globone expansion into the actual
expr evaluation path (where expr() checks TEXP_IGNORE/TEXP_NOGLOB) so `$` and
quoted values are expanded only when the operand is truly evaluated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8c957cc0-f819-44bd-8e84-dfe372e2f959
📒 Files selected for processing (33)
acaux/config.rpathacaux/install-shaclocal.m4alacritty.tomlconfig.h.inconfigureconfigure.acdch-template.ined.chared.ced.decls.hed.defns.ced.hed.inputl.ced.refresh.ced.screen.cm4/build-to-host.m4m4/gettext.m4m4/host-cpu-c-abi.m4m4/iconv.m4m4/intlmacosx.m4m4/lib-ld.m4m4/lib-link.m4m4/lib-prefix.m4m4/nls.m4m4/po.m4m4/progtest.m4nls/catgensh.exp.csh.func.csh.sem.csh.set.ctc.vers.cvms.termcap.c
Add two new prompt escape sequences to tc.prompt.c:
%g - current git branch name (or 7-char detached HEAD hash).
Outputs nothing outside a git worktree.
%G - same as %g, but appends |STATE when a git operation is in
progress (MERGING, REBASING-i, REBASING, AM, CHERRY-PICKING,
REVERTING, BISECTING).
Detection walks up from $cwd reading .git/HEAD directly - no
subprocesses are spawned. Results are cached per CWD pointer using
the same pattern as %~, so there are no extra filesystem hits per
keystroke. Bare repos and interactive rebase (reads head-name) are
also handled.
Update dot.mcshrc to set rprompt='%S%G%s' as a default example,
showing the branch in standout on the right side of the prompt.
💘 Generated with Crush
Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
acaux/install-sh
- Fix case patterns to use glob (*) so names like "=foo" or "(tmp"
are prefixed with ./ (three sites: -t dst_arg, loop dst_arg, src)
alacritty.toml
- Replace hardcoded /home/orpheus497/... path with bare "mcsh" so
the shell resolves it via PATH
- Comment out pywal import; document optional local override path
configure.ac
- TCSH_BASELINE_VERSION: use TCSH_VERSION M4 macro instead of
duplicated literal "6.24.13"
- PACKAGE_PATCHLEVEL: normalize via printf %d to strip leading zeros
that would produce invalid C integer literals (e.g. "08")
dch-template.in
- Change distribution from "unstable" to UNRELEASED
- Replace placeholder changelog line with substantive release notes
ed.chared.c
- e_predict_accept: write NUL terminator after copy loop so input
buffer invariant is preserved
- predict_from_history: stop ghost copy at \n/\r so no trailing
newline leaks into GhostBuf
- e_digit: call predict_from_history() for numeric inserts so ghost
text updates when digits are typed
ed.defns.c
- predict-accept: change CSAVS catalog id from 122 (collision with
newline-and-hold) to 124 (unused)
ed.inputl.c
- Only call Refresh() when GhostBuf is actually non-empty before
clearing, so stale ghost text is erased from screen
ed.refresh.c (DrawGhost)
- Track prev_ghost_cols statically; erase previous overlay with
spaces+backspaces before drawing new ghost text, fixing stale tail
chars on backspace/navigation
- Use backspace-based cursor return instead of raw ESC[nD sequence
m4/lib-prefix.m4
- Fix "dn;" typo to "dnl" so comment is not leaked into configure
m4/po.m4
- Fix C# DLL error-cleanup to remove the actual failed target
(frobbedlang/DOMAIN.resources.dll) not the unrelated .msg file
- Bump GETTEXT_MACRO_VERSION from 0.22 to 0.23 to match file header
vms.termcap.c
- tgetnum/tgetflag/tgetstr: guard initial ':' scan against OOB read
on malformed entries (while *cp && *cp != ':' instead of *++cp)
- tsetup: change sscanf format to %[^|:] and strcmp for exact match,
preventing "vt" from matching "vt100"
- tsetup: fix continuation-line buffer overflow by computing
remaining capacity and passing it to fgets
- tgetstr octal: parse at most 3 octal digits (no unbounded loop);
fix default/unknown-escape path to write character to **area
- tgoto: enlarge ret[] to 64 bytes; add bounds checks on all *rp++
writes; use snprintf for %d so multi-digit coordinates work
💘 Generated with Crush
Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Add `set color` to dot.mcshrc so ls-F completion output and filename coloring are active by default. Without this variable set, tw.color.c never activates color_context_lsmF regardless of LS_COLORS/LSCOLORS. Also verified all paste_1.txt review items against current code: - sh.func.c doif expr/int: correct (upstream pattern, intentional) - sh.sem.c Dfix: correct (restored in 1d93e19, intentional) - sh.set.c getn empty: already returns 0 with comment - sh.exp.c exp6 globone: TEXP_NOGLOB guard already present 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Merge user's .tcshrc configuration into dot.mcshrc, adopting the same section-based architecture and comment style. Key changes: - Full Wayland/GPU env vars, path/editor/pager setup - set color (filetype coloring in tab completion) - set symlinks=chase, histdup=erase, history=10000 - Full keybinding set (arrow history search, Ctrl+Arrow, Home/End) - Complete programmable completion block (cc/make/man/service/ifconfig/tar) - Full alias set (ls -F -G, ll, df, du, .., pd/po/d, objdump, cclean) - Prompt uses native %g/%G git escapes instead of starship - rprompt='%S%G%s' for right-side git branch display - histfile=~/.mcsh_history (separate from tcsh history) 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Code fixes: - configure.ac: TCSH_BASELINE_VERSION now expands TCSH_VERSION M4 macro instead of hardcoding "TCSH_VERSION" as a string literal - configure.ac: PACKAGE_PATCHLEVEL normalisation uses sed to strip leading zeros, preventing invalid C integer literals (e.g. 08, 09) in patchlevel.h - sh.func.c: doif() local variable widened from int to tcsh_number_t to preserve full expression result before branch decision - vms.termcap.c: octal escape cases '4'-'7' added to backslash-escape switch; continuation digit validation now enforces <= '7' Documentation: - README.md: full rewrite adding new features table (predictive autocomplete, %g/%G git escapes, set color), complete bug-fix table covering all PR3 items, prompt reference, dot.mcshrc section, updated build instructions - ISSUES.md: Phase 8 section documenting all PR3 review items with per-item status (fixed vs. already-correct with rationale) - PLAN.md: changelog entry for 2026-04-21 PR3 review pass All other PR3 findings were confirmed already addressed in the previous session (2026-04-20): install-sh glob patterns, m4 typos, po.m4 DLL cleanup, ed.defns.c catalog ID, ed.inputl.c redraw, ed.refresh.c ghost tracking, ed.chared.c NUL terminator and newline strip, alacritty.toml portability, sh.sem.c Dfix gating, sh.exp.c TEXP_NOGLOB guard, sh.set.c getn empty-string fast-path (intentional for ignored arms). 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Response to PR3 Review — Gemini + CodeRabbitThank you both for the thorough review. All items have been addressed in commit Fixed in this pass (commit 0d5f32b)
Already addressed before this review (confirmed in codebase, no change needed)
Gemini summary responseGemini flagged four areas:
All documentation (README, ISSUES, PLAN) has been updated to reflect the full fix inventory. The binary has been rebuilt and redeployed. |
- dot.mcshrc: add `set time = (5 "…")` so commands taking > 5 s report user/sys/real/cpu times, replacing the timing starship provided - [n] in the prompt is %? — last exit status (0 = ok, non-zero = error) 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Without -l, alacritty spawns mcsh as a plain command (not a login shell), so ~/.mcshrc is not sourced and no prompt/color/time settings apply. Also points to the installed /usr/local/bin/mcsh instead of the dev tree. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
There was a problem hiding this comment.
Actionable comments posted: 12
♻️ Duplicate comments (4)
vms.termcap.c (2)
98-107:⚠️ Potential issue | 🔴 CriticalFix the continuation bound calculation;
sizeof(bp)is pointer size.Line 104 computes capacity from the
char *parameter, not the 1024-byte caller buffer. For normal termcap lines this underflowsremainingand can pass a huge/invalid size tofgets(), reintroducing the overflow previously flagged.Proposed fix
+#define TC_BUFSIZE 1024 + - while (fgets(bp, 1024, fp) != NULL) { + while (fgets(bp, TC_BUFSIZE, fp) != NULL) { @@ - size_t remaining = sizeof(bp) - bplen - 1; - if (remaining == 0) break; - if (fgets(cp, (int)remaining + 1, fp) == NULL) break; + size_t used = (size_t)(cp - bp); + size_t remaining; + if (used >= TC_BUFSIZE) break; + remaining = TC_BUFSIZE - used; + if (remaining <= 1) break; + if (fgets(cp, (int)remaining, fp) == NULL) break;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vms.termcap.c` around lines 98 - 107, The continuation logic uses sizeof(bp) (pointer size) to compute remaining capacity, causing underflow and an oversized fgets length; change the calculation to use the actual caller buffer length (e.g., the fixed 1024 byte termcap buffer or a passed-in buffer-size parameter) when computing remaining instead of sizeof(bp), and then call fgets with that correctly computed remaining+1; update the variables in the loop (bp, bplen, cp, remaining) to reference the real buffer size constant or parameter so fgets cannot be passed an invalid huge value.
279-282:⚠️ Potential issue | 🔴 CriticalFix the invalid backslash character literal on line 280.
The
case '\' :statement is not valid C syntax (incomplete escape sequence). Change it tocase '\\' :to properly escape the backslash, matching the pattern already used on line 243.Proposed fix
case '^' : - case '\' : + case '\\' : **area = *cp;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vms.termcap.c` around lines 279 - 282, The switch contains an invalid character literal `case '\' :` which is an incomplete escape sequence; replace that label with the properly escaped backslash literal `case '\\' :` so the switch (the cases including `case '^' :` and the assignment `**area = *cp;`) compiles correctly.ed.chared.c (1)
3882-3892:⚠️ Potential issue | 🟠 MajorMaterialize history lines before skipping them.
This still skips entries where
hp->histlinehas not been built yet, so predictive autocomplete misses valid history commands. Materialize withsprlex(&hp->Hlex)before comparing. This is the unresolved part of the prior history-text review.Proposed fix
for (hp = Histlist.Hnext; hp != NULL; hp = hp->Hnext) { - hl = hp->histline; - if (hl == NULL) - continue; + if (hp->histline == NULL) + hp->histline = sprlex(&hp->Hlex); + hl = hp->histline; + if (hl == NULL) + continue; if (Strncmp(InputBuf, hl, inputlen) == 0 &&🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ed.chared.c` around lines 3882 - 3892, The loop over history entries skips entries whose hp->histline hasn't been materialized; before reading hp->histline in the loop that compares Strncmp(InputBuf, hl, inputlen) (inside the block iterating hp = Histlist.Hnext), call sprlex(&hp->Hlex) to ensure the history text is built, then re-read hp->histline into hl and proceed only if hl is non-NULL; keep the rest of the copy into GhostBuf (respecting INBUFSIZE) unchanged and ensure you still continue when histline remains NULL after materialization.ed.refresh.c (1)
345-363:⚠️ Potential issue | 🟠 MajorDo not erase the previous ghost from the current cursor.
When
Cursor != LastChar, this erasesprev_ghost_colsspaces starting at the new cursor location afterRefresh()has already redrawn the real line. Moving left while a ghost is visible can therefore blank actual input text after the cursor. Track the previous overlay position or render/clear the ghost through the virtual-display pipeline instead. This is the same unresolved class of issue previously raised forDrawGhost()overlay handling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ed.refresh.c` around lines 345 - 363, The current logic in ed.refresh.c that erases the previous ghost overlay whenever GhostBuf is empty or Cursor != LastChar blindly writes spaces at the current cursor, which can overwrite real input after Refresh() has redrawn the line; instead, stop erasing using the current Cursor position and either (a) track the previous ghost start column (store/maintain prev_ghost_start along with prev_ghost_cols) and move the cursor back to that recorded start before clearing the prev_ghost_cols spaces, or (b) remove/clear the ghost via the virtual-display pipeline used by Refresh()/DrawGhost() so clearing is applied against the virtual buffer rather than the current terminal cursor; update the code paths that touch prev_ghost_cols (the conditional block guarded by GhostBuf[0], Cursor, LastChar and the subsequent erase block) to use the tracked previous position or virtual-display clear routine instead of erasing at Cursor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@dot.mcshrc`:
- Around line 80-82: The current bindkey entries use "^[[5~" and "^[[6~"
(PageUp/PageDown) instead of Home/End; change the bindings so Home maps to the
shell "beginning-of-line" and End to "end-of-line" using the correct escape
sequences used by common terminals: add mappings for "\e[1~" and "\e[4~" as well
as the alternate forms "\eOH" and "\eOF" (and optionally "\e[H" and "\e[F") to
cover xterm/vt100 variants, replacing the incorrect "^[[5~" and "^[[6~" bindkey
lines and ensuring the bindkey targets are the existing commands
beginning-of-line and end-of-line.
- Around line 9-23: The rc currently forces machine-specific Wayland/GPU env
vars (e.g., CLUTTER_BACKEND, GDK_BACKEND, QT_QPA_PLATFORM, SDL_VIDEODRIVER,
XDG_CURRENT_DESKTOP, XDG_SESSION_TYPE, WLR_DRM_DEVICES, WLR_NO_HARDWARE_CURSORS,
LIBVA_DRIVER_NAME, __NV_PRIME_RENDER_OFFLOAD, __GLX_VENDOR_LIBRARY_NAME,
__VK_LAYER_NV_optimus) for all users; instead gate these settings behind
explicit environment/host checks or move them to a separate local override
example file (e.g., dot.mcshrc.local) and source it if present; implement simple
detection (session type, presence of /dev/dri, hostname or an env var like
USE_WAYLAND) before exporting the Wayland/NVIDIA/Intel-specific variables so
default copies remain portable across X11, macOS, remote sessions, and
non-hybrid GPUs.
In `@ed.chared.c`:
- Around line 3901-3909: The code currently copies as much of GhostBuf into the
input as will fit, clearing GhostBuf and calling Refresh() even if the
suggestion is truncated; instead, before modifying the buffer check whether the
entire suggestion fits: compute the length of GhostBuf and compare it to the
available space (InputLim - LastChar), and if GhostBuf does not fully fit, do
not modify the input buffer, do not advance Cursor/LastChar, leave GhostBuf
intact (and still call Refresh() or other UI update if appropriate) so the
suggestion is rejected wholesale; update the block that references GhostBuf,
Cursor, LastChar, InputLim and Refresh() to perform this full-fit check and only
copy/clear when the whole GhostBuf can be appended.
In `@ed.refresh.c`:
- Around line 365-370: The call to tgetstr("so", NULL) in the refresh code is
unsafe because some termcap implementations (e.g., vms.termcap.c) dereference
the area pointer; replace the NULL area argument with a real local buffer
pointer (e.g., char capbuf[...]; char *area = capbuf;) or otherwise gate ANSI
SGR detection through a safe API; locate the usage around GhostBuf/tgetstr and
the subsequent putpure calls and change the tgetstr invocation to pass &area (or
a pointer to a local buffer) and use the returned pointer to decide whether to
emit the SGR sequence.
In `@ISSUES.md`:
- Around line 60-63: The ISSUES.md entry claiming the stale ghost rendering is
fixed is inconsistent because DrawGhost() still bypasses Display/Vdisplay;
update the wording for DrawGhost (referencing the DrawGhost function, static
prev_ghost_cols, and the backspace/space-based erase-and-restore approach) to
indicate this is a partial mitigation rather than a full fix, or move that note
into the open-items section; ensure the text explicitly states that while
prev_ghost_cols and backspace-based cursor returns reduce staleness, the root
issue remains because Display/Vdisplay are not used.
In `@PLAN.md`:
- Line 257: The PLAN entry is contradictory about vms.termcap.c—decide whether
vms.termcap.c is retained as a POSIX/Android termcap shim or removed entirely
and update the text accordingly: if retained, change the sentence to state
"vms.termcap.c retained and repurposed as a POSIX/Android termcap shim (non-VMS
support)"; if removed, change the changelog/Phase 2 policy wording to remove
references to modifications and mark vms.termcap.c as deleted; ensure the same
decision is reflected in the Phase 2 policy/changelog and the line mentioning
"VMS dropped entirely" so both places (the PLAN entry and Phase 2 wording)
consistently reference vms.termcap.c's final status.
In `@README.md`:
- Around line 46-48: Update the README to remove `^F` from the documented accept
key (leave only Right-Arrow) or add a default keybinding that maps the existing
`^F` key to the predictive-accept action: either modify the README line in the
Fish-style predictive autocomplete section to drop `^F`, or add a keymap entry
that binds the control-F key (currently F_CHARFWD) to the
`F_PREDICT_ACCEPT`/`predict-accept` handler so `^F` actually invokes predictive
accept (ensure you update the same keymap where `F_CHARFWD` is defined to avoid
conflict).
In `@tc.prompt.c`:
- Around line 738-743: The current cache only refreshes when gcwd pointer
changes (git_oldcwd/gcwd), leaving %g/%G stale across git operations; update the
condition around git_get_info to also detect repository-state changes by
tracking HEAD and operation-marker mtimes (or simply bypass the cache until a
state-aware cache exists). Add static variables like git_head_mtime and
git_op_mtime, stat the repo's HEAD (and relevant op files such as
.git/REBASE_HEAD, MERGE_HEAD, or the operation dir) for the current gcwd via
short2str(gcwd), and if any mtime differs from stored
git_head_mtime/git_op_mtime then set git_oldcwd = gcwd and call
git_get_info(gcwd, git_branch, sizeof(git_branch), git_op, sizeof(git_op)) and
update the mtimes; alternatively, to be simplest, remove the git_oldcwd gate and
always call git_get_info when computing the prompt (function: git_get_info,
variables: git_oldcwd, gcwd, git_valid, git_branch, git_op).
- Around line 205-208: The copy into gitdir can leave it unterminated and later
snprintf calls use gitdir as both source and destination which is undefined;
when truncating set n = sizeof(gitdir) - 1 and explicitly write gitdir[n] = '\0'
(copy only n bytes, not n+1), and avoid overlapping snprintf by composing into a
temporary buffer (e.g., tmp sized like gitdir) and then copy or move the result
back into gitdir; refer to the variables/locations gitdir, dir and the snprintf
call that uses gitdir as both source and destination.
- Around line 747-753: The code is appending raw bytes from git_branch and
git_op via Strbuf_append1 which breaks multibyte/wide builds; replace the manual
loops that call Strbuf_append1 for git_branch and the git_op sequence (including
the '|' prefix when cp == 'G' and git_op[0]) with calls to tprintf_append_mbs so
the strings are rendered through the multibyte path (use
tprintf_append_mbs(&buf, attributes, git_branch) for the branch and
tprintf_append_mbs(&buf, attributes, "|"); followed by tprintf_append_mbs(&buf,
attributes, git_op) or a single call that preserves the '|' then git_op),
keeping the surrounding conditionals (cp == 'G' and git_op[0]) and using the
same attributes value.
- Around line 211-218: The lookup currently assumes gitdir is a directory and
only checks "%s/.git/HEAD"; update the logic around variables path, gitdir and
found so that when "%s/.git" is a regular file you open and read it, parse a
line like "gitdir: <path>", resolve that target to an absolute path (handling
relative paths relative to the directory containing the .git file), and then use
that resolved gitdir to probe "<resolved_gitdir>/HEAD" and the operation marker
files (the same places the code checks in the HEAD/operation marker blocks near
the current access(...) checks). Ensure you handle I/O errors and fall back to
the original behavior if parsing fails, and update uses of path and gitdir in
the HEAD/operation-marker checks (the checks around path, gitdir and found at
the HEAD probe and the later operation-marker probe) to use the resolved gitdir.
In `@vms.termcap.c`:
- Around line 118-132: The code uses unbounded scans ("%[^|:]") into tmp and
skips the final alias before ':'; change both sscanf calls that read into tmp to
use a bounded scanset (e.g. "%1023[^|:]") matching tmp[1024], and rewrite the
alias loop that uses ptr so it does not break when strchr(ptr,'|') == NULL —
instead parse successive aliases by scanning from ptr, comparing tmp to name,
then advance ptr to the next '|' if present or stop when the next delimiter is
':' (or end of string); update the initial sscanf(bp,...) and the inner
sscanf(ptr,...) and the loop termination logic around ptr to ensure the last
alias before ':' is checked.
---
Duplicate comments:
In `@ed.chared.c`:
- Around line 3882-3892: The loop over history entries skips entries whose
hp->histline hasn't been materialized; before reading hp->histline in the loop
that compares Strncmp(InputBuf, hl, inputlen) (inside the block iterating hp =
Histlist.Hnext), call sprlex(&hp->Hlex) to ensure the history text is built,
then re-read hp->histline into hl and proceed only if hl is non-NULL; keep the
rest of the copy into GhostBuf (respecting INBUFSIZE) unchanged and ensure you
still continue when histline remains NULL after materialization.
In `@ed.refresh.c`:
- Around line 345-363: The current logic in ed.refresh.c that erases the
previous ghost overlay whenever GhostBuf is empty or Cursor != LastChar blindly
writes spaces at the current cursor, which can overwrite real input after
Refresh() has redrawn the line; instead, stop erasing using the current Cursor
position and either (a) track the previous ghost start column (store/maintain
prev_ghost_start along with prev_ghost_cols) and move the cursor back to that
recorded start before clearing the prev_ghost_cols spaces, or (b) remove/clear
the ghost via the virtual-display pipeline used by Refresh()/DrawGhost() so
clearing is applied against the virtual buffer rather than the current terminal
cursor; update the code paths that touch prev_ghost_cols (the conditional block
guarded by GhostBuf[0], Cursor, LastChar and the subsequent erase block) to use
the tracked previous position or virtual-display clear routine instead of
erasing at Cursor.
In `@vms.termcap.c`:
- Around line 98-107: The continuation logic uses sizeof(bp) (pointer size) to
compute remaining capacity, causing underflow and an oversized fgets length;
change the calculation to use the actual caller buffer length (e.g., the fixed
1024 byte termcap buffer or a passed-in buffer-size parameter) when computing
remaining instead of sizeof(bp), and then call fgets with that correctly
computed remaining+1; update the variables in the loop (bp, bplen, cp,
remaining) to reference the real buffer size constant or parameter so fgets
cannot be passed an invalid huge value.
- Around line 279-282: The switch contains an invalid character literal `case
'\' :` which is an incomplete escape sequence; replace that label with the
properly escaped backslash literal `case '\\' :` so the switch (the cases
including `case '^' :` and the assignment `**area = *cp;`) compiles correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0969feb4-4531-4a35-9c1e-7474c0766550
📒 Files selected for processing (17)
ISSUES.mdPLAN.mdREADME.mdacaux/install-shalacritty.tomlconfigure.acdch-template.indot.mcshrced.chared.ced.defns.ced.inputl.ced.refresh.cm4/lib-prefix.m4m4/po.m4sh.func.ctc.prompt.cvms.termcap.c
The threshold in set time is CPU time (user+sys seconds), not wall clock. 5 CPU seconds almost never fires. Changed to 1 so any command burning >= 1 CPU second reports timing. Also note: the shell only times forked commands (external programs), not builtins. set time has no effect on cd, echo, etc. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
The prusage format loop only did raw xputchar, so \n printed literally. Added backslash-escape handling: \n=newline, \t=tab, \e=ESC, \=backslash. dot.mcshrc: format now uses \n for newline and \e[...m ANSI color codes — real (cyan), user (green), sys (yellow), cpu (magenta). 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
- dot.mcshrc: gate Wayland/GPU vars behind WAYLAND_DISPLAY+/dev/dri check - dot.mcshrc: fix Home/End keybindings (\e[1~, \e[4~, \eOH, \eOF, \e[H, \e[F) - dot.mcshrc: source ~/.mcshrc.local for machine-local overrides - ed.chared.c: reject ghost wholesale if it won't fit in input buffer - ed.chared.c: materialize histline via sprlex() before prefix comparison - ed.refresh.c: tgetstr uses real capbuf; track prev_ghost_start_h for correct erase - tc.prompt.c: gitdir NUL termination; worktree .git file parsing; bare repo snprintf fix - tc.prompt.c: mtime-based cache invalidation (stat HEAD, invalidate on change) - tc.prompt.c: replace raw Strbuf_append1 loops with tprintf_append_mbs for multibyte - vms.termcap.c: bounded sscanf %1023[^|:]; fix last-alias loop; fix sizeof(bp) -> 1024 - vms.termcap.c: fix case '\' -> case '\\' (invalid escape literal) - ISSUES.md: DrawGhost entry corrected to partial mitigation (Display/Vdisplay not used) - PLAN.md: vms.termcap.c retained as POSIX/Android termcap shim (not removed); consistent - README.md: remove ^F from autocomplete accept key (not bound by default) 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
CodeRabbit Review Round 2 — All findings addressed
|
The prev_ghost_start_h approach was wrong: it stored vcursor_h (the virtual-draw offset) not the physical terminal cursor column, so the backspace count to "return to ghost start" was far too large, sending the cursor to column 0. New logic: - When GhostBuf is cleared and Cursor==LastChar (accept path): Refresh already painted the new real content over the ghost area via update_line; no erase needed, just reset prev_ghost_cols. - When Cursor!=LastChar (move-away path): emit spaces then backspaces from the current cursor position to overwrite trailing ghost chars; Refresh will repaint correct content on the next redraw. - Normal redraw with ghost still active: erase from current position then redraw ghost (unchanged). Drops prev_ghost_start_h entirely. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (8)
alacritty.toml (1)
2-5:⚠️ Potential issue | 🟡 MinorAlign the checked-in config with the documented portable defaults.
The pywal cache import is still active, and the shell is still pinned to
/usr/local/bin/mcsh; both contradict the documented “local override + PATH” setup and can break non-default installs.🛠️ Suggested fix
[general] -import = ["~/.cache/wal/colors-alacritty.toml"] +# Optional local overrides, e.g. pywal colors, can be imported from: +# import = ["~/.config/alacritty/local.toml"] [terminal.shell] -program = "/usr/local/bin/mcsh" +program = "mcsh"Verify the config and docs stay aligned:
#!/bin/bash rg -n -C2 'colors-alacritty|program = "/usr/local/bin/mcsh"|program = "mcsh"|pywal import|resolved via `PATH`|via PATH' alacritty.toml README.md ISSUES.md🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@alacritty.toml` around lines 2 - 5, The checked-in config currently imports pywal cache via the line import = ["~/.cache/wal/colors-alacritty.toml"] and pins the shell program to an absolute path program = "/usr/local/bin/mcsh", which contradicts the documented “local override + PATH” setup; remove or disable the pywal import line and change the [terminal.shell] program entry to use the unqualified executable name (e.g., program = "mcsh") or remove the program key so the shell is resolved via PATH, thereby keeping the config portable and matching the docs.PLAN.md (1)
77-78:⚠️ Potential issue | 🟡 MinorClarify that VMS platform support is dropped, not every VMS-named file.
This now says
vms.termcap.cis retained, but the policy section still says “VMS is dropped entirely.” Tighten the policy wording so readers do not interpret the retained shim as a contradiction.📝 Suggested wording
-- VMS is **dropped** entirely. +- VMS platform support is **dropped**; the legacy-named `vms.termcap.c` + file is retained only as a non-VMS POSIX/Android termcap shim.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PLAN.md` around lines 77 - 78, Update the policy wording to clarify that platform support for VMS is removed while some VMS-legacy files (e.g., the retained vms.termcap.c shim) are kept for portability; specifically, change the phrase "VMS is dropped entirely" to something like "VMS platform support is discontinued; certain VMS‑named legacy source files (for example vms.termcap.c) are retained and repurposed as portable shims" so readers understand the difference between dropping VMS support and keeping legacy files.tc.prompt.c (2)
775-795:⚠️ Potential issue | 🟠 MajorInvalidate
%g/%Gusing the resolved gitdir state, not$cwd/.git/HEAD.The mtime probe only checks
short2str(gcwd)/.git/HEAD, so prompts in repo subdirectories, worktrees/submodules where.gitis a file, and bare repos never see HEAD changes. Operation-state changes likeMERGE_HEADalso leave%Gstale because HEAD mtime may not change.🐛 Correctness-first fix
- int need_refresh = (git_oldcwd != gcwd || git_valid < 0); - if (!need_refresh) { - char _hp[MAXPATHLEN]; - struct stat _st; - snprintf(_hp, sizeof(_hp), "%s/.git/HEAD", - short2str(gcwd)); - if (stat(_hp, &_st) == 0 && - _st.st_mtime != git_head_mtime) - need_refresh = 1; - } - if (need_refresh) { - char _hp[MAXPATHLEN]; - struct stat _st; - git_oldcwd = gcwd; - git_valid = git_get_info(short2str(gcwd), - git_branch, sizeof(git_branch), - git_op, sizeof(git_op)); - snprintf(_hp, sizeof(_hp), "%s/.git/HEAD", - short2str(gcwd)); - git_head_mtime = (stat(_hp, &_st) == 0) - ? _st.st_mtime : 0; - } + git_oldcwd = gcwd; + git_valid = git_get_info(short2str(gcwd), + git_branch, sizeof(git_branch), + git_op, sizeof(git_op));If the cache stays, have
git_get_info()return or fill the resolved gitdir plus HEAD/op marker mtimes, then invalidate against those paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tc.prompt.c` around lines 775 - 795, The current mtime invalidation uses short2str(gcwd)/.git/HEAD which misses worktrees/submodules/bare repos and op-state files; modify git_get_info (and its callers: the block using git_valid, git_oldcwd, git_branch, git_op) to return or populate the resolved gitdir path plus HEAD and operation-marker mtimes (e.g., HEAD mtime and MERGE_HEAD/REBASE_HEAD mtimes), then replace the direct stat() of short2str(gcwd)/.git/HEAD with comparisons against those resolved mtimes (update git_head_mtime and any op-state mtime variables from git_get_info) so need_refresh is computed from the resolved gitdir state rather than cwd/.git/HEAD.
270-271:⚠️ Potential issue | 🔴 CriticalRemove the self-copying
snprintf().Line 271 still passes
gitdiras both source and destination, which is undefined behavior and has no useful effect here. The bare-repo path is already ingitdir.🐛 Proposed fix
- /* Rewrite path to be used below without /.git prefix */ - snprintf(gitdir, sizeof(gitdir), "%s", gitdir); - /* Adjust: bare repo uses gitdir itself as git dir */ + /* Bare repo uses gitdir itself as git dir. */Verify the remaining self-copy pattern is gone:
#!/bin/bash rg -n -C2 'snprintf\(gitdir,\s*sizeof\(gitdir\),\s*"%s",\s*gitdir\)' tc.prompt.c🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tc.prompt.c` around lines 270 - 271, The snprintf call that writes gitdir into itself is undefined and unnecessary; remove the line containing snprintf(gitdir, sizeof(gitdir), "%s", gitdir) (referencing the gitdir buffer and its self-copy via snprintf) and ensure the surrounding code uses the existing gitdir value unchanged; also search for other occurrences of the same self-copy pattern to delete or replace them with a proper copy from a different source if needed.vms.termcap.c (2)
118-140:⚠️ Potential issue | 🟠 MajorStop alias parsing at the first capability delimiter and check
sscanf().If a malformed entry starts with a delimiter,
tmpcan be compared uninitialized. The loop also keeps searching for|after the first:, so a|inside a capability value can be treated as an alias.🐛 Proposed fix
- sscanf(bp,"%1023[^|:]",tmp); - if (strcmp(name, tmp) == 0) { + if (sscanf(bp, "%1023[^|:]", tmp) == 1 && + strcmp(name, tmp) == 0) { @@ - ptr = bp; - while ((ptr = strchr(ptr,'|')) != NULL) { + ptr = bp; + while ((ptr = strchr(ptr, '|')) != NULL && + strchr(bp, ':') != NULL && ptr < strchr(bp, ':')) { ptr++; - sscanf(ptr,"%1023[^|:]",tmp); - if (strcmp(name, tmp) == 0) { + if (sscanf(ptr, "%1023[^|:]", tmp) == 1 && + strcmp(name, tmp) == 0) { fclose(fp); @@ - if (strchr(ptr,'|') == NULL) break; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vms.termcap.c` around lines 118 - 140, The alias-parsing loop can read uninitialized tmp when an entry begins with a delimiter and incorrectly treats '|' inside capability values as aliases; modify the parsing in the block that uses bp/ptr/tmp so that you first locate the first capability delimiter (':' or end-of-entry) and limit alias scanning to the substring before that delimiter, use a bounded sscanf or safer function return check (check sscanf returns 1) before calling strcmp, and stop advancing ptr past the first ':' (i.e., stop on ':' or end-of-line) so strchr(ptr,'|') is only searched within the alias portion; update the logic around sscanf(bp,"%1023[^|:]",tmp), the ptr loop that uses strchr(ptr,'|'), and the strcmp checks to enforce these guards.
333-342:⚠️ Potential issue | 🟠 MajorAvoid out-of-bounds pointer arithmetic in the
tgoto()length check.
rp + tlencan be formed past the end ofretbefore the comparison, which is undefined behavior. Compare lengths using integer offsets instead.🐛 Proposed fix
- if (tlen > 0 && rp + tlen <= rend) { + if (tlen > 0 && + (size_t)tlen <= (size_t)(rend - rp)) { memcpy(rp, tmp, (size_t)tlen); rp += tlen; }Verify the unsafe pointer-add check is removed:
#!/bin/bash rg -n -C2 'rp \+ tlen <= rend|rend - rp|snprintf\(tmp' vms.termcap.c🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vms.termcap.c` around lines 333 - 342, The length check uses pointer arithmetic that can form an out-of-bounds pointer (rp + tlen <= rend); instead, after calling snprintf into tmp and storing the return in tlen, validate tlen is non-negative and compare sizes using integer offsets: ensure (size_t)tlen <= (size_t)(rend - rp) (and also ensure tlen < sizeof(tmp) or handle truncation from snprintf). Update the check in the case 'd' block (variables: rp, rend, tlen, tmp, numval, snprintf) to use the integer difference rend - rp and only memcpy when the integer-size check passes.ed.refresh.c (1)
380-397:⚠️ Potential issue | 🟠 MajorUse termcap output and display widths for ghost rendering.
This only uses
soas a boolean, then emits hardcoded ANSIESC[2m/ESC[0mand counts everyCharas one column. That still breaks non-ANSI terminals and wide-character suggestions.Prefer emitting the actual
so/secapabilities and usingNLSWidth()/putwraw()so cursor restoration matches rendered columns.Verify all ghost rendering still bypasses termcap strings and width accounting:
#!/bin/bash sed -n '333,399p' ed.refresh.c rg -n -C2 'DrawGhost|tgetstr\("so"|ESC|NLSWidth|putwraw|putpure' ed.refresh.cdot.mcshrc (1)
142-142:⚠️ Potential issue | 🟠 MajorRename the
objdumpalias todisto preserve normalobjdumpbehavior.With
alias objdump 'objdump -M intel -d \!*', commands likeobjdump -h libfoo.so,objdump -t a.out, orobjdump -x /bin/lsall becomeobjdump -M intel -d -h …, forcing disassembly and mixing it with the requested output, breaking user intent. The-M intelflag is supported on FreeBSD 15's llvm-objdump (available since FreeBSD 13.3), so the fix is straightforward:-alias objdump 'objdump -M intel -d \!*' +alias dis 'objdump -M intel -d \!*'This preserves the useful
-M intel -ddisassembly functionality under a dedicated alias while keeping plainobjdumpavailable with its standard behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dot.mcshrc` at line 142, The alias `objdump 'objdump -M intel -d \!*'` overrides the real objdump and forces disassembly for all invocations; change it to create a new alias name (e.g., `dis`) that runs `objdump -M intel -d` so users can still call `objdump` normally; update the alias definition in the file (replace the `alias objdump ...` entry with an alias named `dis` or similar) so the symbol `objdump` remains the original tool and `dis` provides the Intel-syntax disassembly shortcut.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@dot.mcshrc`:
- Line 215: The timing threshold is currently set to 0 in the set time = (0
"\n\e[1;36mreal\e[0m %E \e[1;32muser\e[0m %U \e[1;33msys\e[0m %S
\e[1;35mcpu\e[0m %P") setting which causes every command to print a timing
banner; change the threshold from 0 to a higher value (e.g. 2 or 5) to avoid
noisy interactive output, or add a short comment documenting that 0 is
intentional for high-precision build timing so reviewers/users understand the
choice.
- Line 103: The completion definition for make (the line using complete make and
the backticked make -pn invocation) runs make -pn on every tab press; change it
so the command only runs when a Makefile exists (e.g., test for files like
Makefile, makefile, GNUmakefile) or at minimum redirect stderr to suppress "no
target" noise, and update the backticked expression inside the complete make
call accordingly so the guard or redirection is applied to the make -pn
invocation used for completions.
- Around line 29-30: The local override file (~/.mcshrc.local) is being sourced
too early via the if ( -r ~/.mcshrc.local ) source ~/.mcshrc.local statement so
later sections (Environment, Engine, Keybindings, Aliases, Prompt, System and
the set time block) overwrite user customizations; move that source invocation
to the very end of the file (after the set time block) so user overrides take
precedence, and only keep a top-of-file source if you intentionally need
pre-GPU/Wayland hooks (in which case document and/or source it twice as pre/post
hooks).
- Around line 207-212: Replace the incorrect use of VIMINFO with VIMINIT/EXINIT
in the root-only block: instead of setenv VIMINFO ':set viminfo=', set the
environment variable VIMINIT (or EXINIT) to the literal command set viminfo= (no
extra quotes around the empty value) so Vim executes "set viminfo=" on startup
and thus disables writing a .viminfo for root; update the block containing the
$uid == 0 check where unset savehist and setenv LESSHISTFILE are set to use
setenv VIMINIT 'set viminfo=' (or setenv EXINIT 'set viminfo=') accordingly.
In `@ed.refresh.c`:
- Around line 347-361: The code erases the old ghost starting at the current
Cursor, which can overwrite real input when the cursor moved; modify the logic
around GhostBuf handling (the block using prev_ghost_cols, putpure and
putpure('\b')) to move the terminal cursor to the previous ghost start stored in
prev_ghost_h and prev_ghost_v, write spaces for prev_ghost_cols there, then
restore the cursor to the current position (instead of writing spaces at
Cursor); also ensure you update prev_ghost_h and prev_ghost_v before drawing the
new ghost so future erases use the correct coordinates.
In `@ISSUES.md`:
- Around line 75-77: The ISSUES.md entry for alacritty.toml is out of sync with
the actual config: update the bullet for `alacritty.toml` to reflect whether the
file currently imports `~/.cache/wal/colors-alacritty.toml`, whether the pywal
import is commented out, and whether `program` uses `"/usr/local/bin/mcsh"`,
`"/home/.../mcsh"` or just `"mcsh"` (PATH-resolved). Edit the text to accurately
describe the present state (e.g., "imports ~/.cache/wal/colors-alacritty.toml
present" or "pywal import commented out; program set to 'mcsh' resolved via
PATH") and remove contradictory claims so `alacritty.toml`, `pywal` and
`program` descriptions match the repository config.
In `@sh.time.c`:
- Around line 385-397: The for loop and its nested branches around the escape
handling are missing braces and create an ambiguous if/else-if shape; locate the
loop iterating over cp (the for (; *cp; cp++) block) and add explicit braces
around the loop body and around the inner if (*cp != '%') branch and its inner
else so the switch/escape handling and the trailing else if (cp[1]) are clearly
scoped; ensure braces wrap the block containing the backslash handling (the
switch on *++cp), the xputchar(*cp) path, and the subsequent else-if so Clang's
static analysis no longer flags the nested branches.
---
Duplicate comments:
In `@alacritty.toml`:
- Around line 2-5: The checked-in config currently imports pywal cache via the
line import = ["~/.cache/wal/colors-alacritty.toml"] and pins the shell program
to an absolute path program = "/usr/local/bin/mcsh", which contradicts the
documented “local override + PATH” setup; remove or disable the pywal import
line and change the [terminal.shell] program entry to use the unqualified
executable name (e.g., program = "mcsh") or remove the program key so the shell
is resolved via PATH, thereby keeping the config portable and matching the docs.
In `@dot.mcshrc`:
- Line 142: The alias `objdump 'objdump -M intel -d \!*'` overrides the real
objdump and forces disassembly for all invocations; change it to create a new
alias name (e.g., `dis`) that runs `objdump -M intel -d` so users can still call
`objdump` normally; update the alias definition in the file (replace the `alias
objdump ...` entry with an alias named `dis` or similar) so the symbol `objdump`
remains the original tool and `dis` provides the Intel-syntax disassembly
shortcut.
In `@PLAN.md`:
- Around line 77-78: Update the policy wording to clarify that platform support
for VMS is removed while some VMS-legacy files (e.g., the retained vms.termcap.c
shim) are kept for portability; specifically, change the phrase "VMS is dropped
entirely" to something like "VMS platform support is discontinued; certain
VMS‑named legacy source files (for example vms.termcap.c) are retained and
repurposed as portable shims" so readers understand the difference between
dropping VMS support and keeping legacy files.
In `@tc.prompt.c`:
- Around line 775-795: The current mtime invalidation uses
short2str(gcwd)/.git/HEAD which misses worktrees/submodules/bare repos and
op-state files; modify git_get_info (and its callers: the block using git_valid,
git_oldcwd, git_branch, git_op) to return or populate the resolved gitdir path
plus HEAD and operation-marker mtimes (e.g., HEAD mtime and
MERGE_HEAD/REBASE_HEAD mtimes), then replace the direct stat() of
short2str(gcwd)/.git/HEAD with comparisons against those resolved mtimes (update
git_head_mtime and any op-state mtime variables from git_get_info) so
need_refresh is computed from the resolved gitdir state rather than
cwd/.git/HEAD.
- Around line 270-271: The snprintf call that writes gitdir into itself is
undefined and unnecessary; remove the line containing snprintf(gitdir,
sizeof(gitdir), "%s", gitdir) (referencing the gitdir buffer and its self-copy
via snprintf) and ensure the surrounding code uses the existing gitdir value
unchanged; also search for other occurrences of the same self-copy pattern to
delete or replace them with a proper copy from a different source if needed.
In `@vms.termcap.c`:
- Around line 118-140: The alias-parsing loop can read uninitialized tmp when an
entry begins with a delimiter and incorrectly treats '|' inside capability
values as aliases; modify the parsing in the block that uses bp/ptr/tmp so that
you first locate the first capability delimiter (':' or end-of-entry) and limit
alias scanning to the substring before that delimiter, use a bounded sscanf or
safer function return check (check sscanf returns 1) before calling strcmp, and
stop advancing ptr past the first ':' (i.e., stop on ':' or end-of-line) so
strchr(ptr,'|') is only searched within the alias portion; update the logic
around sscanf(bp,"%1023[^|:]",tmp), the ptr loop that uses strchr(ptr,'|'), and
the strcmp checks to enforce these guards.
- Around line 333-342: The length check uses pointer arithmetic that can form an
out-of-bounds pointer (rp + tlen <= rend); instead, after calling snprintf into
tmp and storing the return in tlen, validate tlen is non-negative and compare
sizes using integer offsets: ensure (size_t)tlen <= (size_t)(rend - rp) (and
also ensure tlen < sizeof(tmp) or handle truncation from snprintf). Update the
check in the case 'd' block (variables: rp, rend, tlen, tmp, numval, snprintf)
to use the integer difference rend - rp and only memcpy when the integer-size
check passes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9aef1552-89e5-436b-a7f9-017514c49457
📒 Files selected for processing (10)
ISSUES.mdPLAN.mdREADME.mdalacritty.tomldot.mcshrced.chared.ced.refresh.csh.time.ctc.prompt.cvms.termcap.c
dot.mcshrc: - move ~/.mcshrc.local sourcing to end-of-file so user overrides take precedence - guard make completion behind Makefile existence check; redirect stderr - rename alias objdump -> dis to preserve original objdump for normal use - fix VIMINFO -> VIMINIT (correct env var to suppress .viminfo for root) - add comment documenting set time = (0 ...) threshold is intentional ed.refresh.c: - DrawGhost: replace raw backspace-based cursor return with MoveToLine/MoveToChar using tracked prev_ghost_h/prev_ghost_v (physical CursorH/CursorV coordinates) so ghost erase always targets the correct terminal position sh.time.c: - add explicit braces around for-loop body and inner if/else branches to fix ambiguous if/else shape flagged by static analysis tc.prompt.c: - remove no-op snprintf(gitdir, sizeof(gitdir), "%s", gitdir) self-copy vms.termcap.c: - check sscanf return == 1 before strcmp in alias loop - fix rp+tlen pointer arithmetic in case 'd': use integer (rend-rp) comparison alacritty.toml: - comment out pywal import line; change program to "mcsh" (PATH-resolved) ISSUES.md: update alacritty.toml entry to reflect actual current state PLAN.md: clarify VMS section heading — platform discontinued, shim files retained 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
CodeRabbit Review Round 3 — All findings addressed
|
Implements Option B: full virtual-display pipeline integration.
No raw ESC bypass — colour flows through the same Display/Vdisplay
model used by every other rendering path.
Architecture end-to-end:
1. syntax_colorize() (ed.syntax.c) runs after every keystroke in
ed.inputl.c — single-pass state machine fills SyntaxColor[]
with SynToken values for each InputBuf character.
2. Draw() (ed.refresh.c) reads SyntaxColor[cp-InputBuf] and sets
the vcurrent_color global (SYN_NORMAL for prompt characters).
3. Vdraw() writes vcurrent_color into VcolorDisplay[v][h] in
parallel with the character write into Vdisplay[v][h].
4. Refresh() main loop copies VcolorDisplay -> ColorDisplay after
the Vdisplay->Display diff, keeping colour arrays in sync.
5. so_write() (ed.screen.c) reads ColorDisplay[CursorV][CursorH]
per character and calls SetSGRColor() before putwraw(). Resets
with SetSGRColor(-1) at end of write.
6. SetSGRColor() tracks cur_sgr to suppress redundant SGR emissions;
resets to -1 whenever SetAttributes() clears all attributes.
Token types: keyword, builtin, cmd_ok, cmd_bad, operator, variable,
dquote, squote, backtick, comment, error (unmatched quote).
LRU command cache (CMD_CACHE_SIZE=32) avoids per-keystroke stat(2)
calls on $PATH for command existence checks.
New files:
ed.syntax.h — SynToken enum, SynColor/SynPalette, SyntaxColor[],
extern declarations
ed.syntax.c — tokeniser, LRU cache, cmd_on_path(), syntax_colorize(),
syntax_clear()
Modified files:
ed.h — VcolorDisplay, ColorDisplay, vcurrent_color externals
ed.screen.c — ReBufferDisplay() allocs colour arrays; SetSGRColor()
new static fn; so_write() reads ColorDisplay per char;
SetAttributes() resets cur_sgr on full attribute clear
ed.refresh.c — Draw() sets vcurrent_color; Vdraw() writes VcolorDisplay;
ClearDisp() zeroes ColorDisplay; Refresh() copies arrays
ed.inputl.c — calls syntax_colorize() after every command dispatch
ed.decls.h — extern syntax_colorize(), syntax_clear()
sh.set.c — update_vars() and dounset hook STRsyntax
tc.const.c — STRsyntax[] constant
Makefile.in — ed.syntax.${SUF} added to EDOBJS
dot.mcshrc — set syntax added after set color
Feature is fully gated on adrof(STRsyntax); no overhead when unset.
Also removes alacritty.toml from tracking (machine-local config).
Docs: PLAN.md Phase 9, ISSUES.md completed section, README feature table.
💘 Generated with Crush
Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Phase 9 complete — Native Interactive Syntax Highlighting (
|
| File | Purpose |
|---|---|
ed.syntax.h |
SynToken enum (12 token types), SynColor/SynPalette[] defaults, SyntaxColor[INBUFSIZE] parallel array, extern declarations |
ed.syntax.c |
Single-pass tokeniser state machine, LRU command cache (CMD_CACHE_SIZE=32), cmd_on_path() via stat(2) + $PATH walk, syntax_colorize(), syntax_clear() |
Modified files
| File | Change |
|---|---|
ed.h |
VcolorDisplay, ColorDisplay, vcurrent_color EXTERN declarations |
ed.screen.c |
ReBufferDisplay() allocates parallel colour arrays; SetSGRColor() new static fn (tracks cur_sgr, emits ESC[bold;fg m); so_write() reads ColorDisplay[CursorV][CursorH] per char; SetAttributes() resets cur_sgr on full attribute clear |
ed.refresh.c |
Draw() sets vcurrent_color from SyntaxColor; Vdraw() writes VcolorDisplay; ClearDisp() zeroes ColorDisplay; Refresh() copies VcolorDisplay → ColorDisplay after line diff |
ed.inputl.c |
syntax_colorize() called after every command dispatch |
ed.decls.h |
extern declarations for syntax_colorize() / syntax_clear() |
sh.set.c |
update_vars() + dounset hook STRsyntax → colorize/clear |
tc.const.c / tc.const.h |
STRsyntax[] constant |
Makefile.in / Makefile |
ed.syntax.${SUF} added to EDOBJS |
dot.mcshrc |
set syntax added after set color |
Token palette (defaults, all overridable via SynPalette[])
| Token | Colour |
|---|---|
keyword (if while foreach …) |
bold cyan |
builtin (set alias cd …) |
bold green |
command — found on $PATH |
green |
| command — not found | bold red |
operator (| ; && || > …) |
yellow |
variable ($foo ${foo}) |
magenta |
| double-quoted string | yellow |
| single-quoted string | yellow |
| backtick substitution | cyan |
comment (#…) |
bright black |
| unmatched quote | bold red |
Architecture summary
keystroke → syntax_colorize() → SyntaxColor[]
→ Draw(cp) → vcurrent_color = SyntaxColor[cp-InputBuf]
→ Vdraw(c,w) → VcolorDisplay[v][h] = vcurrent_color
→ Refresh() → ColorDisplay[line] ← VcolorDisplay[line]
→ so_write() → SetSGRColor(ColorDisplay[CursorV][CursorH])
→ putwraw(c)
→ SetSGRColor(-1) ← reset after write
Feature is fully gated on adrof(STRsyntax) — zero overhead when unset.
Also in this commit
alacritty.tomlremoved from tracking (machine-local config, now in.gitignore)PLAN.md— Phase 9 section added; changelog entryISSUES.md— Phase 9 completed section with full architecture breakdownREADME.md—set syntaxrow in features table;dot.mcshrckey settings updated
All completed work on this branch (PR #3 full summary)
Phase 9 — Native syntax highlighting (this commit)
See above.
Phase 8 — Code review fixes (PR3 Gemini + CodeRabbit)
configure.ac:TCSH_BASELINE_VERSIONuses M4 macro;PACKAGE_PATCHLEVELstrips leading zeros viasedsh.func.cdoif():int i→tcsh_number_t(wide expression result truncation fix)vms.termcap.c: octal cases'4'–'7'added;sscanfuses%[^|:]+strcmp;fgetstracks remaining buffer;tgotobuffer enlarged to 64 bytes withsnprintffor%ded.defns.c:predict-acceptNLS catalog ID corrected (124, was 122 — collision withnewline-and-hold)acaux/install-sh: name-guard case patterns use*suffix for multi-char namesm4/lib-prefix.m4:dn;→dnl;m4/po.m4: C# DLL cleanup removes correct target;GETTEXT_MACRO_VERSIONbumped
Phase 7b — Native mcsh features
- Fish-style predictive autocomplete:
predict_from_history()scansHistlistfor prefix match; ghost text rendered dimmed after cursor; Right-Arrow /^Faccepts; cleared on any non-insert command - Git branch prompt escapes
%g/%G:git_get_info()walks upward for.git/HEAD; cached per-CWD;%Gappends operation state (MERGING,REBASING-i,BISECTING, etc.) dot.mcshrcrewrite: full reference startup file with keybindings, completions, aliases, no starship dependency
Phase 5 — Upstream feature PRs
- PR #89 Interactive comments:
#acts as comment in interactive mode - PR #107 Expression short-circuit:
$?a && "$a" != ""no longer throws whenais unset - PR #105 Pipe-to-variable:
echo foo | set xandset x < file - PR #77
functionbuiltin: named shell function definitions - Issue #113 Redirection in
{ }blocks:if ( { cmd >& /dev/null } )works
Phase 4 — Bug fixes
%jprompt token: counts live job leaders only (upstream #110)getn()overflow:strtollwith proper overflow/errno (upstream #101)- Shift operator UB: unsigned arithmetic for
<</>> cryptlink failure:AC_SEARCH_LIBS([crypt], [crypt xcrypt])(upstream #99)- Ghost text rendering fixes: stale erase, cursor jump on accept, NUL terminator
Phases 1–3, 6 — Branding, hygiene, build system
- Full Windows/VMS/dead platform purge;
system/pruned to active POSIX platforms sh.types.hcollapsed to<stdint.h>/<stddef.h>; bundled malloc removed; K&R guards removed- Build system:
configure.acmodernised; dead branches removed;autoconf ≥ 2.72targeted
…Display Two bugs causing colours on wrong/random characters: 1. so_write() was reading ColorDisplay[CursorV][CursorH] — the *previous* frame's colour map — while rendering characters from the current Vdisplay. Every character got last keystroke's token, shifted by the display diff. Changed to read VcolorDisplay which is filled by Vdraw() in the current Refresh() pass. 2. VcolorDisplay was never zeroed before each repaint, so deleted characters left stale SynToken values that bled onto the padding spaces written by cpy_pad_spaces(). Added a memset loop at the top of Refresh() before the prompt/input draw pass. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
CC_NORM falls through without calling Refresh() — it relies on RefPlusOne() for the incremental single-character append path, which never goes through Draw()/Vdraw() and therefore never picks up the new SyntaxColor[] values from syntax_colorize(). When set syntax is active, promote CC_NORM to CC_REFRESH after syntax_colorize() runs so a full Refresh() is always issued and the new token colours reach the screen immediately on every keypress. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
…f bypass) The core problem: update_line() does a glyph-only diff (*o == *n). When you type a character that changes a word's token class (e.g. 'i' -> 'if' becoming a keyword), the *existing* letters didn't change glyph so the diff skips them entirely -- so_write() never fires for those cells and the new SGR colour never reaches the terminal. Two fixes together: 1. ed.refresh.c: before update_line() on each line, compare ColorDisplay (previous colours) vs VcolorDisplay (new colours). If they differ, memset Display[cur_line] to zero to poison the diff -- forcing update_line() to see every cell as changed and redraw the whole line via so_write() with the new colours. 2. ed.inputl.c: keep CC_NORM -> CC_REFRESH promotion when set syntax is active, so RefPlusOne() (which bypasses Draw/Vdraw entirely) is replaced with a full Refresh() that goes through the colour pipeline. Without this, single-char inserts never reach Refresh() at all. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
…correctly) Replace the parallel VcolorDisplay/ColorDisplay arrays and Display[] poisoning with the correct approach: pack the 4-bit SynToken directly into bits 0xF0000000 of each Char written to Vdisplay. ATTRIBUTES uses 0x0F000000; QUOTE (0x80000000) is only ever set by the lexer and never written into Vdisplay/Display. So bits 0xF0000000 are completely free in the display layer -- 4 bits covers all 12 tokens. Result: update_line()'s glyph diff (*o == *n) now naturally detects colour-only changes because the packed token makes the Char values differ. Deleted characters become '\0' in the new Vdisplay which differs from the old coloured Char -- backspace correctly erases. No poisoning, no parallel arrays, no stale state, no off-by-one. so_write() extracts the token with SYN_TOK(), calls SetSGRColor(), then strips the token bits with SYN_GLYPH() before putwraw() so the terminal sees only the actual glyph. New macros in ed.syntax.h: SYN_SHIFT / SYN_MASK -- bit field definition SYN_PACK(c, t) -- embed token t into display Char c SYN_TOK(c) -- extract token from display Char c SYN_GLYPH(c) -- strip token bits, get raw glyph Removed: VcolorDisplay, ColorDisplay parallel arrays and all allocation, zeroing, memcmp/memcpy, and poisoning code. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (7)
tc.prompt.c (1)
414-420:⚠️ Potential issue | 🟠 MajorRefresh
%Gwhen the resolved Git state changes, not only$cwd/.git/HEAD.Line 777 only checks
$cwd/.git/HEAD, which does not exist for worktrees/submodules and does not change when merge/rebase/cherry-pick marker files appear.%Gcan still stay stale in the same directory.🐛 Correctness-first fix: recompute git info for prompt rendering
/* git info cache */ static Char *git_oldcwd = NULL; static char git_branch[256]; static char git_op[64]; static int git_valid = -1; - static time_t git_head_mtime = 0; @@ { - int need_refresh = (git_oldcwd != gcwd || git_valid < 0); - if (!need_refresh) { - char _hp[MAXPATHLEN]; - struct stat _st; - snprintf(_hp, sizeof(_hp), "%s/.git/HEAD", - short2str(gcwd)); - if (stat(_hp, &_st) == 0 && - _st.st_mtime != git_head_mtime) - need_refresh = 1; - } - if (need_refresh) { - char _hp[MAXPATHLEN]; - struct stat _st; - git_oldcwd = gcwd; - git_valid = git_get_info(short2str(gcwd), - git_branch, sizeof(git_branch), - git_op, sizeof(git_op)); - snprintf(_hp, sizeof(_hp), "%s/.git/HEAD", - short2str(gcwd)); - git_head_mtime = (stat(_hp, &_st) == 0) - ? _st.st_mtime : 0; - } + git_oldcwd = gcwd; + git_valid = git_get_info(short2str(gcwd), + git_branch, sizeof(git_branch), + git_op, sizeof(git_op)); }Also applies to: 773-793
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tc.prompt.c` around lines 414 - 420, The prompt's git cache (git_oldcwd, git_branch, git_op, git_valid, git_head_mtime) is only refreshed by checking $cwd/.git/HEAD and thus misses worktrees/submodules and marker-file state changes; update the refresh logic to detect changes in the resolved git directory and any state marker files instead of only .git/HEAD: resolve the actual git dir for the current cwd (follow worktree file if present), compute a composite state signature (e.g., HEAD target path + mtimes of HEAD and marker files like MERGE_HEAD, CHERRY_PICK_HEAD, REBASE_HEAD and refs/* that affect branch name), compare it to the cached signature, and if it differs (or cwd != git_oldcwd) recompute git_branch/git_op, set git_valid accordingly and update git_head_mtime and git_oldcwd so %G is always recomputed when the resolved git state changes.ISSUES.md (1)
91-95:⚠️ Potential issue | 🟡 MinorRemove
^Ffrom the predictive-accept status note unless it is actually bound.This still says Right-Arrow /
^F, while the current documented behavior inREADME.mdis Right-Arrow only and the provided keymap change only binds the right arrow toF_PREDICT_ACCEPT.Suggested fix
- via backspaces. `e_predict_accept` (Right-Arrow / `^F`) copies `GhostBuf` into + via backspaces. `e_predict_accept` (Right-Arrow) copies `GhostBuf` into🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ISSUES.md` around lines 91 - 95, Update the predictive-accept status note to remove the "^F" token so it matches the actual binding; specifically edit the descriptive line that currently reads "Right-Arrow / `^F`" (referencing predict_from_history(), e_predict_accept, and GhostBuf) to mention only "Right-Arrow" (and/or `F_PREDICT_ACCEPT` if you prefer symbolic name) so the docs align with README.md and the provided keymap that binds only the right arrow to F_PREDICT_ACCEPT.vms.termcap.c (1)
126-138:⚠️ Potential issue | 🟡 MinorLimit alias scanning to the name field before the first
:.The loop still searches for
|across the full capability string, so a pipe character in a later capability value can be parsed as another alias. Stop at the first colon and only scan aliases inside that name field.Suggested fix
- ptr = bp; - while ((ptr = strchr(ptr,'|')) != NULL) { + { + char *name_end = strchr(bp, ':'); + ptr = bp; + while ((ptr = strchr(ptr, '|')) != NULL && + (name_end == NULL || ptr < name_end)) { ptr++; if (sscanf(ptr,"%1023[^|:]",tmp) == 1 && strcmp(name, tmp) == 0) { fclose(fp); `#ifdef` DEBUG fprintf(stderr,CGETS(31, 3, "Found %s in %s.\n"), name, termfile); sleep(1); `#endif` /* DEBUG */ return(1); } - if (strchr(ptr,'|') == NULL) break; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vms.termcap.c` around lines 126 - 138, The alias-scanning loop (using ptr, bp, strchr and sscanf(tmp)) can cross the first ':' and treat pipes in capability values as additional aliases; change the loop to stop at the first ':' by locating the name-field terminator (e.g., char *colon = strchr(bp, ':')) and only consider '|' positions that occur before that colon, updating ptr forward but breaking when the next '|' is at or past the colon, and only call sscanf on the substring within that name field when comparing tmp to name.sh.set.c (1)
611-612:⚠️ Potential issue | 🟠 MajorKeep
getn()strict; scope the empty-arm fallback to expression evaluation.This still makes every empty numeric string parse as
0, so callers outside ignored short-circuit arms can silently accept invalid input. The provided cross-file snippets showgetn()results flowing into array arithmetic (sh.func.c), completion range checks (tw.comp.c), and history line selection (sh.hist.c).Proposed direction
- if (*cp == '\0') /* empty string from ignored expression arms */ - return 0; + if (*cp == '\0') + stderror(ERR_NAME | ERR_BADNUM);Then apply the
0fallback only where the expression evaluator can prove the empty value came from an ignored/short-circuited arm.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sh.set.c` around lines 611 - 612, getn() should remain strict and must not treat an empty string as numeric 0; remove or disable the unconditional fallback in getn() that returns 0 when *cp == '\0' and instead let callers explicitly provide a 0 for ignored/short-circuited expression arms. Update the expression evaluator code paths that perform short-circuit/ignored-arm evaluation (the caller(s) in the expression evaluator that invoke getn(), not getn() itself) to detect an empty-arm result and substitute 0 only when the evaluator can prove the value came from an ignored/short-circuited arm; leave other callers (e.g., sh.func.c, tw.comp.c, sh.hist.c) to continue receiving strict getn() errors for empty numeric input. Ensure getn() returns an error/indicator for empty input so callers can decide whether to substitute 0.dot.mcshrc (2)
11-27:⚠️ Potential issue | 🟠 MajorDo not enable Sway/NVIDIA hybrid settings for every Wayland session.
The guard is better, but
/dev/dri/card0 && WAYLAND_DISPLAYalso matches non-Sway, AMD-only, Intel-only, remote, and nested Wayland sessions. Keep generic Wayland variables here if desired, but move compositor/GPU-specific exports behind an explicit local opt-in or into~/.mcshrc.local.Suggested direction
if ( -e /dev/dri/card0 && $?WAYLAND_DISPLAY ) then setenv CLUTTER_BACKEND wayland setenv GDK_BACKEND wayland setenv QT_QPA_PLATFORM wayland setenv SDL_VIDEODRIVER wayland - setenv XDG_CURRENT_DESKTOP sway - setenv XDG_SESSION_TYPE wayland - setenv XDG_SESSION_DESKTOP sway - - # Intel + Nvidia Hybrid - setenv WLR_DRM_DEVICES /dev/dri/card0 - setenv WLR_NO_HARDWARE_CURSORS 1 - setenv LIBVA_DRIVER_NAME iHD - setenv __NV_PRIME_RENDER_OFFLOAD 1 - setenv __GLX_VENDOR_LIBRARY_NAME nvidia - setenv __VK_LAYER_NV_optimus NVIDIA_only endif + +# Put compositor/GPU-specific settings, e.g. Sway/NVIDIA offload, in: +# ~/.mcshrc.local🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dot.mcshrc` around lines 11 - 27, The current guard (checking /dev/dri/card0 and $?WAYLAND_DISPLAY) sets both generic Wayland envs and compositor/GPU-specific NVIDIA/Hybrid variables; split them: keep only generic Wayland exports (CLUTTER_BACKEND, GDK_BACKEND, QT_QPA_PLATFORM, SDL_VIDEODRIVER, XDG_CURRENT_DESKTOP, XDG_SESSION_TYPE, XDG_SESSION_DESKTOP) under the existing Wayland check, and move the hybrid-specific exports (WLR_DRM_DEVICES, WLR_NO_HARDWARE_CURSORS, LIBVA_DRIVER_NAME, __NV_PRIME_RENDER_OFFLOAD, __GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus) into an explicit opt-in block (e.g., sourced from ~/.mcshrc.local or gated by a specific flag like ENABLE_NV_HYBRID or a compositor-specific check such as XDG_CURRENT_DESKTOP = "sway" plus NVIDIA device detection) so they are only enabled for intended Sway/NVIDIA hybrid sessions.
69-217:⚠️ Potential issue | 🟠 MajorRestore an interactive guard around keybindings, completions, prompts, and
stty.This reference rc now runs interactive-only setup unconditionally. If
.mcshrcis sourced by a non-interactive shell,bindkey,complete,stty, prompt mutation, alias resets, and timing settings can change script behavior or fail on non-tty stdin.Suggested structure
# ------------------------------------------------------------------------------ # 4. KEYBINDINGS (TERMINAL I/O MAPPING) # ------------------------------------------------------------------------------ +if ( $?prompt ) then bindkey -e ... # Threshold 0 = time every command; raise to 2 for less noisy sessions set time = (0 "\n\e[1;36mreal\e[0m %E \e[1;32muser\e[0m %U \e[1;33msys\e[0m %S \e[1;35mcpu\e[0m %P") +endif # Source machine-local overrides last so they take full precedence if ( -r ~/.mcshrc.local ) source ~/.mcshrc.local🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dot.mcshrc` around lines 69 - 217, The file runs interactive-only commands unguarded; wrap all interactive setup (blocks containing bindkey, complete invocations, unalias/alias lines, the PROMPT ENGINE where set prompt/set prompt2/set rprompt occur, the stty status / bindkey ^G switch, set time, and the HOST COMPLETION loop using foreach) in an interactive-check such as: surround those sections with if ( $?prompt ) then ... endif (or an equivalent interactive test) so non-interactive shells skip bindkey/complete/stty/prompt changes and avoid failures.ed.refresh.c (1)
348-408:⚠️ Potential issue | 🟠 MajorGhost rendering still bypasses
Vdisplay/Displayand emits hardcoded ANSI.Same root-cause as the prior review:
DrawGhost()writes directly withputpure()plus rawESC[2m/ESC[0m, so the refresh model has no record of the overlay. Consequences that still apply:
- The
tgetstr("so", &caparea)check probes for standout, but the code then emits\033[2m(SGR dim/faint) — a different attribute. A terminal that advertisessobut not SGR 2 will render incorrectly, and one that supports dim but notsowill render unstyled. Either gate on a real dim capability or drop the probe and always emit nothing whenTermCanUL/SGR support is unknown.- The trailing
\033[0munconditionally resets SGR state regardless of whether the probe succeeded, which can clobber attributes the renderer just set (e.g. inRefPlusOneafterPutPlusOneon a literal).putpure((int)c)narrows aCharto a byte; any multibyte/wide ghost glyph (common for history completions with non-ASCII) is silently mangled, andghost_colstracks bytes written, not terminal columns — so the backspace restore andprev_ghost_colserase will be wrong for anything non-ASCII. The objectives note this is a documented limitation, but the packing/tracking path intoVdisplayis what would fix it for real.Minimal correctness tweak for the termcap/dim mismatch (behavior fix, not the structural one)
- char capbuf[64]; - char *caparea = capbuf; - /* * On a full repaint Refresh() already redrew the whole line cleanly — @@ - gp = GhostBuf; - if (tgetstr("so", &caparea) != NULL) { - (void) putpure('\033'); (void) putpure('['); - (void) putpure('2'); (void) putpure('m'); - } + gp = GhostBuf; + /* Emit SGR dim only; do not conflate with termcap "so" (standout). */ + (void) putpure('\033'); (void) putpure('['); + (void) putpure('2'); (void) putpure('m'); while (*gp) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ed.refresh.c` around lines 348 - 408, DrawGhost currently bypasses the refresh/display pipeline by writing directly with putpure(), probes termcap "so" but emits SGR 2, unconditionally emits ESC[0m, and counts bytes not display columns which mangles wide/multibyte ghosts; fix by routing ghost output through the existing display layer (call Vdisplay/Display or the same output path used by PutPlusOne) instead of putpure(), use the correct terminal capability (query true SGR/dim support or a TermCanSGR flag rather than tgetstr("so",...)) and only emit/reset SGR when you actually set it, and compute column width using the project's character-width utility (the Char/width helpers) so prev_ghost_cols and backspace/erase logic track terminal columns correctly; update references in DrawGhost, GhostBuf, prev_ghost_cols, and any SGR/termcap checks to use the shared display and width helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ed.h`:
- Around line 181-187: The comment above vcurrent_color is stale: it still
describes separate parallel colour arrays for Vdisplay/Display, but the renderer
now packs syntax tokens into the display Char struct and extracts them via
SYN_TOK() / SYN_GLYPH() in ed.screen.c; update the comment in ed.h (near
vcurrent_color and ReBufferDisplay()) to remove references to parallel colour
arrays, state that syntax tokens are stored in Char, mention
SYN_TOK()/SYN_GLYPH() as the accessors used by the rendering path, and keep a
short note about vcurrent_color being the SynToken being written into those Char
entries.
In `@ed.inputl.c`:
- Around line 189-193: The current call to syntax_colorize() runs before
dispatch, so later mutations to InputBuf by tenematch() in completion/expansion
cases (CC_COMPLETE*, CC_LIST_GLOB, CC_EXPAND_GLOB, CC_NORMALIZE_PATH,
CC_EXPAND_VARS, CC_NORMALIZE_COMMAND) leave syntax tokens stale when Refresh()
is called; fix this by moving syntax_colorize() to occur immediately before each
Refresh() that follows a path which can modify InputBuf, or introduce a helper
(e.g., RefreshSyntax) that calls syntax_colorize() if adrof(STRsyntax) and then
calls Refresh(), and replace those Refresh() calls in the listed switch cases
with the helper so syntax is recolored after InputBuf mutations.
In `@ed.refresh.c`:
- Line 45: Remove the unused file-scope static variable vcurrent_color_local:
delete the declaration "static int vcurrent_color_local;" (it shadows nothing
and is never referenced); ensure code uses the existing global vcurrent_color
from ed.h and run a build to confirm no remaining references to
vcurrent_color_local remain.
In `@ed.screen.c`:
- Around line 1006-1031: SetSGRColor currently emits ESC[0m when fg < 0 which
resets all terminal attributes but does not update cur_atr, and StopHighlight
emits T_me without clearing cur_sgr; fix by treating syntax SGR as a separate
layer: change SetSGRColor to reset only the color attributes (e.g. use SGR 39)
instead of ESC[0m or, if emitting a full reset, immediately set cur_atr = 0 so
terminal attribute state stays consistent; likewise ensure StopHighlight clears
cur_sgr (or otherwise re-applies cur_atr) so later calls to SetSGRColor don't
get incorrectly skipped—update references in SetSGRColor, StopHighlight, cur_sgr
and cur_atr handling to keep color-layer and attribute-layer state synchronized.
In `@ed.syntax.c`:
- Around line 365-385: The tokenizer incorrectly ends variable state when the
first character after '$' is a special prefix like '?' or '#', causing `$?path`
to color only `$?` as a variable; update the logic in the ST_VARIABLE handling
so that if the initial character (buf[i] & CHAR) is '?' or '#' you keep state =
ST_VARIABLE and allow the subsequent alphanumeric/underscore characters to
remain part of the variable name instead of switching to ST_NORMAL; modify the
conditional that checks ((buf[i] & CHAR) >= 'a' ... '0' ... '_') to also accept
an initial '?' or '#' (or special-case that prefix) so SyntaxColor[i] stays
SYN_VARIABLE and state is not set to ST_NORMAL prematurely (referencing
ST_VARIABLE, ST_NORMAL, SyntaxColor, SYN_VARIABLE, state, buf and CHAR).
- Around line 202-241: The syntax-command cache in ed.syntax.c must be
invalidated when PATH or cwd changes and must check executability correctly:
update the executable test in the search loop and the direct-path branch to use
S_ISREG(st.st_mode) && access(path_or_word, X_OK) instead of (st.st_mode &
S_IXUSR), and ensure directories are excluded; add a cache_clear() (or
timestamp-based invalidation) routine used by update_vars() (the caller that
invokes dohash() on PATH changes) so cache_lookup/cache_store do not return
stale results when PATH or CWD changes.
In `@ed.syntax.h`:
- Around line 39-60: Include <stdint.h> (for uint8_t) and ensure INBUFSIZE is
defined or the proper header that provides it is included; then guard the
token-packing macros by checking whether Char is wide enough (use existing build
macros like WIDE_STRINGS or a compile-time sizeof(Char) / CHAR_BIT test) before
defining SYN_SHIFT/SYN_MASK/SYN_PACK/SYN_TOK/SYN_GLYPH, and provide a clear
fallback when Char is narrow (SHORT_STRINGS): either disable bit-packing (set
SYN_MASK to 0 and make SYN_PACK a no-op) and use a parallel token array (e.g.,
syn_tokens[] referenced where syntax tokens are needed) or switch to alternate
implementations that do not overflow; update all uses of
SYN_PACK/SYN_TOK/SYN_GLYPH and references to Char accordingly so narrow-Char
builds do not rely on 32-bit packing.
In `@ISSUES.md`:
- Around line 21-35: Update the architecture notes to remove references to
separate VcolorDisplay/ColorDisplay arrays and instead describe the packed Char
implementation: explain that syntax_colorize() emits packed token/char info into
the display buffer as Char values, that Draw/ Vdraw write these packed Char
values (not parallel color arrays), and that ed.screen.c extracts token and
glyph with SYN_TOK()/SYN_GLYPH() when deciding colors in so_write(); also note
ed.h no longer declares VcolorDisplay/ColorDisplay and keep
SetSGRColor()/cur_sgr behavior description as-is. Ensure the same changes are
applied to the other affected paragraph(s) that still mention the old
VcolorDisplay/ColorDisplay model.
In `@Makefile.in`:
- Around line 443-445: EDOBJS currently lists ed.syntax.${SUF} but the header
ed.syntax.h is not added to the dependency graph, so changes to that header
won't trigger rebuilds; update the Makefile rules so the object/target that
produces ed.syntax.${SUF} (e.g., ed.syntax.o / the rule that builds
ed.syntax.${SUF}) depends on ed.syntax.h and ensure any display/input targets
that include ed.syntax.h also list ed.syntax.h as a prerequisite so edits to
ed.syntax.h cause those targets (ed.syntax.${SUF}, ed.screen.${SUF},
ed.inputl.${SUF}, etc.) to rebuild.
---
Duplicate comments:
In `@dot.mcshrc`:
- Around line 11-27: The current guard (checking /dev/dri/card0 and
$?WAYLAND_DISPLAY) sets both generic Wayland envs and compositor/GPU-specific
NVIDIA/Hybrid variables; split them: keep only generic Wayland exports
(CLUTTER_BACKEND, GDK_BACKEND, QT_QPA_PLATFORM, SDL_VIDEODRIVER,
XDG_CURRENT_DESKTOP, XDG_SESSION_TYPE, XDG_SESSION_DESKTOP) under the existing
Wayland check, and move the hybrid-specific exports (WLR_DRM_DEVICES,
WLR_NO_HARDWARE_CURSORS, LIBVA_DRIVER_NAME, __NV_PRIME_RENDER_OFFLOAD,
__GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus) into an explicit opt-in block
(e.g., sourced from ~/.mcshrc.local or gated by a specific flag like
ENABLE_NV_HYBRID or a compositor-specific check such as XDG_CURRENT_DESKTOP =
"sway" plus NVIDIA device detection) so they are only enabled for intended
Sway/NVIDIA hybrid sessions.
- Around line 69-217: The file runs interactive-only commands unguarded; wrap
all interactive setup (blocks containing bindkey, complete invocations,
unalias/alias lines, the PROMPT ENGINE where set prompt/set prompt2/set rprompt
occur, the stty status / bindkey ^G switch, set time, and the HOST COMPLETION
loop using foreach) in an interactive-check such as: surround those sections
with if ( $?prompt ) then ... endif (or an equivalent interactive test) so
non-interactive shells skip bindkey/complete/stty/prompt changes and avoid
failures.
In `@ed.refresh.c`:
- Around line 348-408: DrawGhost currently bypasses the refresh/display pipeline
by writing directly with putpure(), probes termcap "so" but emits SGR 2,
unconditionally emits ESC[0m, and counts bytes not display columns which mangles
wide/multibyte ghosts; fix by routing ghost output through the existing display
layer (call Vdisplay/Display or the same output path used by PutPlusOne) instead
of putpure(), use the correct terminal capability (query true SGR/dim support or
a TermCanSGR flag rather than tgetstr("so",...)) and only emit/reset SGR when
you actually set it, and compute column width using the project's
character-width utility (the Char/width helpers) so prev_ghost_cols and
backspace/erase logic track terminal columns correctly; update references in
DrawGhost, GhostBuf, prev_ghost_cols, and any SGR/termcap checks to use the
shared display and width helpers.
In `@ISSUES.md`:
- Around line 91-95: Update the predictive-accept status note to remove the "^F"
token so it matches the actual binding; specifically edit the descriptive line
that currently reads "Right-Arrow / `^F`" (referencing predict_from_history(),
e_predict_accept, and GhostBuf) to mention only "Right-Arrow" (and/or
`F_PREDICT_ACCEPT` if you prefer symbolic name) so the docs align with README.md
and the provided keymap that binds only the right arrow to F_PREDICT_ACCEPT.
In `@sh.set.c`:
- Around line 611-612: getn() should remain strict and must not treat an empty
string as numeric 0; remove or disable the unconditional fallback in getn() that
returns 0 when *cp == '\0' and instead let callers explicitly provide a 0 for
ignored/short-circuited expression arms. Update the expression evaluator code
paths that perform short-circuit/ignored-arm evaluation (the caller(s) in the
expression evaluator that invoke getn(), not getn() itself) to detect an
empty-arm result and substitute 0 only when the evaluator can prove the value
came from an ignored/short-circuited arm; leave other callers (e.g., sh.func.c,
tw.comp.c, sh.hist.c) to continue receiving strict getn() errors for empty
numeric input. Ensure getn() returns an error/indicator for empty input so
callers can decide whether to substitute 0.
In `@tc.prompt.c`:
- Around line 414-420: The prompt's git cache (git_oldcwd, git_branch, git_op,
git_valid, git_head_mtime) is only refreshed by checking $cwd/.git/HEAD and thus
misses worktrees/submodules and marker-file state changes; update the refresh
logic to detect changes in the resolved git directory and any state marker files
instead of only .git/HEAD: resolve the actual git dir for the current cwd
(follow worktree file if present), compute a composite state signature (e.g.,
HEAD target path + mtimes of HEAD and marker files like MERGE_HEAD,
CHERRY_PICK_HEAD, REBASE_HEAD and refs/* that affect branch name), compare it to
the cached signature, and if it differs (or cwd != git_oldcwd) recompute
git_branch/git_op, set git_valid accordingly and update git_head_mtime and
git_oldcwd so %G is always recomputed when the resolved git state changes.
In `@vms.termcap.c`:
- Around line 126-138: The alias-scanning loop (using ptr, bp, strchr and
sscanf(tmp)) can cross the first ':' and treat pipes in capability values as
additional aliases; change the loop to stop at the first ':' by locating the
name-field terminator (e.g., char *colon = strchr(bp, ':')) and only consider
'|' positions that occur before that colon, updating ptr forward but breaking
when the next '|' is at or past the colon, and only call sscanf on the substring
within that name field when comparing tmp to name.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c016f88e-864d-47da-92c3-b436fd0a6ad7
📒 Files selected for processing (18)
.gitignoreISSUES.mdMakefile.inPLAN.mdREADME.mddot.mcshrced.decls.hed.hed.inputl.ced.refresh.ced.screen.ced.syntax.ced.syntax.hsh.set.csh.time.ctc.const.ctc.prompt.cvms.termcap.c
- ed.h: update stale comment; replace parallel-colour-array description with accurate packed-Char / SYN_TOK()/SYN_GLYPH() architecture note - ed.inputl.c: call syntax_colorize() inside Repair() (covers all buffer-mutation CC_* cases) and in the CC_REFRESH path so tokens are always fresh when Refresh() is called after completion/expansion - ed.refresh.c: remove unused vcurrent_color_local; fix DrawGhost to only emit/reset SGR dim when actually set (eliminate unconditional ESC[0m) - ed.screen.c: SetSGRColor() sets cur_atr=0 on full ESC[0m reset; StopHighlight() clears cur_sgr so later SetSGRColor() calls are not skipped - ed.syntax.c: fix ST_VARIABLE to keep state=ST_VARIABLE when first char is '?' or '#' so $?path/$#arr colour the full name; use S_ISREG+access(X_OK) in cmd_on_path(); add syntax_cache_clear() called by update_vars(STRpath) - ed.syntax.h: add <stdint.h>; guard SYN_PACK/SYN_TOK/SYN_GLYPH macros with WIDE_STRINGS check; provide narrow-Char fallback (no-op); document header - ed.decls.h: add extern for syntax_cache_clear() - sh.set.c: remove getn() empty-string-returns-0 fallback; PATH change now calls syntax_cache_clear() to invalidate stale cmd_on_path() results - tc.prompt.c: git cache refresh now checks composite state (HEAD + MERGE_HEAD, CHERRY_PICK_HEAD, REBASE_HEAD, rebase-merge/head-name) so merge/rebase/cherry-pick operations are detected without cwd pointer change - vms.termcap.c: alias-scanning loop stops at first ':' so capability values containing '|' are not mistaken for additional terminal aliases - dot.mcshrc: split generic Wayland exports from NVIDIA/hybrid GPU block (hybrid vars moved to ~/.mcshrc.local opt-in); wrap all interactive-only setup (bindkey, complete, alias, prompt, host-completion loop, stty/time) in if ($?prompt) so non-interactive shells skip them cleanly - ISSUES.md: update Phase 9 architecture notes to reflect packed-Char model (no VcolorDisplay/ColorDisplay arrays); remove '^F' from predict-accept docs; update files-changed list with accurate descriptions 💘 Generated with Crush Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
Review Response — All Inline & Duplicate Comment Findings AddressedCommit Inline Comments
Duplicate Comments
All 13 affected files committed, build passes cleanly. |
There was a problem hiding this comment.
Pull request overview
This PR is a broad WIP update to mcsh that adds new interactive UX features (git-aware prompts, predictive autocomplete, per-keystroke syntax highlighting) and includes a set of portability/build-system updates aimed at improving FreeBSD 15 and general POSIX compatibility.
Changes:
- Add interactive syntax highlighting (
set syntax) by tokenizing the input buffer and emitting ANSI SGR during line rendering. - Add fish-style predictive autocomplete ghost text + accept-on-Right-Arrow behavior.
- Add git branch/operation prompt escapes (
%g/%G) and expand various portability/build fixes and docs.
Reviewed changes
Copilot reviewed 40 out of 43 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
vms.termcap.c |
Adds/repurposes a portable termcap implementation and fixes/bounds checks for various termcap routines. |
tc.vers.c |
Adjusts the reported version string format. |
tc.prompt.c |
Implements %g/%G git prompt expansions with caching and repo-state detection. |
tc.const.c |
Adds STRsyntax constant to support set syntax. |
sh.time.c |
Adds backslash escape handling in time format output. |
sh.set.c |
Wires set/unset syntax behavior and clears syntax cache on PATH changes; adds an empty-string guard in getn(). |
sh.sem.c |
Adjusts when Dfix() runs and preserves builtin detection. |
sh.func.c |
Updates doif() parsing/handling and changes reexecute() behavior to use F_REPEAT. |
sh.exp.c |
Simplifies expr() to call exp0() directly; removes a glob/quote expansion block in exp6(). |
nls/catgen |
Adds a wrapper script for generating catalogs via gencat. |
m4/progtest.m4 |
Adds gettext/autotools macro support file. |
m4/po.m4 |
Adds gettext po-directory support macros. |
m4/nls.m4 |
Adds NLS enable/disable macro. |
m4/lib-prefix.m4 |
Adds library prefix detection macro. |
m4/lib-ld.m4 |
Adds linker detection macro. |
m4/intlmacosx.m4 |
Adds macOS CoreFoundation/CoreServices intl linking detection. |
m4/iconv.m4 |
Adds iconv detection/linking macros. |
m4/host-cpu-c-abi.m4 |
Adds host CPU/ABI detection macros. |
m4/gettext.m4 |
Adds gettext discovery/selection macros. |
m4/build-to-host.m4 |
Adds build-to-host path conversion helpers. |
ed.syntax.h |
Introduces syntax token types, palette, and token packing macros into display Char. |
ed.syntax.c |
Implements tokenizer, $PATH command lookup + cache, and public syntax APIs. |
ed.screen.c |
Emits per-cell SGR colors in so_write() and tracks current SGR state. |
ed.refresh.c |
Packs syntax tokens into Vdisplay and adds ghost text rendering routine. |
ed.inputl.c |
Hooks syntax_colorize() into interactive input loop and clears ghost text on non-insert actions. |
ed.h |
Adds GhostBuf and vcurrent_color globals for new UI features. |
ed.defns.c |
Adds predict-accept editor command and binds Right-Arrow to accept ghost text. |
ed.decls.h |
Declares new editor/syntax functions. |
ed.chared.c |
Implements history-based ghost suggestion generation + accept behavior. |
dot.mcshrc |
Rewrites the reference startup config and enables new features (set syntax, prompt updates, bindings, completions). |
dch-template.in |
Adds Debian changelog template for releases. |
configure.ac |
Adjusts baseline version define and patchlevel normalization. |
config.h.in |
Adds TCSH_BASELINE_VERSION placeholder and relocates HAVE_LIBC_GLOB. |
acaux/install-sh |
Updates Automake install-sh to a newer revision with improved option handling. |
acaux/config.rpath |
Updates rpath helper with CLI help/version handling and newer gnulib content. |
README.md |
Documents new features, prompt escapes, build instructions, and dotfile usage. |
PLAN.md |
Updates plan status/details for VMS, Phase 9, and new features. |
Makefile.in |
Adds ed.syntax to editor object list and updates dependencies. |
ISSUES.md |
Adds detailed design notes and implementation summary for new features. |
.gitignore |
Ignores local config/build artifacts and generated files. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…sh-style pushd/popd configure.ac - AC_DEFINE_UNQUOTED TCSH_BASELINE_VERSION now wraps the value as a quoted string literal so config.h emits a valid C string instead of an undefined identifier. tc.prompt.c - Git prompt cache now tracks git_head_mtime (for .git/HEAD) and a separate git_marker_mtime (max mtime across MERGE_HEAD, CHERRY_PICK_HEAD, REBASE_HEAD, rebase-merge/head-name). Previously all marker files were compared against git_head_mtime, so a live MERGE_HEAD whose mtime differs from HEAD's would force a refresh on every prompt render. ed.screen.c (SetSGRColor) - The default-fg / no-bold branch now emits ESC[22;39m instead of ESC[0m. ESC[0m was clobbering all SGR state (underline, standout) while cur_atr was not reset, causing desynchronisation. cur_atr is now updated to clear the bold bit when bold is not requested. ed.refresh.c (DrawGhost) - SGR reset after ghost text now emits ESC[22;39m instead of ESC[0m, preserving cur_atr synchronisation on the incremental RefPlusOne path. ed.inputl.c - CC_NORM with `set syntax` active now calls syntax_colorize() directly rather than promoting the return code to CC_REFRESH. Commands that already call Refresh() internally return CC_NORM, so the previous approach was triggering an extra full Refresh() per keystroke. sh.dir.c + dot.mcshrc — zsh-style pushd/popd tree navigation - dirs -v marks the current directory (index 0) with a → arrow. - pushd/popd default to the vertical numbered display (dirs -v style) when no explicit format flag is given — replaces the flat horizontal output with a legible stack tree after every navigation. - dfind() extended to handle -N arguments (counting from the bottom of the stack / oldest entry), mirroring zsh's cd -N semantics. - dochngd() (cd built-in) detects cd -N before skipargs so the digit is not rejected as an unknown flag. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Response to PR #3 review findingsThanks to @coderabbitai, @google-gemini, and @Copilot for the thorough reviews across all rounds. This comment addresses everything raised — the latest commit f4821c9 closes all remaining findings. Copilot (final round — 5 unresolved comments)
|
| Area | Fix |
|---|---|
vms.termcap.c |
Buffer overflows (fgets capacity, sscanf bounds, tgoto snprintf, octal parsing, continuation-line guard, backslash literal '\') |
ed.chared.c |
e_predict_accept NUL termination; predict_from_history trailing newline strip and INBUFSIZE bound; history-line materialisation before skip |
ed.refresh.c |
Ghost erase logic overshoot (only erase when Cursor == LastChar); tgetstr safe buffer pass-through |
ed.defns.c |
predict-accept NLS catalog ID collision (was 122, now 124) |
sh.func.c |
doif() stores expr() result in tcsh_number_t instead of truncating int |
sh.sem.c |
Dfix() short-circuits expansion for expression builtins |
sh.set.c |
getn() strict empty-input handling |
m4/lib-prefix.m4 |
dn; → dnl typo |
m4/po.m4 |
DLL error cleanup deletes correct target; GETTEXT_MACRO_VERSION updated to 0.23 |
dot.mcshrc |
Home/End bindings corrected to \e[1~/\e[4~/\eOH/\eOF; machine-specific GPU vars gated behind local override; set time moved to end; ^F accept binding removed from docs |
tc.prompt.c |
gitdir NUL termination; worktree .git file parsing; tprintf_append_mbs for multibyte branch/op names |
alacritty.toml |
Hardcoded shell path → program = "mcsh"; pywal import commented out as optional |
PLAN.md / ISSUES.md / README.md |
Wording clarifications, DrawGhost status updated, ^F binding note removed |
All builds pass clean on FreeBSD 15 (cc -g -O2, no new warnings introduced).
|
Copilot round — noted and confirmed sound:
pushd/popd enhancement — The Triggering a fresh review pass to cover commit ✅ Actions performedReview triggered.
|
…ject state README.md - Complete rewrite of the Features section: language additions (function, short-circuit, pipe-to-variable, redirect in blocks, interactive comments), editor/interactive table (predictive autocomplete, syntax highlighting with token colour table, filetype colouring), prompt reference table, and a full directory-stack navigation section with worked example. - Bug-fixes table extended to cover all PR3 findings: SGR desync fixes, git cache mtime, TCSH_BASELINE_VERSION, ed.inputl double-refresh, and all vms.termcap.c, m4, configure.ac, and acaux items. - dot.mcshrc section rewritten as a section table covering all 9 sections and the root guard / local-override pattern. - Source layout table extended to include ed.syntax.c/h. - All doc cross-links and status notes corrected. PLAN.md - Phase 4 Completed table now includes #110 (%j fix) and #107 (short-circuit). - Phase 7 (Documentation) updated to "complete (first pass)" with accurate per-file status. - Phase 9 (native features) extended with zsh-style pushd/popd entry. - Phase 8 added as its own phase (code review fixes) with status complete. - Risk Register pruned of obsolete entries. - Changelog updated with all 2026-04-21 round-2 work. ISSUES.md - New top section documents Phase 8 round-2 fixes (configure.ac string literal, git cache, SetSGRColor/DrawGhost ESC[22;39m, inputl double refresh) and Phase 9 zsh pushd/popd extension (arrow marker, default tree display, cd -N, dfind extension). - Remaining open items section rewritten: deferred man page sections explicitly list set syntax, %g/%G, cd -N, and pushd display; test-suite section lists cd -N as a required case. - All historical completed work sections preserved and accurately dated. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Documentation update —
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 41 out of 44 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| snprintf(_hp, sizeof(_hp), "%s/.git/HEAD", | ||
| short2str(gcwd)); | ||
| if (stat(_hp, &_st) == 0 && | ||
| _st.st_mtime != git_head_mtime) | ||
| need_refresh = 1; |
There was a problem hiding this comment.
The git prompt cache invalidation checks ${cwd}/.git/HEAD mtime (and .git/* markers). In worktrees/submodules where .git is a file pointing to the real gitdir, these paths often don’t exist, so git_head_mtime can stay 0 and the cache may not refresh after checkouts/merges. Consider caching the resolved gitdir/HEAD path from git_get_info() and stat’ing that instead.
| for (i = 0; i < CMD_CACHE_SIZE; i++) { | ||
| if (cmd_cache[i].found >= 0 && | ||
| strncmp(cmd_cache[i].name, name, CMD_CACHE_NAMELEN - 1) == 0) | ||
| return cmd_cache[i].found; |
There was a problem hiding this comment.
cache_store() truncates command names to CMD_CACHE_NAMELEN-1, but cache_lookup() uses strncmp(..., CMD_CACHE_NAMELEN-1) without checking the original length. For names ≥ 63 chars this can cause false cache hits between distinct commands sharing a prefix. Consider not caching overlong names, or store/compare the full length (or a hash) to avoid collisions.
| /* Mark unterminated quotes as errors */ | ||
| if (state == ST_SQUOTE || state == ST_DQUOTE || | ||
| state == ST_BACKTICK || state == ST_BRACE_VAR) { | ||
| for (i = 0; i < len; i++) { | ||
| if ((SynToken)SyntaxColor[i] == SYN_SQUOTE || | ||
| (SynToken)SyntaxColor[i] == SYN_DQUOTE || | ||
| (SynToken)SyntaxColor[i] == SYN_BACKTICK || | ||
| (SynToken)SyntaxColor[i] == SYN_VARIABLE) | ||
| SyntaxColor[i] = SYN_ERROR; | ||
| } |
There was a problem hiding this comment.
The unterminated-quote error pass treats any SYN_VARIABLE as SYN_ERROR, which can incorrectly mark $var occurrences that appear before the unmatched quote began. Consider tracking the start offset of the active unterminated region and only marking that region (or only tokens belonging to the active quote state) as SYN_ERROR.
| /* Mark unterminated quotes as errors */ | |
| if (state == ST_SQUOTE || state == ST_DQUOTE || | |
| state == ST_BACKTICK || state == ST_BRACE_VAR) { | |
| for (i = 0; i < len; i++) { | |
| if ((SynToken)SyntaxColor[i] == SYN_SQUOTE || | |
| (SynToken)SyntaxColor[i] == SYN_DQUOTE || | |
| (SynToken)SyntaxColor[i] == SYN_BACKTICK || | |
| (SynToken)SyntaxColor[i] == SYN_VARIABLE) | |
| SyntaxColor[i] = SYN_ERROR; | |
| } | |
| /* Mark only the trailing unterminated quote/brace region as errors. */ | |
| if (state == ST_SQUOTE || state == ST_DQUOTE || | |
| state == ST_BACKTICK || state == ST_BRACE_VAR) { | |
| ptrdiff_t mark_start = len; | |
| while (mark_start > 0) { | |
| SynToken tok = (SynToken)SyntaxColor[mark_start - 1]; | |
| if (tok != SYN_SQUOTE && | |
| tok != SYN_DQUOTE && | |
| tok != SYN_BACKTICK && | |
| tok != SYN_VARIABLE) | |
| break; | |
| mark_start--; | |
| } | |
| for (i = mark_start; i < len; i++) | |
| SyntaxColor[i] = SYN_ERROR; |
| * op is empty string when no special operation is in progress, or one of: | ||
| * MERGING, REBASING, REBASING-i, REBASING-m, CHERRY-PICKING, REVERTING, | ||
| * BISECTING. | ||
| * |
There was a problem hiding this comment.
The git_get_info() comment documents operation states including REBASING-m, but the rebase-apply path sets op to "AM" (line 366). Either document AM as a possible value or rename it to match the documented set so %G has stable, documented output values.
|
|
||
| version = xasprintf( | ||
| "mcsh %d.%.2d.%.2d (%s) %s (%" TCSH_S "-%" TCSH_S "-%" TCSH_S ") [tcsh baseline %s] options %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", | ||
| "mcsh %d.%.2d.%.2d (%s) %s (%" TCSH_S "-%" TCSH_S "-%" TCSH_S ") tcsh-baseline:%s options %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", |
There was a problem hiding this comment.
The $version banner format changed from [tcsh baseline %s] to tcsh-baseline:%s. README.md still documents the bracketed form, and any external tooling that greps for the old marker may break. Consider keeping the previous bracketed marker (or updating all docs/consumers consistently).
| execute(t, (tpgrp > 0 ? tpgrp : -1), NULL, NULL, TRUE); | ||
| cleanup_until(t); | ||
| cleanup_until(¶ml); | ||
| execute(kp, (tpgrp > 0 ? tpgrp : -1), NULL, NULL, TRUE); |
There was a problem hiding this comment.
reexecute() now directly calls execute(kp, ...) on the same struct command (line 394). This bypasses the previous re-lex/alias/syntax rebuild, so reexecuted tails may not be parsed (pipes/redirections/metachars become plain args) and aliases may not expand. Consider restoring the prior wordent+alias()+syntax() path (or equivalent) before executing.
| if ((t->t_dflg & F_REPEAT) == 0) | ||
| Dfix(t); /* $ " ' \ */ |
There was a problem hiding this comment.
Dfix(t) is now gated only on F_REPEAT. Expression-evaluating builtins (e.g. if, while, test, let) need to skip Dfix() so $/quote expansion doesn’t happen eagerly and break short-circuit evaluation. Consider computing bifunc = isbfunc(t) before this point and skipping Dfix() for those builtins (as the previous code did), rather than keying off F_REPEAT.
- configure.ac: remove extra M4 quoting brackets around TCSH_VERSION in AC_DEFINE_UNQUOTED so the macro is expanded correctly and config.h emits the literal version string "6.24.13" instead of the identifier TCSH_VERSION. - sh.sem.c: skip Dfix() for expression-evaluating builtins (doif, dowhile, dotest, dolet, doexit) to preserve lazy evaluation semantics. Previously all NODE_COMMAND paths called Dfix() unconditionally, expanding $, ", ', \ before operands reached expr(), defeating TEXP_IGNORE / TEXP_NOGLOB guards. isbfunc() is now called early, before Dfix(), to detect the exempted builtins. - ISSUES.md: document both fixes in the completed-work log. All items raised in the CodeRabbit PR3 round-1 and round-2 reviews are now resolved; remaining notes (acaux/install-sh patterns, dch-template.in, m4 files, alacritty.toml, dot.mcshrc) were either already correct or addressed in prior rounds. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Response to CodeRabbit PR3 round-2 review (review 4148466671)Thank you for the thorough second-pass review. Below is a full accounting of every comment with disposition and commit reference. ✅ Fixed in this round (commit 543b61f)
Fixed. The extra inner M4 quoting brackets
Fixed. ✅ Already fixed in prior rounds (addressed but re-confirmed here)
All review comments from both rounds are now addressed. Build is clean ( |
Skipping Dfix() for doif/dowhile/dotest/dolet/doexit causes $?VAR and other variable references inside if-condition operands to never be expanded, producing "No match." errors at runtime (e.g. the $?WAYLAND_DISPLAY guard in dot.mcshrc). Dfix() *must* run before expr() — it is the sole mechanism that expands $var, "...", '...' and \ in command words. The reviewer's concern about lazy evaluation applies inside expr()/exp6() itself, not at the Dfix layer. The pre-existing TEXP_NOGLOB/TEXP_IGNORE handling in exp6() is correct and sufficient; no Dfix change is needed. Document the regression and rationale in ISSUES.md. 💘 Generated with Crush Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Summary by CodeRabbit
New Features
set syntax).set colorwith LSCOLORS/LS_COLORS).Bug Fixes
Documentation
dot.mcshrc) with modern defaults and aliases.