0

I'm new to emacs. I'm a spacemacs implant, really love ORG mode and evil mode. Been using for the past year or so.

I've read some of the emacs documentation, which is fantastically written. But I'm struggling with getting emacsclient and emacs daemon to work with a single frame.

I'm ok with scripting bash outside of emacs, but if there is an 'emacs configuration' way, I would prefer that - assume I'm clueless here.

What I want to achieve

With a script named 'editfile' on my path, running:

editfile file-path

NOTE: file-path is optional.

I would like the following to happen:

  1. If the emacs-daemon isn't running, start it up (with the correct chemacs2 profile)
  2. If no frames are open, open file-path in a new frame
  3. If a frame exists, switch focus to that frame and open file-path in the already existing frame.

I got #1 solved (with chemacs2 no less), and I love it.

I need help with #2 and #3, Currently I always open a new frame, and I would rather it didn't.

My editfile script looks like this:

#! /usr/bin/env bash

MY_EMACS_PROFILE=${CHEMACS_PROFILE:-spacemacs}
MY_SILENT="/dev/null"
MY_EMACS_CREATEFRAME="--create-frame"
MY_EMACS_NO_WAIT="--no-wait"

# MY_SILENT="/dev/tty"
# MY_EMACS_NO_WAIT=""

if [ "$DISPLAY" = "" ]; then
  MY_EMACS_NO_WAIT=""
fi

function q_()  { printf  "'%s'\n"  "${@}"; }

