471 lines
17 KiB
Org Mode
471 lines
17 KiB
Org Mode
#+TITLE: Init.El Literate
|
|
#+PROPERTY: header-args:elisp :tangle ~/.config/emacs/init.el
|
|
#+auto_tangle: t
|
|
|
|
#+BEGIN_SRC elisp
|
|
;; NOTE:
|
|
;; This file is generated from init.org
|
|
#+END_SRC
|
|
|
|
* Variables
|
|
|
|
#+BEGIN_SRC elisp
|
|
(defvar ./variable-pitch-display-font
|
|
"Fira Sans"
|
|
"Font used for titles")
|
|
|
|
(defvar ./face-fixed-pitch
|
|
'(:foundry "apple" :family "Fira Code" :height 160)
|
|
"Contents to be passed to (`set-face-attribute' 'fixed-pitch nil [...]) ")
|
|
(defvar ./face-variable-pitch
|
|
'(:foundry "apple" :family "Inter" :height 170)
|
|
"Contents to be passed to (`set-face-attribute' 'variable-pitch nil [...]) ")
|
|
#+END_SRC
|
|
|
|
* Local custom init (pre)
|
|
|
|
This file is not tracked in dotfiles and is to be unique, custom configurations for each machine.
|
|
|
|
It is loaded after [[Variables]] so that I can customize those variables within the file.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(load (locate-user-emacs-file "dotslash-local-pre.el") :no-error :no-message)
|
|
#+END_SRC
|
|
|
|
* Basics
|
|
|
|
Make sure custom settings are in a separate file so I can find (and extract) then easily. If they were to be appended to =init.el= it won't work anyway, since my =init.el= is controlled by this Literate config.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
|
|
(defun display-startup-echo-area-message ()
|
|
(message (format "Emacs %s loaded from %s in %s"
|
|
emacs-version user-emacs-directory (emacs-init-time))))
|
|
|
|
(setq initial-scratch-message
|
|
(concat ";; *SCRATCH*\n"
|
|
";; C-c C-b (eval-buffer) C-c C-r (eval-region)\n"
|
|
";; C-x C-s will let you choose where to save\n\n; "))
|
|
#+END_SRC
|
|
|
|
** Display
|
|
|
|
#+BEGIN_SRC elisp
|
|
;; Setting this in early-init doesn't seem to work
|
|
(set-frame-position (selected-frame) 100 10)
|
|
(set-face-attribute 'default nil :height 160)
|
|
|
|
(add-to-list 'bdf-directory-list "~/Library/Fonts")
|
|
|
|
;; (custom-theme-set-faces
|
|
;; 'user
|
|
;; '(variable-pitch ((t (:inherit ./face-variable-pitch))))
|
|
;; '(fixed-pitch ((t (:inherit ./face-fixed-pitch)))))
|
|
|
|
(apply #'set-face-attribute 'variable-pitch nil ./face-variable-pitch)
|
|
(apply #'set-face-attribute 'fixed-pitch nil ./face-fixed-pitch)
|
|
(apply #'set-face-attribute 'default nil ./face-fixed-pitch)
|
|
#+END_SRC
|
|
|
|
** Better defaults (misc)
|
|
|
|
#+BEGIN_SRC elisp
|
|
;;(global-display-line-numbers-mode 1)
|
|
(add-hook 'prog-mode-hook (lambda () (display-line-numbers-mode 1)))
|
|
(add-hook 'text-mode-hook (lambda () (display-line-numbers-mode 1)))
|
|
|
|
;; I think this saves the last cursor pos or something
|
|
;; FIXME: doesn't work
|
|
(require 'saveplace)
|
|
(setq-default save-place t)
|
|
(setq save-place-file (expand-file-name ".places" user-emacs-directory))
|
|
|
|
(setq make-backup-files nil)
|
|
(setq auto-save-default nil)
|
|
(setq-default major-mode 'text-mode)
|
|
(setq sentence-end-double-space nil)
|
|
|
|
;; Always tabs except for *.go?
|
|
;; TODO: set up language mode packages
|
|
(setq-default c-basic-offset 4
|
|
tab-width 4
|
|
indent-tabs-mode nil)
|
|
;; Some modes
|
|
(recentf-mode 1)
|
|
(show-paren-mode 1)
|
|
(setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit)
|
|
(electric-pair-mode 1)
|
|
(winner-mode 1)
|
|
(delete-selection-mode 1) ;; Select and type would replace the selected text
|
|
|
|
;; IDK
|
|
(add-hook 'prog-mode-hook #'subword-mode)
|
|
(add-hook 'minibuffer-setup-hook #'subword-mode)
|
|
#+END_SRC
|
|
|
|
** Elisp
|
|
|
|
#+BEGIN_SRC elisp
|
|
(add-hook 'elisp-mode-hook
|
|
(lambda ()
|
|
;; These are never used...
|
|
;; (local-set-key (kbd "C-c C-x") #'ielm)
|
|
;; (local-set-key (kbd "C-c C-c") #'eval-defun)
|
|
(local-set-key (kbd "C-c C-r") 'eval-region)
|
|
(local-set-key (kbd "C-c C-b") #'eval-buffer)))
|
|
#+END_SRC
|
|
|
|
** WIndow divider
|
|
|
|
This makes resizing by mouse dragging easier, as it increases the divider width between window borders to make it easy to drag by the pointer targetting that divider area.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(setq window-divider-default-right-width 8)
|
|
(setq window-divider-default-places 'right-only)
|
|
(window-divider-mode 1)
|
|
#+END_SRC
|
|
|
|
* Helper functions
|
|
|
|
Hopefully I'll be consistent with the convention of using =./= for all self-defined functions.
|
|
|
|
To search for these in completion systems, use =\./= (\o/ :D). =./= stands for DOTSLASH
|
|
|
|
** Org insert src
|
|
|
|
The function below is adapted from a blog post (link lost, sorry) which describes how you can define a function to quickly insert a src code block.
|
|
|
|
I've adapted it to automatically use =elisp :tangle yes= if the file currently being edited is in the =user-emacs-directory=, since I use a literate emacs configuration and the only time I'll use this function when I'm in the user emacs directory is when I'm editing my literate org configuration.
|
|
|
|
Otherwise (editing any other files) it will read input from the minibuffer for text to insert after the =#+BEGIN_SRC=. The original function used ido completing read with a list of known code types and I've nuked it and just let the user append what ever they want after =#+BEGIN_SRC=: this could be useful for adding =:stuff like this= after the code-type.
|
|
|
|
Recently I've also added ability to wrap a selected region with src instead, if a region is active, without editting it afterwards.
|
|
|
|
This is useful when I transfer org documents written with fixed-pitch font environment where I relied on plain text formatting, so when editting in variable-pitch font I can wrap it within src blocks to have it be formatted properly.
|
|
|
|
Most recently I've also added deleting an empty src block (created by =./org-insert-src= itself) if point is within a src block with no content other than a single empty line.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(defun ./org-insert-src (beg end)
|
|
"Insert (or wrap region with, or cancel) src block.
|
|
|
|
If cursor currently on an empty line, it is wrapped in src block, with
|
|
no other content in the src block other than the empty line, then the
|
|
src block together with the line after it (if empty) is deleted. This
|
|
undoes the effect of ./org-insert-src without active region, and
|
|
cancelling org-edit-src with C-c C-k.
|
|
|
|
Code type is prompted and `org-edit-src-code' called only for insert,
|
|
not for wrap.
|
|
|
|
Uses 'elisp' if currently in user-emacs-directory."
|
|
(interactive (if (use-region-p)
|
|
(list (region-beginning) (region-end))
|
|
(list (point-min) (point-min))))
|
|
(let ((selection (buffer-substring-no-properties beg end))
|
|
(code-type '(if (string=
|
|
(file-truename user-emacs-directory)
|
|
(file-name-directory (buffer-file-name)))
|
|
"elisp"
|
|
(read-from-minibuffer "#+BEGIN_SRC "))))
|
|
(if (< (length selection) 2)
|
|
(if (./org-empty-src-p)
|
|
;; Delete empty src block and exit
|
|
(progn
|
|
(previous-line)
|
|
(delete-line) ;; Newline also deleted
|
|
(delete-line)
|
|
(delete-line)
|
|
;; Delete empty line inserted by ./org-insert-src itself
|
|
(if (./match-line-p "")
|
|
(delete-line)))
|
|
;; Otherwise:
|
|
;; Insert src block with given code type and edit
|
|
(progn
|
|
(setq code-type (eval code-type))
|
|
(deactivate-mark)
|
|
(beginning-of-line)
|
|
(newline)
|
|
(insert (format "#+BEGIN_SRC %s\n" code-type))
|
|
(newline)
|
|
(insert "#+END_SRC\n")
|
|
(previous-line 2)
|
|
(org-edit-src-code)))
|
|
;; Wrap selected region
|
|
;;(setq code-type (eval code-type))
|
|
(goto-char beg)
|
|
(previous-line) (end-of-line)
|
|
(newline)
|
|
(insert "#+BEGIN_SRC ")
|
|
;; save-excursion doesn't seem to work here
|
|
(goto-char (+ end 11)) (end-of-line)
|
|
(newline)
|
|
(insert "#+END_SRC")
|
|
;; FIXME: putting cursor at the begin src part afterwards doesn't work
|
|
(re-search-backward "^\\s-*#\\+BEGIN_SRC")
|
|
(end-of-line))))
|
|
#+END_SRC
|
|
|
|
** Utility functions for 'org-insert-src'
|
|
|
|
Used by the above function.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(defun ./match-line-p (regexp &optional move keep-pos start)
|
|
"Wrapper around string-match-p to use contents of current line.
|
|
|
|
Returns whether current line, after moving down by MOVE lines, can be
|
|
matched with REGEXP.
|
|
|
|
If REGEXP is an empty string, return t for empty line, nil otherwise.
|
|
MOVE argument is passed to `next-line'.
|
|
|
|
If REGEXP and is non-nil, REGEXP and START is passed to
|
|
`string-match-p' with no changes, and its return-value is returned
|
|
as-is.
|
|
|
|
MOVE argument is passed as-is to `next-line' immediately.
|
|
|
|
If KEEP-POS is non-nil, pass MOVE argument to `previous-line' after obtaining
|
|
contents of the required line."
|
|
(next-line move)
|
|
(let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
|
|
(if keep-pos
|
|
(previous-line move))
|
|
(if (= (length regexp) 0)
|
|
(if (= (length line) 0)
|
|
t nil)
|
|
(string-match-p regexp line start))))
|
|
|
|
(defun ./org-empty-src-p ()
|
|
"Return whether point is within a org src block with a single empty line."
|
|
(let ((line))
|
|
(save-excursion
|
|
(if (./match-line-p "^\\s-*#\\+begin_src" -1)
|
|
(if (./match-line-p "" +1)
|
|
(if (./match-line-p "^\\s-*#\\+end_src" +1)
|
|
t nil) nil) nil))))
|
|
#+END_SRC
|
|
|
|
** Org split src
|
|
|
|
Below is also another helper function for editing my literate configuration. It uses the =#+BEGIN_SRC= content from the current code block (that the cursor is in) and splits the block into two by inserting =#+END_SRC= and copying whatever =#+BEGIN_SRC <...>= that was used in the current code block.
|
|
|
|
This is extremely useful when I want to paste some chunk of code into a big code block, then I decide to split them up in order to add documentation for them.
|
|
|
|
Note that ~re-search-backward~ seems to be case-insensitive, surprisingly, so both =BEGIN_SRC= and =begin_src= could be matched.
|
|
|
|
#+BEGIN_SRC elisp
|
|
(defun ./org-split-src ()
|
|
"Split current src block into two blocks at point.
|
|
|
|
Retains src properties."
|
|
(interactive)
|
|
(insert "#+END_SRC\n\n")
|
|
(save-excursion
|
|
(re-search-backward "^\\s-*#\\+begin_src")
|
|
(defvar beginsrc (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
|
|
(insert beginsrc)
|
|
(previous-line))
|
|
#+END_SRC
|
|
|
|
FIXME: Sometimes retaining the src properties of the backward closest to point =BEGIN_SRC= line doesn't work. When I'm editting packages.org and at some position outside of the Evil heading, using the split src function copies a =BEGIN_SRC= containing =:noweb-ref evil-config= somehow.
|
|
|
|
** Load file and directory
|
|
|
|
https://www.emacswiki.org/emacs/LoadingLispFiles#h5o-2 -- Thanks!
|
|
|
|
This is used for loading my =modules/= dir in =packages.el=.
|
|
|
|
2023-09-15 ish: Not anymore. After taking inspiration from Prot's emacs config I've decided to put modules and lisp directories into load path and =require= them rather than =load=.
|
|
|
|
I've also decided not to tangle this to not clog up my namespace (not that it matters that much lol).
|
|
|
|
#+BEGIN_SRC elisp no
|
|
(defun ./load-directory (dir)
|
|
"Load *.el files in a given directory"
|
|
(let ((load-it (lambda (f)
|
|
(load-file (concat (file-name-as-directory dir) f)))))
|
|
(mapc load-it (directory-files dir nil "\\.el$"))))
|
|
#+END_SRC
|
|
|
|
TBH this is such a small function but like, does save some parens y'know.
|
|
|
|
#+BEGIN_SRC elisp no
|
|
(defun ./load-file-if-exists (file)
|
|
"Same as load-file but NOP if file does not exist"
|
|
(if (file-exists-p file)
|
|
(load-file file)))
|
|
#+END_SRC
|
|
|
|
** Toggle window split
|
|
|
|
https://www.emacswiki.org/emacs/ToggleWindowSplit
|
|
|
|
#+BEGIN_SRC elisp
|
|
(defun toggle-window-split ()
|
|
(interactive)
|
|
(if (= (count-windows) 2)
|
|
(let* ((this-win-buffer (window-buffer))
|
|
(next-win-buffer (window-buffer (next-window)))
|
|
(this-win-edges (window-edges (selected-window)))
|
|
(next-win-edges (window-edges (next-window)))
|
|
(this-win-2nd (not (and (<= (car this-win-edges)
|
|
(car next-win-edges))
|
|
(<= (cadr this-win-edges)
|
|
(cadr next-win-edges)))))
|
|
(splitter
|
|
(if (= (car this-win-edges)
|
|
(car (window-edges (next-window))))
|
|
'split-window-horizontally
|
|
'split-window-vertically)))
|
|
(delete-other-windows)
|
|
(let ((first-win (selected-window)))
|
|
(funcall splitter)
|
|
(if this-win-2nd (other-window 1))
|
|
(set-window-buffer (selected-window) this-win-buffer)
|
|
(set-window-buffer (next-window) next-win-buffer)
|
|
(select-window first-win)
|
|
(if this-win-2nd (other-window 1))))))
|
|
#+END_SRC
|
|
|
|
|
|
* Packages - Elpaca
|
|
|
|
Bootstrap installation of Elpaca
|
|
#+BEGIN_SRC elisp
|
|
(defvar elpaca-installer-version 0.5)
|
|
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
|
|
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
|
|
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
|
|
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
|
|
:ref nil
|
|
:files (:defaults (:exclude "extensions"))
|
|
:build (:not elpaca--activate-package)))
|
|
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
|
|
(build (expand-file-name "elpaca/" elpaca-builds-directory))
|
|
(order (cdr elpaca-order))
|
|
(default-directory repo))
|
|
(add-to-list 'load-path (if (file-exists-p build) build repo))
|
|
(unless (file-exists-p repo)
|
|
(make-directory repo t)
|
|
(when (< emacs-major-version 28) (require 'subr-x))
|
|
(condition-case-unless-debug err
|
|
(if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
|
|
((zerop (call-process "git" nil buffer t "clone"
|
|
(plist-get order :repo) repo)))
|
|
((zerop (call-process "git" nil buffer t "checkout"
|
|
(or (plist-get order :ref) "--"))))
|
|
(emacs (concat invocation-directory invocation-name))
|
|
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
|
|
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
|
|
((require 'elpaca))
|
|
((elpaca-generate-autoloads "elpaca" repo)))
|
|
(progn (message "%s" (buffer-string)) (kill-buffer buffer))
|
|
(error "%s" (with-current-buffer buffer (buffer-string))))
|
|
((error) (warn "%s" err) (delete-directory repo 'recursive))))
|
|
(unless (require 'elpaca-autoloads nil t)
|
|
(require 'elpaca)
|
|
(elpaca-generate-autoloads "elpaca" repo)
|
|
(load "./elpaca-autoloads")))
|
|
(add-hook 'after-init-hook #'elpaca-process-queues)
|
|
(elpaca `(,@elpaca-order))
|
|
#+END_SRC
|
|
|
|
Note that disabling the built-in =package.el= is in =early-init.el=, which also includes setting of frame size and others.
|
|
|
|
Enable =use-package= for elpaca and load my package definitions in packages.org
|
|
#+BEGIN_SRC elisp
|
|
;; Install use-package support
|
|
(elpaca elpaca-use-package
|
|
;; Enable :elpaca use-package keyword.
|
|
(elpaca-use-package-mode)
|
|
;; Assume :elpaca t unless otherwise specified.
|
|
(setq elpaca-use-package-by-default t))
|
|
|
|
;; Block until current queue processed.
|
|
(elpaca-wait)
|
|
|
|
;;When installing a package which modifies a form used at the top-level
|
|
;;(e.g. a package which adds a use-package key word),
|
|
;;use `elpaca-wait' to block until that package has been installed/configured.
|
|
;;For example:
|
|
;;(use-package general :demand t)
|
|
;;(elpaca-wait)
|
|
#+END_SRC
|
|
|
|
* TODO Fontaine
|
|
|
|
This package is the only =use-package= package that is defined outside of =packages.el=.
|
|
|
|
FIXME: This doesn't work. Does this have to do with foundries?
|
|
|
|
Currently not tangled as I still have to test this.
|
|
|
|
#+BEGIN_SRC elisp :noweb yes :noweb-prefix no :tangle no
|
|
(use-package fontaine
|
|
:config
|
|
(setq fontaine-presets <<fontaine-presets>>)
|
|
<<fontaine-theme-hook>>
|
|
)
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC elisp :noweb-ref fontaine-presets :tangle no
|
|
'((regular
|
|
:default-family "Fira Code"
|
|
:default-weight normal
|
|
:default-height 160
|
|
:fixed-pitch-family "Fira Code"
|
|
:fixed-pitch-height 1.0
|
|
:variable-pitch-family "Inter"
|
|
:variable-pitch-weight normal
|
|
:variable-pitch-height 170
|
|
:bold-weight bold
|
|
:italic-family "Source Code Pro"
|
|
:italic-slant italic
|
|
:line-spacing 1)
|
|
(large
|
|
:default-family "Fira Code"
|
|
:default-weight normal
|
|
:default-height 190
|
|
:variable-pitch-family "Fira Sans"
|
|
:variable-pitch-weight normal
|
|
:variable-pitch-height 1.5
|
|
:bold-weight bold
|
|
:italic-slant italic
|
|
:line-spacing 1.5))
|
|
#+END_SRC
|
|
|
|
https://github.com/protesilaos/fontaine?tab=readme-ov-file#41-persist-font-configurations-on-theme-switch
|
|
|
|
This allows the font settings to be re-applied after a theme switch.
|
|
|
|
#+BEGIN_SRC elisp :noweb-ref fontaine-theme-hook :tangle no
|
|
(defvar ./post-enable-theme-hook nil
|
|
"Normal hook run after enabling a theme.")
|
|
|
|
(defun ./run-post-enable-theme-hook (&rest _args)
|
|
"Run `./post-enable-theme-hook'."
|
|
(run-hooks './post-enable-theme-hook))
|
|
|
|
(advice-add 'enable-theme :after #'./run-post-enable-theme-hook)
|
|
(add-hook './post-enable-theme-hook #'fontaine-apply-current-preset)
|
|
#+END_SRC
|
|
|
|
* Packages & Modules
|
|
|
|
#+BEGIN_SRC elisp
|
|
(dolist (path '("dotslash-lisp" "dotslash-modules"))
|
|
(add-to-list 'load-path (locate-user-emacs-file path)))
|
|
(require 'packages)
|
|
(require 'init-highlight)
|
|
#+END_SRC
|
|
|
|
* Local custom init (post)
|
|
|
|
This file is not tracked in dotfiles and is to be unique, custom configurations for each machine, run at the very end of =init.el=. (Also see [[Local custom init (pre)]].)
|
|
|
|
#+BEGIN_SRC elisp
|
|
(load (locate-user-emacs-file "dotslash-local-post.el") :no-error :no-message)
|
|
#+END_SRC
|