Creating ad-hoc workspaces/projects in self-contained frames

What are you trying to do?

I often want to open some third-party code (eg a package in node_modules, or a ruby gem) and be able to easily search it or pop open files. Even though I have emacs as my main editor, I’d usually open these in TextMate or VSCode because I can just run code node_modules/bootstrap and have a new self-contained window where ⌘P allows me to jump to any file in that directory, and ⌘⇧F searches that directory, and only that directory. I’ve been trying to replicate that sort of workflow by opening a new emacs frame with a new workspace, and setting projectile-project-root to try and limit the scope of searches with SPC s p and file-finding with SPC SPC.

What happens instead?

It doesn’t work great, especially if I have two separate projects open at the same time where projectile-project-root gets overwritten - ideally I’d like each emacs frame to be dedicated to its own project.

What code do you want reviewed?

Here’s my bash script that I use to launch projects from the terminal. I have iterm2 set up so that command-clicking a file path runs it, or I can manually run something like edit ./node_modules/bootstrap.

#!/bin/bash

if [ -d "$1" ]; then
  dir=$(realpath "$1")/
  target="$dir"
  projectname=$(basename "$dir")
else
  dir=$(realpath "$(dirname "$1")")/
  # read filename and line number out of ./foo.rb:41
  IFS=: read -r target linenumber <<< "$1"
  target=$(realpath "$target")
fi
emacsclient --alternate-editor="" --no-wait --quiet --suppress-output --eval "
;; using make-frame rather than 'emacsclient --create-frame' because the latter is a bit weird on macOS,
;; creating a second dock icon with the generic file icon rather than an emacs icon, and doesn't allow cmd-~ to switch windows
(let ((frame (make-frame '((display . \":0\"))))
      (frame-count (length (frame-list))))
  ;; center it, but offset successive frames so that they don't all open in the exact same spot
  (modify-frame-parameters
    frame
    '((user-position . t) (top . 0.5) (left . 0.5)))
  (set-frame-parameter frame 'top (+ (frame-parameter frame 'top) (* 10 frame-count)))
  (set-frame-parameter frame 'left (+ (frame-parameter frame 'left) (* 10 frame-count)))
  (select-frame-set-input-focus frame)

  (if (file-directory-p \"$target\")
    ;; Try and set up the directory as if it's a project-root (eg for SPC-s-p searching)
    (progn
      (dir-locals-set-class-variables 'vendor-directory
       '((nil . ((projectile-project-root . \"$dir\")))))
      (dir-locals-set-directory-class \"$dir\" 'vendor-directory)
      (add-to-list 'safe-local-variable-values '(projectile-project-root . \"$dir\"))
      (+workspace/new \"$projectname\"))

    ;; Start a new workspace. Seems redundant for a single file, but I don't want to pollute the existing workspaces.
    ;; Plus it means that cmd-w will actually close the current frame.
    (+workspace/new)
    ;; todo: try & figure out the project root if we're opening a single file
   )

  ;; setup hook to delete workspace when the frame is closed
  (set-frame-parameter frame 'workspace (+workspace-current-name))

  (find-file \"$target\")
  (goto-line ${linenumber:-1})
)"

or for ease of syntax-highlighting, here’s just the lispy bit:

(let ((frame (make-frame '((display . ":0"))))
      (frame-count (length (frame-list))))
  ;; center it, but offset successive frames so that they don't all open in the exact same spot
  (modify-frame-parameters
    frame
    '((user-position . t) (top . 0.5) (left . 0.5)))
  (set-frame-parameter frame 'top (+ (frame-parameter frame 'top) (* 10 frame-count)))
  (set-frame-parameter frame 'left (+ (frame-parameter frame 'left) (* 10 frame-count)))
  (select-frame-set-input-focus frame)

  (if (file-directory-p "$target")
    ;; Try and set up the directory as if it's a project-root (eg for SPC-s-p searching)
    (progn
      (dir-locals-set-class-variables 'vendor-directory
        '((nil . ((projectile-project-root . "$dir")))))
      (dir-locals-set-directory-class "$dir" 'vendor-directory)
      (add-to-list 'safe-local-variable-values '(projectile-project-root . \"$dir\"))
      (+workspace/new "$projectname"))

    ;; Start a new workspace. Seems redundant for a single file, but I don't want to pollute the existing workspaces.
    ;; Plus it means that cmd-w will actually close the current frame.
    (+workspace/new)
    ;; todo: try & figure out the project root if we're opening a single file
   )

  ;; setup hook to delete workspace when the frame is closed
  (set-frame-parameter frame 'workspace (+workspace-current-name))

  (find-file "$target")
  (goto-line "${linenumber:-1}")
)

Would appreciate any tips on how to clean this up / alternate workflows.

This topic was automatically closed after 90 days. New replies are no longer allowed.