My .emacs.d directory
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

97 KiB

Add some directories to load-path

My own (version controlled) lisp directory

(add-to-list 'load-path (expand-file-name
                         (convert-standard-filename "lisp/")
                         user-emacs-directory))
(add-to-list 'load-path (expand-file-name
                         (convert-standard-filename "lisp/nyan-prompt")
                         user-emacs-directory))

The local site-lisp

…if it exists.

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

Load some prerequisites

Load xdg-paths

(load "xdg-paths")

Load the tango dark theme

(load-theme 'tango-dark t)

Emacs configuration

Set up the package archives

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

Configure use-package and preload bind-key

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(require 'use-package)

(customize-set-variable 'use-package-always-ensure t)
(customize-set-variable 'use-package-verbose nil)

(use-package bind-key)

Install and configure Quelpa

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

And quelpa-use-package, too!

(quelpa
 '(quelpa-use-package
   :fetcher git
   :url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)

Set up the really basic things

(use-package emacs
  :ensure nil
  :custom
  (user-full-name "Gergely Polonkai")
  (user-mail-address "gergely@polonkai.eu")
  (kill-read-only-ok t)
  (use-dialog-box nil))

Set up my personal keymap

I set it up early so i can use it in use-package calls immediately.

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

I really don’t want to type more than i really must…

(defalias 'yes-or-no-p 'y-or-n-p)

Set UTF-8 as the default encoding

Just to make sure, although most Linux DEs do this for me.

(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)

Set Org’s main directory

Since a lot of packages use it as their base (org-projectile, org-caldav, etc.), i need it as early as possible.

(setq org-directory (expand-file-name "NextCloud/orgmode" user-documents-directory))

Custom commands and functions

Utility functions

Check if something is nil or a list of strings

(defun nil-or-list-of-strings-p (var)
  "Return t if VAR is either nil or a list holding only strings."
  (or (null var)
      (not (null (delq nil
            (mapcar (lambda (x) (and (stringp x) x)) var))))))

Get the number at point

(defun get-number-at-point ()
  (interactive)
  (skip-chars-backward "0123456789.-")
  (or (looking-at "[0123456789.-]+")
      (error "No number at point"))
  (string-to-number (match-string 0)))

Round number at point to the given decimals

(defun round-number-at-point-to-decimals (decimal-count)
  (interactive "NDecimal count: ")
  (let ((mult (expt 10 decimal-count)))
    (replace-match (number-to-string
              (/
               (fround
                (*
                 mult
                 (get-number-at-point)))
                mult)))))

Make a backup filename under user-emacs-cache-directory

Taken from Xah’s site.

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

Run a function on a region

(defun func-region (start end func)
  "Run a function over the region between START and END in current buffer."
  (save-excursion
    (let ((text (delete-and-extract-region start end)))
      (insert (funcall func text)))))

Check if we are running under Termux

We need to do things differently, if so.

(defun termux-p ()
  "Check if Emacs is running under Termux."
  (string-match-p
   (regexp-quote "/com.termux/")
   (expand-file-name "~")))

Misc text manipulation functions

Delete the current line

(defun gpolonkai/delete-current-line ()
  "Kill the whole line on which point is."
  (interactive)

  (beginning-of-line)
  (kill-line 1))

Duplicate current line

(defun gpolonkai/duplicate-line()
  "Duplicate line at point."

  (interactive)

  (save-excursion
    (move-beginning-of-line 1)
    (kill-line)
    (yank)
    (open-line 1)
    (forward-line 1)
    (yank)))

Toggle case of character at point

Based on Xah’s toggle letter case defun version 2015-12-22

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

Open a new line below

Copied from http://whattheemacsd.com/editing-defuns.el-01.html

(defun open-line-below ()
  "Open a new line below point."

  (interactive)

  (end-of-line)
  (newline)
  (indent-for-tab-command))

Open a new line above

(defun open-line-above ()
  "Open a new line above point."

  (interactive)

  (beginning-of-line)
  (newline)
  (forward-line -1)
  (indent-for-tab-command))

TODO Kill or copy the whole line

Got from Xah’s site (TODO is for adding a link here.)

(defun æ-kill-or-copy-whole-line (kill)
  "Kill or copy the whole line point is on.

If KILL is non-nil, the line gets killed.  Otherwise, it gets just
copied to the kill ring."
  (interactive "P")

  (if kill
      (kill-whole-line)
    (let ((beginning (progn (beginning-of-line) (point)))
          (end (progn (end-of-line) (point))))
      (copy-region-as-kill beginning end))))

Enclose region in a specific character

(defun gpolonkai/enclose-region (character &optional start end)
  "Enclose region between CHARACTER.

If region is empty and neither START nor END is set, simply inserts CHARACTER
two times and moves point between them.

If character is present in `insert-pair-alist', this function will enclose
region in the corresponding pair.  In this case, CHARACTER must be the opening
member of the pair."

  (interactive "cWhat character? \nr")

  (let ((open character)
        (close character)
        (pair (assq character insert-pair-alist)))
    (if pair
        (if (nth 2 pair)
            (setq open (nth 1 pair) close (nth 2 pair))
          (setq open (nth 0 pair) close (nth 1 pair))))
    (unless (and open close)
      (setq open character
            close character))
    (unless (use-region-p)
      (setq start (point)
            end (point)))
    (save-excursion
      (goto-char end)
      (insert-char close)

      (goto-char start)
      (insert-char open))
    (unless (use-region-p)
      (forward-char))))

Convert camelCase to snake_case

(defun camel-to-snake-case (arg)
  "Convert a camelCase word to snake_case.

If the prefix argument ARG is non-nil, convert the text to uppercase."
  (interactive "p")
  (progn
    (let ((start (region-beginning))
          (end (region-end))
          (case-fold-search nil)
          (had-initial-underscore nil))
      (goto-char start)
      (when (looking-at "_") (setq had-initial-underscore t))
      (while (re-search-forward "\\([A-Z]\\)" end t)
        (replace-match "_\\1")
        (setq end (1+ end)))
      (if arg
          (upcase-region start end)
        (downcase-region start end))
      (goto-char start)
      (unless had-initial-underscore (delete-char 1)))))

Insert two spaces after specific characters

(defun org-space-key (&optional arg)
  "Insert two spaces after a period.

ARG will be passed down verbatim to `self-insert-command'"
  (interactive "p")

  (when (looking-back "[.!?…]" nil)
    (call-interactively 'self-insert-command arg))
  (call-interactively 'self-insert-command arg))

Fill or unfill a paragraph

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

Swap occurences of strings

(defun so/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))

Insert the current file’s name at point

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

Navigation

Move to different beginnings of the current line

Inspired by Bozhidar Batsov's solution.

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

Move to the different ends of the current line

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

Move to the next occurence of a character within the same line

(defun goto-next-char (chr)
  (interactive "c")
  (let ((substr (char-to-string chr))
        (end (save-excursion (end-of-line) (point))))
    (when (search-forward substr end t)
      (backward-char))))

File manipulation

Rename the current file

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

Delete the current file

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

Allow reopening the previously closed file

(defvar gpolonkai/last-killed-buffer-file-name
  nil
  "The last killed buffer.

Used by `gpolonkai/save-killed-buffer-filename' and `gpolonkai/undo-buffer-kill'.")

(defun gpolonkai/save-killed-buffer-filename ()
  "Save the filename of the killed buffer in `gpolonkai/last-killed-buffer-file-name'."
  (let ((filename (buffer-file-name)))
   (unless filename
    (setq gpolonkai/last-killed-buffer-file-name (buffer-file-name)))))

(add-hook 'kill-buffer-hook 'gpolonkai/save-killed-buffer-filename)

(defun gpolonkai/undo-buffer-kill ()
  "Undo killing the last buffer.

Esentially it visits the file again."
  (interactive)
  (if gpolonkai/last-killed-buffer-file-name
      (progn
        (find-file gpolonkai/last-killed-buffer-file-name)
        (setq gpolonkai/last-killed-buffer-file-name nil))
    (message "The buffer last killed didn’t visit a file.")))

Open this file as another user

(defun open-this-file-as-other-user (user)
  "Edit current file as USER, using `tramp' and `sudo'.

If the current buffer is not visiting a file, prompt for a file
name."
  (interactive "sEdit as user (default: root): ")
  (when (string= "" user)
    (setq user "root"))
  (let* ((filename (or buffer-file-name
                       (read-file-name (format "Find file (as %s): "
                                               user))))
         (tramp-path (concat (format "/sudo:%s@localhost:" user) filename)))
    (if buffer-file-name
        (find-alternate-file tramp-path)
      (find-file tramp-path))))

Open my own init.el

(defun gpolonkai/visit-init-file ()
  "Open the init file."
  (interactive)
  (find-file-other-window (expand-file-name "configuration.org" user-emacs-directory)))

Open my Org-mode index file

(defun gpolonkai/visit-org-index ()
  "Visit the root of Org-mode notes."
  (interactive)
  (find-file-other-window (expand-file-name "index.org" org-directory)))

Frame manipulation

Hidden modeline mode

To temporarily hide the mode line.

(defvar hidden-mode-line-mode nil)
(defvar hide-mode-line nil)

(define-minor-mode hidden-mode-line-mode
  "Minor mode to hide the mode-line in the current buffer."
  :init-value nil
  :global nil
  :variable hidden-mode-line-mode
  :group 'editing-basics
  (if hidden-mode-line-mode
      (setq hide-mode-line mode-line-format
            mode-line-format nil)
    (setq mode-line-format hide-mode-line
          hide-mode-line nil))
  (force-mode-line-update)
  (redraw-display)
  (when (and (called-interactively-p 'interactive)
             hidden-mode-line-mode)
    (run-with-idle-timer
     0 nil 'message
     (concat "Hidden Mode Line Mode enabled.  "
             "Use M-x hidden-mode-line-mode to make mode-line appear."))))

Window manipulation

Transpose windows

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

Toggle window split between horizontal and vertical

(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))))
    (error "This works only for two windows!")))

Scroll up or down in a specific window

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

Bury a window

(defun gpolonkai/bury-window (window)
  "Quit WINDOW without killing it."
  (interactive)
  (quit-window nil window))

c-mode related

Copy the prototype of the current function

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

Programming related

Check if we are inside a string

(defun gpolonkai/prog-in-string-p ()
  "Return t if point is inside a string."
  (nth 3 (syntax-ppss)))

Check if we are inside a comment

(defun gpolonkai/prog-in-comment-p ()
  "Return t if point is inside a comment."
  (nth 4 (syntax-ppss)))

Add code references to Org documents

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

python-mode related

Add a docstring to the current thing

…be it a function, class, or a module

(defun gpolonkai/python-add-docstring ()
  "Add a Python docstring to the current thing.

If point is inside a function, add docstring to that.  If point
is in a class, add docstring to that.  If neither, add docstring
to the beginning of the file."
  (interactive)
  (save-restriction
    (widen)
    (beginning-of-defun)
    (if (not (looking-at-p "\\(def\\|class\\) "))
        (progn
          (goto-char (point-min))
          (back-to-indentation)
          (forward-char)
          (while (gpolonkai/prog-in-comment-p)
            (forward-line)
            (back-to-indentation)
            (forward-char)))
      (search-forward ":")
      (while (or (gpolonkai/prog-in-string-p)
                 (gpolonkai/prog-in-comment-p))
        (search-forward ":")))
    (if (eq 1 (count-lines 1 (point)))
        (open-line-above)
      (open-line-below))
    (insert "\"\"\"")
    (let ((point-pos (point)))
      (open-line-below)
      (insert "\"\"\"\n")
      (goto-char point-pos))))

Font-lock variables in f-strings

(with-eval-after-load "python"
  (setq python-font-lock-keywords
        (append python-font-lock-keywords
                '(;; this is the full string.
                  ;; group 1 is the quote type and a closing quote is matched
                  ;; group 2 is the string part
                  ("f\\(['\"]\\{1,3\\}\\)\\([^\\1]+?\\)\\1"
                   ;; these are the {keywords}
                   ("{[^}]*?}"
                    ;; Pre-match form
                    (progn (goto-char (match-beginning 0)) (match-end 0))
                    ;; Post-match form
                    (goto-char (match-end 0))
                    ;; face for this match
                    (0 font-lock-variable-name-face t)))))))

Jinja related

Mark a string as translatable

(defun jinja-mark-translatable (begin end)
  (interactive (if (use-region-p)
                   (list (region-beginning) (region-end))
                 (list (point-min) (point-max))))
  (save-excursion
    (goto-char end)
    (insert "{% endtrans %}")
    (goto-char begin)
    (insert "{% trans %}")))

URL-ify and de-URL-ify region

These functions URL-encode/decode a text. Might be helpful when embedding/extracting data to/from HTML.

(defun hex-region (start end)
  "urlencode the region between START and END in current buffer."
  (interactive "r")
  (func-region start end #'url-hexify-string))

(defun unhex-region (start end)
  "de-urlencode the region between START and END in current buffer."
  (interactive "r")
  (func-region start end #'url-unhex-string))

Automatically zone out after 60 seconds

(defun gpolonkai/zone-enable ()
  "Enable zoning out."
  (interactive)
  (zone-when-idle 60)
  (message "I will zone out after idling for 60 seconds."))

Utility functions for editing Zim wiki files

(defun zim-timestamp ()
  (with-temp-buffer
    (insert (format-time-string "%Y-%m-%dT%H:%M:%S%z"))
    (forward-char -2)
    (insert ":")
    (buffer-string)))

(defun insert-zim-timestamp ()
  (interactive)
  (insert (zim-timestamp)))

(defun insert-zim-header ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (insert
     (concat "Content-Type: text/x-zim-wiki\n"
             "Wiki-Format: zim 0.4\n"
             "Creation-Date: " (zim-timestamp) "\n\n"))))

Utility functions for EDiff

EDiff and Org-mode files don’t play nice together…

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

Leave isearch at the other end of the matched string

(defun isearch-exit-other-end ()
  "Exit isearch, at the opposite end of the string."
  (interactive)

  (isearch-exit)
  (goto-char isearch-other-end))

Mark the current match after leaving isearch

(defun isearch-exit-mark-match ()
  "Exit isearch and mark the current match."
  (interactive)
  (isearch-exit)
  (push-mark isearch-other-end)
  (activate-mark))

Turn the cursor to purple if Yasnippet’s TAB function would fire

Taken from Sacha Chua’s config.

;; TODO: 'cursor-color is sometimes nil, but why?
(setq default-cursor-color (or (frame-parameter nil 'cursor-color) "#ffd85c"))
(setq yasnippet-can-fire-cursor-color "purple")

;; It will test whether it can expand, if yes, cursor color -> purple.
(defun yasnippet-can-fire-p (&optional field)
  "Check if the word before point can be expanded with yasnippet.

TODO: What is FIELD for?"
  (interactive)
  (setq yas--condition-cache-timestamp (current-time))
  (let (templates-and-pos)
    (unless (and yas-expand-only-for-last-commands
                 (not (member last-command yas-expand-only-for-last-commands)))
      (setq templates-and-pos (if field
                                  (save-restriction
                                    (narrow-to-region (yas--field-start field)
                                                      (yas--field-end field))
                                    (yas--templates-for-key-at-point))
                                (yas--templates-for-key-at-point))))
    (and templates-and-pos (first templates-and-pos))))

(defun sachachua/change-cursor-color-when-can-expand (&optional field)
  "Change cursor color if the text before point can be expanded with yasnippet.

TODO: What is FIELD for?"
  (interactive)
  (when (eq last-command 'self-insert-command)
    (set-cursor-color (if (sachachua/can-expand)
                          yasnippet-can-fire-cursor-color
                        default-cursor-color))))

(defun sachachua/can-expand ()
  "Return t if right after an expandable thing."
  (or (abbrev--before-point) (yasnippet-can-fire-p)))

Org mode stuff

Filter out tasks from the Org agenda if they have a specific priority

The whole idea comes from here, which i use almost verbatim. This is also the reason it has the air- prefix.

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

Filter out habits from the Org agenda

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

Filter out entries from the Org agenda with a specific state

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

Wrapper around org-agenda to open my own custom list

(defun gpolonkai/org-agenda-list (&optional arg)
  (interactive "P")
  (org-agenda arg "c"))

Insert the current timestamp

(defun gpolonkai/org-insert-current-timestamp (&optional arg)
  "Insert the current timestamp"
  (interactive "P")
  (org-time-stamp '(16) arg))

Insert a heading with CREATED set to the current time

This emulates how Orgzly work with my current settings.

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

UI preferences

Tweak window chrome

Turn off the scroll bar (that’s why Nyan-cat is here), the toolbar (I don’t really use it), and the menu bar (I rarely use it, and in those rare occasions I can simply turn it on.)

Also, maximise the frame.

(tool-bar-mode 0)
(menu-bar-mode 0)
(when window-system
  (scroll-bar-mode -1))

(set-frame-parameter nil 'fullscreen 'maximized)

Set the default font and configure font resizing

Before this can be used, make sure the Symbola font is installed.

(defun gpolonkai/set-font-size (frame)
  (when (display-graphic-p frame)
    (set-face-attribute 'default frame :font "Hack-12")
    (set-frame-font "Hack-12" t (list frame))))

(defun --set-emoji-font (frame)
  "Adjust the font setting of FRAME so Emacs can display Emoji properly."
  (when (display-graphic-p frame)
    (set-fontset-font t 'symbol
                      (font-spec :family "Symbola")
                      frame 'prepend)
    (set-fontset-font t 'unicode
                      "Noto Emoji"
                      nil 'append)))

(add-hook 'after-make-frame-functions 'gpolonkai/set-font-size)
(add-hook 'after-make-frame-functions '--set-emoji-font)

(gpolonkai/set-font-size nil)
(--set-emoji-font nil)

Set up global minor modes provided by Emacs

Pretty lambdas

Because we can.

(global-prettify-symbols-mode t)

And set up all the pretty symbols.

(setq prettify-symbols-alist
      '(("lambda" . 955)    ; λ
        ("function" . 402)  ; ƒ
        ("->" . 8594)       ; →
        ("=>" . 8658)       ; ⇒
        ("map" . 8614)      ; ↦
        ("not" . 172)))     ; ¬

…and some pairs to complete

TODO Maybe add-to-list is a better way to do it
END
(setq insert-pair-alist '(
                          (40 41)      ; ()
                          (91 93)      ; []
                          (123 125)    ; {}
                          (60 62)      ; <>
                          (34 34)      ; ""
                          (39 39)      ; ''
                          (96 39)      ; `'
                          (8220 8221)  ; “”
                          (8222 8221)  ; „”
                          (8216 8217)  ; ‘’
                          (8249 8250)  ; ‹›
                          (8250 8249)  ; ›‹
                          (171 187)    ; «»
                          (187 171)))  ; »«

Finally, set the frame title to the current file name

(setq frame-title-format '((:eval (concat system-name
                                          ": "
                                          (if (buffer-file-name)
                                              (abbreviate-file-name (buffer-file-name))
                                            "%b")))))

Treat soft line breaks as hard ones in textual modes

(add-hook 'text-mode-hook (lambda () (visual-line-mode t)))

Enable disabled commands

Because i’m a rock star like that.

(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
(put 'erase-buffer 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'set-goal-column 'disabled nil)
(put 'scroll-left 'disabled nil)
(put 'dired-find-alternate-file 'disabled nil)
(put 'Info-edit 'disabled nil)
(put 'list-timers 'disabled nil)

Packages installed from Quelpa

For some strange reason having this at the end of my configuration can cause unexpected results.

Matrix client

(use-package matrix-client
  :after quelpa-use-package
  :ensure nil
  :quelpa (matrix-client :fetcher github :repo "alphapapa/matrix-client.el"
                         :files (:defaults "logo.png" "matrix-client-standalone.el.sh")))

Ement

(use-package plz
  :ensure nil
  :quelpa (plz :fetcher github :repo "alphapapa/plz.el"))

(use-package ement
  :ensure nil
  :quelpa (ement :fetcher github :repo "alphapapa/ement.el")
  :custom
  (ement-save-token t))

Load some built-in libraries

thingatpt

(use-package thingatpt
  :ensure nil)

Calendar

(use-package calendar
  :ensure nil
  :init
  (setq calendar-week-start-day 1
        calendar-latitude 47.4
        calendar-longitude 19.0
        calendar-location-name "Budapest, Hungary"
        calendar-time-zone 60
        calendar-standard-time-zone-name "CET"
        calendar-daylight-time-zone-name "CEST"))

Add the SysAdmin day to the calendar

Because I’m a sysadmin, too.

(add-to-list 'holiday-other-holidays '(holiday-float 7 5 -1 "SysAdmin Day") t)
(add-to-list 'holiday-other-holidays '(holiday-fixed 10 21 "Reptile Awareness Day") t)

nXML

(use-package nxml-mode
  :ensure nil
  :custom
  (nxml-attribute-indent 4)
  (nxml-child-indent 4)
  (nxml-outline-child-indent 4))

recentf

(use-package recentf
  :ensure nil
  :config
  (run-at-time nil (* 5 60) 'recentf-save-list)
  (add-to-list 'recentf-exclude (expand-file-name "elpa" user-emacs-directory)))

files

(use-package files
  :ensure nil
  :custom
  (make-backup-file-name-function 'xah/backup-file-name))

whitespace

whitespace-mode is turned on by default, and can be toggled with F10.

(defun prevent-whitespace-mode-for-some ()
  "Prevent whitespace-mode from running in some modes."
  (and
   (not (derived-mode-p 'magit-mode))
   (not (derived-mode-p 'org-mode))))

(use-package whitespace
  :demand
  :config
  (add-function :before-while whitespace-enable-predicate 'prevent-whitespace-mode-for-some)
  (global-whitespace-mode 1)
  :custom
  (whitespace-line-column 100)
  :bind
  (([f10] . whitespace-mode)
   ([(shift f10)] . global-whitespace-mode)
   :map gpolonkai/pers-map
   ("w" . whitespace-cleanup)))

eshell

This is a function to delete a character, or close eshell if there’s nothing to delete. Taken from here.

(defun eshell-C-d ()
  "Either call `delete-char' interactively or quit."
  (interactive)

  (condition-case err
      (call-interactively #'delete-char)
    (error (if (and (eq (car err) 'end-of-buffer)
                    (looking-back eshell-prompt-regexp nil))
               (kill-buffer)
             (signal (car err) (cdr err))))))

Function to bind it locally to C-d.

(defun gpolonkai/eshell-set-c-d-locally ()
  (local-set-key (kbd "C-d") #'eshell-C-d))

Now set up eshell.

(use-package eshell
  :bind
  (:map gpolonkai/pers-map
   ("e" . eshell))
  :hook
  (eshell-mode . gpolonkai/eshell-set-c-d-locally))

saveplace

;; Save place
(use-package saveplace
  :config'
  (save-place-mode 1)
  :custom
  (save-place-file (expand-file-name ".places" user-emacs-directory)))

EDiff

(use-package ediff
  :custom
  (ediff-merge-split-window-function 'split-window-horizontally)
  (ediff-split-window-function 'split-window-vertically)
  (ediff-window-setup-function 'ediff-setup-windows-plain))

Automatically revert changed files

…unless they are modified, of course.

(use-package autorevert
  :config
  (global-auto-revert-mode 1))

Eww

For in-Emacs browsing needs.

(use-package eww
  :custom
  (eww-search-prefix "https://duckduckgo.com/html/?q="))

Electric indent mode

(use-package electric
  :config
  ;; This seems to be the default, but let’s make sure…
  (electric-indent-mode 1))

Save history

(use-package savehist
  :config
  (savehist-mode 1))

Web jump

(use-package webjump
  :bind
  (:map gpolonkai/pers-map
   ("j" . webjump)))

Which function am i in?

(defun gpolonkai/activate-which-func-mode ()
  (if (fboundp 'which-function-mode)
      (which-function-mode)
    (which-func-mode)))
(use-package which-func
  :config
  (setq which-func-unknown "∅")
  :hook
  (prog-mode . gpolonkai/activate-which-func-mode))

Fortune cookies

The cookies are from the Hungarian version an ancient MS-DOS based program called TAGLINE.

(use-package cookie1
  :demand t
  :custom
  (cookie-file (expand-file-name "fortune-cookies.txt" user-emacs-directory))
  :bind
  (:map gpolonkai/pers-map
   ("k" . cookie)))

Browse URL functionality in Termux

(when (termux-p)
  (use-package browse-url
    :ensure nil
    :config
    (advice-add 'browse-url-default-browser :override
                (lambda (url &rest args)
                  (start-process-shell-command
                   "open-url"
                   nil
                   (concat "am start -a android.intent.action.VIEW --user 0 -d "
                           url))))))

Dired customisation

(use-package dired
  :ensure nil
  :custom
  (dired-dwim-target t)
  (wdired-create-parent-directories t)
  (wdired-allow-to-change-permissions t)
  :bind
  (:map dired-mode-map
   ("RET" . dired-find-alternate-file)
   ("^" . (lambda () (interactive) (find-alternate-file "..")))
   ("W" . wdired-change-to-wdired-mode)))

Actionable URLs

(use-package goto-addr
  :hook ((compilation-mode . goto-address-mode)
         (prog-mode . goto-address-prog-mode)
         (eshell-mode . goto-address-mode)
         (shell-mode . goto-address-mode))
  :bind
  (:map goto-address-highlight-keymap
   ("<RET>" . goto-address-at-point)
   ("M-<RET>" . newline))
  :commands (goto-address-prog-mode goto-address-mode))

Do things at midnight

By default, it closes a bunch of unused buffers. I might add some more things there later.

(use-package midnight
  :ensure nil
  :config
  (setq clean-buffer-list-kill-never-buffer-names '("*scratch*"
                                                    "*Messages*"
                                                    "*dashboard*"))
  (midnight-mode t))

Show line numbers

I don’t usually like to see them, but there are occasions when they can be useful.

(use-package display-line-numbers
  :bind
  (:map gpolonkai/pers-map
   ("C-n" . display-line-numbers-mode)))

Check for spelling errors

(use-package ispell
  :custom
  (ispell-dictionary "en_GB")
  (ispell-program-name "/usr/bin/aspell")
  :hook
  (mail-send . ispell-message)
  (message-send . ispell-message))

Speed bar

(use-package speedbar)

use-package packages

Make sure we have the latest ELPA GPG keys

(use-package gnu-elpa-keyring-update)

Automatically upgrade packages every week

(use-package auto-package-update
  :custom
  (auto-package-update-interval 7)
  (auto-package-update-delete-old-versions t)
  ;; Let’s do this in after-init-hook, as use-package invocations may modify
  ;; the list of installed packages
  :hook
  (after-init . auto-package-update-maybe))

Nyanyanyanyanya

Nyan-cat style position marker

(use-package nyan-mode
  :config
  (nyan-mode t)
  :custom
  (nyan-bar-length 20)
  (nyan-animate-nyancat t)
  (nyan-wavy-trail t))

Nyan prompt in EShell

(use-package nyan-prompt
  :disabled
  :hook
  (eshell-load . nyan-prompt-enable))

Zone out with Nyancat

Unfortunately, this works only in a graphical mode.

(use-package zone-nyan
  :after
  zone
  :config
  (setq zone-programs (vconcat zone-programs [zone-nyan]))
  :custom
  (zone-nyan-hide-progress t))

De-light some minor modes

(use-package delight)

Eye candy

Moody mode-line

(use-package moody
  :config
  (moody-replace-mode-line-buffer-identification)
  (moody-replace-vc-mode)
  (set-face-attribute 'mode-line nil :box nil :foreground "#7e7486")
  (set-face-attribute 'mode-line-inactive nil :box nil)
  :custom
  (moody-mode-line-height 18))

Minions

(use-package minions
  :config
  (minions-mode 1))

Spinner, e.g. to display running background tasks

(use-package spinner)

Beacon

Highlight point. Sometimes it’s not easy to see.

(use-package beacon
  :demand
  :config
  (beacon-mode 1)
  :bind
  (:map gpolonkai/pers-map
   ("b" . beacon-blink)))

Display the status of the last command in the fringe of EShell

(use-package eshell-fringe-status
  :hook
  (eshell-mode . eshell-fringe-status-mode))

Extras for the EShell prompt

(use-package eshell-prompt-extras
  :config
  (with-eval-after-load "esh-opt"
    (autoload 'epe-theme-lambda "eshell-prompt-extras"))
  :custom
  (eshell-highlight-prompt nil)
  (eshell-prompt-function 'epe-theme-lambda))

Show form feeds as a horizontal line

(use-package form-feed
  :hook
  (emacs-lisp-mode . form-feed-mode)
  (compilation-mode . form-feed-mode)
  (help-mode . form-feed-mode))

Highlight the current line

(use-package hl-line
  :config
  (when window-system
    (global-hl-line-mode)))

GNU Globals

(defun gpolonkai/cond-enable-ggtags-mode ()
  (when (derived-mode-p 'c-mode 'c++-mode 'java-mode)
    (ggtags-mode t)))

(use-package ggtags
  :hook
  (c-mode-common . gpolonkai/cond-enable-ggtags-mode))

Multiple cursors

Because one is never enough.

(defun gpolonkai/no-blink-matching-paren ()
  (customize-set-variable 'blink-matching-paren nil))

(defun gpolonkai/blink-matching-paren ()
  (customize-set-variable 'blink-matching-paren t))

(use-package multiple-cursors
  :init
  (defvar gpolonkai/mc-prefix-map (make-sparse-keymap)
    "Prefix keymap for multiple-cursors")
  (define-prefix-command 'gpolonkai/mc-prefix-map)
  (define-key global-map (kbd "C-c m") 'gpolonkai/mc-prefix-map)
  :hook
  (multiple-cursors-mode-enabled . gpolonkai/no-blink-matching-paren)
  (multiple-cursors-mode-disabled . gpolonkai/blink-matching-paren)
  :bind
  (:map gpolonkai/mc-prefix-map
   ("t" . mc/mark-all-like-this)
   ("m" . mc/mark-all-like-this-dwim)
   ("l" . mc/edit-lines)
   ("e" . mc/edit-ends-of-lines)
   ("a" . mc/edit-beginnings-of-lines)
   ("n" . mc/mark-next-like-this)
   ("p" . mc/mark-previous-like-this)
   ("s" . mc/mark-sgml-tag-pair)
   ("d" . mc/mark-all-like-this-in-defun)
   ("M-<mouse-1>" . mc/add-cursor-on-click)))

Incremental search for multiple cursors

(use-package phi-search)

(use-package phi-search-mc
  :config
  (phi-search-mc/setup-keys))

Some extras

(use-package mc-extras
  :demand
  :bind
  (:map mc/keymap
   ("C-c m =" . mc/compare-chars)))

Add extra cursors via ace-jump

(use-package ace-mc
  :bind
  (:map gpolonkai/mc-prefix-map
   ("SPC" . ace-mc-add-multiple-cursors)
   ("C-SPC" . ace-mc-add-single-cursor)))

Magit

(use-package magit
  :custom
  (magit-auto-revert-mode nil)
  (magit-last-seen-setup-instructions "1.4.0")
  :bind
  (:map ctl-x-map
   ("g" . magit-status))
  :hook
  (git-commit-mode . turn-on-flyspell))

Make Magit work with Git forges

(use-package forge)

Zone

(use-package zone
  :demand
  :config
  (zone-when-idle 60)
  :bind
  (:map gpolonkai/pers-map
   ("zi" . gpolonkai/zone-enable)
   ("zq" . zone-leave-me-alone)))

Origami

(use-package origami
  :demand
  :config
  (define-prefix-command 'origami-mode-map)
  (define-key ctl-x-map (kbd "z") 'origami-mode-map)
  (global-origami-mode)
  :bind
  (:map origami-mode-map
   ("o" . origami-open-node)
   ("O" . origami-open-node-recursively)
   ("c" . origami-close-node)
   ("C" . origami-close-node-recursively)
   ("a" . origami-toggle-node)
   ("A" . origami-recursively-toggle-node)
   ("R" . origami-open-all-nodes)
   ("M" . origami-close-all-nodes)
   ("v" . origami-show-only-node)
   ("k" . origami-previous-fold)
   ("j" . origami-forward-fold)
   ("x" . origami-reset)))

Smart parens

(use-package smartparens
  :demand
  :config
  (show-smartparens-global-mode t)
  :hook
  (prog-mode . turn-on-smartparens-strict-mode)
  (markdown-mode . turn-on-smartparens-strict-mode)
  :bind
  (([f9] . smartparens-strict-mode)
   ("C-c s u" . sp-unwrap-sexp)
   ("C-c s k" . sp-kill-sexp)))

Projectile

(use-package projectile
  :delight '(:eval (concat " [" projectile-project-name "]"))
  :pin melpa-stable
  :config
  (projectile-mode t)
  :bind
  (:map projectile-mode-map
   ("C-c p" . projectile-command-map)))

Repository-based ToDo management with Org mode

(use-package org-projectile
  :after (:all projectile org)
  :defer t
  :bind
  (:map projectile-command-map
   ("n" . org-projectile-project-todo-completing-read))
  :custom
  (org-projectile-projects-file (expand-file-name "projects.org" org-directory))
  :config
  (push (org-projectile-project-todo-entry) org-capture-templates))

Text object manipulation

From the package description:

Text objects are textual patterns like a line, a top level definition, a word, a sentence or any other unit of text. When objed-mode is enabled, certain editing commands (configurable) will activate objed and enable its modal editing features.

(use-package objed
  :demand t
  :bind
  (:map global-map
   ("M-SPC" . objed-activate)))

Ace window

Besides its standard functionality, I also make add key bindings for burying or scrolling another window.

(use-package ace-window
  :custom
  (aw-background nil)
  (aw-dispatch-always t)
  :config
  (add-to-list 'aw-dispatch-alist
               '(?s gpolonkai/scroll-window-up " Scroll window up")
               t)
  (add-to-list 'aw-dispatch-alist
               '(?S gpolonkai/scroll-window-down " Scroll window down")
               t)
  (add-to-list 'aw-dispatch-alist
               '(?q gpolonkai/bury-window " Bury (quit) window")
               t)
  :bind
  (:map ctl-x-map
   ("o" . ace-window)))

Golden ratio

(use-package golden-ratio
  :config
  (add-to-list 'golden-ratio-extra-commands 'ace-window)
  (golden-ratio-mode t))

Avy

(use-package avy
  :demand
  :config
  (avy-setup-default)
  :bind
  (("M-g c" . avy-goto-char)
   ("M-g C" . avy-goto-char-2)
   ("M-g f" . avy-goto-line)
   ("M-g w" . avy-goto-word-1)
   ("M-g e" . avy-goto-word-0)))

Focus mode

It is similar to narrow mode, except the narrowing part; it dims everything outside of the current context.

(use-package focus
  :bind
  (([f8] . focus-mode)))

Command logging mode

For occasional screen casting recordings.

(use-package command-log-mode)

Emamux

For controlling tmux from within Emacs.

(use-package emamux)

Use StackExchange sites in an Emacs window

(use-package sx
  :demand
  :bind
  (:map gpolonkai/pers-map
   ("qi" . sx-inbox)
   ("qs" . sx-search)))

Goto last change

(use-package goto-last-change
  :bind
  (("M-g /" . goto-last-change)))

Rainbow mode

To highlight colours based on their name or hex code.

(use-package rainbow-mode
  :hook
  (css-mode . rainbow-mode)
  (scss-mode . rainbow-mode)
  (sass-mode . rainbow-mode))

Zygospore

Toggle other windows for maximum focus. When focus is no longer needed, they can be toggled back. C-x 1 is conveniently bound to it.

(use-package zygospore
  :bind
  (:map ctl-x-map
   ("1" . zygospore-toggle-delete-other-windows)))

Kanban board

(use-package kanban)

Highlight dired buffer by file size, modified time, git status

(use-package dired-k
  :bind
  (:map dired-mode-map
   ("K" . dired-k)))

Show number of matches in the mode line while searching

(use-package anzu
  :delight
  :config
  (global-anzu-mode 1))

Gradually expand region

(use-package expand-region
  :bind
  (:map gpolonkai/pers-map
   ("x" . er/expand-region)))

Read and Edit MediaWiki pages in an Emacs window

(use-package mediawiki
  :after
  id-manager
  :config
  (add-to-list 'mediawiki-site-alist
               '("WikEmacs"
                 "http://wikemacs.org/wiki/"
                 (password-store-get-field "wikemacs.org" "username")
                 (password-store-get "wikemacs.org" "password"))))

An Emacs Dashboard

(use-package dashboard
  :after
  projectile
  :config
  (add-to-list 'dashboard-items '(projects . 5) t)
  (dashboard-setup-startup-hook))

Hungarian holidays in the Calendar

(use-package hungarian-holidays
  :config
  (hungarian-holidays-add))

FlySpell

For all your spell-checking needs.

(use-package flyspell
  :hook
  (prog-mode . flyspell-prog-mode)
  (text-mode . flyspell-mode))

Delete all the whitespace

(use-package hungry-delete
  :config
  (global-hungry-delete-mode))

Send alerts to a notification system

(use-package alert
  :config
  (setq alert-default-style
        (if (termux-p)
            (progn
              ;; TODO Remove this as soon as my PR gets merged
              ;; https://github.com/jwiegley/alert/pull/41
              (unless (fboundp 'alert-termux-notify)
                (defcustom alert-termux-command (executable-find "termux-notification")
                  "Path to the termux-notification command.
This is found in the termux-api package, and it requires the Termux
API addon app to be installed."
                  :type 'file
                  :group 'alert)

                (defun alert-termux-notify (info)
                  "Send INFO using termux-notification.
Handles :TITLE and :MESSAGE keywords from the
INFO plist."
                  (if alert-termux-command
                      (let ((args (nconc
                                   (when (plist-get info :title)
                                     (list "-t" (alert-encode-string (plist-get info :title))))
                                   (list "-c" (alert-encode-string (plist-get info :message))))))
                        (apply #'call-process alert-termux-command nil
                               (list (get-buffer-create " *termux-notification output*") t)
                               nil args))
                    (alert-message-notify info)))

                (alert-define-style 'termux :title "Notify using termux"
                                    :notifier #'alert-termux-notify))
              'termux)
          'libnotify)))

Replace the GUI popup menu with something more efficient

(use-package ace-popup-menu
  ;; Unfortunately, avy-menu (used by this package) is not compatible with minions.  I just
  ;; disable it for now.
  :disabled
  :config
  (ace-popup-menu-mode 1))

I’m an achiever!

(use-package achievements
  :demand
  :config
  (achievements-mode 1)
  :bind
  (:map gpolonkai/pers-map
   ("C-a" . achievements-list-achievements)))

Secretaria

Because even secretaries need a secretary today.

(use-package secretaria
  :after
  alert
  :hook
  ;; use this for getting a reminder every 30 minutes of those tasks
  ;; scheduled for today and which have no time of day defined.
  (after-init . secretaria-unknown-time-always-remind-me))

The Silver Searcher

(use-package ag
  :after projectile
  :bind
  (:map projectile-mode-map
   ("C-c p C-a" . ag-project)))

A fancier narrow-mode

(use-package fancy-narrow
  :config
  (fancy-narrow-mode))

Undo tree

(use-package undo-tree)

All the icons!

Should the fonts be missing, run (all-the-icons-install-fonts). It’s not run automatically, as it requires connecting to a website and download a pretty large font file.

(use-package all-the-icons)

NeoTree, if Dired is not an option

(defun gpolonkai/neo-set-theme ()
  (customize-set-variable 'neo-theme (if (display-graphic-p) 'icons 'arrow)))

(use-package neotree
  :after
  all-the-icons
  :bind
  (([f5] . neotree-toggle))
  :hook
  (neo-enter . gpolonkai/neo-set-theme))

Waka-waka (AKA ActivityWatch)

(use-package activity-watch-mode
  :config
  (global-activity-watch-mode))

Jump to character, word, line

(use-package ace-jump-mode
  :bind
  (:map gpolonkai/pers-map
   ("SPC" . ace-jump-mode)))

Command frequency meter

(use-package keyfreq
  :demand
  :custom
  (keyfreq-file (expand-file-name "keyfreq" user-emacs-directory))
  (keyfreq-file-lock (expand-file-name "keyfreq.lock" user-emacs-directory))
  :config
  (keyfreq-mode 1)
  (keyfreq-autosave-mode 1)
  :bind
  (:map gpolonkai/pers-map
   ("C-f" . keyfreq-show)))

EditorConfig

EditorConfig is a nice tool to unify, well, configuration of different editors.

(use-package editorconfig
  :config
  (editorconfig-mode t))

occur-like folding in the current buffer

(defun gpolonkai/toggle-loccur ()
  "Toggle `loccur-mode'.

If `loccur-mode' is not active, starts it (which, in turn, will ask for the
pattern to look for).  If it is active, it will disable it."
  (interactive)
  (if loccur-mode
      (loccur-mode nil)
    (call-interactively 'loccur)))

(use-package loccur
  :bind
  (:map gpolonkai/pers-map
   ("C-o" . gpolonkai/toggle-loccur)))

Help merging pacsave and pacnew files

(use-package pacfiles-mode
  :commands pacfiles)

Mailing with mu4e

Due to my programming (and maybe a bit of OCD) needs, i set trailing whitespace to have a red background so it stands out a lot. However, many emails contain a lot of trailing whitespace which makes them really hard to read in Emacs.

The below snippet creates a trailing whitespace face specific to mu4e view buffers. The accompanying function will be added to mu4e-view-mode-hook.

(copy-face 'trailing-whitespace 'trailing-whitespace-mu4e)
(set-face-attribute 'trailing-whitespace-mu4e nil :background 'unspecified)

(defun gpolonkai/mu4e-trailing-whitespace-fix ()
  (set (make-local-variable 'face-remapping-alist)
       '((trailing-whitespace trailing-whitespace-mu4e))))
(use-package mu4e
  :after ivy
  :ensure nil
  :config
  (require 'org-mu4e)
  (setq mu4e-contexts
        `( ,(make-mu4e-context
             :name "polonkai.eu"
             :enter-func (lambda () (mu4e-message "Entering polonkai.eu Context"))
             :leave-func (lambda () (mu4e-message "Leaving polonkai.eu Context"))
             :match-func (lambda (msg)
                           (when msg
                             (mu4e-message-contact-field-matches
                              msg
                              :to "gergely@polonkai.eu")))
             :vars '((user-mail-address . "gergely@polonkai.eu")
                     (mu4e-sent-folder . "/Polonkai/[Gmail].Sendur p&APM-stur")
                     (mu4e-drafts-folder . "/Polonkai/[Gmail].Dr&APY-g")
                     (mu4e-trash-folder . "/Polonkai/[Gmail].Rusl")
                     (mu4e-refile-folder . "/Polonkai/[Gmail].Dr&APY-g")
                     (message-sendmail-extra-arguments . ("--account=polonkai"))))
           ,(make-mu4e-context
             :name "Benchmark.games"
             :enter-func (lambda () (mu4e-message "Entering Benchmark.games Context"))
             :leave-func (lambda () (mu4e-message "Leaving Benchmark.games Context"))
             :match-func (lambda (msg)
                           (when msg
                             (or
                              (mu4e-message-contact-field-matches
                               msg
                               :to "gergo@gt2.io")
                              (mu4e-message-contact-field-matches
                               msg
                               :to "gergo@benchmarked.games")
                              (mu4e-message-contact-field-matches
                               msg
                               :to "gergo@benchmark.games"))))
             :vars '((user-mail-address . "gergo@benchmark.games")
                     (mu4e-sent-folder . "/GT2/[Gmail].Sent Mail")
                     (mu4e-drafts-folder . "/GT2/[Gmail].Drafts")
                     (mu4e-trash-folder . "/GT2/[Gmail].Trash")
                     (mu4e-refile-folder . "/GT2/[Gmail].Drafts")
                     (message-sendmail-extra-arguments . ("--account=gt2"))))
           ,(make-mu4e-context
             :name "Private"
             :enter-func (lambda () (mu4e-message "Entering Private Context"))
             :leave-func (lambda () (mu4e-message "Leaving Private Context"))
             :match-func (lambda (msg)
			               (when msg
			                 (string-match-p "^/Private" (mu4e-message-field msg :maildir))))
             :vars '((user-mail-address . "me@gergely.polonkai.eu")
                     (mu4e-sent-folder . "/Private/Sent")
                     (mu4e-drafts-folder . "/Private/Drafts")
                     (mu4e-trash-folder . "/Private/Trash")
                     (mu4e-refile-folder . "/Private/Drafts")
                     (message-sendmail-extra-arguments . ("--account=private" "--read-envelope-from")))))
        org-mu4e-link-query-in-headers-mode nil)
  :custom
  (mu4e-completing-read-function 'ivy-completing-read)
  (mu4e-context-policy 'pick-first)
  (mu4e-confirm-quit nil)
  (mail-user-agent 'sendmail-user-agent)
  :hook
  (mu4e-view-mode . gpolonkai/mu4e-trailing-whitespace-fix)
  :bind
  (:map gpolonkai/pers-map
   ("m m" . mu4e)
   ("m i" . mu4e~headers-jump-to-maildir)
   ("m c" . mu4e-compose-new)
   ("m s" . mu4e-headers-search)))

Maildir extension so i see a summary of mailboxes

(use-package mu4e-maildirs-extension
  :custom
  (mu4e-maildirs-extension-custom-list '("INBOX")))

Also, some sendmail hacking

(use-package sendmail
  :custom
  (sendmail-program "/usr/bin/msmtp")
  (message-sendmail-f-is-evil t)
  (message-sendmail-extra-arguments '("--read-envelope-from"))
  (send-mail-function 'sendmail-send-it)
  (message-send-mail-function 'message-send-mail-with-sendmail))

ViM’s ci functionality

(use-package ciel
  :bind
  (:map global-map
   ("C-c i" . ciel-ci)
   ("C-c o" . ciel-co)))

Access files in Docker containers using TRAMP

(use-package docker-tramp)

Transient for creating beautiful command menus

(use-package transient)

Speed bar in the same frame

(use-package sr-speedbar
  :after speedbar)

Speedbar for projectile

(use-package projectile-speedbar
  :after (:all projectile sr-speedbar)
  :bind
  (:map projectile-mode-map
   ("C-c p B" . projectile-speedbar-toggle)))

Kubernetes dashboard

(use-package kubernetes
  :commands (kubernetes-overview))

A Gopher client

Gopher is the next generation text protocol. Despite its age (40-ish, as of writing), it still beats the Web in a lot of aspects.

(use-package elpher)

Tidal, for improvising music

(use-package tidal)

Paradox, for better package management

I don’t always use the package menu, but when i do, i want to do it in style…

(use-package paradox
  :custom
  (paradox-lines-per-entry 2)
  (paradox-automatically-star t)
  (paradox-github-token (nth 1 (auth-source-user-and-password "api.github.com" "gergelypolonkai^paradox")))
  :bind
  (:map gpolonkai/pers-map
   ("C-p" . paradox-list-packages)))

Help editing Ansible files

(use-package ansible)

Edit browser textareas in Emacs

(use-package edit-server
  :ensure t
  :commands edit-server-start
  :init (if after-init-time
            (edit-server-start)
          (add-hook 'after-init-hook
                    #'(lambda() (edit-server-start))))
  :config (setq edit-server-new-frame-alist
                '((name . "Edit with Emacs FRAME")
                  (top . 200)
                  (left . 200)
                  (width . 80)
                  (height . 25)
                  (minibuffer . t)
                  (menu-bar-lines . t)
                  (window-system . x))))

Dired related packages

Collapse directories that only contain one file somewhere deep

(use-package dired-collapse)

Show directory sizes

(use-package dired-du)

Show Git version information

(use-package dired-git-info
  :bind
  (:map dired-mode-map
   (")" . dired-git-info-mode)))

Show/hide dot (hidden) files

(use-package dired-hide-dotfiles
  :bind
  (:map dired-mode-map
   ("." . dired-hide-dotfiles-mode)))

Rainbow to the stars…

(use-package dired-rainbow
  :config
  (dired-rainbow-define-chmod executable-unix "Green" "-.*x.*"))

Make programming a bit easier

Electric case

Insert snake_case and camelCase without using the Shift key. It is automatically enabled in C mode.

(use-package electric-case
  :hook
  (c-mode . electric-case-c-init))

Electric operator

Automatically add spaces around operators.

(use-package electric-operator
  :config
  ;; Apply electric-operator-mode to vala-mode, too
  (apply #'electric-operator-add-rules-for-mode 'vala-mode
         (electric-operator-get-rules-for-mode 'prog-mode))
  :hook
  (c-mode-common . electric-operator-mode)
  (python-mode . electric-operator-mode))

Yasnippets

(use-package yasnippet
  :demand
  :config
  (yas-global-mode 1)
  :hook
  (post-command . sachachua/change-cursor-color-when-can-expand))

Extra snippets for Vala

(use-package vala-snippets
  :after
  yasnippet)

Miscellanous extra snippets

(use-package yasnippet-snippets
  :after
  yasnippet)

Colourful delimiters

(use-package rainbow-delimiters
  :hook
  (prog-mode . rainbow-delimiters-mode))

Colourful identifiers

(use-package rainbow-identifiers)

REST Client

(use-package restclient)

REST Client power for Org-mode!

(use-package ob-restclient
  :after org
  :init
  (org-babel-do-load-languages 'org-babel-load-languages
                               '((restclient . t))))

Highlight current symbol

A big help during refactoring.

(use-package auto-highlight-symbol
  :config
  (global-auto-highlight-symbol-mode t))

Make ReallyLongCamelCaseWords more readable

(use-package glasses
  :delight " 👓"
  :hook
  (prog-mode . glasses-mode))

GObject boilerplate generator

(use-package gobgen)

Highlight TODO, FIXME, and XXX

(use-package hl-todo)

Bug and patch links

(defvar gpolonkai/bug-reference-url-bug-string "issues"
  "String to insert in a `bug-reference-url-format' for bug references.")

(put 'gpolonkai/bug-reference-url-bug-string 'safe-local-variable 'stringp)

(defvar gpolonkai/bug-reference-url-patch-string "merge_requests"
  "String to insert in a `bug-reference-url-format' for patch references.")

(defvar-local bug-reference-host "gitlab.com"
  "The hostname to use in `bug-reference-url-format'.")

(defvar-local bug-reference-group "gamesystems"
  "The group name or username to use in `bug-reference-url-format'.")

(defvar-local bug-reference-repository "game-app"
  "The repository name to use in `bug-reference-url-format'.")

(put 'gpolonkai/bug-reference-url-patch-string 'safe-local-variable 'stringp)

(defun gpolonkai/bug-reference-url ()
  "Return a GitLab issue or Merge Request URL.
Intended as a value for `bug-referecne-url-format'."
  (format "https://%s/%s/%s/%s/%s"
          bug-reference-host
          bug-reference-group
          bug-reference-repository
          (if (string-suffix-p "!" (match-string-no-properties 1))
              gpolonkai/bug-reference-url-patch-string
            gpolonkai/bug-reference-url-bug-string)
          (match-string-no-properties 2))
  )

(use-package bug-reference
  :custom
  (bug-reference-bug-regexp (rx (group (in ?! ?#))
                                (group (+ digit))))
  (bug-reference-url-format #'my-gitlab-url)
  :hook
  (text-mode . bug-reference-mode)
  (prog-mode . bug-reference-prog-mode))

Highlight indentation

(use-package highlight-indentation
  :hook
  (python-mode . highlight-indentation-mode))

Python related setup and use-package calls

Because, well, that’s my job now. Of course it gets a dedicated section.

Set up pretty symbols for Python

Because they are fancy.

  • not: ¬

  • in: ∈

  • def: ƒ

Maybe add ∉ for not in later, if possible.

(add-hook 'python-mode-hook
          (lambda ()
            (add-to-list 'prettify-symbols-alist
                         '("not" . 172))
            (add-to-list 'prettify-symbols-alist
                         '("in" . 8712))
            (add-to-list 'prettify-symbols-alist
                         '("def" . 402))))

Pyre-based LSP

(use-package lsp-pyre
  :hook
  (python-mode . lsp))

Poetry

Because it’s also great.

(use-package poetry
  :config
  (poetry-tracking-mode)
  (remove-hook 'post-command-hook 'poetry-track-virtualenv)
  :hook
  (poetry-tracking-mode . (lambda () (remove-hook 'post-command-hook 'poetry-track-virtualenv)))
  (python-mode . poetry-track-virtualenv)
  (projectile-after-switch-project-hook . poetry-track-virtualenv))

Automatically insert Sphinx-style docstrings

(use-package sphinx-doc
  :hook
  (python-mode . sphinx-doc-mode))

C mode

Because that’s still my favourite language.

Set up my own C style

(defconst my-c-style
  '((c-tab-always-indent        . t)
    (c-comment-only-line-offset . 4)
    (c-hanging-braces-alist     . ((substatement-open after)
                                   (brace-list-open)))
    (c-hanging-colons-alist     . ((member-init-intro before)
                                   (inher-intro)
                                   (case-label after)
                                   (label after)
                                   (access-label after)))
    (c-cleanup-list             . (scope-operator
                                   empty-defun-braces
                                   defun-close-semi))
    (c-offsets-alist             . ((arglist-close . +)
                                    (arglist-intro . +)
                                    (substatement-open . 0)
                                    (case-label . 4)
                                    (block-open . 0)
                                    (knr-argdecl-intro . -)
                                    (comment-intro . 0)
                                    (member-init-intro . ++)))
    (c-echo-syntactic-information-p . t))
  "My C Programming Style.")
(c-add-style "PERSONAL" my-c-style)

Some common initialisation for C mode

(add-hook 'c-mode-common-hook
          (lambda ()
            (local-set-key (kbd "C-c o") 'ff-find-other-file)
            (c-set-style "PERSONAL")
            (customize-set-variable 'c-basic-offset 4)
            (customize-set-variable 'tab-width 4)
            (customize-set-variable 'indent-tabs-mode nil)
            (c-toggle-auto-newline 1)))
(add-hook 'c-initialization-hook
          (lambda ()
            (define-key c-mode-base-map (kbd "C-m") 'c-context-line-break)))

Set indentation levels to the same as the tab width

(defvaralias 'c-basic-offset 'tab-width)
(defvaralias 'cperl-indent-level 'tab-width)

Web development

Web mode

(use-package web-mode
  :mode "\\.html?\\'"
  :custom
  (web-mode-enable-auto-indentation nil)
  (web-mode-enable-engine-detection t))

Emmet mode

(use-package emmet-mode
  :custom
  (emmet-self-closing-tag-style "")
  :hook
  (web-mode . emmet-mode)
  (css-mode . emmet-mode))

Query HTML tags by CSS selectors

(use-package enlive)

FlyCheck

(use-package flycheck
  :config
  (global-flycheck-mode)
  :custom
  (flycheck-python-pylint-executable "python3"))

FlyCheck for pkg-config files

(use-package flycheck-pkg-config)

Org mode

This is a big one; I use a lot of customisation here.

(use-package org
  :demand
  :init
  (require 'xdg-paths)
  (defface org-checkbox-todo-text
    '((t (:inherit org-todo)))
    "Face for the text part of an unchecked org-mode checkbox.")
  (defface org-checkbox-done-text
    '((t (:inherit org-done)))
    "Face for the text part of a checked org-mode checkbox.")
  (font-lock-add-keywords
   'org-mode
   `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?: \\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 1 'org-checkbox-todo-text prepend))
   'append)
  (font-lock-add-keywords
   'org-mode
   `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 12 'org-checkbox-done-text prepend))
   'append)
  (setq-default org-default-notes-file (expand-file-name "notes.org" org-directory)
                org-agenda-files `(,org-directory)
                org-time-stamp-formats '("<%Y-%m-%d>" . "<%Y-%m-%d %H:%M>")
                org-todo-keywords '((sequence "TODO(t)"
                                              "DOING(w@/!)"
                                              "BLOCKED(b@/!)"
                                              "SOMEDAY(s!)"
                                              "|"
                                              "CANCELED(c@/!)"
                                              "REVIEW(r@/!)"
                                              "DONE(d@/!)"))
                org-todo-keyword-faces '(("SOMEDAY" . (:foreground "goldenrod"))
                                         ("CANCELED" . (:foreground "#228b22" :strike-through t)))
                org-html-checkbox-types
                '((unicode (on . "<span class=\"task-done\">☑</span>")
                           (off . "<span class=\"task-todo\">☐</span>")
                           (trans . "<span class=\"task-in-progress\">▣</span>"))))
  :config
  ;; Load the markdown exporter
  (require 'ox-md)
  ;; Handle org-protocol:// links
  (require 'org-protocol)
  ;; Make it possible to encrypt headings
  (require 'org-crypt)
  ;; Make it possible to use inline tasks
  (require 'org-inlinetask)
  ;; Make it possible to use structure templates with < X TAB
  (require 'org-tempo)

  :custom
  (org-log-into-drawer t)
  (org-ellipsis "…#")
  (org-startup-folded 'content)
  (org-log-done 'time)
  (org-src-preserve-indentation t)
  (org-tags-column 0)
  (org-startup-indented t)
  (org-special-ctrl-a/e t)
  (org-return-follows-link t)
  (org-src-fontify-natively t)
  (org-goto-interface 'outline-path-completion)
  (org-goto-max-level 10)
  (org-html-checkbox-type 'unicode)
  (org-src-window-setup 'current-window)
  (org-pretty-entities t)
  (org-pretty-entities-include-sub-superscripts t)
  (org-use-speed-commands t)
  (org-hide-leading-stars t)
  (org-enforce-todo-dependencies t)
  (org-enforce-todo-checkbox-dependencies t)
  (org-catch-invisible-edits 'show)
  (org-log-reschedule 'time)
  (org-log-redeadline 'note)
  (org-refile-use-outline-path 'file)
  (org-outline-path-complete-in-steps nil)
  (org-refile-allow-creating-parent-nodes 'confirm)
  (org-crypt-key "B0740C4C")
  (org-speed-commands-user '(("m" . org-mark-subtree)))
  (org-refile-targets '((org-agenda-files :maxlevel . 3)))
  (org-agenda-custom-commands '(("c" "Simple agenda view"
                                 ((tags "PRIORITY=\"A\""
                                        ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                                         (org-agenda-overriding-header "High priority unfinished tasks")))
                                  (agenda "")
                                  (alltodo ""
                                           ((org-agenda-skip-function
                                             '(or (air-org-skip-subtree-if-habit)
                                                  (air-org-skip-subtree-if-priority ?A)
                                                  (gpolonkai/org-skip-subtree-if-state "SOMEDAY")
                                                  (org-agenda-skip-if nil '(scheduled deadline))))
                                            (org-agenda-overriding-header "ALL normal priority tasks"))))
                                 ((org-agenda-compact-blocks t)))))
  (org-log-note-clock-out t)
  (org-capture-templates '(("L" "Org-protocol capture" entry
                            (file+headline
                             (lambda ()
                               (expand-file-name "index.org" org-directory))
                             "Captures")
                            "** %:description\n:PROPERTIES:\n:SOURCE: %:link\n:END:\n\n%:initial"
                            :empty-lines 1)
                           ("R" "Region to Current Clocked Task" plain
                            (clock)
                            "%i"
                            :immediate-finish t
                            :empty-lines 1)
                           ("K" "Kill-ring to Current Clocked Task" plain
                            (clock)
                            "%c"
                            :immediate-finish t
                            :empty-lines 1)
                           ("c" "Item to current Clocked Task" item
                            (clock)
                            "%i%?"
                            :empty-lines 1)
                           ("g" "GT2 note" entry
                            (file+headline
                             (lambda ()
                               (expand-file-name "gt2-notes.org" org-directory))
                             "Captures")
                            "** %^{Title}\n:PROPERTIES:\n:CREATED: %T\n:END:\n\n%a\n\n%i%?")
                           ("p" "Blog post" entry
                            (file+olp+datetree
                             (lambda ()
                               (expand-file-name "blog.org" org-directory)))
                            "* %^{Title}  :blog:\n:PROPERTIES:\n:CREATED:  %T\n:END:\n\n%i%?")))
  (org-read-date-force-compatible-dates nil)
  (org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s")
                              (todo . " %i %-12:c %(concat \"[ \"(org-format-outline-path (org-get-outline-path)) \" ]\") ")
                              (tags . " %i %-12:c %(concat \"[ \"(org-format-outline-path (org-get-outline-path)) \" ]\") ")
                              (timeline . "  % s")
                              (search . " %i %-12:c")))
  (org-agenda-dim-blocked-tasks t)
  :hook
  (ediff-select . f-ediff-org-unfold-tree-element)
  (ediff-unselect . f-ediff-org-fold-tree)
  :bind
  (:map gpolonkai/pers-map
   ("a" . gpolonkai/org-agenda-list)
   ("C" . org-capture)
   ("l" . org-store-link)
   :map org-mode-map
   ("SPC" . org-space-key)
   ("C-c l" . org-toggle-link-display)
   ("C-a" . gpolonkai/move-to-beginning-of-line)
   ("C-e" . gpolonkai/move-to-end-of-line)
   ("C-c h" . outline-previous-heading)
   ("C-c ;" . org-toggle-timestamp-type)))

Show a random ToDo every hour

(use-package org-random-todo
  :demand
  :config
  ;; Don’t bug me too often…
  (setq org-random-todo-how-often 3600)
  :custom
  (org-random-todo-skip-keywords '("SOMEDAY"))
  :bind
  (:map gpolonkai/pers-map
   ("r" . org-random-todo)))

Load CalDAV entries from NextCloud

(use-package org-caldav
  :after org
  :config
  (setq
   org-caldav-url "https://cloud.polonkai.eu/remote.php/dav/calendars/gergely"
   org-caldav-calendar-id "org"
   org-caldav-inbox (expand-file-name "cloud-calendar.org" org-directory)
   org-caldav-files nil))

Improved list management

(use-package org-autolist
  :hook
  (org-mode . org-autolist-mode))

Write messages with Org-mode

(use-package org-msg
  :after mu4e
  :defer t
  :config
  (org-msg-mode)
  :custom
  (org-msg-supported-mua '((sendmail-user-agent . "mu4e")))
  (org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil")
  (org-msg-startup "hidestars indent inlineimages")
  (org-msg-greeting-fmt "\nHello,\n\n")
  (org-msg-greeting-fmt-mailto nil)
  (org-msg-signature "\n\nBest,\n\n,#+begin_signature\n-- *Gergely Polonkai* \\\\\n,#+end_signature"))

Sticky headers so i always know where i am

(use-package org-sticky-header
  :custom
  (org-sticky-header-full-path 'full)
  :hook
  (org-mode . org-sticky-header-mode))

Custom bullets

(use-package org-bullets
  :custom
  (org-bullets-face-name 'org-bullet-face)
  (org-bullets-bullet-list '("✙" "♱" "♰" "☥" "✞" "✟" "✝" "†" "✠" "✚" "✜" "✛" "✢" "✣" "✤" "✥"))
  :hook
  (org-mode . org-bullets-mode))

Edit diagrams with mermaid

(use-package ob-mermaid
  :custom
  (ob-mermaid-cli-path "/home/polesz/.local/node/bin/mmdc"))

Allow exporting to ReSTructured text

(use-package ox-rst
  :after org)

Export currently clocked in task for Waybar

(use-package org-clock-waybar
  :ensure nil
  :quelpa (org-clock-waybar
           :fetcher git
           :url "https://gitea.polonkai.eu/gergely/org-clock-waybar.git")
  :config
  (org-clock-waybar-setup))

Git & Co.

Git status on the fringe

In graphical modes we use git-gutter-fringe, and git-gutter otherwise.

;; Git gutter
;; If we are in text-only mode, there is no fringe.
(let ((gitgutter-package
       (if (display-graphic-p)
           "git-gutter-fringe"
         "git-gutter")))
  (eval `(use-package ,gitgutter-package
    :demand
    :config
    (global-git-gutter-mode t)
    :bind
    (:map gpolonkai/pers-map
     ("gg" . git-gutter:update-all-windows)
     ("gn" . git-gutter:next-hunk)
     ("gp" . git-gutter:previous-hunk)))))

Git messenger

AKA blame current line.

(use-package git-messenger
  :bind
  (:map gpolonkai/pers-map
   ("gm" . git-messenger:popup-message)))

Git time machine

See previous versions of the current file.

(use-package git-timemachine
  :bind
  (([f6] . git-timemachine-toggle)))

Company & Co.

(use-package company
  :delight " 🏢"
  :custom
  (company-idle-delay nil)
  (company-dabbrev-downcase nil)
  :config
  (setq company-frontends '(company-pseudo-tooltip-frontend
                            company-echo-metadata-frontend))
  (put 'company-clang-arguments 'safe-local-variable #'nil-or-list-of-strings-p)
  (global-company-mode))

Company completion based on local C headers

(use-package company-c-headers)

Company mode in the shell

(use-package company-shell)

REST Client completion via Company

(use-package company-restclient)

Insert Emoji with Company

(use-package company-emoji
  :after
  company
  :init
  (--set-emoji-font nil)
  :config
  (add-to-list 'company-backends 'company-emoji))

Web mode (web-mode and emmet-mode, too) backend for Company

(use-package company-web
  :config
  (require 'company-web-html))

Ivy & Co.

Ivy & Counsel

(use-package ivy
  :config
  (ivy-mode 1)
  :custom
  (ivy-use-virtual-buffers t)
  (ivy-count-format "(%d/%d) ")
  (ivy-use-selectable-prompt t))
(use-package counsel
  :config
  (counsel-mode)
  :bind
  (:map company-mode-map
   ("C-c j" . counsel-company)
   :map company-active-map
    ("C-c j" . counsel-company)))

GNU Globals with Counsel

(defun gpolonkai/enable-counsel-gtags-mode ()
  (counsel-gtags-mode t))

(use-package counsel-gtags
  :custom
  (counsel-gtags-auto-update t)
  (counsel-gtags-path-style 'relative)
  :hook
  (c-mode . gpolonkai/enable-counsel-gtags-mode)
  :bind
  (:map counsel-gtags-mode-map
   ("M-t" . counsel-gtags-find-definition)
   ("M-r" . counsel-gtags-find-reference)
   ("M-s" . counsel-gtags-find-symbol)
   ("C-c <" . counsel-gtags-go-backward)
   ("C-c >" . counsel-gtags-go-forward)))

Ag with Counsel

(use-package counsel-ag-popup
  :bind
  (:map gpolonkai/pers-map
   ("s" . counsel-ag-popup)))

Projectile with Counsel

(use-package counsel-projectile
  :custom
  (projectile-completion-system 'ivy)
  :config
  (counsel-projectile-mode))

FlySpell with Ace

(use-package ace-flyspell
  :bind
  (:map flyspell-mode-map
   ("C-M-i" . ace-flyspell-correct-word)))

C Yasnippets with Ivy

(use-package ivy-yasnippet
  :after
  yasnippet
  :bind
  (("C-c y" . ivy-yasnippet)))

PyDoc with Counsel

(use-package counsel-pydoc)

BibTex with Ivy

First, set up bibtex-completion:

(use-package bibtex-completion
  :after
  org
  :custom
  (bibtex-completion-bibliography (expand-file-name "references.bib" org-directory))
  (bibtex-completion-library-path (expand-file-name "bibtex-pdfs" org-directory))
  (bibtex-completion-pdf-open-function 'org-open-file)
  (bibtex-completion-notes-path (expand-file-name
                                 (convert-standard-filename "bibliography/ivy-bibtex-notes")
                                 org-directory)))
(use-package ivy-bibtex
  :after bibtex-completion)

Access passwords in pass using Ivy

(use-package ivy-pass)

Mode specific use-package calls

JavaScript

(use-package js2-mode
  :pin melpa-stable
  :mode "\\.js\\'")

Jinja templates

(use-package jinja2-mode
  :mode "\\.j2\\'")

TypeScript

(use-package typescript-mode
  :mode "\\.ts\\'")

CoffeeScript

(use-package coffee-mode
  :mode "\\.coffee\\'")

JSON

(use-package json-mode
  :mode "\\.json\\'")

YAML

(use-package yaml-mode
  :mode (("\\.yml\\'" . yaml-mode)
         ("\\.yaml\\'" . yaml-mode))
  :init
  (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode)))

Markdown

(use-package markdown-mode
  :mode (("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode)))

Less

(use-package less-css-mode
  :mode "\\.less\\'")

Sass

(use-package sass-mode
  :mode "\\.sass\\'")

Vala

(use-package vala-mode
  :mode "\\.vala\\'")

Dockerfile

(use-package dockerfile-mode)

po-mode

(use-package po-mode
  :ensure nil
  :mode "\\.po\\'")

C#

(use-package csharp-mode
  :mode "\\.cs\\'")

Gherkin (BDD) feature files

(use-package feature-mode
  :mode "\\.feature\\'")

PlantUML

Before using this, make sure the latest PlantUML JAR file is downloaded into the downloads directory. It is available from here.

(use-package plantuml-mode
  :init
  (setq plantuml-jar-path
        (expand-file-name
         ;; Make sure we have a download location even if XDG is not working
         (cond
          ((xdg-user-dir "DOWNLOAD")
           (expand-file-name "plantuml.jar" (xdg-user-dir "DOWNLOAD")))
          (t
           "~/Downloads/plantuml.jar"))))
  (defvaralias 'org-plantuml-jar-path 'plantuml-jar-path)
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((plantuml . t))))

For editing CSV files

(use-package csv-mode
  :mode "\\.csv\\'")

The Go programming language

(use-package go-mode
  :mode "\\.go\\'")

Meson build system

(use-package meson-mode
  :mode "\\.meson\\'")

GraphQL

(use-package graphql
  :mode "\\.graphql\\'")

Gitlab-CI

(use-package gitlab-ci-mode
  :mode "\\.gitlab-ci.yml\\'")
(use-package gitlab-ci-mode-flycheck
  :after flycheck gitlab-ci-mode
  :init
  (gitlab-ci-mode-flycheck-enable))

Arduino

(use-package arduino-mode
  :mode "\\.ino\\'")

Vue templates

(use-package vue-html-mode
  :mode "\\.vue\\'")

Pony is awesome!

(use-package ponylang-mode
  :mode "\\.pony\\'")

Rust

(use-package rust-mode
  :mode "\\.rs\\'")
(use-package cargo
  :mode "\\.rs\\'")
(use-package flycheck-rust)

Fish shell

(use-package fish-mode
  :hook
  (fish-mode . (lambda () (add-hook 'before-save-hook 'fish_indent-before-save))))

Clojure

(use-package clojure-mode)

Flutter specific things

(use-package lsp-mode)

(use-package lsp-dart
  :hook
  (dart-mode . lsp))

(use-package lsp-ui)

(use-package hover)

Bats, for testing shell scripts

(use-package bats-mode
  :mode "\\.bats\\'")

Terraform

(use-package terraform-mode
  :mode "\\.tf\\'")

Games

Gnu Go

(use-package gnugo)

Last, but not least, key bindings!

TODO My own, personal, Magit-popup!   experimental

With the new Transient package it will be easier. I just don’t know how to use it yet.

(define-transient-command gpolonkai/clock-menu ()
  [("I" "Clock in to last clocked task" org-clock-in-last)
   ("g" "Go to the currently clocked task" org-clock-goto)
   ("i" "Clock in here" org-clock-in)
   ("o" "Clock out" org-clock-out)])

(define-transient-command gpolonkai/special-files-menu ()
  [("i" "My Emacs init file" gpolonkai/visit-init-file)
   ("o" "My main Org file" gpolonkai/visit-org-index)])

(define-transient-command gpolonkai/git-menu ()
  [("g" "Update Git gutter everywhere" git-gutter:update-all-windows)
   ("m" "Show the origin of this line" git-messenger:popup-message)
   ("n" "Next hunk" git-gutter:next-hunk)
   ("p" "Previous hunk" git-gutter:previous-hunk)])

(define-transient-command gpolonkai/mail-menu ()
  [("c" "Compose mail" mu4e-compose-new)
   ("i" "Jump to folder" mu4e~headers-jump-to-maildir)
   ("m" "Mu4e main screen" mu4e)
   ("s" "Mu4e search" mu4e-headers-search)])

(define-transient-command gpolonkai/sx-menu ()
  [("i" "Inbox" sx-inbox)
   ("s" "Search" sx-search)])

(define-transient-command gpolonkai/zone-menu ()
  [("i" "Enable zone on idle" gpolonkai/zone-enable)
   ("q" "Disable zone on idle" zone-leave-me-alone)])

(define-transient-command gpolonkai/python-menu ()
  [("p" "Poetry" poetry)])

(define-transient-command gpolonkai/menu ()
  [("a" "My agenda" gpolonkai/org-agenda-list)
   ("b" "Where has my cursor gone?" beacon-blink)
   ("C" "Capture" org-capture)
   ("c" "Clocking…" gpolonkai/clock-menu)
   ("M-C" "Clean up old buffers" clean-buffer-list)
   ("e" "EShell" eshell)
   ("g" "Git…" gpolonkai/git-menu)
   ("h" "Hide mode-line" hidden-mode-line-mode)
   ("l" "Store Org link" org-store-link)
   ("m" "Mail…" gpolonkai/mail-menu)
   ("o" "Special files…" gpolonkai/special-files-menu)
   ("p" "Python…" gpolonkai/python-menu)
   ("q" "Stack Exchange…" gpolonkai/sx-menu)
   ("r" "Show a random TODO" org-random-todo)
   ("u" "Open URL" browse-url-at-point)
   ("w" "Clean up whitespace problems" whitespace-cleanup)
   ("x" "Expand region" er/expand-region)
   ("z" "Zone…" gpolonkai/zone-menu)])

The actual key bindings

(defun gpolonkai/isearch-regexp (prefix)
  "Call `isearch-forward-regex'.  If PREFIX is non-nil, call `isearch-backward-regex' instead."
  (interactive "P")
  (call-interactively
   (if prefix
       'isearch-backward-regexp
     'isearch-forward-regexp)))
(bind-keys
 :map global-map
 ("M-(" . gpolonkai/enclose-region)
 ("<C-return>" . open-line-below)
 ("<C-S-return>" . open-line-above)
 ("M-t" . nil) ;; Remove the old keybinding
 ("M-t c" . transpose-chars)
 ("M-t w" . transpose-words)
 ("M-t l" . transpose-lines)
 ("M-t e" . transpose-sexps)
 ("M-t s" . transpose-sentences)
 ("M-t p" . transpose-paragraphs)
 ("M-t W" . transpose-windows)
 ("C-a" . gpolonkai/move-to-beginning-of-line)
 ("C-e" . gpolonkai/move-to-end-of-line)
 ("M-q" . sachachua/fill-or-unfill-paragraph)
 ("C-c r" . round-number-at-point-to-decimals)
 ("C-s" . swiper)
 ("C-r" . gpolonkai/isearch-regexp)
 ("C-M-s" . isearch-forward)
 ("C-M-r" . isearch-backward)
 ("C-~" . toggle-char-case)
 ("C-z" .  nil)
 ("M-g SPC" . goto-next-char)
 :map ctl-x-map
 ("C-y" . gpolonkai/duplicate-line)
 ("_" . maximize-window)
 ("C-r" . rename-current-buffer-file)
 ("C-d" . delete-current-buffer-file)
 ("|" . toggle-window-split)
 ("k" . kill-this-buffer)
 ("M-k" . gpolonkai/undo-buffer-kill)
 ("C-b" . bury-buffer)
 ("/" . repeat)
 ("C-f" . counsel-find-file)
 ("b" . counsel-switch-buffer)
 :map isearch-mode-map
 ("<C-return>" . isearch-exit-other-end)
 ("<S-return>" . isearch-exit-mark-match)
 :map gpolonkai/pers-map
 ("C-t" . gpolonkai/menu)
 ("h" . hidden-mode-line-mode)
 ("C-i e" . "gergely@polonkai.eu")
 ("C-i w" . "http://gergely.polonkai.eu/")
 ("o i" . gpolonkai/visit-init-file)
 ("o o" . gpolonkai/visit-org-index)
 ("u" . browse-url-at-point)
 ("M-C" . clean-buffer-list)
 ("C-c" . calc)
 ("c i" . org-clock-in)
 ("c I" . org-clock-in-last)
 ("c o" . org-clock-out)
 ("c g" . org-clock-goto)
 ("M-o" . insert-current-file-name-at-point))

TODO These fail to work using bind-keys, but why?

(define-key 'help-command (kbd "C-l") 'find-library)
(define-key 'help-command (kbd "C-f") 'find-function)
(define-key 'help-command (kbd "C-k") 'find-function-on-key)
(define-key 'help-command (kbd "C-v") 'find-variable)

And finally, server mode

Sometimes i start an emacsclient process, like for editing a commit message or something similar. As my startup time is pretty long, waiting for everything to complete is undesirable.

(require 'server)
(unless (server-running-p)
  (server-start))