mirror of https://github.com/Flinner/dots.git
parent
62c866f358
commit
57682c060e
@ -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 |
@ -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 <server-name>=. |
||||
- =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. |
@ -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) |
@ -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) |
@ -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) |
Loading…
Reference in new issue