diff --git a/README.org b/README.org index 58bef57..c127b08 100644 --- a/README.org +++ b/README.org @@ -100,10 +100,20 @@ On first use, ~mxp~ boots a lightweight TCP eval server inside your running Emac The server starts automatically - no manual ~M-x~ step required. If the socket is unavailable (e.g., bash built without ~/dev/tcp~ support), ~mxp~ falls back to ~emacsclient~ transparently. #+attr_html: :width 50% -| Variable | Default | Description | -|---------------+---------+-----------------------------------------------| -| ~MXP_PORT~ | ~17394~ | TCP port for the eval server (localhost only) | -| ~MXP_NO_SOCKET~ | (unset) | Set to ~1~ to force ~emacsclient~-only mode | +| Variable | Default | Description | +|-------------------+---------+-----------------------------------------------------| +| ~MXP_PORT~ | ~17394~ | TCP port for the eval server (localhost only) | +| ~MXP_NO_SOCKET~ | (unset) | Set to ~1~ to force ~emacsclient~-only mode | +| ~EMACS_SOCKET_NAME~ | (unset) | Named socket for ~emacsclient -s~ (e.g., daemon name) | + +You can also pass ~-s~ / ~--socket-name~ on the command line: + +#+begin_src shell +echo "hello" | mxp -s myserver "*my-buffer*" +mxp --socket-name myserver --from "*my-buffer*" +#+end_src + +This is useful when the Emacs daemon was started with a specific socket name, e.g., ~emacs --daemon=myserver~. To stop the server manually: diff --git a/changelog.org b/changelog.org index 68f28b8..6521f10 100644 --- a/changelog.org +++ b/changelog.org @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. * [0.6.0] - 2026-04-06 ** Added +- ~-s~ / ~--socket-name~ flag and ~EMACS_SOCKET_NAME~ env var for connecting to a named Emacs daemon socket ([[https://github.com/agzam/mxp/issues/1][#1]]) - Persistent TCP socket transport between ~mxp~ and Emacs - Auto-bootstrapping: ~mxp~ starts a lightweight eval server inside Emacs on first use (no manual ~M-x~ required) - All communication goes over a single persistent connection per invocation via bash's ~/dev/tcp~ diff --git a/mxp b/mxp index d1f3236..448c1fa 100755 --- a/mxp +++ b/mxp @@ -11,6 +11,7 @@ CHUNK_SIZE=100 STREAM_TIMEOUT=0.3 MXP_PORT="${MXP_PORT:-17394}" MXP_SOCK_FD="" +EMACS_SOCKET_NAME="${EMACS_SOCKET_NAME:-}" usage() { cat << EOF @@ -31,6 +32,7 @@ OPTIONS: -f, --from BUFFER Read from buffer (name or regex) and output to stdout -a, --append Append to buffer instead of replacing content -p, --prepend Prepend to buffer (insert at the beginning) + -s, --socket-name NAME Connect to Emacs daemon socket NAME -F, --force Overwrite existing buffer (skip conflict resolution) -h, --help Show this help message -v, --version Show version @@ -85,6 +87,15 @@ die() { exit 1 } +# Wrapper for emacsclient - passes -s flag when socket name is set +run_emacsclient() { + if [ -n "$EMACS_SOCKET_NAME" ]; then + command emacsclient -s "$EMACS_SOCKET_NAME" "$@" + else + command emacsclient "$@" + fi +} + # Find the actual script location, resolving symlinks find_script_location() { local script="$0" @@ -284,7 +295,7 @@ MXP_ELISP elisp="${elisp//MXP_PORT_PLACEHOLDER/$port}" local encoded encoded=$(printf '%s' "$elisp" | encode_for_elisp) - emacsclient --eval \ + run_emacsclient --eval \ "(eval (read (decode-coding-string (base64-decode-string \"$encoded\") 'utf-8)))" \ >/dev/null 2>&1 || return 1 sleep 0.1 @@ -343,7 +354,7 @@ emacs_eval() { mxp_disconnect fi # Fallback: "$code" preserves embedded quotes as literal characters - emacsclient --eval "$code" 2>/dev/null + run_emacsclient --eval "$code" 2>/dev/null } # Verify Emacs is reachable and set up the best transport @@ -355,7 +366,7 @@ init_emacs_connection() { mxp_ensure_connection && return 0 fi # Socket unavailable - verify emacsclient can reach Emacs - if ! emacsclient --eval "t" &> /dev/null; then + if ! run_emacsclient --eval "t" &> /dev/null; then die "Cannot connect to Emacs server. Start Emacs daemon with: emacs --daemon" fi } @@ -473,10 +484,10 @@ open_mode() { # Open in Emacs (emacsclient -n handles frame management and interactive prompts # correctly, unlike find-file from a process filter context) if [ -d "$path" ]; then - emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'" + run_emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'" echo "Opened directory in Emacs: $path" else - emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'" + run_emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'" echo "Opened file in Emacs: $path" fi } @@ -635,6 +646,10 @@ main() { -p|--prepend) prepend="true" ;; + -s|--socket-name) + shift + EMACS_SOCKET_NAME="$1" + ;; -F|--force) force="true" ;; diff --git a/test-mxp.sh b/test-mxp.sh index bb6762f..69524de 100755 --- a/test-mxp.sh +++ b/test-mxp.sh @@ -182,6 +182,12 @@ else fail "Version missing" fi +if $SCRIPT --help | grep -q "\-s, --socket-name"; then + pass "Help shows -s/--socket-name option" +else + fail "Help missing -s/--socket-name option" +fi + # Test 2: Write mode - pipe to new buffer section "Test 2: Write Mode - Create Buffer" cleanup_buffer "*test-buffer*" @@ -739,6 +745,59 @@ if should_run_test "socket"; then fi fi +section "Test 30: Socket Name CLI Flag" +if should_run_test "socket"; then + if [ "$LIST_TESTS" = true ]; then + echo " [socket] Socket name -s flag" + else + # Use -s server (default socket name) with emacsclient fallback to verify + # the flag plumbing works end-to-end + cleanup_buffer "*test-socket-name-flag*" + echo "socket name flag test" | MXP_NO_SOCKET=1 $SCRIPT -s server "*test-socket-name-flag*" &>/dev/null + output=$(MXP_NO_SOCKET=1 $SCRIPT -s server --from "*test-socket-name-flag*" 2>/dev/null) + + if [[ "$output" == *"socket name flag test"* ]]; then + pass "Write/read round-trip with -s flag works" + else + fail "Socket name -s flag failed: got '$output'" + fi + fi +fi + +section "Test 31: Socket Name Env Var" +if should_run_test "socket"; then + if [ "$LIST_TESTS" = true ]; then + echo " [socket] EMACS_SOCKET_NAME env var" + else + cleanup_buffer "*test-socket-name-env*" + echo "socket name env test" | MXP_NO_SOCKET=1 EMACS_SOCKET_NAME=server $SCRIPT "*test-socket-name-env*" &>/dev/null + output=$(MXP_NO_SOCKET=1 EMACS_SOCKET_NAME=server $SCRIPT --from "*test-socket-name-env*" 2>/dev/null) + + if [[ "$output" == *"socket name env test"* ]]; then + pass "Write/read round-trip with EMACS_SOCKET_NAME env var works" + else + fail "EMACS_SOCKET_NAME env var failed: got '$output'" + fi + fi +fi + +section "Test 32: Socket Name --socket-name Long Flag" +if should_run_test "socket"; then + if [ "$LIST_TESTS" = true ]; then + echo " [socket] Socket name --socket-name long flag" + else + cleanup_buffer "*test-socket-name-long*" + echo "long flag test" | MXP_NO_SOCKET=1 $SCRIPT --socket-name server "*test-socket-name-long*" &>/dev/null + output=$(MXP_NO_SOCKET=1 $SCRIPT --socket-name server --from "*test-socket-name-long*" 2>/dev/null) + + if [[ "$output" == *"long flag test"* ]]; then + pass "Write/read round-trip with --socket-name flag works" + else + fail "Socket name --socket-name long flag failed: got '$output'" + fi + fi +fi + # Summary section "Test Summary" echo ""