function myemacs_full() {

  local _cli="$(q_ "${@}")"
  _cli="${_cli// /\\ }"
  _cli=$(printf "$_cli")

  _cli=$(printf "/usr/bin/emacsclient \ 
    --socket-name=$MY_EMACS_PROFILE \
    $MY_EMACS_CREATEFRAME \
    $MY_EMACS_NO_WAIT \
    --alternate-editor='sh -c \"( \
        /usr/bin/emacs --with-profile $MY_EMACS_PROFILE --daemon \
      ); echo ''Daemon started'' > $MY_SILENT; ( \
        /usr/bin/emacsclient \
        --socket-name=$MY_EMACS_PROFILE \
        $MY_EMACS_NO_WAIT \
        --create-frame \
        ${_cli//\'/\'\'} \
      )\"' \
    $_cli" \
    )

  2>$MY_SILENT eval $_cli
}

myemacs_full "${@}"

Here's what is going on there:

  • I'm building a _cli command line which I then eval
  • the _cli command comprises of calling emacsclient with the following options:
    • --socket-name... - the name of the server's socket. (That's because I use chemacs2, and more than one daemon might be running, this is the way to distinguish between them.
    • --create-frame - I would want this to be dynamic, and I'm trying to figure out how to determine whether this is required or not.
      Currently I always create a new frame.
    • $MY_EMACS_NO_WAIT will either be empty or --no-wait (text/terminal mode should use wait, GUI mode should not)
    • --alternate-editor... - If the daemon isn't listening, spin up the daemon and run the emacsclient one more time.
      The daemon is launched with chemacs2's --with-profile argument.

      This is the main reason I'm using the funky process of building $_cli with printf and eval

Update #1 : Things I've tried:

  • Tyler's suggestion below isn't working for me.
    Where it fails for me is the need to pass --with-profile=spacemacs (or any other profile name) to the daemon. With the default, it is not passed.

Update #2 : How --alternate-editor works

When an empty string is provided and the daemon isn't live yet, observing this with htop filtered on the string emacs, emacs --daemon is run, and if --socker=<socketname> is specificed, that parameter is passed as well.
Then the frame is opened, however, I did not see any new process.

To better understand what is passed to the alternate-editor, I wrote a very simple /tmp/tst script, and made it executable:

/usr/bin/env bash
echo "tst called with: [${@}]"

When I run emacsclient --socket=spacemacs --no-wait --alternate-editor="/tmp/tst a b" -- --with-profile=spacemacs, the output is:

emacsclient: can't find socket; have you started the server?
emacsclient: To start the server in Emacs, type "M-x server-start".
tst called with: [a b --with-profile=spacemacs]

The first 2 lines are expected, the last line shows what is passed to the alternate editor.

Possible solutions (?)

The way I see it, to resolve this, at least one of the following strategies might work:

  • Have the script above determine whether any frames are already open, and clear the $MY_EMACS_CREATEFRAME variable.
  • Have something in the init.el that will make sure to reuse the latest active frame.

However, I have failed to figure out how to achieve either.

Reaching out to the community for help

If you have a suggestion on how to do this, I'd love to learn.
If you think I am doing this completely wrong, I would love to learn of better/different ways to achieve this.

2 Answers 2

1

Emacsclient reuses an existing frame by default. If you don't want to create a new frame, then don't ask Emacs to create a new frame - don't use the --create-frame argument.

Set --alternate-editor="" to get Emacs to automatically start a server if one isn't already running.

I think all you need is emacsclient --alternate-editor="", the default behaviour should make your script unnecessary.

The available options are described in the manual section emacsclient Options. Relevant arguments for your use-case:

  • ‘--alternate-editor=command’

Specify a shell command to run if ‘emacsclient’ fails to contact Emacs. ...

As a special exception, if command is the empty string, then emacsclient starts Emacs in daemon mode (as ‘emacs --daemon’) and then tries connecting again.

  • ‘-r’

Create a new graphical client frame if none exists, otherwise use an existing Emacs frame.

In the case of chemacs, it looks like falling back to emacs --daemon won't be enough, you need to specify your profile. ie., emacs --daemon --with-profile spacemacs.

That being the case, I think something like the following could replace your custom script:

 emacsclient -r -s spacemacs --alternate-editor="emacs --daemon --with-profile spacemacs" <filename>

I don't have chemacs installed, so I can't test this locally.

8
  • That doesn't work for me... it might be because I'm entering stuff into --alternative-editor - When I omit the --create-frame nothing appears.
    – Lockszmith
    Jun 28 at 21:35
  • I don't understand what you mean. You shouldn't be entering anything into alternate-editor, just --alternate-editor="". It looks like you're trying something very complex to provide a feature that Emacs already does for you.
    – Tyler
    Jun 28 at 22:31
  • I need to pass --with-profile=<profilename> to the daemon, this does not pass that. Also, when I tried it just now without any special parameters, it opened the emacs frame in the terminal - not what I wanted either.
    – Lockszmith
    Jun 28 at 22:45
  • trying '-r' failed for me. My output of emacsclient --version is emacsclient 27.1. I'm running on Pop!_OS 22.04. In the page you linked I couldn't find the text associated with -r
    – Lockszmith
    Jun 29 at 3:27
  • 1
    sorry, the -r argument was added in Emacs 29.1
    – Tyler
    Jun 29 at 14:19
0

I'll probably mark Tyler's answer as the selected answer, but I'm not able to test it, as that relies on a commit from Nov-2021, targeted at Emacs 29.1, which introduces a new switch --reuse-frame/-r.
Currently Emacs 29.1 is not GA*.

The script below works as intended, but there is an issue with the way Emacs does this, and others have encountered this as well. See

The problem is, although the frame gets the focus, it doesn't switch to the correct virtual desktop, so I'll need to figure an additional solution here.

The Script

Replacing the script I had in my question, below is what I'm using now until Emacs 29.1 will be available. Luckily for me, I think it's a bit 'cleaner' than before:
(Link to my actual script in my dotfiles repo)

#! /usr/bin/env bash

MY_EMACS_PROFILE=${CHEMACS_PROFILE:-spacemacs}
MY_SILENT=${MY_SILENT:-"/dev/null"}
MY_EMACS_CREATEFRAME="--create-frame"
MY_EMACS_NO_WAIT="--no-wait"
DEBUG_MY_EMACS=${DEBUG_MY_EMACS}
_cli="/usr/bin/emacsclient --socket-name=$MY_EMACS_PROFILE "

if [ "$DISPLAY" = "" ]; then
  MY_EMACS_NO_WAIT=""
fi

function q_()  { printf  "'%s'\n"  "${@}"; }

# This is needed for files with spaces and special chars
function prep_path() {
  local _cli_path="$(q_ "${@}")"
  _cli_path="${_cli_path// /\\ }"
  _cli_path=$(printf "$_cli_path")

  printf $_cli_path
}

$_cli --eval '(+ 1 0)' > /dev/null || {
  echo "Starting server..."
  2>$MY_SILENT /usr/bin/emacs --bg-daemon=$MY_EMACS_PROFILE -- --with-profile=$MY_EMACS_PROFILE
}
# At this point the server should be up and running, and so now we just
# need to see if we have any frames open
# If there are frames, we are NOT going to create a new frame, and so 
# MY_EMACS_CREATEFRAME will be set to blank.
[ "$($_cli --eval '(> (length (frame-list)) 1)')" == "t" ] && MY_EMACS_CREATEFRAME=''

client_args=$(prep_path "${@}")
[ "$client_args" == "''" ] && client_args='--eval "(raise-frame)"'
2>$MY_SILENT eval "$_cli $MY_EMACS_CREATEFRAME $MY_EMACS_NO_WAIT $client_args"

*   I use this to track whether my repos have a desired version or not
    for Ubuntu flavors there is also Kevin Kelley's Emacs stable releases PPA.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.