Merge features: QTerminalWidget + external jsh spawn, crash reporter, AWS, stress tests
ober
0e13cbd632efe07f333f69e69acd5fb158fd93ab
new file mode 100644 --- /dev/null +++ b/.mcp.json --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ Common pitfalls when step 3 fails: - **`library (std ...) not found`**: The Docker image has a stale jerboa `std/` tree. New modules must be added to the sync list in the `linux-static-qt-docker` Makefile target AND compiled into the WPO step in `build-binary-qt.ss`. - **`Dynamic loading not supported`**: Any `load-shared-object` call must be guarded with the `JEMACS_STATIC` env var check (the binary sets `JEMACS_STATIC=1`). - **`final:` or other unsupported keywords**: jerbuild doesn't support all Gerbil `defstruct` keywords — remove them. -- **Dependencies not in gerbil-qt**: This project uses `~/mine/chez-qt`, NOT `~/mine/gerbil-qt`. The `CHEZ_QT_SHIM_DIR` must point to `.` (local project root) for runtime, and `vendor/` for the build header. +- **Dependencies not in gerbil-qt**: This project uses `vendor/chez-qt` (vendored from `github.com/ober/chez-qt`), NOT `~/mine/gerbil-qt`. The `CHEZ_QT_SHIM_DIR` must point to `.` (local project root) for runtime, and `vendor/` for the build header. Do NOT rely on `make run-qt` (interpreted mode) as proof the binary works — the static binary has different constraints (no `load-shared-object`, all libraries must be compiled in). @@ -59,6 +59,61 @@ After modifying `.ss` files, always rebuild before testing: make build && make test-functional ``` +## Stress Testing & Crash Reporting + +The editor has automated stress testing infrastructure for finding segfaults. See `docs/stress-test.md` for the full design. + +### Crash Reporter + +`vendor/qt_shim.cpp` includes a SIGSEGV/SIGBUS/SIGABRT crash reporter that: +- Records the last 64 FFI calls in a lock-free ring buffer +- On crash, writes a diagnostic report to `~/.jemacs-crash.log` with the FFI ring buffer and native backtrace +- Re-raises the signal so gdb/core dumps still work + +The crash reporter is automatically installed when `qt_application_create()` is called. No configuration needed. + +### Stress Test Commands + +```bash +make stress-run # Launch interpreted jemacs-qt headless with REPL (port 9999) +make stress-run-static # Launch static binary under gdb with REPL +make stress-test # Run stress driver against already-running REPL +make stress-burn # All-in-one: launch interpreted + run stress driver +make stress-burn-static # All-in-one: launch static under gdb + run stress driver +``` + +Override the REPL port: `make stress-test STRESS_PORT=8888` + +### Stress Test Driver (`tests/stress-test.ss`) + +Connects to jemacs-qt's `--repl` TCP interface via `nc` subprocess and drives random editor commands in a continuous loop until the editor crashes (connection drops). Runs 10 stress phases per cycle: + +1. **Window Chaos** — split/delete/balance windows randomly +2. **Vterm Storm** — open multiple vterm buffers +3. **File Churn** — create/open/edit/kill temp files, copy/paste between buffers +4. **Navigation Stress** — rapid cursor movement and scrolling +5. **EWW** — web browser buffer +6. **Edit Storm** — insert/delete/undo/redo +7. **Buffer Management** — open many buffers, rapid switching +8. **Combined Chaos** — random mix of all operations +9. **Window Thrash** — rapid split-then-delete cycles +10. **Search Operations** — word-motion navigation + +All commands are logged to `stress-test.log` with timestamps. When a crash occurs: +- `~/.jemacs-crash.log` shows the FFI call ring buffer and backtrace +- `stress-test.log` shows the exact command sequence that triggered it +- gdb (in `stress-burn-static`) shows the native stack trace + +### Typical Debugging Workflow + +```bash +make stress-burn-static # Run until crash +# Examine: ~/.jemacs-crash.log, stress-test.log, gdb output +# Fix the bug +make static-qt # Rebuild +make stress-burn-static # Run again +``` + ## jsh Shell Library The jerboa-shell (jsh) library is a separate project at `~/mine/jerboa-shell`. After modifying any file in `~/mine/jerboa-shell/src/jsh/`, recompile: --- a/Dockerfile +++ b/Dockerfile @@ -53,6 +53,19 @@ RUN cd /tmp && \ make -j$(nproc) && make install && \ cd / && rm -rf /tmp/xcb-util-0.4.1* +# Build static OpenSSL (needed by chez-ssl for AWS API calls) +# Alpine's openssl-dev only ships shared libs; we need .a files for the static binary. +RUN apk add --no-cache openssl-dev perl && \ + OPENSSL_VER=$(apk info openssl 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "3.3.2") && \ + wget -q https://www.openssl.org/source/openssl-${OPENSSL_VER}.tar.gz || \ + wget -q https://www.openssl.org/source/openssl-3.3.2.tar.gz && \ + tar xf openssl-*.tar.gz && \ + cd openssl-*/ && \ + ./Configure linux-x86_64 no-shared no-tests no-apps -O2 --prefix=/usr && \ + make -j$(nproc) && \ + cp libssl.a libcrypto.a /usr/lib/ && \ + cd / && rm -rf openssl-*/ openssl-*.tar.gz + # ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── ARG QT6_VERSION=6.8.3 RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ @@ -252,12 +265,14 @@ COPY --from=pcre2-src . /deps/chez-pcre2 COPY --from=sci-src . /deps/chez-scintilla COPY --from=qt-src . /deps/chez-qt COPY --from=qtshim-src . /deps/gerbil-qt +# qt_chez_shim.c is maintained in vendor/ (not in chez-qt) — copy it in +COPY vendor/qt_chez_shim.c vendor/qt_shim.h /deps/chez-qt/ # Pre-compile all Chez library dependencies. # These .so files are baked into the image so jemacs builds only # need to compile jemacs-specific modules. RUN /opt/chez/bin/scheme \ - --libdirs /deps/jerboa/lib:/deps/gherkin/src:/deps/jsh/src:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt \ + --libdirs /deps/jerboa/lib:/deps/gherkin:/deps/jsh/src:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt \ --compile-imported-libraries \ --script /dev/stdin <<'EOF' #!chezscheme --- a/Makefile +++ b/Makefile @@ -2,8 +2,11 @@ SCHEME = scheme JERBOA = $(HOME)/mine/jerboa JSH = $(if $(wildcard vendor/jerboa-shell/src/jsh),vendor/jerboa-shell/src,$(HOME)/mine/jerboa-shell/src) COREUTILS = $(if $(wildcard $(HOME)/mine/jerboa-coreutils/lib),$(HOME)/mine/jerboa-coreutils/lib,$(HOME)/mine/jerboa-shell/vendor/jerboa-coreutils/lib) -GHERKIN = $(HOME)/mine/gherkin/src -LIBDIRS = --libdirs lib:$(JERBOA)/lib:$(JSH):$(COREUTILS):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src:$(HOME)/mine/chez-qt +GHERKIN = $(if $(wildcard vendor/gherkin-runtime),vendor/gherkin-runtime,$(HOME)/mine/gherkin/src) +JAWS = $(if $(wildcard vendor/jerboa-aws),vendor/jerboa-aws,$(HOME)/mine/jerboa-aws) +CSSL = $(if $(wildcard vendor/chez-ssl/src),vendor/chez-ssl/src,$(HOME)/mine/chez-ssl/src) +CHTTPS = $(if $(wildcard vendor/chez-https/src),vendor/chez-https/src,$(HOME)/mine/chez-https/src) +LIBDIRS = --libdirs lib:$(JERBOA)/lib:$(JSH):$(COREUTILS):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src:$(HOME)/mine/chez-qt:$(JAWS):$(CSSL):$(CHTTPS) JERBUILD = $(SCHEME) --libdirs $(JERBOA)/lib --script $(JERBOA)/jerbuild.ss # --- Platform detection ------------------------------------------------------- @@ -50,7 +53,9 @@ endif test-org-lint test-org-num test-org-property test-org-src test-org-tempo \ test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary binary-qt \ test-pty test-emacs test-functional test-term-hang \ - docker-deps static-qt clean-docker check-root build-jemacs-qt-static macos + docker-deps static-qt clean-docker check-root build-jemacs-qt-static macos \ + stress-run stress-run-static stress-test stress-burn stress-burn-static \ + test-behavioral all: @echo "Available targets:" @@ -166,7 +171,7 @@ libqt_shim.$(SHLIB_EXT): vendor/qt_shim.cpp -I$(QT_INC)/Qsci \ vendor/qt_shim.cpp \ -o libqt_shim.$(SHLIB_EXT) \ - $(QT_LIBS) $(QSCI_LIBS) + $(QT_LIBS) $(QSCI_LIBS) -lvterm $(PTY_LINK) qt_chez_shim.$(SHLIB_EXT): vendor/qt_chez_shim.c vendor/qt_shim.h gcc $(SHLIB_FLAGS) -O2 -o qt_chez_shim.$(SHLIB_EXT) vendor/qt_chez_shim.c -Ivendor -DQT_SCINTILLA_AVAILABLE -Wall @@ -373,13 +378,18 @@ GID := $(shell id -g) # Dependency source directories (all ~/mine/* local checkouts) JERBOA_SRC ?= $(HOME)/mine/jerboa -GHERKIN_SRC ?= $(HOME)/mine/gherkin +GHERKIN_SRC ?= $(CURDIR)/vendor/gherkin-runtime JSH_SRC ?= $(HOME)/mine/jerboa-shell PCRE2_SRC ?= $(HOME)/mine/chez-pcre2 SCI_SRC ?= $(HOME)/mine/chez-scintilla -QT_SRC ?= $(HOME)/mine/chez-qt +QT_SRC ?= $(CURDIR)/vendor/chez-qt QTSHIM_SRC ?= $(HOME)/mine/gerbil-qt -COREUTILS_SRC ?= $(HOME)/mine/jerboa-coreutils +JAWS_SRC ?= $(HOME)/mine/jerboa-aws/lib +CSSL_SRC ?= $(HOME)/mine/chez-ssl +CHTTPS_SRC ?= $(HOME)/mine/chez-https +# Use stub if the Rust musl build hasn't been compiled yet (regular file check) +_RUST_COREUTILS := $(JSH_SRC)/rust-coreutils/target/x86_64-unknown-linux-musl/release/libjsh_coreutils.a +JSH_COREUTILS_LIB ?= $(shell test -f $(_RUST_COREUTILS) && echo $(_RUST_COREUTILS) || echo $(CURDIR)/vendor/libjsh_coreutils_stub.a) DEPS_IMAGE := jemacs-deps:$(ARCH) @@ -398,6 +408,9 @@ docker-deps: --build-context sci-src=$(SCI_SRC) \ --build-context qt-src=$(QT_SRC) \ --build-context qtshim-src=$(QTSHIM_SRC) \ + --build-context jaws-src=$(JAWS_SRC) \ + --build-context chez-ssl-src=$(CSSL_SRC) \ + --build-context chez-https-src=$(CHTTPS_SRC) \ -t $(DEPS_IMAGE) \ $(CURDIR) @@ -420,6 +433,7 @@ CHEZ_MT ?= ta6le CHEZ_MUSL_DIR ?= $(shell ls -d /opt/chez/lib/csv*/$(CHEZ_MT) 2>/dev/null | head -1) build-jemacs-qt-static: check-root + rm -f /src/src/.jerbuild-hashes; \ cp /src/vendor/jerboa-shell/embed-crypto.c /deps/jsh/ 2>/dev/null; \ cp /src/vendor/jerboa-shell/embed-crypto.h /deps/jsh/ 2>/dev/null; \ cp /src/vendor/jerboa-shell/ffi-shim.c /deps/jsh/ 2>/dev/null; \ @@ -428,6 +442,8 @@ build-jemacs-qt-static: check-root gcc -c -O2 /src/vendor/jerboa-shell/crypto_stub.c -o /tmp/jemacs-build/crypto_stub.o; \ fi; \ cd /src && find lib -name '*.so' -o -name '*.wpo' | xargs rm -f 2>/dev/null; \ + find /src/src/jerboa-emacs -name '*.ss' | sed 's|/src/src/|/src/lib/|; s|\.ss$$|.sls|' | xargs rm -f 2>/dev/null; \ + rm -f /src/src/.jerbuild-hashes 2>/dev/null; \ cd /src && make build SCHEME=/opt/chez/bin/scheme JERBOA=/deps/jerboa && \ if [ -f /src/vendor/qt_shim.cpp ]; then \ echo "Rebuilding libqt_shim.a from updated qt_shim.cpp..." && \ @@ -442,8 +458,9 @@ build-jemacs-qt-static: check-root ar rcs /deps/gerbil-qt/vendor/libqt_shim.a \ /deps/gerbil-qt/vendor/qt_shim_static.o; \ fi && \ - cp /src/vendor/chez-qt-ffi-static.ss /deps/chez-qt/chez-qt/ffi.ss && \ - cp /src/vendor/chez-qt-qt.ss /deps/chez-qt/chez-qt/qt.ss && \ + cp -a /src/vendor/chez-qt/. /deps/chez-qt/ && \ + find /deps/chez-qt -name '*.so' -delete && \ + find /deps/chez-qt -name '*.wpo' -delete && \ /opt/chez/bin/scheme --libdirs /deps/chez-qt \ --compile-imported-libraries --script /deps/chez-qt/compile-libs.ss && \ rm -f /deps/chez-qt/chez-qt/*.wpo && \ @@ -487,16 +504,36 @@ build-jemacs-qt-static: check-root /src/support/treesitter_shim.c -Wall && \ gcc -c -O2 -o /tmp/jemacs-build/treesitter_queries.o \ /src/support/treesitter_queries.c -Wall && \ + cp /src/vendor/chez-ssl-static.sls /deps/chez-ssl/src/chez-ssl.sls && \ + cp /src/vendor/jerboa-aws-crypto-native.sls /deps/jerboa-aws/jerboa-aws/crypto.sls && \ + find /deps/chez-ssl -name '*.so' -delete && find /deps/chez-ssl -name '*.wpo' -delete && \ + find /deps/chez-https -name '*.so' -delete && find /deps/chez-https -name '*.wpo' -delete && \ + find /deps/jerboa-aws -name '*.so' -delete && find /deps/jerboa-aws -name '*.wpo' -delete && \ + JEMACS_STATIC=1 /opt/chez/bin/scheme \ + --libdirs /deps/chez-ssl/src:/deps/jerboa/lib \ + --compile-imported-libraries -q --script /src/vendor/chez-ssl-compile-libs.ss && \ + find /deps/chez-ssl -name '*.wpo' -delete && \ + JEMACS_STATIC=1 /opt/chez/bin/scheme \ + --libdirs /deps/chez-https/src:/deps/chez-ssl/src \ + --compile-imported-libraries -q --script /src/vendor/chez-https-compile-libs.ss && \ + find /deps/chez-https -name '*.wpo' -delete && \ + JEMACS_STATIC=1 /opt/chez/bin/scheme \ + --libdirs /deps/jerboa-aws:/deps/chez-https/src:/deps/chez-ssl/src:/deps/jerboa/lib \ + --compile-imported-libraries -q --script /src/vendor/jerboa-aws-compile-libs.ss && \ + find /deps/jerboa-aws -name '*.wpo' -delete && \ JEMACS_STATIC=1 \ CHEZ_DIR=$(CHEZ_MUSL_DIR) \ JERBOA_DIR=/deps/jerboa/lib \ JSH_DIR=/deps/jsh/src \ - GHERKIN_DIR=/deps/gherkin/src \ + GHERKIN_DIR=/src/vendor/gherkin-runtime \ CHEZ_PCRE2_DIR=/deps/chez-pcre2 \ CHEZ_SCINTILLA_DIR=/deps/chez-scintilla/src \ CHEZ_QT_DIR=/deps/chez-qt \ CHEZ_QT_SHIM_DIR=/deps/gerbil-qt/vendor \ - COREUTILS_DIR=/deps/coreutils \ + JSH_COREUTILS_LIB=/deps/jsh/libjsh_coreutils.a \ + JAWS_DIR=/deps/jerboa-aws \ + CHEZ_SSL_DIR=/deps/chez-ssl \ + CHEZ_HTTPS_DIR=/deps/chez-https/src \ TREE_SITTER_INCLUDE=/opt/tree-sitter-include \ TREE_SITTER_LIB=/opt/tree-sitter-lib \ TREE_SITTER_GRAMMARS=/opt/tree-sitter-grammars \ @@ -504,7 +541,7 @@ build-jemacs-qt-static: check-root TREE_SITTER_QUERIES_OBJ=/tmp/jemacs-build/treesitter_queries.o \ PKG_CONFIG_PATH=/opt/qt6-static/lib/pkgconfig \ /opt/chez/bin/scheme \ - --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/deps/coreutils:/deps/gherkin/src:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt \ + --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/src/vendor/gherkin-runtime:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt:/deps/jerboa-aws:/deps/chez-ssl/src:/deps/chez-https/src \ --script build-binary-qt.ss linux-static-qt-docker: @@ -514,49 +551,150 @@ linux-static-qt-docker: --ulimit nofile=8192:8192 \ -v $(CURDIR):/src:z \ -v $(JERBOA)/lib/std:/host-jerboa-std:ro \ - -v $(COREUTILS_SRC)/lib:/host-coreutils:ro \ + -v $(JERBOA)/lib/jerboa:/host-jerboa-core:ro \ -v $(JSH_SRC)/src:/host-jsh-src:ro \ + -v $(JSH_COREUTILS_LIB):/host-jsh-coreutils.a:ro \ + -v $(JAWS_SRC):/host-jaws:ro \ + -v $(CSSL_SRC):/host-chez-ssl:ro \ + -v $(CHTTPS_SRC):/host-chez-https:ro \ $(DEPS_IMAGE) \ - sh -c "apk add --no-cache libvterm-dev libvterm-static >/dev/null 2>&1; \ + sh -c "apk add --no-cache libvterm-dev libvterm-static openssl-dev; \ + if [ ! -f /usr/lib/libssl.a ]; then \ + echo 'Building static OpenSSL (one-time)...' && \ + cd /tmp && wget -q https://www.openssl.org/source/openssl-3.3.2.tar.gz && \ + tar xf openssl-3.3.2.tar.gz && cd openssl-3.3.2 && \ + ./Configure linux-x86_64 no-shared no-tests no-apps -O2 --prefix=/usr && \ + make -j$(nproc) && cp libssl.a libcrypto.a /usr/lib/ && \ + cd / && rm -rf /tmp/openssl-3.3.2*; \ + fi; \ + cp /host-jsh-coreutils.a /deps/jsh/libjsh_coreutils.a; \ cp -a /host-jsh-src/. /deps/jsh/src/; \ - cp -a /host-coreutils/. /deps/coreutils/; \ - find /deps/coreutils -name '*.sls' -exec sed -i 's/(load-shared-object #f)/(void)/g' {} +; \ - for f in \ - misc/atom.sls misc/channel.sls misc/completion.sls misc/list.sls \ - misc/memo.sls misc/number.sls misc/ports.sls misc/process.sls \ - misc/rwlock.sls misc/shuffle.sls misc/string.sls misc/terminal.sls \ - cli/getopt.sls \ - net/request.sls net/uri.sls \ - os/fdio.sls os/signal.sls os/tty.sls os/sandbox.sls \ - text/base64.sls text/diff.sls text/glob.sls text/hex.sls text/json.sls \ - crypto/digest.sls \ - engine.sls fiber.sls guardian.sls select.sls stm.sls task.sls \ - amb.sls \ - misc/thread.sls misc/wg.sls misc/pqueue.sls misc/lru-cache.sls \ - misc/channel.sls misc/atom.sls misc/rbtree.sls \ - misc/rwlock.sls misc/completion.sls misc/barrier.sls \ - result.sls misc/result.sls misc/fmt.sls \ - misc/custodian.sls misc/config.sls misc/memoize.sls \ - misc/terminal.sls misc/trie.sls \ - actor/mpsc.sls actor/core.sls actor/transport.sls \ - crypto/random.sls \ - format.sls iter.sls pregexp.sls sort.sls sugar.sls \ - srfi/srfi-1.sls srfi/srfi-13.sls srfi/srfi-19.sls; do \ - if [ -f /host-jerboa-std/\$$f ]; then \ - mkdir -p /deps/jerboa/lib/std/$$(dirname \$$f); \ - cp /host-jerboa-std/\$$f /deps/jerboa/lib/std/\$$f; \ - rm -f /deps/jerboa/lib/std/\$${f%.sls}.so /deps/jerboa/lib/std/\$${f%.sls}.wpo; \ - echo SYNC: \$$f; \ - else \ - echo SKIP: \$$f not found on host; \ - fi; \ - done; \ + echo 'SYNC: bulk-copying host jerboa std/ and jerboa/ into container...'; \ + cp -a /host-jerboa-std/. /deps/jerboa/lib/std/ && \ + cp -a /host-jerboa-core/. /deps/jerboa/lib/jerboa/ && \ + find /deps/jerboa/lib -name '*.so' -delete && \ + find /deps/jerboa/lib -name '*.wpo' -delete && \ + echo '(import (chezscheme)) (compile-imported-libraries #t) (import (jerboa core)) (import (jerboa prelude))' \ + > /tmp/compile-jerboa-core.ss && \ + cd /deps/jerboa/lib && /opt/chez/bin/scheme --libdirs /deps/jerboa/lib \ + -q --script /tmp/compile-jerboa-core.ss && \ + rm -f /deps/jerboa/lib/jerboa/*.wpo && \ + echo 'COMPILED: jerboa core + prelude'; \ + mkdir -p /deps/jerboa-aws /deps/chez-ssl/src /deps/chez-https/src && \ + cp -a /host-jaws/. /deps/jerboa-aws/ && \ + cp -a /host-chez-ssl/. /deps/chez-ssl/ && \ + cp -a /host-chez-https/. /deps/chez-https/ && \ + echo 'SYNC: jerboa-aws, chez-ssl, chez-https copied'; \ chmod 755 /root && \ chown -R $(UID):$(GID) /opt/ /deps && \ mkdir -p /tmp/jemacs-build && chown $(UID):$(GID) /tmp/jemacs-build && \ exec su-exec $(UID):$(GID) env HOME=/tmp/jemacs-build sh -c '\ cd /src && make build-jemacs-qt-static'" +# ============================================================================= +# Stress testing targets +# ============================================================================= + +STRESS_PORT ?= 9999 + +# Launch jemacs-qt (interpreted) headless with REPL for manual stress testing +stress-run: build repl_shim.so libqt_shim.so vterm_shim.so qt_chez_shim.so + xvfb-run -a env LD_PRELOAD=./qt_chez_shim.so \ + $(SCHEME) $(LIBDIRS) --script qt-main.ss --repl $(STRESS_PORT) + +# Launch jemacs-qt (static binary) under gdb with REPL for crash diagnosis +stress-run-static: + xvfb-run -a gdb -batch \ + -ex 'handle SIGALRM nostop noprint' \ + -ex 'handle SIG34 nostop noprint' \ + -ex run \ + -ex 'bt full' \ + -ex 'thread apply all bt full' \ + -ex 'info registers' \ + --args ./jemacs-qt --repl $(STRESS_PORT) + +# Run the stress test driver against an already-running jemacs-qt REPL +stress-test: + $(SCHEME) $(LIBDIRS) --script tests/stress-test.ss --port $(STRESS_PORT) + +# All-in-one: launch interpreted jemacs-qt + run stress test driver +stress-burn: build repl_shim.so libqt_shim.so vterm_shim.so qt_chez_shim.so + @echo "=== Starting jemacs-qt stress burn-in ===" + @rm -f $(HOME)/.jerboa-repl-port stress-test.log + @xvfb-run -a env CHEZ_QT_SHIM_DIR=$(CURDIR) \ + $(SCHEME) $(LIBDIRS) --script $(CURDIR)/qt-main.ss --repl 0 & + @for i in $$(seq 1 30); do \ + [ -f $(HOME)/.jerboa-repl-port ] && break; \ + sleep 0.5; \ + done + @if [ ! -f $(HOME)/.jerboa-repl-port ]; then \ + echo "ERROR: jemacs-qt failed to start (no REPL port file after 15s)"; exit 1; \ + fi + @PORT=$$(grep -oP '\d+' $(HOME)/.jerboa-repl-port); \ + echo "jemacs-qt running on REPL port $$PORT"; \ + $(SCHEME) $(LIBDIRS) --script tests/stress-test.ss --port $$PORT; \ + echo ""; \ + echo "=== Stress test ended ==="; \ + if [ -f $(HOME)/.jemacs-crash.log ]; then \ + echo "=== CRASH LOG ==="; \ + cat $(HOME)/.jemacs-crash.log; \ + fi; \ + echo "=== STRESS LOG (last 50 lines) ==="; \ + tail -50 stress-test.log 2>/dev/null + +# All-in-one: launch static jemacs-qt under gdb + run stress test driver +stress-burn-static: + @echo "=== Starting jemacs-qt (static) stress burn-in under gdb ===" + @rm -f $(HOME)/.jerboa-repl-port stress-test.log + @xvfb-run -a gdb -batch \ + -ex 'handle SIGALRM nostop noprint' \ + -ex 'handle SIG34 nostop noprint' \ + -ex run \ + -ex 'bt full' \ + -ex 'thread apply all bt full' \ + -ex 'info registers' \ + --args ./jemacs-qt --repl 0 & + @for i in $$(seq 1 30); do \ + [ -f $(HOME)/.jerboa-repl-port ] && break; \ + sleep 0.5; \ + done + @if [ ! -f $(HOME)/.jerboa-repl-port ]; then \ + echo "ERROR: jemacs-qt failed to start (no REPL port file after 15s)"; exit 1; \ + fi + @PORT=$$(grep -oP '\d+' $(HOME)/.jerboa-repl-port); \ + echo "jemacs-qt (static) running under gdb on REPL port $$PORT"; \ + $(SCHEME) $(LIBDIRS) --script tests/stress-test.ss --port $$PORT; \ + echo ""; \ + echo "=== Stress test ended ==="; \ + if [ -f $(HOME)/.jemacs-crash.log ]; then \ + echo "=== CRASH LOG ==="; \ + cat $(HOME)/.jemacs-crash.log; \ + fi; \ + echo "=== STRESS LOG (last 50 lines) ==="; \ + tail -50 stress-test.log 2>/dev/null + +# Behavioral regression tests: headless jemacs-qt + deterministic REPL test driver +# Tests key routing, window splitting, terminal focus, and related invariants. +test-behavioral: build repl_shim.so libqt_shim.so vterm_shim.so qt_chez_shim.so + @echo "=== Starting jemacs-qt behavioral tests ===" + @rm -f $(HOME)/.jerboa-repl-port + @xvfb-run -a env CHEZ_QT_SHIM_DIR=$(CURDIR) \ + $(SCHEME) $(LIBDIRS) --script $(CURDIR)/qt-main.ss --repl 0 & + @for i in $$(seq 1 30); do \ + [ -f $(HOME)/.jerboa-repl-port ] && break; \ + sleep 0.5; \ + done + @if [ ! -f $(HOME)/.jerboa-repl-port ]; then \ + echo "ERROR: jemacs-qt failed to start (no REPL port file after 15s)"; exit 1; \ + fi + @PORT=$$(grep -oP '\d+' $(HOME)/.jerboa-repl-port); \ + echo "jemacs-qt running on REPL port $$PORT"; \ + $(SCHEME) $(LIBDIRS) --script tests/test-behavioral.ss --port $$PORT; \ + STATUS=$$?; \ + pkill -f "qt-main.ss.*--repl" 2>/dev/null; true; \ + echo "=== Behavioral tests done ==="; \ + exit $$STATUS + clean: find lib -name '*.so' -delete 2>/dev/null; true --- a/build-binary-qt.ss +++ b/build-binary-qt.ss @@ -82,10 +82,15 @@ (define qt-shim-dir (or (getenv "CHEZ_QT_SHIM_DIR") (format "~a/mine/gerbil-qt/vendor" home))) - -(define coreutils-dir - (or (getenv "COREUTILS_DIR") - "/deps/coreutils")) +(define jaws-dir + (or (getenv "JAWS_DIR") + (format "~a/mine/jerboa-aws/lib" home))) +(define chez-ssl-dir + (or (getenv "CHEZ_SSL_DIR") + (format "~a/mine/chez-ssl" home))) +(define chez-https-dir + (or (getenv "CHEZ_HTTPS_DIR") + (format "~a/mine/chez-https/src" home))) ;; Static build detection (needed before dep checks) (define jemacs-static? @@ -129,6 +134,9 @@ (printf "Sci dir: ~a~n" sci-dir) (printf "Qt dir: ~a~n" qt-dir) (printf "Qt shim dir: ~a~n" qt-shim-dir) +(printf "jaws dir: ~a~n" jaws-dir) +(printf "chez-ssl dir: ~a~n" chez-ssl-dir) +(printf "chez-https dir:~a~n" chez-https-dir) ;; --- Step 1: Compile all modules + entry point --- (printf "~n[1/7] Compiling all modules (optimize-level 3, WPO)...~n") @@ -184,6 +192,9 @@ "std/misc/terminal" "std/misc/trie" "std/misc/lru-cache" + "std/misc/ports" + "std/srfi/srfi-13" + "std/text/json" "std/actor/mpsc" "std/actor/core" "std/actor/transport" @@ -192,9 +203,13 @@ "std/os/sandbox" "std/os/landlock" "std/security/capsicum")) - ;; Jerboa core + sugar + repl + ;; Jerboa core + sugar + repl + dependencies + ;; std/typed: jerboa/core imports it + ;; std/result: std/sugar imports it (map (lambda (m) (format "~a/~a.so" jerboa-dir m)) - '("jerboa/core" + '("std/result" + "std/typed" + "jerboa/core" "std/sugar" "std/repl")) ;; std/net/tcp and std/net/uri (compiled by step 1) @@ -260,6 +275,14 @@ '("ffi" "pcre2")) ;; std/net/request (WPO-missing) (list (format "~a/std/net/request.so" jerboa-dir)) + ;; chez-ssl + chez-https (TLS for AWS API) + (list (format "~a/src/chez-ssl.so" chez-ssl-dir) + (format "~a/chez-https.so" chez-https-dir)) + ;; jerboa-aws EC2 modules + (map (lambda (m) (format "~a/jerboa-aws/~a.so" jaws-dir m)) + '("creds" "crypto" "xml" "json" "time" "uri" "sigv4" "request" "api")) + (map (lambda (m) (format "~a/jerboa-aws/ec2/~a.so" jaws-dir m)) + '("xml" "params" "api" "instances")) ;; chez-scintilla (all modules — WPO-missing) (map (lambda (m) (format "~a/chez-scintilla/~a.so" sci-dir m)) '("ffi" "constants" "style" "lexer" "scintilla" "tui")) @@ -415,7 +438,7 @@ ;; define-foreign name "c-name" — jsh macro (C name is the second string) (gen-cmd (format - "{ { cat ~a ~a; find ~a ~a/jerboa ~a/chez-scintilla lib/jerboa-emacs lib/jerboa vendor -name '*.sls' -o -name '*.ss' | xargs cat 2>/dev/null; cat ~a/jerboa-coreutils/top.sls 2>/dev/null; } | \ + "{ { cat ~a ~a; find ~a ~a/jerboa ~a/std/os ~a/std/net ~a/std/crypto ~a/std/security ~a/chez-scintilla lib/jerboa-emacs lib/jerboa vendor -name '*.sls' -o -name '*.ss' | xargs cat 2>/dev/null; } | \ sed 's/;;.*//' | grep -oE '(foreign-procedure|foreign-entry\\?) \"[^\"]*\"' | sed 's/.* \"//;s/\"//'; \ { cat ~a ~a; } | sed 's/;;.*//' | grep -o 'define-optional-ffi [^ ]* \"[^\"]*\"' | sed 's/.*define-optional-ffi [^ ]* \"//;s/\"//'; \ find ~a -name '*.sls' -o -name '*.ss' | \ @@ -426,6 +449,7 @@ sort -u | grep -v '^$' | grep -v '^_NSGetExecutablePath$' | grep -v '^io_uring_' grep -v '^jerboa_' | grep -v '^SSL_' | grep -v '^TLS_' | grep -v '^EVP_' | \ grep -v '^CRYPTO_' | grep -v '^PKCS5_' | grep -v '^RAND_' | \ grep -v '^QRcode_' | grep -v '^embed_encrypt$' | grep -v '^embed_random_bytes$' | \ +grep -v '^kqueue$' | grep -v '^kevent$' | grep -v '^sandbox_' | \ grep -v '^__error$' > /tmp/ffi_syms.txt && \ awk '\ BEGIN{ print \"/* Auto-generated — do not edit */\"; \ @@ -439,7 +463,7 @@ echo \"}\" >> qt_static_symbols.c && \ rm /tmp/ffi_syms.txt && \ echo OK" ffi-path pcre2-ffi-path - jsh-dir jerboa-dir sci-dir coreutils-dir + jsh-dir jerboa-dir jerboa-dir jerboa-dir jerboa-dir jerboa-dir sci-dir ffi-path pcre2-ffi-path jsh-dir)) (result (shell-output gen-cmd ""))) @@ -519,6 +543,14 @@ echo OK" (display "Error: chez_scintilla_stubs.c compilation failed\n") (exit 1)))) +;; chez-ssl shim (TLS FFI for jerboa-aws EC2 API calls) +(when jemacs-static? + (let* ((cmd (format "gcc -c -O2 -o jemacs-qt-chez-ssl-shim.o ~a/chez_ssl_shim.c -Wall 2>&1" + chez-ssl-dir))) + (unless (= 0 (system cmd)) + (display "Error: chez_ssl_shim.c compilation failed\n") + (exit 1)))) + ;; pty shim (needed for static builds — pty_* symbols from support/pty_shim.c) (when jemacs-static? (let* ((cmd "gcc -c -O2 -o jemacs-qt-pty-shim.o support/pty_shim.c -Wall 2>&1")) @@ -669,18 +701,26 @@ grep -v '^$' | grep -v '^register_static_foreign_symbols$'") ts-shim-obj ts-queries-obj ts-lib-dir ts-gram-dir)) (crypto-stub (if (file-exists? "/tmp/jemacs-build/crypto_stub.o") "/tmp/jemacs-build/crypto_stub.o" "")) + ;; jsh Rust coreutils static library (musl build, pre-compiled on host) + (jsh-coreutils-lib (or (getenv "JSH_COREUTILS_LIB") "")) + (ssl-libs (let ((pkgconf (shell-output "pkg-config --static --libs openssl 2>/dev/null" ""))) + (if (> (string-length pkgconf) 0) + pkgconf + "-L/usr/lib -lssl -lcrypto"))) (cmd (format "g++ -static -Wl,--export-dynamic -o jemacs-qt \ jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o jemacs-qt-jsh-ffi.o \ jemacs-qt-libcoreutils.o jemacs-qt-jsh-coreutils-stubs.o \ jemacs-qt-embed-crypto.o jemacs-qt-ssh-agent-stub.o ~a \ jemacs-qt-pty-shim.o jemacs-qt-vterm-shim.o jemacs-qt-repl-shim.o jemacs-qt-jerboa-landlock.o jemacs-qt-sci-stubs.o \ +jemacs-qt-chez-ssl-shim.o \ qt_static_symbols.o \ -~a ~a ~a ~a ~a \ +~a ~a ~a ~a ~a ~a \ -L~a -lkernel -llz4 -lz \ +~a \ -lvterm -lm -ldl -lpthread -luuid -lncurses -lstdc++ 2>&1" crypto-stub - libqt-shim qt-plugins ts-link qt-libs pcre2-libs - chez-dir))) + libqt-shim qt-plugins ts-link qt-libs pcre2-libs jsh-coreutils-lib + chez-dir ssl-libs))) (printf " ~a~n" cmd) (unless (= 0 (system cmd)) (display "Error: Static link failed\n") @@ -738,7 +778,8 @@ jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o \ "jemacs-qt-ssh-agent-stub.o" "jemacs-qt-ssh-agent-stub.c" "jemacs-qt-pty-shim.o" "jemacs-qt-vterm-shim.o" "jemacs-qt-jerboa-landlock.o" - "jemacs-qt-sci-stubs.o" "qt_static_symbols.o" "qt_static_symbols.c") + "jemacs-qt-sci-stubs.o" "jemacs-qt-chez-ssl-shim.o" + "qt_static_symbols.o" "qt_static_symbols.c") '()))) (printf "~n========================================~n") --- a/docs/jemacs-vs-emacs.md +++ b/docs/jemacs-vs-emacs.md @@ -1,9 +1,9 @@ # Jemacs vs GNU Emacs — Feature Comparison -> **Last updated:** 2026-03-10 -> **Jemacs version:** master (262cd55) +> **Last updated:** 2026-03-27 +> **Jemacs version:** features (87a8177) > **Compared against:** GNU Emacs 29.x / 30.x feature set -> **Command parity:** 2168+ commands registered in both TUI and Qt layers (zero gap) +> **Command parity:** 2268+ commands registered in both TUI and Qt layers (zero gap) ## Status Legend @@ -1243,6 +1243,18331 @@ No remaining Tier 1 gaps. All core editing, completion, and navigation features --- +## Recently Added Features (2026-03-27, Rounds 7–11) + +| Package / Feature | Status | Notes | +|---------|--------|-------| +| Spray (RSVP speed reading) | :orange_circle: | Speed reading mode with configurable WPM | +| Ledger-mode | :orange_circle: | Ledger file report via external `ledger` command | +| Buffer-move | :orange_circle: | Swap buffer positions (up/down) | +| Fortune | :orange_circle: | Display fortune cookie from `fortune` command | +| Snake game | :orange_circle: | Text-based snake game in buffer | +| Graphviz preview | :orange_circle: | Render DOT files via `dot` command | +| Thesaurus | :orange_circle: | Word synonym lookup via API | +| Grammar-check | :orange_circle: | Grammar checking via `languagetool` CLI | +| Morse code encode/decode | :orange_circle: | Convert text to/from Morse code | +| Highlight-sentence | :orange_circle: | Highlight current sentence with indicator overlay | +| Mastodon client | :orange_circle: | Post to Mastodon via `toot` CLI | +| QR code generator | :orange_circle: | Generate QR codes via `qrencode` | +| Keychain status | :orange_circle: | SSH/GPG keychain status and add keys | +| Eyebrowse (workspace switch) | :orange_circle: | Named workspace switching with save/restore | +| Chess | :orange_circle: | Chess game in buffer with text board | +| Sudoku | :orange_circle: | Sudoku puzzle generator and solver | +| Pong | :orange_circle: | Classic pong game in buffer | +| Org-pomodoro | :orange_circle: | Pomodoro timer with org-mode integration | +| LanguageTool check | :orange_circle: | Grammar/style checking via LanguageTool CLI | +| Newsticker (RSS) | :orange_circle: | RSS feed reader with configurable feeds, title extraction | +| Auth-source | :orange_circle: | In-memory credential store (save/search) | +| Gomoku | :orange_circle: | Five-in-a-row game with win detection | +| Dissociated Press | :orange_circle: | Scramble buffer text by random word mixing | +| MPUZ | :orange_circle: | Multiplication puzzle game | +| Blackbox | :orange_circle: | Logic puzzle game with guess/reveal | +| Literate-calc | :orange_circle: | Evaluate arithmetic expressions inline | +| Htmlize | :orange_circle: | Export buffer content as styled HTML file | +| Keycast mode | :orange_circle: | Show last key press and command name | +| Command-log | :orange_circle: | Log executed commands to a reviewable buffer | +| Macrostep | :orange_circle: | Expand Scheme macro at point and display | +| Eat (terminal toggle) | :orange_circle: | Toggle dedicated terminal buffer | +| Envrc (direnv) | :orange_circle: | Load `.envrc` environment via direnv | +| Org-present | :orange_circle: | Slide-based presentation from org headings | +| Denote (simple notes) | :orange_circle: | Timestamp-based note creation and search in ~/notes/ | +| Detached processes | :orange_circle: | Run background processes, list sessions | +| Inheritenv | :orange_circle: | Refresh process environment from login shell | +| Calc-grab-region | :orange_circle: | Evaluate selected text as numeric expression | +| Coterm | :orange_circle: | Run shell commands with output in buffer, history | +| Atomic-chrome | :orange_circle: | Browser text editing setup (GhostText compatible) | +| Wordle | :orange_circle: | 5-letter word guessing game with color feedback | +| Minesweeper | :orange_circle: | Minesweeper game with reveal and flag | +| Sokoban | :orange_circle: | Box-pushing puzzle game | +| 2048 game | :orange_circle: | Tile sliding number game | +| Git-link | :orange_circle: | Copy GitHub/GitLab URL for current file+line | +| Browse-at-remote | :orange_circle: | Open current file in remote forge browser | +| Code-review | :orange_circle: | Interactive git diff review in buffer | +| Conventional-commit | :orange_circle: | Guided conventional commit (feat/fix/docs/etc) | +| Clippy | :orange_circle: | Random helpful editor tips | +| Ellama (LLM) | :orange_circle: | Query local LLM via `ollama` | +| Hacker News client | :orange_circle: | Fetch top HN stories via Firebase API | +| Biblio (bibliography) | :orange_circle: | Academic paper search via CrossRef API | +| EPA encrypt/decrypt | :orange_circle: | GPG symmetric encryption/decryption of files | +| Typit (typing test) | :orange_circle: | Typing accuracy and speed test | +| Diff-at-point | :orange_circle: | Show git diff for current file | +| Magit-delta | :orange_circle: | Pretty diff via `delta` or colored git diff | +| Figlet | :orange_circle: | Convert text to ASCII art via `figlet` | +| Cowsay | :orange_circle: | Insert cowsay ASCII art | +| Habit tracker | :orange_circle: | Track daily habit completions with report | +| Ement (Matrix chat) | :orange_circle: | Matrix chat via `matrix-commander` | +| Journalctl viewer | :orange_circle: | View systemd journal entries by unit | +| Bluetooth control | :orange_circle: | List bluetooth devices via `bluetoothctl` | +| Volume control | :orange_circle: | Show/adjust system volume via PulseAudio/ALSA | +| ASCII table | :orange_circle: | Display full ASCII character reference table | +| Unicode search | :orange_circle: | Search and insert Unicode characters by name (50+ symbols) | +| Emoji insert | :orange_circle: | Insert emoji by name with completion (44 emoji) | +| Kaomoji | :orange_circle: | Insert Japanese emoticons by mood (19 kaomoji) | +| XKCD viewer | :orange_circle: | Fetch and display latest XKCD comic info | +| Cheat.sh | :orange_circle: | Look up cheat sheets from cheat.sh | +| TLDR pages | :orange_circle: | TLDR command reference lookup | +| HTTP stat | :orange_circle: | HTTP request timing statistics via curl | +| JWT decode | :orange_circle: | Decode JWT tokens (header + payload) | +| XML format | :orange_circle: | Pretty-print XML via xmllint/python | +| CSV sort | :orange_circle: | Sort CSV data by column | +| Markdown TOC | :orange_circle: | Generate table of contents from markdown headings | +| Focus mode | :orange_circle: | Minimal UI mode (hide margins/line numbers) | +| Typewriter mode | :orange_circle: | Keep cursor centered vertically | +| Matrix rain | :orange_circle: | Matrix-style digital rain animation | +| WiFi status | :orange_circle: | Show WiFi networks via nmcli/iwconfig | +| Screenshot | :orange_circle: | Take screenshot via import/scrot/gnome-screenshot | +| NPM scripts | :orange_circle: | Run npm scripts from package.json with completion | +| Cargo (Rust) | :orange_circle: | Run cargo commands with completion | +| Brew (Homebrew) | :orange_circle: | Run homebrew commands with package completion | +| Git stash list | :orange_circle: | List git stashes with details | +| Git cherry-pick | :orange_circle: | Cherry-pick commits by hash | +| Git worktree | :orange_circle: | List/add/remove git worktrees | +| IP info | :orange_circle: | Show public IP information via ipinfo.io | +| Whois lookup | :orange_circle: | Whois domain lookup | +| Traceroute | :orange_circle: | Network route tracing | +| Netstat | :orange_circle: | Show active network connections via ss/netstat | +| Crontab editor | :orange_circle: | View user crontab in buffer | +| Htop (process list) | :orange_circle: | Top processes by memory usage | +| Disk usage summary | :orange_circle: | Directory disk usage sorted by size | +| File permissions | :orange_circle: | Show file permissions and ownership | +| Compress | :orange_circle: | Compress files (tar.gz/bz2/xz/zip) | +| Extract | :orange_circle: | Extract archives (tar/zip/gz/bz2/xz) | +| Diff buffers | :orange_circle: | Diff two named buffers | +| Sort lines by field | :orange_circle: | Sort buffer lines by whitespace-delimited field | +| Vagrant | :orange_circle: | Run vagrant commands with completion | +| Pip (Python) | :orange_circle: | Run pip commands with package completion | +| Docker PS | :orange_circle: | List running Docker containers with formatted output | +| Docker logs | :orange_circle: | View container logs by name/ID | +| Git log graph | :orange_circle: | Graphical git log with branch visualization | +| Git bisect | :orange_circle: | Interactive git bisect (start/good/bad/reset) | +| Git reflog | :orange_circle: | View git reflog with timestamps | +| Git tag | :orange_circle: | List/create/delete git tags | +| Randomize lines | :orange_circle: | Fisher-Yates shuffle of buffer lines | +| Titlecase region | :orange_circle: | Convert selection to Title Case | +| Goto percent | :orange_circle: | Jump to percentage position in buffer | +| Copy filename | :orange_circle: | Copy current buffer's filename to kill ring | +| Copy filepath | :orange_circle: | Copy current buffer's full path to kill ring | +| Hex to decimal | :orange_circle: | Convert hex values to decimal | +| Decimal to hex | :orange_circle: | Convert decimal values to hex | +| Binary to decimal | :orange_circle: | Convert binary values to decimal/hex | +| String to hex | :orange_circle: | Show hex representation of text | +| ROT47 | :orange_circle: | ROT47 encoding/decoding of text | +| SHA256 hash | :orange_circle: | Compute SHA256 hash of text via sha256sum | +| MD5 hash | :orange_circle: | Compute MD5 hash of text via md5sum | +| Word frequency | :orange_circle: | Word frequency analysis with top-50 display | +| Text statistics | :orange_circle: | Characters, words, lines, sentences, reading time | +| String reverse | :orange_circle: | Reverse selected text or current line | +| Sort words | :orange_circle: | Alphabetically sort words in selection | +| Unique lines | :orange_circle: | Remove duplicate lines from buffer | +| Encode HTML entities | :orange_circle: | Encode &<>"' as HTML entities | +| Decode HTML entities | :orange_circle: | Decode HTML entities back to characters | +| URL decode | :orange_circle: | Decode URL-encoded text via Python | +| CamelCase to snake_case | :orange_circle: | Case conversion for identifiers | +| snake_case to camelCase | :orange_circle: | Case conversion for identifiers | +| kebab-case to camelCase | :orange_circle: | Case conversion for identifiers | +| Wrap region | :orange_circle: | Wrap selection with user-specified chars | +| Unwrap region | :orange_circle: | Remove outermost wrapping characters | +| Quote region | :orange_circle: | Prefix each line with > | +| Strip comments | :orange_circle: | Remove comment lines (#, //, ;) | +| Insert file header | :orange_circle: | Auto-detect comment style, insert header template | +| Insert license | :orange_circle: | MIT, Apache, GPL, BSD, Unlicense templates | +| Insert shebang | :orange_circle: | Shebang lines for bash, python, ruby, node, etc. | +| Open in external app | :orange_circle: | Open file with xdg-open | +| Copy line number | :orange_circle: | Copy current line number to kill ring | +| Rename file and buffer | :orange_circle: | Rename file on disk and update buffer | +| Sudo edit | :orange_circle: | Re-open file with sudo privileges | +| Insert date header | :orange_circle: | Insert formatted date/time header at point | +| Highlight phrase | :orange_circle: | Highlight all occurrences of a phrase (indicator overlay) | +| Unhighlight all | :orange_circle: | Clear all phrase highlights | +| Widen buffer | :orange_circle: | Remove narrowing, show full buffer | +| Move region up | :orange_circle: | Move selected lines up | +| Move region down | :orange_circle: | Move selected lines down | +| JSON to YAML | :orange_circle: | Convert JSON to YAML via Python | +| YAML to JSON | :orange_circle: | Convert YAML to JSON via Python | +| CSV to JSON | :orange_circle: | Convert CSV to JSON via Python | +| JSON to CSV | :orange_circle: | Convert JSON array to CSV via Python | +| Hex to RGB | :orange_circle: | Convert hex color (#FF8800) to rgb() format | +| RGB to hex | :orange_circle: | Convert rgb() color to hex format | +| Unix timestamp | :orange_circle: | Insert/convert Unix timestamps (now, from-date, to-date) | +| Format JSON | :orange_circle: | Pretty-print JSON via python3 json.tool | +| Minify JSON | :orange_circle: | Compact JSON to single line | +| File info | :orange_circle: | Show file size, permissions, owner, type, line count | +| Git contributors | :orange_circle: | Show top contributors via git shortlog | +| Git file history | :orange_circle: | Show git log for current file | +| Copy git branch | :orange_circle: | Copy current git branch name to kill ring | +| Eval and replace | :orange_circle: | Evaluate selection as shell/bc expression, replace with result | +| String inflection cycle | :orange_circle: | Cycle camelCase/snake_case/SCREAMING_SNAKE/kebab-case | +| Crux kill whole line | :orange_circle: | Kill entire line regardless of cursor position | +| Crux transpose windows | :orange_circle: | Swap buffers between current and next window | +| Crux delete file and buffer | :orange_circle: | Delete file from disk and kill its buffer | +| Smartscan symbol forward | :orange_circle: | Jump to next occurrence of symbol at point | +| Smartscan symbol backward | :orange_circle: | Jump to previous occurrence of symbol at point | +| Toggle quotes | :orange_circle: | Toggle between single and double quotes | +| Browse URL at point | :orange_circle: | Open URL under cursor in web browser | +| Dumb jump | :orange_circle: | Jump to definition using grep/rg (no LSP needed) | +| Diff buffer with file | :orange_circle: | Show diff between buffer and file on disk | +| Copy as format | :orange_circle: | Copy selection as markdown/org/html/slack/jira code block | +| Edit indirect | :orange_circle: | Edit selected region in a separate buffer | +| Crux indent defun | :orange_circle: | Re-indent entire function/defun | +| Crux cleanup buffer | :orange_circle: | Remove trailing whitespace, blank lines, cleanup | +| Recover file | :orange_circle: | Recover file from auto-save backup | +| Hexl mode | :orange_circle: | View/edit buffer in hexadecimal via xxd | +| Zone | :orange_circle: | Screensaver-like text melt animation | +| Doctor | :orange_circle: | Eliza psychotherapist session | +| Animate string | :orange_circle: | Animate text dropping from top of buffer | +| Tetris | :orange_circle: | Classic Tetris game in editor buffer | +| Morse region | :orange_circle: | Convert text to Morse code | +| Unmorse region | :orange_circle: | Convert Morse code back to text | +| Proced mode | :orange_circle: | Process viewer/manager (ps aux) | +| EWW open file | :orange_circle: | Render HTML file as text via w3m/lynx | +| Webjump | :orange_circle: | Quick jump to configured search engines | +| RSS feed | :orange_circle: | Simple RSS/Atom feed reader | +| Garbage collect | :orange_circle: | Run GC and display heap statistics | +| Benchmark run | :orange_circle: | Benchmark a shell command with timing stats | +| Describe personal keybindings | :orange_circle: | Show user-customized keybindings | +| Newsticker show news | :orange_circle: | Fetch Hacker News top headlines | +| Local set key | :orange_circle: | Set a local keybinding for session | +| Unbind key | :orange_circle: | Unbind a key sequence | +| Align entire | :orange_circle: | Align entire buffer by separator character | +| Studlify region | :orange_circle: | StUdLiFy text (alternating case) | +| Compile goto error | :orange_circle: | Jump to file:line from compile error | +| Signal process | :orange_circle: | Send signal to process by PID | +| Kill process | :orange_circle: | Kill process by PID or name | +| Text scale adjust | :orange_circle: | Interactive text zoom +/-/0 | +| Memory use counts | :orange_circle: | Display Chez Scheme memory statistics | +| Execute named kbd macro | :orange_circle: | Execute/list named keyboard macros | +| Emoji search | :orange_circle: | Search and insert emoji by name | +| Emoji list | :orange_circle: | Display emoji in a buffer | +| UCS insert | :orange_circle: | Insert Unicode character by codepoint or name | +| Char info | :orange_circle: | Show character details (decimal, hex, octal) | +| List colors display | :orange_circle: | Display named color palette | +| List faces display | :orange_circle: | Display Scintilla style information | +| Display battery mode | :orange_circle: | Show battery status from sysfs | +| View hello file | :orange_circle: | Multilingual HELLO greetings | +| Auto highlight symbol | :orange_circle: | Highlight all instances of symbol at point | +| Pulse momentary highlight | :orange_circle: | Flash/pulse current line (indicator overlay) | +| Prettier mode | :orange_circle: | Format code via prettier | +| Clang format | :orange_circle: | Format C/C++ via clang-format | +| Eglot format | :orange_circle: | Format buffer via detected formatter | +| Reformatter | :orange_circle: | Format with custom formatter command | +| Nav flash show | :orange_circle: | Flash line after navigation jump | +| Describe symbol | :orange_circle: | Look up Scheme symbol documentation | +| Apropos variable | :orange_circle: | Search variables by name pattern | +| Locate library | :orange_circle: | Find library file path | +| Load library | :orange_circle: | Load a Scheme library file | +| Finder by keyword | :orange_circle: | Find commands by keyword search | +| Insert Lorem Ipsum | :orange_circle: | Generate Lorem Ipsum placeholder text | +| Generate password | :orange_circle: | Generate random password (configurable length) | +| Insert UUID | :orange_circle: | Insert UUID v4 at point | +| ASCII art text | :orange_circle: | Convert text to ASCII art via figlet | +| Matrix effect | :orange_circle: | Matrix-style rain animation | +| Game of Life | :orange_circle: | Conway's Game of Life simulation | +| Mandelbrot | :orange_circle: | Text-based Mandelbrot set rendering | +| Maze generator | :orange_circle: | Random maze via recursive backtracker | +| Typing speed test | :orange_circle: | WPM typing speed test | +| Pomodoro timer | :orange_circle: | 25-minute work session timer | +| Stopwatch | :orange_circle: | Simple stopwatch | +| Countdown timer | :orange_circle: | Countdown timer with configurable duration | +| Snow effect | :orange_circle: | Animated snowfall in buffer | +| Hangman | :orange_circle: | Hangman word game | +| Image to ASCII | :orange_circle: | Convert image to ASCII art via jp2a | +| Buffer menu | :orange_circle: | Enhanced buffer list with modification status | +| Fire effect | :orange_circle: | Animated fire simulation | +| Lolcat | :orange_circle: | Rainbow text simulation | +| Toggle narrow to region | :orange_circle: | Toggle narrowing to selected region | +| Password store | :orange_circle: | Interact with pass password manager | +| Tic-tac-toe | :orange_circle: | Two-player tic-tac-toe game | +| Rock paper scissors | :orange_circle: | Play against the computer | +| Dice roller | :orange_circle: | Roll dice with notation (2d6, 1d20+5) | +| Coin flip | :orange_circle: | Flip a coin (heads/tails) | +| Towers of Hanoi | :orange_circle: | Hanoi puzzle visualization with solution | +| System info | :orange_circle: | Comprehensive system information | +| CPU info | :orange_circle: | Display CPU details via lscpu | +| Free memory | :orange_circle: | Show memory usage via free | +| Network interfaces | :orange_circle: | List network interfaces and IPs | +| Environment variables | :orange_circle: | Display sorted env vars | +| Kernel info | :orange_circle: | Show kernel information via uname | +| Hostname info | :orange_circle: | Show hostname and domain | +| List processes tree | :orange_circle: | Process tree visualization via pstree | +| Systemd status | :orange_circle: | Show systemd service status | +| Journal log | :orange_circle: | View journalctl logs | +| Dmesg view | :orange_circle: | View kernel dmesg messages | +| Installed packages | :orange_circle: | List installed packages (dpkg/rpm/pacman) | +| Apt search | :orange_circle: | Search apt packages | +| Connect Four | :orange_circle: | Connect Four board game | +| Fifteen puzzle | :orange_circle: | 15-puzzle sliding tile game | +| Currency convert | :orange_circle: | Currency conversion via Python | +| Wikipedia summary | :orange_circle: | Fetch Wikipedia article summaries | +| Man page | :orange_circle: | View man pages in buffer | +| Info page | :orange_circle: | View info pages in buffer | +| TLDR page | :orange_circle: | View tldr simplified man pages | +| Tutorial mode | :orange_circle: | Built-in jemacs tutorial | +| Version info | :orange_circle: | Display jemacs version info | +| Changelog view | :orange_circle: | View git changelog in buffer | +| Bug report mode | :orange_circle: | Generate bug report template | +| Color theme select | :orange_circle: | Theme selector with preview | +| Paredit mode | :orange_circle: | Paredit structural editing reference | +| Hi-lock mode | :orange_circle: | Highlight pattern occurrences | +| Syntax highlight region | :orange_circle: | Region syntax statistics | +| Stack Overflow search | :orange_circle: | Stack Overflow search helper | +| Cheat sheet | :orange_circle: | Editor keybinding cheat sheet | +| Apropos documentation | :orange_circle: | Search commands by keyword | +| Scratch message | :orange_circle: | Insert default *scratch* message | +| Geiser mode | :orange_circle: | Geiser Scheme interaction reference | +| SLY mode | :orange_circle: | SLY Common Lisp IDE reference | +| SLIME mode | :orange_circle: | SLIME Common Lisp IDE reference | +| Auto-fill mode | :orange_circle: | Toggle auto line wrapping at fill column | +| Display line numbers mode | :orange_circle: | Toggle line number margin | +| Visual line mode | :orange_circle: | Toggle word wrap display | +| Whitespace cleanup | :orange_circle: | Remove trailing whitespace | +| Indent rigidly | :orange_circle: | Indent/dedent region by N spaces | +| Align regexp | :orange_circle: | Align region on a pattern | +| Comment DWIM | :orange_circle: | Smart comment/uncomment line or region | +| Uncomment region | :orange_circle: | Remove comment markers from region | +| Toggle comment | :orange_circle: | Toggle comment on line/region | +| Fill paragraph | :orange_circle: | Wrap paragraph to fill column | +| Fill region | :orange_circle: | Wrap all paragraphs in region | +| Justify paragraph | :orange_circle: | Right-justify paragraph text | +| Center line | :orange_circle: | Center current line in fill column | +| Set fill column | :orange_circle: | Set the fill column width | +| Auto-revert mode | :orange_circle: | Toggle auto-refresh from disk | +| Revert buffer quick | :orange_circle: | Reload buffer from disk without confirm | +| Rename visited file | :orange_circle: | Rename file and update buffer | +| Make directory | :orange_circle: | Create a new directory | +| Delete directory | :orange_circle: | Delete a directory recursively | +| Copy directory | :orange_circle: | Copy a directory recursively | +| Abbrev mode | :orange_circle: | Toggle abbreviation expansion | +| Expand abbrev | :orange_circle: | Expand abbreviation at point | +| Define abbrev | :orange_circle: | Define a new abbreviation | +| List abbrevs | :orange_circle: | List all defined abbreviations | +| Insert register | :orange_circle: | Insert text from named register | +| Copy to register | :orange_circle: | Store region in named register | +| Point to register | :orange_circle: | Save cursor position to register | +| Jump to register | :orange_circle: | Jump to saved position in register | +| View register | :orange_circle: | Show contents of a register | +| List registers | :orange_circle: | List all registers and contents | +| Append to buffer | :orange_circle: | Append region to another buffer | +| Prepend to buffer | :orange_circle: | Prepend region to another buffer | +| Copy to buffer | :orange_circle: | Replace buffer contents with region | +| Insert buffer | :orange_circle: | Insert another buffer at point | +| Append to file | :orange_circle: | Append region to a file | +| Write region | :orange_circle: | Write region to a file | +| Print buffer | :orange_circle: | Send buffer to printer via lpr | +| LPR buffer | :orange_circle: | Print buffer (lpr alias) | +| Flush lines | :orange_circle: | Delete lines matching pattern | +| Keep lines | :orange_circle: | Keep only lines matching pattern | +| How many | :orange_circle: | Count occurrences of a pattern | +| Count matches | :orange_circle: | Count pattern matches (alias) | +| Occur mode | :orange_circle: | Show all lines matching pattern | +| Delete matching lines | :orange_circle: | Delete lines matching pattern | +| Delete non-matching lines | :orange_circle: | Keep only matching lines | +| Transpose lines | :orange_circle: | Swap current and previous line | +| Transpose words | :orange_circle: | Swap words around cursor | +| Transpose sexps | :orange_circle: | Swap S-expressions (placeholder) | +| Transpose paragraphs | :orange_circle: | Swap current and previous paragraph | +| Upcase word | :orange_circle: | Convert word to uppercase | +| Downcase word | :orange_circle: | Convert word to lowercase | +| Capitalize word | :orange_circle: | Capitalize word at cursor | +| Upcase initials | :orange_circle: | Upcase first letter of each word | +| Tabify | :orange_circle: | Convert spaces to tabs in region | +| Untabify | :orange_circle: | Convert tabs to spaces in region | +| Indent region | :orange_circle: | Indent all lines in region | +| Back to indentation | :orange_circle: | Move to first non-whitespace on line | +| Delete indentation | :orange_circle: | Join line with previous | +| Fixup whitespace | :orange_circle: | Collapse whitespace around point | +| Just one space | :orange_circle: | Replace whitespace with single space | +| Delete horizontal space | :orange_circle: | Delete spaces/tabs around point | +| Cycle spacing | :orange_circle: | Cycle between one/no/original spacing | +| Zap to char | :orange_circle: | Delete to next char occurrence (inclusive) | +| Zap up to char | :orange_circle: | Delete up to next char occurrence | +| Delete pair | :orange_circle: | Delete matching pair characters | +| Mark word | :orange_circle: | Select word at point | +| Mark sexp | :orange_circle: | Select S-expression at point | +| Mark paragraph | :orange_circle: | Select current paragraph | +| Mark page | :orange_circle: | Select current page | +| Mark whole buffer | :orange_circle: | Select entire buffer | +| Narrow to page | :orange_circle: | Narrow buffer to current page | +| Widen | :orange_circle: | Restore buffer from narrowing | +| Goto char | :orange_circle: | Go to character position | +| Goto line relative | :orange_circle: | Go to line relative to current | +| Set goal column | :orange_circle: | Set/clear goal column for movement | +| What line | :orange_circle: | Show current line number | +| What page | :orange_circle: | Show current page number | +| What cursor position | :orange_circle: | Show detailed cursor position info | +| Count words region | :orange_circle: | Count words in region or buffer | +| Count lines region | :orange_circle: | Count lines in region or buffer | +| Count lines page | :orange_circle: | Count lines on current page | +| Find file literally | :orange_circle: | Open file without conversions | +| Find file read-only | :orange_circle: | Open file in read-only mode | +| Find alternate file | :orange_circle: | Replace buffer with another file | +| Insert file contents | :orange_circle: | Insert file contents at point | +| Recover this file | :orange_circle: | Recover from auto-save file | +| Auto-save mode | :orange_circle: | Toggle auto-save mode | +| Not modified | :orange_circle: | Clear buffer modified flag | +| Set visited file name | :orange_circle: | Change file associated with buffer | +| Toggle read-only | :orange_circle: | Toggle read-only mode | +| Rename buffer | :orange_circle: | Rename current buffer | +| Clone buffer | :orange_circle: | Create copy of current buffer | +| Clone indirect buffer | :orange_circle: | Create indirect buffer copy | +| Bury buffer | :orange_circle: | Move buffer to end of list | +| Unbury buffer | :orange_circle: | Switch to least recently used buffer | +| Previous buffer | :orange_circle: | Switch to previous buffer | +| Next buffer | :orange_circle: | Switch to next buffer | +| List buffers | :orange_circle: | Show all buffers (C-x C-b) | +| IBuffer | :orange_circle: | Interactive buffer list | +| Display buffer | :orange_circle: | Display buffer in other window | +| Switch to buffer other window | :orange_circle: | Switch buffer in other window | +| Balance windows | :orange_circle: | Make all windows equal size | +| Shrink window | :orange_circle: | Shrink window vertically | +| Enlarge window | :orange_circle: | Enlarge window vertically | +| Shrink window horizontally | :orange_circle: | Shrink window horizontally | +| Enlarge window horizontally | :orange_circle: | Enlarge window horizontally |