diff --git a/chemacs/.emacs.d/CHANGELOG.md b/chemacs/.emacs.d/CHANGELOG.md new file mode 100644 index 0000000..4ef510f --- /dev/null +++ b/chemacs/.emacs.d/CHANGELOG.md @@ -0,0 +1,43 @@ +# Unreleased + +- Add support for early-init.el +- Change installation from `~/.emacs` to `~/.emacs.d` +- Allow .emacs-profiles and .emacs-profile to be stored in $XDG\_CONFIG\_HOME/chemacs +- Allow loading a literal profile from the cli (e.g. `emacs --with-profile '((user-emacs-directory . "/path/to/config"))'` works) + +# 1.0 (2020-10-01 / 4dad0684) + +- Only load `custom-file` when it is different from `init-file` (prevent double loading of `init-file`) +- Fixes to the install script for OS X +- Documentation fixes +- Introduce `chemacs-version` variable +- start keeping a CHANGELOG + +# 0.6 (2020-02-23 / 71e30878) + +- NixOS support in installation script +- Add Powershell installation script + +# 0.5 (2020-01-14 / 4c279476) + +- First class support for straight.el +- Documentation fixes + +# 0.4 (2018-10-01 / 68382d50) + +- Support GNU style `--with-profile=profilename` (so with equal sign) + +# 0.3 (2018-09-30 / 1140501d) + +- Allow selection of default profile using `~/.emacs-profile` + +# 0.2 (2018-06-06 / 1f5601a9) + +- Add installer script +- Improve documentation +- Improve support for older Emacsen +- Document how to use with Doom + +# 0.1 (2018-05-18 / 8500636a) + +- Initial release diff --git a/chemacs/.emacs.d/README.org b/chemacs/.emacs.d/README.org new file mode 100644 index 0000000..262008b --- /dev/null +++ b/chemacs/.emacs.d/README.org @@ -0,0 +1,225 @@ +#+BEGIN_SRC + ___ ___ ___ ___ ___ ___ ___ + / /\ /__/\ / /\ /__/\ / /\ / /\ / /\ + / /:/ \ \:\ / /:/_ | |::\ / /::\ / /:/ / /:/_ + / /:/ \__\:\ / /:/ /\ | |:|:\ / /:/\:\ / /:/ / /:/ /\ + / /:/ ___ ___ / /::\ / /:/ /:/_ __|__|:|\:\ / /:/~/::\ / /:/ ___ / /:/ /::\ + /__/:/ / /\ /__/\ /:/\:\ /__/:/ /:/ /\ /__/::::| \:\ /__/:/ /:/\:\ /__/:/ / /\ /__/:/ /:/\:\ + \ \:\ / /:/ \ \:\/:/__\/ \ \:\/:/ /:/ \ \:\~~\__\/ \ \:\/:/__\/ \ \:\ / /:/ \ \:\/:/~/:/ + \ \:\ /:/ \ \::/ \ \::/ /:/ \ \:\ \ \::/ \ \:\ /:/ \ \2.0 /:/ + \ \:\/:/ \ \:\ \ \:\/:/ \ \:\ \ \:\ \ \:\/:/ \__\/ /:/ + \ \::/ \ \:\ \ \::/ \ \:\ \ \:\ \ \::/ /__/:/ + \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ + + 222222222222222 + 2:::::::::::::::22 + 2::::::222222:::::2 + 2222222 2:::::2 + 2:::::2 + 2:::::2 + 2222::::2 + 22222::::::22 + 22::::::::222 + 2:::::22222 + 2:::::2 + 2:::::2 + 2:::::2 222222 + 2::::::2222222:::::2 + 2::::::::::::::::::2 + 22222222222222222222 + +#+END_SRC + +* Chemacs + +Chemacs 2 is an Emacs profile switcher, it makes it easy to run multiple Emacs +configurations side by side. + +Think of it as a bootloader for Emacs. + +** Differences from Chemacs 1 + +Emacs intialization used to have a single entry point, either =~/.emacs= or +=~/.emacs.d/init.el=. More recent Emacsen have introduced a second startup +script, =~/.emacs/early-init.el=, which runs earlier in the boot process, and +can be used for things that should happen very early on, like tweaking the GC, +or disabling UI elements. + +Chemacs 2 supports =early-init.el=, Chemacs 1 does not. This does also imply +that Chemacs 2 needs to be installed as =~/.emacs.d= (a directory), rather than +simply linking it to =~/.emacs= (a single file). + +** Rationale + +Emacs configuration is either kept in a =~/.emacs= file or, more commonly, in a +=~/.emacs.d= directory. These paths are hard-coded. If you want to try out +someone else's configuration, or run different distributions like Prelude or +Spacemacs, then you either need to swap out =~/.emacs.d=, or run Emacs with a +different =$HOME= directory set. + +This last approach is quite common, but has some real drawbacks, since now +packages will no longer know where your actual home directory is. + +All of these makes trying out different Emacs configurations and distributions +needlessly cumbersome. + +Various approaches to solving this have been floated over the years. There's an +Emacs patch around that adds an extra command line option, and various examples +of how to add a command line option in userspace from Emacs Lisp. + +Chemacs tries to implement this idea in a user-friendly way, taking care of the +various edge cases and use cases that come up. + +** Installation + +Clone the Chemacs 2 repository as =$HOME/.emacs.d=. Note that if you already +have an Emacs setup in =~/.emacs.d= you need to move it out of the way first. If +you have an =~/.emacs= startup script then move that out of the way as well. + +#+BEGIN_SRC shell + [ -f ~/.emacs ] && mv ~/.emacs ~/.emacs.bak + [ -d ~/.emacs.d ] && mv ~/.emacs.d ~/.emacs.default + git clone https://github.com/plexus/chemacs2.git ~/.emacs.d +#+END_SRC + +Note that this is different from Chemacs 1. Before Chemacs installed itself as +=~/.emacs= and you could have your own default setup in =~/.emacs.d=. This +approach no longer works because of =~/.emacs.d/early-init.el=, so Chemacs 2 +needs to be installed as =~/.emacs.d=. + +Next you will need to create a =~/.emacs-profiles.el= file, for details see +below. + +#+begin_src emacs-lisp + (("default" . ((user-emacs-directory . "~/.emacs.default")))) +#+end_src + +** Usage + +Chemacs adds an extra command line option to Emacs, =--with-profile=. Profiles +are configured in =~/.emacs-profiles.el=. + +If no profile is given at the command line then the =default= profile is used. + +#+BEGIN_SRC shell +$ emacs --with-profile my-profile +#+END_SRC + +There is an option for using profile that is not preconfigured in =~/.emacs-profiles.el=. To accomplish that you can directly provide the profile via the command line, like so +#+BEGIN_SRC shell +$ emacs --with-profile '((user-emacs-directory . "/path/to/config"))' +#+END_SRC +This method supports all the profile options given below. + +** .emacs-profiles.el + +This file contains an association list, with the keys/cars being the profile +names, and the values/cdrs their configuration. + +The main thing to configure is the =user-emacs-directory= + +#+BEGIN_SRC emacs-lisp + (("default" . ((user-emacs-directory . "~/.emacs.default"))) + ("spacemacs" . ((user-emacs-directory . "~/spacemacs")))) +#+END_SRC + +Chemacs will set this to be the =user-emacs-directory= in use, and load +=init.el= from that directory. + +Other things you can configure + +- =custom-file= : The file where Customize stores its customizations. If this + isn't configured, and the =custom-file= variable is still unset after loading + the profile's =init.el=, then this will get set to the profile's =init.el= +- =server-name= : Sets the =server-name= variable, so you can distinguish multiple + instances with =emacsclient -s =. +- =env= An association list of environment variables. These will get set before + loading the profile, so they can influence the initialization, and they are + visible to any subprocesses spawned from Emacs. +- =straight-p= Enable the [[https://github.com/raxod502/straight.el][Straight]] + functional package manager. + +Store =.emacs-profiles.el= together with your dotfiles. If you're not yet keeping +a version controlled directory of dotfiles, then check out +[[https://github.com/plexus/dotfiles/blob/master/connect-the-dots][connect-the-dots]] +for a helpful script to do that. + +** Changing the default profile (e.g. for GUI editors) + +Where it is not possible to use the =--with-profile= flag, the default profile +can be set using a =~/.emacs-profile= file. + +If your =~/.emacs-profiles.el= file contains the following: + +#+BEGIN_SRC emacs-lisp + (("default" . ((user-emacs-directory . "~/.emacs.default"))) + ("spacemacs" . ((user-emacs-directory . "~/spacemacs"))) + ("prelude" . ((user-emacs-directory . "~/prelude")))) +#+END_SRC + +you can create a file called =~/.emacs-profile=, containing the name of the +profile you'd like to be used when none is given on the command line: + +#+BEGIN_SRC shell +$ echo 'spacemacs' > ~/.emacs-profile +#+END_SRC + +This will set the default profile to be the "spacemacs" profile, instead of +"default". You can change the default by simply changing the contents of this +file: + +#+BEGIN_SRC shell +$ echo 'prelude' > ~/.emacs-profile +#+END_SRC + +If this file doesn't exist, then "default" will be used, as before. + +** Spacemacs + +Spacemacs is typically installed by cloning the Spacemacs repo to =~/.emacs.d=, +and doing extra customization from =~/.spacemacs= or =~/.spacemacs.d/init.el=. +This makes it tedious to switch between version of Spacemacs, or between +different Spacemacs configurations. + +With Chemacs you can point your =user-emacs-directory= to wherever you have +Spacemacs installed, and use the =SPACEMACSDIR= environment variable to point at +a directory with customizations that are applied on top of the base install. + +#+BEGIN_SRC emacs-lisp +(("spacemacs" . ((user-emacs-directory . "~/spacemacs") + (env . (("SPACEMACSDIR" . "~/.spacemacs.d"))))) + + ("spacemacs-develop" . ((user-emacs-directory . "~/spacemacs/develop") + (env . (("SPACEMACSDIR" . "~/.spacemacs.d"))))) + + ("new-config" . ((user-emacs-directory . "~/spacemacs/develop") + (env . (("SPACEMACSDIR" . "~/my-spacemacs-config")))))) +#+END_SRC + +** DOOM emacs + +You can add an entry similar to the following to your =.emacs-profiles.el= + +In the following snippet =~/doom-emacs= is where you have cloned doom emacs. + +(Depending on when you read this) =DOOMDIR= support is only in =develop= branch of doom emacs. Check commit history of =master= branch of doom emacs + +#+BEGIN_SRC emacs-lisp +("doom" . ((user-emacs-directory . "~/doom-emacs") + (env . (("DOOMDIR" . "~/doom-config"))))) +#+END_SRC + +Please refer to [[https://github.com/plexus/chemacs/issues/5][this]] discussion for details. + +** FreeDesktop Directories + +Both =~/.emacs-profiles.el= and =~/.emacs-profile= can also be stored under =$XDG_CONFIG_HOME/chemacs= (typically =~/.config/chemacs=) as =$XGD_CONFIG_HOME/chemacs/profiles.el= and =$XDG_CONFIG_HOME/chemacs/profile= respectively. + +Further, as indicated by the [[http://git.savannah.gnu.org/cgit/emacs.git/tree/etc/NEWS?h=emacs-27][Emacs 27.1 changelog]], Emacs is now compatible with XDG Standards, looking for its configuration files in =${XDG_CONFIG_HOME}/emacs= directory too (provided the traditional =~/.emacs.d= and =~/.emacs= does not exist). +Therefore, it is perfectly viable to install Chemacs 2 in =${XDG_CONFIG_HOME}/emacs= (usually =~/.config/emacs=) directory - with the aforementioned caveat: _the directory =~/.emacs.d"= and the file ="~/.emacs"= does not exist_. + +** LICENSE + +Copyright © Arne Brasseur 2018-2020 + +Distributed under the terms of the GPL v3. diff --git a/chemacs/.emacs.d/chemacs.el b/chemacs/.emacs.d/chemacs.el new file mode 100644 index 0000000..0af2b77 --- /dev/null +++ b/chemacs/.emacs.d/chemacs.el @@ -0,0 +1,170 @@ +;;; chemacs.el --- -*- lexical-binding: t; -*- +;;; Commentary: +;; :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +;; +;; ___ ___ ___ ___ ___ ___ ___ +;; / /\ /__/\ / /\ /__/\ / /\ / /\ / /\ +;; / /:/ \ \:\ / /:/_ | |::\ / /::\ / /:/ / /:/_ +;; / /:/ \__\:\ / /:/ /\ | |:|:\ / /:/\:\ / /:/ / /:/ /\ +;; / /:/ ___ ___ / /::\ / /:/ /:/_ __|__|:|\:\ / /:/~/::\ / /:/ ___ / /:/ /::\ +;; /__/:/ / /\ /__/\ /:/\:\ /__/:/ /:/ /\ /__/::::| \:\ /__/:/ /:/\:\ /__/:/ / /\ /__/:/ /:/\:\ +;; \ \:\ / /:/ \ \:\/:/__\/ \ \:\/:/ /:/ \ \:\~~\__\/ \ \:\/:/__\/ \ \:\ / /:/ \ \:\/:/~/:/ +;; \ \:\ /:/ \ \::/ \ \::/ /:/ \ \:\ \ \::/ \ \:\ /:/ \ \2.0 /:/ +;; \ \:\/:/ \ \:\ \ \:\/:/ \ \:\ \ \:\ \ \:\/:/ \__\/ /:/ +;; \ \::/ \ \:\ \ \::/ \ \:\ \ \:\ \ \::/ /__/:/ +;; \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ +;; +;; :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +;; +;; Chemacs - Emacs Profile Switcher +;; +;; See README.md for instructions. + +;; NOTE Don't require any libraries in this file. When emacs loads a library that +;; is byte compiled, it may start native-compiling it, so if we require anything +;; here, native compilation can start before the user has had a chance to configure +;; it in their init files. + +;;; Code: +(defvar chemacs-version "2.0") +(defvar config-home (or (getenv "XDG_CONFIG_HOME") "~/.config")) +(defvar chemacs-profiles-paths (list "~/.emacs-profiles.el" (format "%s/%s" config-home "chemacs/profiles.el" ))) +(defvar chemacs-default-profile-paths (list "~/.emacs-profile" (format "%s/%s" config-home "chemacs/profile"))) + +;; Copy `seq' library's `seq-filter' to avoid requiring it, see note above. +(defun chemacs--seq-filter (pred sequence) + (let ((exclude (make-symbol "exclude"))) + (delq exclude (mapcar (lambda (elt) + (if (funcall pred elt) + elt + exclude)) + sequence)))) + +(defvar chemacs-profiles-path (or (car (chemacs--seq-filter #'file-exists-p chemacs-profiles-paths)) + (car chemacs-profiles-paths))) +(defvar chemacs-default-profile-path (or (car (chemacs--seq-filter #'file-exists-p chemacs-default-profile-paths)) + (car chemacs-default-profile-paths))) + +(defun chemacs-handle-command-line (args) + (when args + ;; Handle either --with-profile profilename or + ;; --with-profile=profilename + (let ((s (split-string (car args) "="))) + (cond ((equal (car args) "--with-profile") + ;; This is just a no-op so Emacs knows --with-profile + ;; is a valid option. If we wait for + ;; command-switch-alist to be processed then + ;; after-init-hook has already run. + (add-to-list 'command-switch-alist + '("--with-profile" . + (lambda (_) (pop command-line-args-left)))) + (cadr args)) + + ;; Similar handling for `--with-profile=profilename' + ((equal (car s) "--with-profile") + (add-to-list 'command-switch-alist `(,(car args) . (lambda (_)))) + (mapconcat 'identity (cdr s) "=")) + + (t (chemacs-handle-command-line (cdr args))))))) + +(defvar chemacs--with-profile-value + (let* ((value (chemacs-handle-command-line command-line-args)) + (read-value (read value))) + (when value + (if (listp read-value) + read-value + value)))) + +(defvar chemacs-literal-profile-provided + (and chemacs--with-profile-value + (listp chemacs--with-profile-value))) + +(unless (or (file-exists-p chemacs-profiles-path) + (and chemacs--with-profile-value + (listp chemacs--with-profile-value))) + (error "[chemacs] %s does not exist." chemacs-profiles-path)) + +(defvar chemacs-default-profile-name + (if (file-exists-p chemacs-default-profile-path) + (with-temp-buffer + (insert-file-contents chemacs-default-profile-path) + (goto-char (point-min)) + ;; (buffer-string)) + (symbol-name (read (current-buffer)) )) + "default")) + + +(defvar chemacs-profile-name + (if (and chemacs--with-profile-value + (stringp chemacs--with-profile-value)) + chemacs--with-profile-value + chemacs-default-profile-name)) + +(defvar chemacs-profile + (if (and chemacs--with-profile-value + (listp chemacs--with-profile-value)) + chemacs--with-profile-value + (let ((profiles + (with-temp-buffer + (insert-file-contents chemacs-profiles-path) + (goto-char (point-min)) + (read (current-buffer))))) + (cdr (assoc chemacs-profile-name profiles))))) + +(unless chemacs-profile + (error "No profile `%s' in %s" chemacs-profile-name chemacs-profiles-path)) + +(defun chemacs-profile-get (key &optional default) + (alist-get key chemacs-profile default)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq user-emacs-directory (file-name-as-directory + (chemacs-profile-get 'user-emacs-directory))) + +;; Allow multiple profiles to each run their server +;; use `emacsclient -s profile_name' to connect +(let ((name (chemacs-profile-get 'server-name))) + (when name (setq server-name name))) + +;; Set environment variables, these are visible to init-file with +;; getenv +(mapcar (lambda (env) + (setenv (car env) (cdr env))) + (chemacs-profile-get 'env)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun chemacs-load-user-early-init () + (let ((early-init-file (expand-file-name "early-init.el" user-emacs-directory))) + (load early-init-file t t))) + +(defun chemacs-load-user-init () + (when (chemacs-profile-get 'straight-p) (chemacs-load-straight)) + (let ((init-file (expand-file-name "init.el" user-emacs-directory))) + (setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) + (load init-file t t) + ;; Prevent customize from changing ~/.emacs (this file), but if + ;; init.el has set a value for custom-file then don't touch it. + (let ((chemacs-custom-file (chemacs-profile-get 'custom-file init-file))) + (when (not custom-file) + (setq custom-file chemacs-custom-file) + (unless (equal custom-file init-file) + (unless (file-exists-p custom-file) + (with-temp-buffer (write-file custom-file))) + (load custom-file)))))) + +(defun chemacs-load-straight () + (defvar bootstrap-version) + (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) + (bootstrap-version 5)) + (unless (file-exists-p bootstrap-file) + (with-current-buffer + (url-retrieve-synchronously + "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" + 'silent 'inhibit-cookies) + (goto-char (point-max)) + (eval-print-last-sexp))) + (load bootstrap-file nil 'nomessage))) + +(provide 'chemacs) diff --git a/chemacs/.emacs.d/early-init.el b/chemacs/.emacs.d/early-init.el new file mode 100644 index 0000000..c4ae04b --- /dev/null +++ b/chemacs/.emacs.d/early-init.el @@ -0,0 +1,7 @@ +;;; early-init.el --- -*- lexical-binding: t; -*- + +(require 'chemacs + (expand-file-name "chemacs.el" + (file-name-directory + (file-truename load-file-name)))) +(chemacs-load-user-early-init) diff --git a/chemacs/.emacs.d/init.el b/chemacs/.emacs.d/init.el new file mode 100644 index 0000000..44948e1 --- /dev/null +++ b/chemacs/.emacs.d/init.el @@ -0,0 +1,11 @@ +;;; init.el --- -*- lexical-binding: t; -*- + +(require 'chemacs + (expand-file-name "chemacs.el" + (file-name-directory + (file-truename load-file-name)))) +(chemacs-load-user-init) + +;; this must be here to keep the package system happy, normally you do +;; `package-initialize' for real in your own init.el +;; (package-initialize)