dotfiles/.config/emacs/init.org

17 KiB
Raw Permalink Blame History

Init.El Literate

;; NOTE:
;; This file is generated from init.org

Variables

(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 [...]) ")

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 /hedy/dotfiles/src/branch/master/.config/emacs/Variables so that I can customize those variables within the file.

(load (locate-user-emacs-file "dotslash-local-pre.el") :no-error :no-message)

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.

(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; "))

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 (: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)

Better defaults (misc)

;;(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)

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)))

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.

(setq window-divider-default-right-width 8)
(setq window-divider-default-places 'right-only)
(window-divider-mode 1)

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.

(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))))

Utility functions for 'org-insert-src'

Used by the above function.

(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))))

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.

(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))

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).

(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$"))))

TBH this is such a small function but like, does save some parens y'know.

(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)))

Toggle window split

https://www.emacswiki.org/emacs/ToggleWindowSplit

(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))))))

Packages - Elpaca

Bootstrap installation of Elpaca

(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))

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

  ;; 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)

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.

(use-package fontaine
  :config
  (setq fontaine-presets <<fontaine-presets>>)
  <<fontaine-theme-hook>>
)
'((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))

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.

(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)

Packages & Modules

(dolist (path '("dotslash-lisp" "dotslash-modules"))
  (add-to-list 'load-path (locate-user-emacs-file path)))
(require 'packages)
(require 'init-highlight)

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 /hedy/dotfiles/src/branch/master/.config/emacs/Local%20custom%20init%20%28pre%29.)

(load (locate-user-emacs-file "dotslash-local-post.el") :no-error :no-message)