* Custom functions and commands This is a collection of functions and commands i wrote or stole from all around the internet. ** 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 ** Magit *** Go to the beginning of the current section #+begin_src emacs-lisp (defun gpolonkai/magit-goto-beginning-of-section () (interactive) (let ((section (magit-current-section))) (magit-section-goto section))) #+end_src *** Go to the end of the current section #+begin_src emacs-lisp (defun gpolonkai/magit-goto-end-of-section () "Move to the beginning of the next visible section." (interactive) (unless (eobp) (let ((section (magit-current-section))) (if (oref section parent) (let ((next (and (not (oref section hidden)) (not (= (oref section end) (1+ (point)))) (car (oref section children))))) (while (and section (not next)) (unless (setq next (car (magit-section-siblings section 'next))) (setq section (oref section parent)))) (if next (magit-section-goto next) (end-of-buffer))) (magit-section-goto 1))))) #+end_src *** Find the first URL in the current section #+begin_src emacs-lisp (defun gpolonkai/magit-find-first-link () (interactive) (let ((bos (save-excursion (gpolonkai/magit-goto-beginning-of-section) (point))) (eos (save-excursion (gpolonkai/magit-goto-end-of-section) (point)))) (goto-char bos) (when (re-search-forward "https?://" eos t) (goto-char (match-beginning 0))))) #+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 "org-mode" 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"))) ;; <>