dotfiles/.config/emacs/init.org

336 lines
14 KiB
Org Mode
Raw Normal View History

#+title: Init.El Literate
:PROPERTIES:
:tangle: ~/.config/emacsd/init.el
:END:
#+auto_tangle: t
* Basics
#+BEGIN_SRC emacs-lisp :tangle yes
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(defun display-startup-echo-area-message ()
(message (format "Emacs %s loaded from %s"
emacs-version user-emacs-directory)))
(global-set-key (kbd "C-c C-r") 'eval-region)
(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; "))
;; Display
;; 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 (:foundry "apple" :family "IBM Plex Sans" :weight normal :height 170))))
'(fixed-pitch ((t (:foundry "apple" :family "Fira Code" :height 160)))))
(set-face-attribute 'default nil :foundry "apple" :family "Fira Code" :height 160)
(set-face-attribute 'variable-pitch nil :foundry "apple" :family "IBM Plex Sans" :height 170)
;;(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
(require 'saveplace)
(setq-default save-place t)
(setq save-place-file (expand-file-name ".places" user-emacs-directory))
;; Some better defaults?
(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)
;; IDK
(add-hook 'prog-mode-hook #'subword-mode)
(add-hook 'minibuffer-setup-hook #'subword-mode)
;; elisp
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(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-b") #'eval-buffer)))
#+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
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 =emacs-lisp :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 emacs-lisp :tangle yes
(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 'emacs-lisp :tangle yes' 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)))
"emacs-lisp :tangle yes"
(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))))
(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
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.
- ~save-excursion~ allows execution of some expressions then move the point back to where it was (before the save-excursion call).
- Note that the ~re-search-backward~ seems to be case-insensitive, surprisingly, so both =BEGIN_SRC= and =begin_src= could be matched.
#+begin_src emacs-lisp :tangle yes
(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
** Load file and directory
https://www.emacswiki.org/emacs/LoadingLispFiles#h5o-2 -- Thanks!
This is used for loading my =modules/= dir in =packages.el=.
#+BEGIN_SRC emacs-lisp :tangle yes
(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 emacs-lisp :tangle yes
(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 emacs-lisp :tangle yes
(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 emacs-lisp :tangle yes
(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 emacs-lisp :tangle yes
;; 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)
(load (expand-file-name "packages.el" user-emacs-directory))
#+END_SRC
Below is useful when elpaca installs a package for the first time, and I passed my org literate config files from calling emacs, which would have already been opened, this prevents the right hooks from packages to be loaded; hence I should source my packages config again.
TODO: doesn't seem to work if called interactively, but if I'm here and use =C-x C-e= on the =( load ...)= call it works -- doesn't trigger elpaca?
#+BEGIN_SRC emacs-lisp :tangle yes
(defun ./reload-packages.el ()
"Source packages.el again"
(interactive)
(load (expand-file-name "packages.el" user-emacs-directory)))
#+END_SRC