* Pre-init ** Add my own, version controlled ~lisp~ directory to ~load-path~ #+begin_src emacs-lisp (add-to-list 'load-path (expand-file-name (convert-standard-filename "lisp/") user-emacs-directory)) #+end_src ** Do the same with the local ~site-lisp~ if it’s there #+begin_src emacs-lisp (let ((site-lisp-dir "/usr/local/share/emacs/site-lisp")) (when (file-directory-p site-lisp-dir) (dolist (elt (directory-files site-lisp-dir)) (unless (or (string= elt ".") (string= elt "..")) (add-to-list 'load-path (expand-file-name elt site-lisp-dir)))))) #+end_src ** Load ~xdg-paths~ I prefer using freedesktop’s desktop specification everywhere. #+begin_src emacs-lisp (load "xdg-paths") #+end_src * Set up the package manager I’m a ~package.el~ user. Don’t package-shame me! ** Set up the package archives #+begin_src emacs-lisp (require 'package) (add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/")) (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t) (package-initialize) #+end_src ** Set up ~use-package~ It is built-in since 29.1, and let’s hope I never get back using older versions, but let’s stay on the safe side for now. #+begin_src emacs-lisp (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (require 'use-package) (use-package use-package :custom (use-package-always-ensure t) (use-package-verbose nil)) (use-package bind-key) #+end_src *** Install Quelpa so we can add packages not present in MELPA yet #+begin_src emacs-lisp (unless (package-installed-p 'quelpa) (with-temp-buffer (url-insert-file-contents "https://raw.githubusercontent.com/quelpa/quelpa/master/quelpa.el") (eval-buffer) (quelpa-self-upgrade))) #+end_src *** Finally, combine the powers of use-package and quelpa #+begin_src emacs-lisp (quelpa '(quelpa-use-package :fetcher git :url "https://github.com/quelpa/quelpa-use-package.git")) (require 'quelpa-use-package) #+end_src * Custom functions and commands This is a collection of functions and commands i wrote or stole from all around the internet. ** Utilities *** Make a backup filename under ~user-emacs-cache-directory~ Taken from [[http://ergoemacs.org/emacs/emacs_set_backup_into_a_directory.html][Xah’s site]]. #+begin_src emacs-lisp (defun xah/backup-file-name (fpath) "Return a new file path for FPATH under `user-emacs-cache-directory'" (let* ((backup-root-dir (expand-file-name "backup" user-emacs-cache-directory)) (file-path (replace-regexp-in-string "[A-Za-z]:" "" fpath)) (backup-file-path (replace-regexp-in-string "//" "/" (concat backup-root-dir file-path "~")))) (make-directory (file-name-directory backup-file-path) (file-name-directory backup-file-path)) backup-file-path)) #+end_src *** Check if we’re running under Termux We need to do things differently, if so. There’s probably a better way, though, other than checking the path of our home directory. #+begin_src emacs-lisp (defun gpolonkai/termux-p () "Check if Emacs is running under Termux." (string-match-p (regexp-quote "/com.termux/") (expand-file-name "~"))) #+end_src *** Find the first number on line #+begin_src emacs-lisp (defun gpolonkai/find-number-on-line () "Find the first number on the current line." (save-excursion (without-restriction (goto-char (pos-bol)) (when (re-search-forward "[0-9]+" (pos-eol) t) (number-at-point))))) #+end_src *** Round number at point to the given decimals #+begin_src emacs-lisp (defun gpolonkai/round-number-at-point-to-decimals (decimal-count) (interactive "NDecimal count: ") (let ((mult (expt 10 decimal-count))) (replace-match (number-to-string (/ (fround (* mult (number-at-point))) mult))))) #+end_src *** Leave ~isearch~ at the other end of the matched string From [[http://endlessparentheses.com/leave-the-cursor-at-start-of-match-after-isearch.html][Endless Parentheses]]. #+begin_src emacs-lisp (defun ep/isearch-exit-other-end () "Exit isearch, at the opposite end of the string." (interactive) (isearch-exit) (goto-char isearch-other-end)) #+end_src *** Mark the current match after leaving ~isearch~ From [[http://emacs.stackexchange.com/a/31321/507][Emacs SE]]. #+begin_src emacs-lisp (defun e-se/isearch-exit-mark-match () "Exit isearch and mark the current match." (interactive) (isearch-exit) (push-mark isearch-other-end) (activate-mark)) #+end_src *** Copy the prototype of the current C function #+begin_src emacs-lisp (defun gpolonkai/copy-func-prototype () "Copy the current function's prototype to the kill ring." (interactive) (save-excursion (beginning-of-defun) (let ((protocopy-begin (point))) (forward-list) (let ((protocopy-end (point))) (kill-ring-save protocopy-begin protocopy-end))))) #+end_src ** Window manipulation *** Bury a window #+begin_src emacs-lisp (defun gpolonkai/bury-window (window) "Quit WINDOW without killing it." (interactive) (quit-window nil window)) #+end_src *** Scroll a specific window up or down #+begin_src emacs-lisp (defun gpolonkai/scroll-window-up (window) "Scroll WINDOW up as `scroll-up-command' would." (interactive) (save-selected-window (select-window window) (scroll-up))) (defun gpolonkai/scroll-window-down (window) "Scroll WINDOW down as `scroll-down-command' would." (interactive) (save-selected-window (select-window window) (scroll-down))) #+end_src *** Transpose windows #+begin_src emacs-lisp (defun gpolonkai/transpose-windows (arg) "Transpose the buffers shown in two windows." (interactive "p") (let ((selector (if (>= arg 0) 'next-window 'previous-window))) (while (/= arg 0) (let ((this-win (window-buffer)) (next-win (window-buffer (funcall selector)))) (set-window-buffer (selected-window) next-win) (set-window-buffer (funcall selector) this-win) (select-window (funcall selector))) (setq arg (if (plusp arg) (1- arg) (1+ arg)))))) #+end_src *** Toggle window split between horizontal and vertical #+begin_src emacs-lisp (defun gpolonkai/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)))) (error "This works only for two windows!"))) #+end_src ** Org-mode related *** Wrapper around ~org-agenda~ to open my own custom list #+begin_src emacs-lisp (defun gpolonkai/org-agenda-list (&optional arg) (interactive "P") (org-agenda arg "c")) #+end_src *** Insert the current timestamp #+begin_src emacs-lisp (defun gpolonkai/org-insert-current-timestamp (&optional arg) "Insert the current timestamp" (interactive "P") (org-time-stamp '(16) arg)) #+end_src *** Insert two spaces after certain marks #+begin_src emacs-lisp (defcustom gpolonkai/org-dbl-space-punct-marks (list ?. ?! ?? ?…) "Punctuation marks after which the space key should insert two space characters." :type '(set character)) (defun gpolonkai/org-space-key (&optional arg) "Insert two spaces after certain markers. ARG will be passed down verbatim to `self-insert-command'." (interactive "p") (when (and (not (org-in-block-p '("src"))) (looking-back (rx-to-string `(any,@ gpolonkai/org-dbl-space-punct-marks)) nil)) (call-interactively 'self-insert-command arg)) (call-interactively 'self-insert-command arg)) #+end_src *** Filter out tasks from the Org agenda if they have a specific priority The whole idea comes from [[https://blog.aaronbieber.com/2016/09/24/an-agenda-for-life-with-org-mode.html][here]] which i use almost verbatim, hence the ~air-~ prefix. #+begin_src emacs-lisp (defun air-org-skip-subtree-if-priority (priority) "Skip an agenda subtree if it has a priority of PRIORITY. PRIORITY may be one of the characters ?A, ?B, or ?C." (let ((subtree-end (save-excursion (org-end-of-subtree t))) (pri-value (* 1000 (- org-lowest-priority priority))) (pri-current (org-get-priority (thing-at-point 'line t)))) (if (= pri-value pri-current) subtree-end nil))) #+end_src *** Filter out habits from the Org agenda #+begin_src emacs-lisp (defun air-org-skip-subtree-if-habit () "Skip an agenda entry if it has a STYLE property equal to \"habit\"." (let ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (string= (org-entry-get nil "STYLE") "habit") subtree-end nil))) #+end_src *** Filter out entries from the Org agenda with a specific state #+begin_src emacs-lisp (defun gpolonkai/org-skip-subtree-if-state (state) "Skip an agenda entry if its state is STATE." (let ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (string= (org-get-todo-state) state) subtree-end nil))) #+end_src *** Insert a heading with CREATED set to the current time This emulates how Orgzly work with my current settings. #+begin_src emacs-lisp (defun gpolonkai/org-insert-heading-created (&optional arg) (interactive "p") (let* ((org-insert-heading-respect-content t) (format-string (concat "[" (substring (cdr org-time-stamp-formats) 1 -1) "]")) (timestamp (format-time-string format-string (current-time)))) (if (> arg 1) (org-insert-subheading '(4)) (org-insert-heading)) (org-set-property "CREATED" timestamp))) #+end_src *** Unfold everything during ~ediff~ sessions EDiff and Org-mode files don’t play nice together… From [[https://list.orgmode.org/loom.20130801T011342-572@post.gmane.org/][the org-mode mailing list]]. #+begin_src emacs-lisp (defun f-ediff-org-showhide (buf command &rest cmdargs) "If buffer BUF exists and in `org-mode', execute COMMAND with CMDARGS." (when buf (when (eq (buffer-local-value 'major-mode (get-buffer buf)) 'org-mode) (save-excursion (set-buffer buf) (apply command cmdargs))))) (defun f-ediff-org-unfold-tree-element () "Unfold tree at diff location." (f-ediff-org-showhide ediff-buffer-A 'org-reveal) (f-ediff-org-showhide ediff-buffer-B 'org-reveal) (f-ediff-org-showhide ediff-buffer-C 'org-reveal)) (defun f-ediff-org-fold-tree () "Fold tree back to top level." (f-ediff-org-showhide ediff-buffer-A 'hide-sublevels 1) (f-ediff-org-showhide ediff-buffer-B 'hide-sublevels 1) (f-ediff-org-showhide ediff-buffer-C 'hide-sublevels 1)) #+end_src *** Add code references to Org documents From the [[http://www.howardism.org/Technical/Emacs/capturing-content.html][Howardism blog]]. #+begin_src emacs-lisp (defun ha/org-capture-fileref-snippet (f type headers func-name) (let* ((code-snippet (buffer-substring-no-properties (mark) (- (point) 1))) (file-name (buffer-file-name)) (file-base (file-name-nondirectory file-name)) (line-number (line-number-at-pos (region-beginning))) (initial-txt (if (null func-name) (format "From [[file:%s::%s][%s]]:" file-name line-number file-base) (format "From ~%s~ (in [[file:%s::%s][%s]]):" func-name file-name line-number file-base)))) (format " %s ,#+begin_%s %s %s ,#+end_%s" initial-txt type headers code-snippet type))) (defun ha/org-capture-clip-snippet (f) "Given a file, F, this captures the currently selected text within an Org EXAMPLE block and a backlink to the file." (with-current-buffer (find-buffer-visiting f) (ha/org-capture-fileref-snippet f "example" "" nil))) (defun ha/org-capture-code-snippet (f) "Given a file, F, this captures the currently selected text within an Org SRC block with a language based on the current mode and a backlink to the function and the file." (with-current-buffer (find-buffer-visiting f) (let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode))) (func-name (which-function))) (ha/org-capture-fileref-snippet f "src" org-src-mode func-name)))) #+end_src ** Text manipulation *** Fill or unfill a paragraph From Sacha Chua’s [[http://pages.sachachua.com/.emacs.d/Sacha.html][blog]]. #+begin_src emacs-lisp (defun sachachua/fill-or-unfill-paragraph (&optional unfill region) "Fill (or unfill, if UNFILL is non-nil) paragraph (or REGION)." (interactive (progn (barf-if-buffer-read-only) (list (if current-prefix-arg 'unfill) t))) (let ((fill-column (if unfill (point-max) fill-column))) (fill-paragraph nil region))) #+end_src *** Toggle case of character at point Based on [[http://ergoemacs.org/emacs/modernization_upcase-word.html][Xah’s toggle letter case defun version 2015-12-22]] #+begin_src emacs-lisp (defun gpolonkai/toggle-char-case (arg-move-point) "Toggle the case of the char after point. If prefix argument ARG-MOVE-POINT is non-nil, move point after the char." (interactive "P") (let ((case-fold-search nil)) (cond ((looking-at "[[:lower:]]") (upcase-region (point) (1+ (point)))) ((looking-at "[[:upper:]]") (downcase-region (point) (1+ (point))))) (cond (arg-move-point (right-char))))) #+end_src *** Sort lines by the first number on it #+begin_src emacs-lisp (defun gpolonkai/numeric-sort-lines (reverse beg end) "Sort lines in region by version. Interactively, REVERSE is the prefix argument, and BEG and END are the region. Non-nil REVERSE means to sort in reverse order." (interactive "P\nr") (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (let ((indibit-field-text-motion t)) (sort-subr reverse 'forward-line 'end-of-line #'gpolonkai/find-number-on-line))))) #+end_src *** Open a new line above #+begin_src emacs-lisp (defun wted/open-line-above () "Open a new line above point." (interactive) (beginning-of-line) (newline) (forward-line -1) (let ((tab-always-indent t) (c-tab-always-indent t)) (indent-for-tab-command))) #+end_src *** Open a new line below Copied from [[http://whattheemacsd.com/editing-defuns.el-01.html][whattheemacsd.com]]. #+begin_src emacs-lisp (defun wted/open-line-below () "Open a new line below point." (interactive) (end-of-line) (newline) (let ((tab-always-indent t) (c-tab-always-indent t)) (indent-for-tab-command))) #+end_src *** Insert the current file’s name at point From [[http://mbork.pl/2019-02-17_Inserting_the_current_file_name_at_point][Marcin Borkowski]]. #+begin_src emacs-lisp (defun mbork/insert-current-file-name-at-point (&optional full-path) "Insert the current filename at point. With prefix argument, use full path." (interactive "P") (let* ((buffer (if (minibufferp) (window-buffer (minibuffer-selected-window)) (current-buffer))) (filename (buffer-file-name buffer))) (if filename (insert (if full-path filename (file-name-nondirectory filename))) (error (format "Buffer %s is not visiting a file" (buffer-name buffer)))))) #+end_src *** Swap occurences of strings From [[http://emacs.stackexchange.com/a/27170/507][Emacs SE]]. #+begin_src emacs-lisp (defun e-se/query-swap-strings (from-string to-string &optional delimited start end) "Swap occurrences of FROM-STRING and TO-STRING. DELIMITED, START, and END are passed down verbatim to `perform-replace'." (interactive (let ((common (query-replace-read-args (concat "Query swap" (if current-prefix-arg (if (eq current-prefix-arg '-) " backward" " word") "") (if (use-region-p) " in region" "")) nil))) (list (nth 0 common) (nth 1 common) (nth 2 common) (if (use-region-p) (region-beginning)) (if (use-region-p) (region-end))))) (perform-replace (concat "\\(" (regexp-quote from-string) "\\)\\|" (regexp-quote to-string)) `(replace-eval-replacement replace-quote (if (match-string 1) ,to-string ,from-string)) t t delimited nil nil start end)) #+end_src ** Navigation *** Move to different beginnings/ends of the current line Inspired by Bozhidar Batsov's [[http://emacsredux.com/blog/2013/05/22/smarter-navigation-to-the-beginning-of-a-line/][solution]]. #+begin_src emacs-lisp (defun gpolonkai/move-to-beginning-of-line () "Move to different beginnings of the line. These are, in order: - beginning of the visual line if `visual-line-mode' is active, - the first non-whitespace (indentation), - the actual beginning of the line. This function will jump between the first character and the indentation if used multiple times." (interactive) (let ((last-pos (point))) (when visual-line-mode (beginning-of-visual-line)) (when (= (point) last-pos) (back-to-indentation)) (when (= (point) last-pos) (beginning-of-line)) (when (and (eq major-mode 'org-mode) (= (point) last-pos)) (org-beginning-of-line)) (when (= (point) last-pos) (back-to-indentation)))) (defun gpolonkai/move-to-end-of-line () "Move to the end of the line. If `visual-line-mode' is active, jump to the end of the visual line first. Then jump to the actual end of the line." (interactive) (let ((last-pos (point))) (when visual-line-mode (end-of-visual-line)) (when (= (point) last-pos) (end-of-line)) (when (and (eq major-mode 'org-mode) (= (point) last-pos)) (org-end-of-line)))) #+end_src *** Move to the next occurence of a character within the same line #+begin_src emacs-lisp (defun gpolonkai/goto-next-char (chr) (interactive "c") (when (search-forward (char-to-string chr) (pos-eol) t) (backward-char))) #+end_src *** Move to the beginning of the next word #+begin_src emacs-lisp (defun gpolonkai/beginning-of-next-word () (interactive) (let ((current-point (point))) (forward-word 1) (backward-word 1) (when (<= (point) current-point) (forward-word 2) (backward-word 1)))) #+end_src ** File manipulation *** Rename the current file From [[http://whattheemacsd.com/file-defuns.el-01.html][whattheemacsd.org]]. #+begin_src emacs-lisp (defun wted/rename-current-buffer-file () "Renames current buffer and file it is visiting." (interactive) (let ((name (buffer-name)) (filename (buffer-file-name))) (if (not (and filename (file-exists-p filename))) (error "Buffer '%s' is not visiting a file!" name) (let ((new-name (read-file-name "New name: " filename))) (if (get-buffer new-name) (error "A buffer named '%s' already exists!" new-name) (rename-file filename new-name 1) (rename-buffer new-name) (set-visited-file-name new-name) ; TODO: this is suspicious for me… (set-buffer-modified-p nil) (message "File '%s' successfully renamed to '%s'" name (file-name-nondirectory new-name))))))) #+end_src *** Delete the current file From [[http://whattheemacsd.com/file-defuns.el-02.html][whattheemacsd.org]]. #+begin_src emacs-lisp (defun wted/delete-current-buffer-file () "Remove file connected to current buffer and kill the buffer." (interactive) (let ((filename (buffer-file-name)) (name (buffer-name)) (buffer (current-buffer))) (if (not (and filename (file-exists-p filename))) (kill-buffer buffer) (when (yes-or-no-p "Are you sure you want to remove this file? ") (delete-file filename) (kill-buffer buffer) (message "File '%s' successfully removed" filename))))) #+end_src * Set up the very basics Now that we have package management configured we can set up defaults more easily. This includes every builtin packages, font faces, and the like. #+begin_src emacs-lisp (use-package emacs :ensure nil :init ;; Required for Consult/Vertico (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) (setq frame-title-format '((:eval (concat system-name ": " (if (buffer-file-name) (abbreviate-file-name (buffer-file-name)) "%b"))))) :custom (user-full-name "Gergely Polonkai") (user-mail-address "gergely@polonkai.eu") (kill-read-only-ok t) (use-dialog-box nil) (cursor-type 'bar) (echo-keystrokes .01) (fill-column 120) (initial-scratch-message "") (minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (enable-recursive-minibuffers t) (completion-cycle-threshold 3) (tab-always-indent 'complete) :hook (minibuffer-setup . cursor-intangible-mode)) #+end_src ** Set Org’s main directory Since a lot of packages (org-projectile, org-caldav, etc.) rely on it, it needs to be set as early as possible. #+begin_src emacs-lisp (setq org-directory (expand-file-name "NextCloud/orgmode" user-documents-directory)) #+end_src ** Set up my personal keymap I set it up at the beginning so i can use it with ~use-package~ invocations from early on This might (should?) become a [[*general][general]] map later. #+begin_src emacs-lisp (defvar gpolonkai/pers-map (make-sparse-keymap) "My own, personal, keymap!") (define-prefix-command 'gpolonkai/pers-map) (define-key ctl-x-map "t" 'gpolonkai/pers-map) (define-key global-map (kbd "C-t") 'gpolonkai/pers-map) #+end_src ** Set up some faces #+begin_src emacs-lisp (use-package faces :ensure nil :custom-face (default ((t (:family "Fira Code Retina" :foundry "simp" :slant normal :weight normal :height 100 :width normal)))) (trailing-whitespace ((t (:inherit nil :background "red3"))))) #+end_src *** Fira Code comes with nice ligatures, let’s use them! #+begin_src emacs-lisp (use-package ligature :config ;; Enable the "www" ligature in every possible major mode (ligature-set-ligatures 't '("www")) ;; Enable traditional ligature support in eww-mode, if the ;; `variable-pitch' face supports it (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi")) ;; Enable all Cascadia and Fira Code ligatures in programming modes (ligature-set-ligatures 'prog-mode '(;; == === ==== => =| =>>=>=|=>==>> ==< =/=//=// =~ ;; =:= =!= ("=" (rx (+ (or ">" "<" "|" "/" "~" ":" "!" "=")))) ;; ;; ;;; (";" (rx (+ ";"))) ;; && &&& ("&" (rx (+ "&"))) ;; !! !!! !. !: !!. != !== !~ ("!" (rx (+ (or "=" "!" "\." ":" "~")))) ;; ?? ??? ?: ?= ?. ("?" (rx (or ":" "=" "\." (+ "?")))) ;; %% %%% ("%" (rx (+ "%"))) ;; |> ||> |||> ||||> |] |} || ||| |-> ||-|| ;; |->>-||-<<-| |- |== ||=|| ;; |==>>==<<==<=>==//==/=!==:===> ("|" (rx (+ (or ">" "<" "|" "/" ":" "!" "}" "\]" "-" "=" )))) ;; \\ \\\ \/ ("\\" (rx (or "/" (+ "\\")))) ;; ++ +++ ++++ +> ("+" (rx (or ">" (+ "+")))) ;; :: ::: :::: :> :< := :// ::= (":" (rx (or ">" "<" "=" "//" ":=" (+ ":")))) ;; // /// //// /\ /* /> /===:===!=//===>>==>==/ ("/" (rx (+ (or ">" "<" "|" "/" "\\" "\*" ":" "!" "=")))) ;; .. ... .... .= .- .? ..= ..< ("\." (rx (or "=" "-" "\?" "\.=" "\.<" (+ "\.")))) ;; -- --- ---- -~ -> ->> -| -|->-->>->--<<-| ("-" (rx (+ (or ">" "<" "|" "~" "-")))) ;; *> */ *) ** *** **** ("*" (rx (or ">" "/" ")" (+ "*")))) ;; www wwww ("w" (rx (+ "w"))) ;; <>