Skip to content

Compro-Prasad/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Emacs configuration

Downloading Emacs

I prefer using master branch of Emacs with all the latest and greatest features. So, I would recommend installing the same. Preferably from https://github.com/emacs-mirror/emacs.

  • TODO Make an automation script to install Emacs

Arch Linux and its derivatives

pacman -S emacs

Arch User Repository

yay -S emacs-git

Ubuntu and its derivatives

The following set of commands will install the master branch of Emacs (Reference) which gets updated daily:

sudo add-apt-repository ppa:ubuntu-elisp/ppa
sudo apt update
sudo apt install -y emacs-snapshot

If you still insist on using a stable release of Emacs available on your distro then you can use the following command:

sudo apt update && sudo apt install -y emacs

If you don’t plan on having a GUI version of Emacs then using the emacs-nox package should be enough as it doesn’t install the GUI libraries. You can use the following command:

sudo apt update && sudo apt install -y emacs-nox

The tutorial

After Emacs has been installed I would recommend you to go through the tutorial accessible from the startup screen of Emacs. This will help you to understand the basic terminologies used in Emacs.

Copyright notice

TL; DR -> License is GNU GPL v3

;; Copyright (C) 2018-2022  Abhishek(Compro) Prasad

;; Author: Abhishek(Compro) Prasad
;; Keywords: emacs, configuration, elisp

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Comment


;;; Code:
;;; early-init.el ---                                -*- lexical-binding: t; -*-

<<GPL-v3>>
;;; init.el ---                                      -*- lexical-binding: t; -*-

<<GPL-v3>>

Enable debugging

(setq debug-on-error  t
      init-file-debug t)

Aliases

Emacs indentifier names are long. Lets shorten the common ones that we will be using.

(defalias 'ft 'file-truename)
(defvaralias 'emacs-d 'user-emacs-directory)

Change load-path

(add-to-list 'load-path (concat emacs-d "lisp"))

Cache directory

(setq cache-d (locate-user-emacs-file (concat emacs-d ".cache/"))
      package-user-dir (concat cache-d "elpa/"))

Connecting to client

To connect to client, make sure the following code returns t:

(server-running-p)

If the above code returns nil then the server hasn’t been started yet. You can start the server manually using M-x server-start. Or put the following code in init.el. This is already taken care of if you are using my init file.

(use-package server
  :config
  (when (not (server-running-p))
    (let ((server-file (concat cache-d "server/server")))
      (when (file-exists-p server-file)
        (delete-file server-file)
        (message "Old server file deleted")))
    (message "Starting server")
    (server-start)))

Then make sure EMACS_SERVER_FILE environment variable is set. In this case it should be like the following:

export EMACS_SERVER_FILE="$HOME/.emacs.d/.cache/server/server"

After doing the necessary setup you can connect using the emacsclient binary:

emacsclient -c

Check Operating System

(require 'seq)
(setq is-windows
      (seq-find
       (lambda (x) (string= system-type x))
       '("ms-dos" "windows-nt" "cygwin")))
(setq is-unix
      (seq-find
       (lambda (x) (string= system-type x))
       '("gnu" "gnu/linux" "gnu/kfreebsd" "darwin" "cygwin")))
(setq is-gnu
      (seq-find
       (lambda (x) (string= system-type x))
       '("gnu" "gnu/linux" "gnu/kfreebsd")))
(setq is-linux
      (or
       (string= system-type "gnu")
       (string= system-type "gnu/linux")))
(setq is-mac (string= system-type "darwin"))
(setq is-bsd
      (or
       (string= system-type "gnu/kfreebsd")
       (string= system-type "darwin")))

Tangle README.org to init.el

Automatically tangle / export code blocks from README.org to init.el when saving README.org inside of Emacs.

(defun tangle-README.org-to-init.el ()
  "Tangle README.org to init.el"
  (let ((readme (ft (concat emacs-d "README.org")))
        (current-file (ft (buffer-file-name))))
    (when (string= readme current-file)
      (call-interactively 'org-babel-tangle))))

(defun early-init ()
  "Return `early-init.el' if greater than Emacs 27.
Else it will return `init.el'. Useful for tangling source code."
  (if (< emacs-major-version 27)
      "init.el"
    "early-init.el"))

(add-hook 'after-save-hook 'tangle-README.org-to-init.el)

Setup package repositories

Setup MELPA and Non GNU repositories.

(require 'package)

(defvar sslp (and (not (memq system-type '(windows-nt ms-dos)))
                  (gnutls-available-p))
  "Tells if SSL is enabled or not.")

(defvar protocol (if sslp "https" "http")
  "Protocol value as string.")

(defun compro/add-package-list (name url)
  "Add NAME and URL to `package-archives'.

URL should not have http:// or https:// as a prefix."
  (setf (alist-get name package-archives nil nil 'string=) (concat protocol "://" url)))

(compro/add-package-list "elpa-devel" "elpa.gnu.org/devel/")
(compro/add-package-list "melpa" "melpa.org/packages/")
(compro/add-package-list "nongnu" "elpa.nongnu.org/nongnu/")

(package-initialize)

Install use-package

(when (< emacs-major-version 29)
  (unless (package-installed-p 'use-package)
    (package-refresh-contents)
    (package-install 'use-package)))

Install f and s

f is a package that makes it easier to interact with the filesytem.

(use-package f :ensure t)
(use-package s :ensure t)

Helper functions

mplist-remove

(defun mplist-remove (plist prop)
  "Return a copy of a modified PLIST without PROP and its values.

If there are multiple properties with the same keyword, only the first property
and its values are removed."
  (let ((tail plist)
        result)
    (while (and (consp tail) (not (eq prop (car tail))))
      (push (pop tail) result))
    (when (eq prop (car tail))
      (pop tail)
      (while (and (consp tail) (not (keywordp (car tail))))
        (pop tail)))
    (while (consp tail)
      (push (pop tail) result))
    (nreverse result)))

Set default font

(defun set-default-font (plists)
  "Set the font given the passed PLISTS.

PLISTS has either the form (\"fontname\" :prop1 val1 :prop2 val2 ...)
or is a list of such. The first font that can be found will be used.

The return value is nil if no font was found, truthy otherwise."
  (unless (listp (car plists))
    (setq plists (list plists)))
  (catch 'break
    (dolist (plist plists)
      (when (find-font (font-spec :name (car plist)))
        (let* ((font (car plist))
               (props (cdr plist))
               (font-props (mplist-remove
                            ;; although this keyword does not exist anymore
                            ;; we keep it for backward compatibility
                            (mplist-remove props :powerline-scale)
                            :powerline-offset))
               (fontspec (apply 'font-spec :name font font-props)))
          (set-frame-font fontspec nil t)
          (push `(font . ,(frame-parameter nil 'font)) default-frame-alist)
          (pcase system-type
            (`gnu/linux
             (setq fallback-font-name "NanumGothic")
             (setq fallback-font-name2 "NanumGothic"))
            (`darwin
             (setq fallback-font-name "Arial Unicode MS")
             (setq fallback-font-name2 "Arial Unicode MS"))
            (`windows-nt
             (setq fallback-font-name "MS Gothic")
             (setq fallback-font-name2 "Lucida Sans Unicode"))
            (`cygwin
             (setq fallback-font-name "MS Gothic")
             (setq fallback-font-name2 "Lucida Sans Unicode"))
            (other
             (setq fallback-font-name nil)
             (setq fallback-font-name2 nil)))
          (when (and fallback-font-name fallback-font-name2)
            ;; remove any size or height properties in order to be able to
            ;; scale the fallback fonts with the default one (for zoom-in/out
            ;; for instance)
            (let* ((fallback-props (mplist-remove
                                    (mplist-remove font-props :size)
                                    :height))
                   (fallback-spec (apply 'font-spec
                                         :name fallback-font-name
                                         fallback-props))
                   (fallback-spec2 (apply 'font-spec
                                          :name fallback-font-name2
                                          fallback-props)))
              ;; window numbers
              (set-fontset-font "fontset-default"
                                '(#x2776 . #x2793) fallback-spec nil 'prepend)
              ;; mode-line circled letters
              (set-fontset-font "fontset-default"
                                '(#x24b6 . #x24fe) fallback-spec nil 'prepend)
              ;; mode-line additional characters
              (set-fontset-font "fontset-default"
                                '(#x2295 . #x22a1) fallback-spec nil 'prepend)
              ;; new version lighter
              (set-fontset-font "fontset-default"
                                '(#x2190 . #x2200) fallback-spec2 nil 'prepend))))
        (throw 'break t)))
    nil))

comint kill word

(defun compro/comint/kill-word (arg)
  (interactive "p")
  (unless buffer-read-only
    (let ((beg (point))
          (end (save-excursion (forward-word arg) (point)))
          (point (save-excursion (goto-char
                                  (if (> arg 0)
                                      (next-single-char-property-change
                                       (point) 'read-only)
                                    (previous-single-char-property-change
                                     (point) 'read-only)))
                                 (point))))
      (unless (get-char-property (point) 'read-only)
        (if (if (> arg 0) (< point end) (> point end))
            (kill-region beg point)
          (kill-region beg end))))))

comint output text read only

(defun compro/comint/preoutput-read-only (text)
  (propertize text 'read-only t))

Kill process related buffers on exit

(defun compro/shell-kill-buffer-sentinel (process event)
  (when (and (memq (process-status process) '(exit signal))
             (buffer-live-p (process-buffer process)))
    (kill-buffer)))

(defun compro/kill-process-buffer-on-exit ()
  (set-process-sentinel (get-buffer-process (current-buffer))
                        #'compro/shell-kill-buffer-sentinel))

(dolist (hook '(ielm-mode-hook term-exec-hook comint-exec-hook))
  (add-hook hook 'compro/kill-process-buffer-on-exit))

Get empty packages

(defun compro/get-empty-pkgs ()
  "Get 0 bytes .el packages."
  (let ((default-directory package-user-dir))
    (seq-reduce
     (lambda (value-list file)
       (if (= (file-attribute-size (file-attributes file)) 0)
           (cons file value-list)
         value-list))
     (seq-filter
      (apply-partially #'s-suffix-p ".el")
      (seq-reduce
       (lambda (value-list file)
         (if (and
              (not (s-prefix-p "." file))
              (file-accessible-directory-p file))
             (append
              (seq-map
               (apply-partially #'concat file "/")
               (directory-files file))
              value-list)
           value-list))
       (directory-files "")
       '()))
     '())))

Re-download empty packages

(defun compro/redownload-empty-pkgs ()
  "Redownload empty packages."
  (interactive)
  (let* ((pkgs (compro/get-empty-pkgs))
         (default-directory package-user-dir)
         (choice-list (list
                       (cons (intern "Delete and re-download all") 1)
                       (cons (intern "Manually select for re-downloading") 2)
                       (cons (intern "Fix everything manually") 3)))
         (choice (if pkgs
                     (alist-get
                      (intern
                       (completing-read
                        (concat
                         "Some files were not properly downloaded namely "
                         (s-join ", " pkgs)
                         ". What action do you want to take?  ")
                        choice-list))
                      choice-list)
                   3)))
    (if (= choice 3)
        (when (null pkgs)
          (message "No empty packages were found"))
      (package-refresh-contents)
      (seq-each
       (lambda (file)
         (let* ((values (s-split "/" file))
                (dir-name (car values))
                (pkg-values (s-split "-" dir-name))
                (pkg-name (s-join "-" (butlast pkg-values 1)))
                (each-choice
                 (if (= choice 1)
                     t
                   (yes-or-no-p
                    (concat "Delete and re-download " dir-name "? ")))))
           (when each-choice
             (delete-directory dir-name t)
             (ignore-errors
               (package-reinstall (intern pkg-name))))))
       pkgs))))

Re-download advice after package is installed

(defun re-download (pkg &optional arg)
  "Advice for package-install."
  (let* ((pkg-name (symbol-name (if (package-desc-p pkg)
                                    (package-desc-name pkg)
                                  pkg)))
         (file-name (car
                     (sort
                      (seq-filter
                       (apply-partially #'s-prefix-p pkg-name)
                       (compro/get-empty-pkgs))
                      #'string-greaterp)))
         (dir (when file-name (car (s-split "/" file-name)))))
    (when dir
      (delete-directory dir)
      (ignore-errors (package-reinstall pkg)))))
(advice-add 'package-install :after 're-download)

Switch to buffer based on current major mode

(defun switch-to-buffer-current-major-mode ()
  "Switch to buffer like functionality based on current major mode."
  (interactive)
  (let* ((m-mode major-mode)
         (prompt (concat (symbol-name m-mode) " buffers: ")))
    (read-buffer
     prompt nil (confirm-nonexistent-file-or-buffer)
     (lambda (buf)
       (with-current-buffer (cdr buf)
         (eq m-mode major-mode))))))

(global-set-key (kbd "C-x C-b") 'switch-to-buffer-current-major-mode)

Check if its my laptop

(setq compro/laptop-p (equal system-name "hp-archlinux"))

Install general

General is used for setting keybindings in a simpler way as compared to bind-key.

(use-package general :ensure t)

Native Emacs configurations

Speedup find-file

(remove-hook 'file-name-at-point-functions 'ffap-guess-file-name-at-point)

Tab bar

Tab bar is a feature in Emacs to show tabs but works like workspaces in window managers.

(use-package tab-bar
  :when (> emacs-major-version 27)
  :bind (("C-t" . tab-bar-new-tab-event)
         ([C-f4] . tab-bar-close-tab)
         ("C-S-t" . tab-bar-undo-close-tab)
         ([C-tab] . tab-next)
         ([C-backtab] . tab-previous)
         ([C-S-tab] . tab-previous)
         ([C-iso-lefttab] . tab-previous))
  :init
  (defun switch-to-untitled-buffer ()
    (interactive)
    (let ((buf (format "untitled-%d" (random 100000))))
      (generate-new-buffer buf)
      (switch-to-buffer buf)
      (setq buffer-offer-save 'always)))

  (defvar tab-bar-new-commands
    '((?p "Project" project-switch-project)
      (?n "New buffer" switch-to-untitled-buffer)
      (?f "List Files" find-file)
      (?b "List Buffers" switch-to-buffer)
      (?r "Run command" execute-extended-command)
      (?q "Do nothing" ignore)))
  (defun tab-bar-new--keymap-prompt ()
    "Return a prompt for the project swithing dispatch menu."
    (mapconcat
     (pcase-lambda (`(,key ,label))
       (format "%s %s"
               (propertize
                (key-description `(,(concat " " (char-to-string key) " ")))
                'face '(:foreground "black" :background "cyan" :weight bold))
               label))
     tab-bar-new-commands
     "  "))
  (defun tab-bar-new-tab-event ()
    (interactive)
    (when-let ((choice (assq (read-event (tab-bar-new--keymap-prompt))
                             tab-bar-new-commands))
               (inhibit-quit t))
      (tab-bar-new-tab)
      (when (not (char-equal (nth 0 choice) ?q))
        (switch-to-buffer "waiting...")
        (insert "Churning data or waiting for IO")
        (with-local-quit (call-interactively (nth 2 choice)))
        (kill-buffer "waiting..."))
      (message "New tab created with `%s' option" (nth 1 choice))))

  :config
  (setq tab-bar-format
        '(tab-bar-format-history
          tab-bar-separator tab-bar-separator
          tab-bar-format-tabs
          tab-bar-separator tab-bar-separator tab-bar-separator
          tab-bar-format-add-tab
          tab-bar-separator tab-bar-separator tab-bar-separator
          tab-bar-format-global))
  (tab-bar-mode))

Dired - File manager

Dired is a good file manager but we can make it better by adding some more functionality on top using:

  • dired-collapse-mode to show long paths having single directories
  • dired-du-mode to show file and dir size
  • dired-subtree-toggle to show tree like structure under the dir
(use-package dired
  :hook (dired-mode-hook . dired-hide-details-mode)
  :bind (:map dired-mode-map
              ("C-c C-c" . dired-collapse-mode)
              ("C-c C-d C-u" . dired-du-mode)
              ("." . dired-hide-dotfiles-mode)
              ("<tab>" . dired-subtree-toggle)
              ("q"      . kill-current-buffer)
              ("RET"    . compro/dired-open-dir)
              ("^"      . compro/dired-up-dir)
              ("DEL"    . compro/dired-up-dir)
              ("<left>" . compro/dired-up-dir)
              ("C-x <C-j>" . dired-jump))
  :init
  (use-package dired-collapse :ensure t)
  (use-package dired-du :ensure t :after dired)
  (use-package dired-dups :ensure t :after dired)
  (use-package dired-filetype-face :ensure t :after dired)
  (use-package dired-hide-dotfiles :ensure t
    :after dired
    :hook (dired-mode-hook . dired-hide-dotfiles-mode))
  (use-package dired-subtree :ensure t :after dired)
  (defun compro/dired-up-dir ()
    (interactive)
    (find-alternate-file ".."))

  (defun compro/dired-open-dir ()
    (interactive)
    (set-buffer-modified-p nil)
    (let ((file-or-dir (dired-get-file-for-visit)))
      (if (f-dir-p file-or-dir)
          (find-alternate-file file-or-dir)
        (find-file file-or-dir))))

  (defun compro/dired/mp3-to-ogg ()
    "Used in dired to convert mp3 files to ogg"
    (interactive)
    (let* ((files (dired-get-marked-files)))
      (dolist (file files)
        (let* ((basename (file-name-nondirectory file))
               (file-base (file-name-base file))
               (dirname (file-name-directory file))
               (extension (file-name-extension file))
               (ogg-file (concat dirname file-base ".ogg"))
               (command (format "mpg123 -s -v \"%s\" | oggenc --raw -o \"%s\" -" file ogg-file)))
          (if (string= "mp3" (downcase extension))
              (progn
                (shell-command command nil nil)
                (message command)
                (if (file-exists-p ogg-file)
                    (delete-file file))))))))

  :config
  (setq dired-dwim-target t)
  (defun mydired-sort ()
    "Sort dired listings with directories first."
    (save-excursion
      (let (buffer-read-only)
        (forward-line 2) ;; beyond dir. header
        (sort-regexp-fields t "^.*$" "[ ]*." (point) (point-max)))
      (set-buffer-modified-p nil)))

  (defadvice dired-readin
      (after dired-after-updating-hook first () activate)
    "Sort dired listings with directories first before adding marks."
    (mydired-sort)))

Set my details

(when compro/laptop-p
  (setq user-mail-address "comproprasad@gmail.com"
        user-full-name "Compro Prasad"))

setq-default

(setq-default
 ;;;   Use spaces and not tabs for indentation
 indent-tabs-mode nil

 ;;;   Don't highlight trailing whitespaces by default
 show-trailing-whitespace nil

 ;;;   Org
 org-src-fontify-natively t ;; Fontify source blocks

 ;;;   More number of characters on a single line
 fill-column 80
 )

setq

(setq
 ;;;   Load newer files
 load-prefer-newer t

 ;;;   Initial major mode for *scratch* buffer
 initial-major-mode 'fundamental-mode

 ;;;   Only use ~/.authinfo.gpg
 auth-sources (list (ft "~/.authinfo.gpg"))

 ;;;   Security settings
 gnutls-verify-error t

 ;;;   Customizations go to this file
 custom-file (expand-file-name "custom.el" cache-d)

 ;;;   Follow symlinks to the actual file
 find-file-visit-truename t
 vc-follow-symlinks t

 ;;;   Don't redisplay if input is in buffer. Makes scrolling smoother.
 redisplay-skip-fontification-on-input t

 ;;;   Jump by words separated by punctuations
 global-subword-mode t

 ;;;   Prompt GNUPG passwords in the minibuffer only
 epg-pinentry-mode 'loopback

 ;;;   Show keystrokes in minibuffer after 0.5 seconds
 echo-keystrokes 0.5

 ;;;   Turn on every disabled function
 disabled-command-function nil

 ;;;   Use UTF-8 characters in buffer
 buffer-file-coding-system 'utf-8

 ;;;   Disable bidirectional text for tiny performance boost
 bidi-display-reordering nil

 ;;;   Don't blink parens
 blink-matching-paren nil

 ;;;   Hide cursors in other windows
 cursor-in-non-selected-windows nil

 ;;;   Prevent frames from automatically resizing themselves
 frame-inhibit-implied-resize t

 ;;;   Clipboard length
 kill-ring-max 1024

 ;;;   Stretch cursor according to the character under it
 x-stretch-cursor t

 ;;;   Time to wait before start of stealth fontify
 jit-lock-stealth-time 120

 ;;;   Sentences are separated by single space after dot(.)
 sentence-end-double-space nil

 ;;;   Don't compact font cache during GC to optimize redisplay
 inhibit-compacting-font-caches t

 ;;;   GC triggers per 100 MB increase in memory
 gc-cons-threshold (* 100 1024 1024)
 gc-cons-threshold-bak gc-cons-threshold  ;; Backup

 ;;;   Increase buffer size for reading output of processes (5 MB)
 read-process-output-max (* 5 1024 1024)

 ;;;   Prevent recursion limits
 max-lisp-eval-depth 700
 max-specpdl-size 700

 ;;;   No bells
 ring-bell-function 'ignore
 visible-bell nil

 ;;;   Themes are safe after all
 custom-safe-themes t

 ;;;   No startup show off
 inhibit-startup-screen t

 ;;;   Show line number for any normal width line
 line-number-display-limit-width 10000000

 ;;;   Some TLS connections might have larger PRIME bits
 gnutls-min-prime-bits 4096

 ;;;   Better unique names of similar filenames and buffer-names
 uniquify-buffer-name-style 'forward

 ;;;   We can use TCP connection to connect to remote Emacs instance
 server-use-tcp t

 ;;;   Server location
 server-auth-dir (concat cache-d "server/")

 ;;;   Save existing interprogram clipboard text before replacing it
 save-interprogram-paste-before-kill t

 ;;;   Set REPL programs' prompt as read only
 comint-prompt-read-only t

 ;;;   Read more output from a process (2mb)
 read-process-output-max 2097152

 ;;;   Use commands when in in minibuffer
 enable-recursive-minibuffers t

 ;;;   Scroll one line at a time no matter what
 scroll-conservatively  10000

 ;;;   Increase update time
 idle-update-delay 1.0

 ;;;   Initial scratch message is nil
 initial-scratch-message ""

 ;;;   Use directory local variables in tramp session
 enable-remote-dir-locals t

 ;;;   Backup configuration
 tramp-persistency-file-name (concat cache-d "tramp")
 backup-directory-alist `(("." . ,(concat cache-d "backups")))
 delete-old-versions -1
 version-control t
 vc-make-backup-files t
 vc-handled-backends '(Git)
 auto-save-file-name-transforms `((".*" ,(concat cache-d "auto-save-list") t))
 auto-save-list-file-prefix (concat cache-d "auto-save-list/saves-")

 ;;;   ERC configurations
 erc-hide-list '("PART" "QUIT" "JOIN")
 erc-server    "irc.libera.chat"
 erc-nick      "compro"

 ;;;   Dired
 dired-dwim-target t
 dired-listing-switches "-lAh --group-directories-first"

 ;;;   Ediff
 ediff-window-setup-function 'ediff-setup-windows-plain ;; Single frame ediff session

 ;;;   Ido mode
 ido-enable-flex-matching t
 ido-save-directory-list-file (concat cache-d "ido.last")

 ;;;   TAB cycle if there are only few candidates
 completion-cycle-threshold 5

 ;;;   Complete after indenting
 tab-always-indent 'complete

 ;;;   Increase interval at which eldoc is shown
 eldoc-idle-delay 1.5
 )

Convert yes/no to y/n

(if (>= emacs-major-version 28)
    (setq use-short-answers t)
  (fset 'yes-or-no-p 'y-or-n-p))

Load custom file

(when (file-readable-p custom-file)
  (load custom-file))

Load git tokens

(when (file-readable-p "~/.git-tokens")
  (load-file "~/.git-tokens"))

Use UTF 8 everywhere

(set-language-environment 'utf-8)
(set-default-coding-systems 'utf-8)
(set-selection-coding-system 'utf-8)
(set-locale-environment "en.UTF-8")
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

Change UI

  • Hide menu bar, tool bar and scroll bar
  • Delete selected text when typing
  • Enable mouse in terminal
  • Disable cursor blinking
(menu-bar-mode 0)
(menu-bar-no-scroll-bar)
(blink-cursor-mode 0)
(tool-bar-mode 0)

(delete-selection-mode 1)

(when (not window-system)
  (xterm-mouse-mode 1))  ; Enable mouse in terminal

Maximize the frame

Presently I use Emacs on i3 and in the terminal, so maximizing isn’t an issue. Uncomment if needed. Not tested.

;; start the initial frame maximized
;; (add-to-list 'initial-frame-alist '(fullscreen . maximized))

;; start every frame maximized
;; (add-to-list 'default-frame-alist '(fullscreen . maximized))

;; This modifies both of the above
(modify-all-frames-parameters '((fullscreen . maximized)))

Disable overlapping keybindings

server-after-make-frame-hook is triggered when an emacsclient frame is launched.

(defun compro/unset-keys ()
  (general-define-key
   :keymaps 'input-decode-map
   [?\C-m] [C-m]
   [?\C-i] [C-i]
   [?\C-j] [C-j]
   [?\C-\[] (kbd "<C-[>"))
  (remove-hook 'server-after-make-frame-hook 'compro/unset-keys))

;; For daemon / server sessions
(add-hook 'server-after-make-frame-hook 'compro/unset-keys)

;; For non daemon / server sessions
(compro/unset-keys)

Some common keybindings

(general-define-key
 "C-z"             'undo
 "C-x C-o"         'ff-find-other-file
 [C-m]             'delete-other-windows
 "<C-S-mouse-1>"   'imenu
 "C-c r"           'imenu
 "M-/"             'hippie-expand
 "M-^"             'compile)

Context menu on right click

(if (< emacs-major-version 28)
    (global-set-key [mouse-3] menu-bar-edit-menu)
  (context-menu-mode 1))

Auto revert files

(use-package autorevert
  :config
  (setq auto-revert-remote-files nil)
  (global-auto-revert-mode t))

Highlight matching brackets

(use-package paren
  :config
  (setq show-paren-style 'mixed
        show-paren-when-point-inside-paren t
        show-paren-when-point-in-periphery t)
  (show-paren-mode t))

Enable line numbers

(if (>= emacs-major-version 26)
    (add-hook 'prog-mode-hook 'display-line-numbers-mode)
  (add-hook 'prog-mode-hook 'linum-mode))

Which function mode

(add-hook 'prog-mode-hook 'which-function-mode)

Enable pair completion

A pair can be “”, ”, <>, {}, (), [], etc.

(add-hook 'prog-mode-hook 'electric-pair-mode)

Show 80 character mark

(when (>= emacs-major-version 27)
  (add-hook 'prog-mode-hook 'display-fill-column-indicator-mode))

Set default font

(cond
 ((find-font (font-spec :name "Source Code Pro"))
  (set-default-font '("Source Code Mono" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "Fira Code"))
  (set-default-font '("Fira Code" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "Ubuntu Mono"))
  (set-default-font '("Ubuntu Mono" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "Noto Mono"))
  (set-default-font '("Noto Mono" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "Input Mono"))
  (set-default-font '("Input Mono" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "DejaVu Sans Mono"))
  (set-default-font '("Dejavu Sans Mono" :size 12 :weight normal :width normal)))
 ((find-font (font-spec :name "Monospace"))
  (set-default-font '("Monospace" :size 12 :weight normal :width normal))))

Colorize compilation buffer

(require 'ansi-color)
(defun colorize-compilation-buffer ()
  "Colorize the compilation buffer with ANSI escape sequences."
  (read-only-mode)
  (ansi-color-apply-on-region (point-min) (point-max))
  (read-only-mode))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

Rename file and buffer

(defun compro/rename-file-buffer (&optional arg)
  "Rename current buffer and the file it is linked to.

If no prefix argument is provided simple string input is provided
using `read-string' function.

If a prefix argument (\\[universal-argument]) is provided full
featured `read-file-name' is used to read the filename. This is
useful if you want to move the file from one directory to another."
  (interactive "p")
  (when (null (buffer-file-name))
    (error "Buffer `%s' is not linked to a file" (buffer-name)))
  (let* ((filepath (buffer-file-name))
         (filename (f-filename filepath))
         (filedir (file-name-directory (directory-file-name filepath)))
         (prompt (concat "Rename '" filename "' to: "))
         (move-p (> arg 1))
         (new-location (if move-p
                           (read-file-name prompt filedir filepath)
                         (read-string prompt filename)))
         (new-filepath (if (string-suffix-p "/" new-location)
                           (concat new-location filename)
                         new-location)))
    (rename-file filename new-location 1)
    (set-visited-file-name new-filepath t t)))

(global-set-key (kbd "C-c f r") 'compro/rename-file-buffer)

Some smart additions

  • Smart C-a
  • C-o opens line below current line while C-S-o opens above current line
  • C-S-p lists processes started from Emacs
(use-package simple
  :bind (("C-a" . compro/beginning-of-line)
         ("C-o" . compro/open-line-below)
         ("C-S-p" . list-processes)
         ("" . list-processes)
         ("C-S-o" . compro/open-line-above)
         ("" . compro/open-line-above))
  :init
  (defun compro/beginning-of-line ()
    (interactive)
    (if (bolp)
        (back-to-indentation)
      (let ((pos (point))
            npos)
        (save-excursion
          (back-to-indentation)
          (setq npos (point)))
        (if (= pos npos)
            (beginning-of-line)
          (back-to-indentation)))))
  (defun compro/open-line-below ()
    (interactive)
    (end-of-line)
    (newline-and-indent))
  (defun compro/open-line-above ()
    (interactive)
    (back-to-indentation)
    (newline-and-indent)
    (previous-line 1)
    (indent-according-to-mode)))

comint keybindings

(with-eval-after-load 'comint
  (general-define-key
   :kemaps 'comint-mode-map
   "<remap> <kill-word>" 'compro/comint/kill-word))

comint make output text read-only

(add-hook 'comint-preoutput-filter-functions
          'compro/comint/preoutput-read-only)

Save history for future Emacs sessions

(require 'savehist)
(setq history-length t
      history-delete-duplicates t
      savehist-file (concat cache-d "savehist")
      save-place-file (concat cache-d "saveplace")
      savehist-additional-variables (nconc savehist-additional-variables
                                           '(kill-ring
                                             extended-command-history
                                             global-mark-ring
                                             mark-ring
                                             regexp-search-ring
                                             search-ring)))
(save-place-mode 1)
(savehist-mode 1)

Recent files

(require 'recentf)
(setq recentf-max-saved-items 512
      recentf-save-file (concat cache-d "recentf"))
(add-to-list 'recentf-exclude
             (concat (regexp-quote (ft (format cache-d))) ".*"))
(recentf-mode 1)

xwidget webkit

Browsing web in Emacs.

(use-package xwidget :when (fboundp 'xwidget-webkit-browse-url)
  :bind
  (:map xwidget-webkit-mode-map
        ("<mouse-4>" . xwidget-webkit-scroll-down)
        ("<mouse-5>" . xwidget-webkit-scroll-up)
        ("<up>" . xwidget-webkit-scroll-down)
        ("<down>" . xwidget-webkit-scroll-up)
        ("M-w" . xwidget-webkit-copy-selection-as-kill)
        ("C-c" . xwidget-webkit-copy-selection-as-kill))

  :hook
  (window-configuration-change-hook . compro/xwidget-webkit/adjust-size)

  :init
  ;; by default, xwidget reuses previous xwidget window,
  ;; thus overriding your current website, unless a prefix argument
  ;; is supplied
  ;; This function always opens a new website in a new window
  (defun xwidget-browse-url-no-reuse (url &optional session)
    (interactive
     (progn
       (require 'browse-url)
       (browse-url-interactive-arg "xwidget-webkit URL: ")))
    (xwidget-webkit-browse-url url t))

  (defun compro/xwidget-webkit/adjust-size ()
    (when (equal major-mode 'xwidget-webkit-mode)
      (xwidget-webkit-adjust-size-dispatch))))

Highlight current line in some modes

(add-hook 'tabulated-list-mode-hook 'hl-line-mode)

Winner mode for undo

Undo and redo window configurations.

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

Handling trailing whitespace

Delete trailing whitespaces and show them in the buffer.

(defun compro/set-show-whitespace-mode ()
  "Show white space in current buffer"
  (setq show-trailing-whitespace t))
;; Show whitespaces only in buffers pointing to specific files
(add-hook 'find-file-hook 'compro/set-show-whitespace-mode)
;; Remove the trailing whitespaces on save
(add-hook 'before-save-hook
          #'(lambda ()
              (when (not (eq major-mode 'org-mode))
                (delete-trailing-whitespace))))

Minibuffer performance optimization

(defun my/minibuffer-setup-hook ()
  (setq gc-cons-threshold most-positive-fixnum))

(defun my/minibuffer-exit-hook ()
  (setq gc-cons-threshold gc-cons-threshold-bak)
  (garbage-collect))

(add-hook 'minibuffer-setup-hook #'my/minibuffer-setup-hook)
(add-hook 'minibuffer-exit-hook #'my/minibuffer-exit-hook)

C style

(c-add-style "mylinux"
             '("linux"
               (tab-width . 4)
               (c-basic-offset . 4)
               (fill-column . 80)
               (c-hanging-semi&comma-criteria . my/c-semi&comma)
               (c-cleanup-list empty-defun-braces ;; {}
                               brace-else-brace   ;; } else {
                               brace-elseif-brace ;; } else if {
                               ;;defun-close-semi   ;; };
                               )
               (c-hanging-braces-alist (brace-list-open)
                                       (brace-entry-open)
                                       (substatement-open after)
                                       (block-close . c-snug-do-while)
                                       (arglist-cont-nonempty)
                                       (class-open . (after))
                                       (class-close . (before)))
               (c-offsets-alist (inline-open . 0)
                                (comment-intro . 0))))

(setq-default c-default-style
              '((java-mode . "java")
                (awk-mode . "awk")
                (other . "mylinux")))

Smart terminal opener

Mimics behaviour of toggling Visual Studio Code’s terminal.

(defun default-ansi-term-opener (dir)
  (let ((buf-name (funcall terminal-buffer-name-generator dir "ansi-term" nil))
        (*buf-name* (concat "*" buf-name "*")))
    (if (get-buffer *buf-name*)
        `(,*buf-name* . nil)
      (ansi-term "/bin/bash" buf-name)
      `(,*buf-name* . t))))

(defun default-eshell-opener (dir)
  (let ((eshell-buffer-name (funcall terminal-buffer-name-generator dir "eshell" t)))
    (if (get-buffer eshell-buffer-name)
        `(,eshell-buffer-name . nil)
      (eshell)
      `(,eshell-buffer-name . t))))

(defun default-vterm-opener (dir)
  (let ((buf-name (funcall terminal-buffer-name-generator dir "vterm" t)))
    (if (get-buffer buf-name)
        `(,buf-name . nil)
      (vterm buf-name)
      `(,buf-name . t))))

(defun default-terminal-buffer-name-generator (dir terminal-type use-star)
  (if use-star
      (concat "*" terminal-type "-" dir "*")
    (concat terminal-type "-" dir)))

(defun detect-terminal-and-open (dir)
  (if (featurep 'vterm)
      (default-vterm-opener dir)
    (prog1
        (default-eshell-opener dir)
      (message "Opened eshell because vterm was not installed"))))

(defun below-window-checker (buffer action)
  (with-current-buffer buffer
    (or
     (s-starts-with? "*eshell" buffer)
     (s-starts-with? "*vterm" buffer)
     (s-starts-with? "*ansi-term" buffer))))

(defvar terminal-buffer-name-generator 'default-terminal-buffer-name-generator)
(defvar terminal-generator 'detect-terminal-and-open)

(defun toggle-terminal ()
  "Toggles terminal like VS Code does.

TODO:

1. Creating new terminals in the same project for different purposes.
2. Editing path in which the terminal is opened. Currently best guess it taken.
3. Allow switching between different terminals in the same path with ease.
"
  (interactive)
  (let* ((prj (project-current))
         (dir (if prj
                  (expand-file-name (project-root prj))
                default-directory))
         (ret-val (let ((default-directory dir)) (funcall terminal-generator dir)))
         (created (cdr ret-val))
         (buf-name (car ret-val))
         (buf-obj (get-buffer buf-name))
         (buf-win (get-buffer-window buf-name nil)))

    (if buf-win
        (if (and (not created) (eq buf-win (selected-window)))
            (progn (message "Hiding terminal window") (delete-window buf-win))

          (progn (message "Selecting terminal window") (select-window buf-win t)))

      (message "Showing terminal buffer in a window")
      (display-buffer-in-side-window buf-obj '((side . bottom)))
      (select-window (get-buffer-window buf-obj) t))))

(defun create-new-terminal ()
  "WIP"
  (let* ((prj (project-current))
         (dir (if prj
                  (expand-file-name (project-root prj))
                default-directory))
         (ret-val (funcall terminal-generator dir))
         (created (cdr ret-val))
         (buf-name (car ret-val))
         (buf-obj (get-buffer buf-name))
         (buf-win (get-buffer-window buf-name nil)))

    (if buf-win
        (if (and (not created) (eq buf-win (selected-window)))
            (progn (message "Hiding terminal window") (delete-window buf-win))

          (progn (message "Selecting terminal window") (select-window buf-win t)))

      (message "Showing terminal buffer in a window")
      (display-buffer-in-side-window buf-obj '((side . bottom)))
      (select-window (get-buffer-window buf-obj) t))))

(add-to-list
 'display-buffer-alist
 '(below-window-checker
   display-buffer-in-side-window
   (side . bottom)
   ;; (slot . 1)
   ;; (window-width . 0.33)
   ;; (reusable-frames . nil)
   ))

(global-set-key (kbd "C-`") 'toggle-terminal)
(global-set-key (kbd "C-c `") 'toggle-terminal)

Third party packages and configurations

Restclient Mode

(use-package restclient :ensure t)

Hydra

Keybindings that stick around.

(use-package hydra :ensure t)

UI hydra

(global-set-key
 (kbd "C-c u")
 (defhydra hydra-ui (:hint nil)
   "
  ^Emacs^              ^Move to window^   ^Move window to^   ^Buffer^
  ^^^^-----------------------------------------------------------------------
  _M-+_: Inc font      _<left>_           _S-<left>_         _f_: Col indicator
  _M-=_: Inc font      _<right>_          _S-<right>_        _l_: Line numbers
  _M--_: Dec font      _<up>_             _S-<up>_           _+_: Inc font
  _F_: Col indicator   _<down>_           _S-<down>_         _=_: Inc font
  _L_: Line numbers    ^ ^                ^ ^                _-_: Dec font
  _t_: Tabs
  _T_: Toolbar
  _m_: Menubar
  _s_: Scrollbar"
   ("+" text-scale-increase)
   ("=" text-scale-increase)
   ("-" text-scale-decrease)
   ("M-+" default-text-scale-increase)
   ("M-=" default-text-scale-increase)
   ("M--" default-text-scale-decrease)
   ("t" tab-bar-mode)
   ("m" menu-bar-mode)
   ("s" scroll-bar-mode)
   ("f" display-fill-column-indicator-mode)
   ("l" display-line-numbers-mode)
   ("F" global-display-fill-column-indicator-mode)
   ("L" global-display-line-numbers-mode)
   ("T" tool-bar-mode)
   ("<left>" windmove-left)
   ("<right>" windmove-right)
   ("<up>" windmove-up)
   ("<down>" windmove-down)
   ("S-<left>" buf-move-left)
   ("S-<right>" buf-move-right)
   ("S-<up>" buf-move-up)
   ("S-<down>" buf-move-down)))

Text navigation hydra

(global-set-key
 (kbd "C-c t")
 (defhydra hydra-text ()
   ("x" whole-line-or-region-kill-region "Cut")
   ("c" whole-line-or-region-kill-ring-save "Copy")
   ("v" yank "Paste")
   ("C-x" whole-line-or-region-kill-region "Cut")
   ("C-c" whole-line-or-region-kill-ring-save "Copy")
   ("C-v" yank "Paste")
   ("C" consult-yank-pop "Clipboard")
   ("<up>" previous-line nil)
   ("C-p" previous-line nil)
   ("<down>" next-line nil)
   ("C-n" next-line nil)
   ("<left>" left-char nil)
   ("<right>" right-char nil)
   ("C-<left>" left-word nil)
   ("M-b" backward-word nil)
   ("C-<right>" right-word nil)
   ("M-f" forward-word nil)
   ("s" avy-goto-char-2 "Goto 2 chars")
   ("S" avy-goto-symbol-1 "Goto symbol")
   ("C-s" ctrlf-forward-default "Find Next")
   ("C-f" ctrlf-forward-default "Find Next")
   ("C-r" ctrlf-backward-default "Find Previous")
   ("C-S-f" ctrlf-backward-default "Find Previous")
   ("<home>" compro/beginning-of-line nil)
   ("C-a" compro/beginning-of-line "Home")
   ("<end>" move-end-of-line nil)
   ("C-e" move-end-of-line "End")
   ("C-SPC" set-mark-command "Mark/Unmark")
   ("S-<down>" move-text-down "Move line down")
   ("S-<up>" move-text-up "Move line up")
   ("+" er/expand-region "Expand")
   ("=" er/expand-region "Expand")
   ("C-+" hydra-er/er/expand-region "Expand")
   ("C-=" hydra-er/er/expand-region "Expand")
   ("-" er/contract-region "Contract")
   ("C--" hydra-er/er/contract-region "Contract")))

Gamified navigation

(global-set-key
 (kbd "C-c g")
 (defhydra hydra-gamify (:hint nil)
   "Game mode"
   ("w" previous-line)
   ("s" next-line)
   ("W" previous-line)
   ("S" next-line)
   ("a" left-char)
   ("d" right-char)
   ("A" left-word)
   ("D" right-word)
   ("C-s" ctrlf-forward-default)
   ("C-r" ctrlf-backward-default)
   ("c" whole-line-or-region-kill-ring-save)
   ("x" whole-line-or-region-kill-region)
   ("v" yank)
   ("C-c" whole-line-or-region-kill-ring-save)
   ("C-x" whole-line-or-region-kill-region)
   ("C-v" yank)
   ("V" consult-yank-pop "Clipboard")
   ("g" set-mark-command "Mark")
   ("f" avy-goto-char-2 "Goto 2 chars")
   ("F" avy-goto-symbol-1 "Goto symbol")
   ("t" treemacs "Treemacs")
   ("<left>" windmove-left)
   ("<right>" windmove-right)
   ("<up>" windmove-up)
   ("<down>" windmove-down)
   ("S-<left>" buf-move-left)
   ("S-<right>" buf-move-right)
   ("S-<up>" buf-move-up)
   ("S-<down>" buf-move-down)
   ("j" windmove-left)
   ("l" windmove-right)
   ("i" windmove-up)
   ("j" windmove-down)
   ("J" buf-move-left)
   ("L" buf-move-right)
   ("I" buf-move-up)
   ("K" buf-move-down)
   ("u" undo)
   ("U" undo-tree-visualize)
   ("z" undo)
   ("Z" undo-tree-visualize)
   ("e" end-of-buffer)
   ("E" beginning-of-buffer)
   ("M-c" capitalize-word "Capitalize")
   ("M-l" downcase-word "Lower")
   ("M-u" upcase-word "Upper")
   ("o" compro/beginning-of-line)
   ("p" move-end-of-line)))

Hungry delete everywhere

There is a native function c-hungry-delete which is only for cc-mode. This has been ported to an external package which provides hungry deletion to other modes as well.

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

Hide minor modes from modeline using Minions

(use-package minions :ensure t
  :bind ([S-down-mouse-3] . minions-minor-modes-menu)
  :config
  (minions-mode 1))

Move transient history to .cache

(use-package transient :ensure t
  :config
  (setq transient-history-file (locate-user-emacs-file
                                (concat cache-d "transient/history.el"))
        transient-values-file (locate-user-emacs-file
                               (concat cache-d "transient/values.el"))
        transient-levels-file (locate-user-emacs-file
                               (concat cache-d "transient/levels.el"))))

Git integration

Magit is an awesome package for doing most git related tasks in Emacs.

(use-package magit :ensure t
  :bind (("C-x g" . magit-status)
         :map magit-mode-map
         ([C-tab] . nil)
         ([C-backtab] . nil)
         ([M-tab] . nil)
         :map magit-status-mode-map
         ("q" . compro/kill-magit-buffers)
         ([C-tab] . nil)
         ([C-backtab] . nil)
         ([M-tab] . nil)
         :map magit-log-mode-map
         ([C-tab] . nil)
         ([C-backtab] . nil)
         ([M-tab] . nil))
  :init
  ;; (use-package forge :unless is-windows :after magit :ensure t)
  :config
  (remove-hook 'magit-refs-sections-hook 'magit-insert-tags)
  (remove-hook 'server-switch-hook 'magit-commit-diff)
  (defun compro/kill-magit-buffers ()
    "Kill magit buffers related to a project."
    (interactive)
    (magit-mode-bury-buffer 16))
  (with-eval-after-load 'magit-diff
    (define-key magit-diff-mode-map [C-tab] nil)
    (define-key magit-file-section-map [C-tab] nil)
    (define-key magit-hunk-section-map [C-tab] nil)
    (define-key magit-diff-mode-map [C-backtab] nil)
    (define-key magit-file-section-map [C-backtab] nil)
    (define-key magit-hunk-section-map [C-backtab] nil)
    (define-key magit-diff-mode-map [M-tab] nil)
    (define-key magit-file-section-map [M-tab] nil)
    (define-key magit-hunk-section-map [M-tab] nil)))

Get commit message for why a line was changed using git-messenger.

(use-package git-messenger :ensure t
  :bind (("C-x v p" . git-messenger:popup-message)))

Expand Region

Expand region is a technique to iteratively select larger or smaller blocks of text based on the context using a single keybinding.

(use-package expand-region :ensure t
  :commands (er/expand-region
             er/mark-paragraph
             er/mark-inside-pairs
             er/mark-outside-pairs
             er/mark-inside-quotes
             er/mark-outside-quotes
             er/contract-region)
  :bind (("C-=" . hydra-er/er/expand-region)
         ("C--" . hydra-er/er/contract-region)
         ("M-[ 1 ; 5 k" . hydra-er/er/expand-region)  ; Strange key in git bash (msys2) on windows
         ("M-[ 1 ; 5 m" . hydra-er/er/contract-region))  ; Strange key in git bash (msys2) on windows
  :config
  (require 'hydra)
  (defhydra hydra-er (:hint nil)
    "
^Expand^  ^Reduce^
^──────^──^────^─────────────────
_C-=_     _C-+_
_=_       _+_
        _-_"
    ("C-=" er/expand-region)
    ("=" er/expand-region)
    ("C-+" er/contract-region)
    ("C--" er/contract-region)
    ("+" er/contract-region)
    ("-" er/contract-region)))

Project integration

Now Emacs comes with native project support since 25.1. Investigate and set up project.el.

Until then we can rely on the more powerful projectile package.

(use-package projectile :ensure t
  :unless (> emacs-major-version 27)  ;; Use project.el for > 27
  :bind (("C-x p" . projectile-command-map))
  :config
  (setq
   projectile-cache-file (concat cache-d "projectile")
   projectile-known-projects-file (concat cache-d "projectile-bookmarks.eld")
   projectile-completion-system 'default)
  (projectile-mode 1))

Setting up project-x. From its Github README:

  • Recognize any directory with a .project file as a project. Also works if any parent directory has this file.
  • Save and restore project files and window configurations across sessions. Project-X will load all saved project files and directories (as dired buffers) and try to recreate the window configuration at the time of saving.
(use-package project-x
  :config
  (project-x-mode 1))

Silver Searcher

the_silver_searcher is an alternative to grep which is faster.

(use-package ag :ensure t :when (executable-find "ag"))

Switch window

C-x o is a longer keybinding and for more number of windows it becomes hard to repeatedly press the same keybinding. switch-window tends to solve this problem.

(use-package switch-window :ensure t
  :bind ("C-x o" . switch-window))

Which key

Look for the next keybinding you can press.

(use-package which-key :ensure t
  :config
  (setq which-key-idle-delay (if is-windows 0.212 1.0))
  (which-key-mode))

Multiple cursors

Make multiple cursors in a buffer to make text editing less repetitive and also less boring.

(use-package multiple-cursors :ensure t
  :bind
  (("C-S-c" . mc/edit-lines)
   ("M-S-<up>" . mc/mark-previous-like-this)
   ("M-<up>" . mc/skip-to-previous-like-this)
   ("M-S-<down>" . mc/mark-next-like-this)
   ("M-<down>" . mc/skip-to-next-like-this)
   ("C-c C-<" . mc/mark-all-like-this)
   ("M-S-<mouse-1>" . mc/add-cursor-on-click)
   ("M-S-<mouse-2>" . mc/add-cursor-on-click)
   ("M-S-<mouse-3>" . mc/add-cursor-on-click))
  :init
  (use-package phi-search-mc :ensure t
    :hook (isearch-mode-hook . phi-search-from-isearch-mc/setup-keys)
    :config
    (phi-search-mc/setup-keys)))

Vundo

Replacement for undo-tree.

(use-package vundo :ensure t
  :bind ("C-x u" . vundo)
  :config
  (setq vundo-glyph-alist vundo-unicode-symbols))

Themes

Doom Themes

(use-package doom-themes :ensure t
  :commands (doom-themes-org-config)
  :config
  (doom-themes-org-config)
  ;; (setq doom-themes-enable-bold t     ;; Causes font-lock to slow down
  ;;       doom-themes-enable-italic t)
  (when (>= emacs-major-version 27)
    (with-eval-after-load 'org
      (dolist (face '(org-block
                      org-block-begin-line
                      org-block-end-line
                      org-level-1
                      org-quote))
        (set-face-attribute face nil :extend t)))
    (with-eval-after-load 'ediff
      (dolist (face '(ediff-current-diff-A
                      ediff-current-diff-Ancestor
                      ediff-current-diff-B
                      ediff-current-diff-C
                      ediff-even-diff-A
                      ediff-even-diff-Ancestor
                      ediff-even-diff-B
                      ediff-even-diff-C
                      ediff-fine-diff-A
                      ediff-fine-diff-Ancestor
                      ediff-fine-diff-B
                      ediff-fine-diff-C
                      ediff-odd-diff-A
                      ediff-odd-diff-Ancestor
                      ediff-odd-diff-B
                      ediff-odd-diff-C))
        (set-face-attribute face nil :extend t)))
    (with-eval-after-load 'hl-line
      (set-face-attribute 'hl-line nil :extend t))
    (with-eval-after-load 'faces
      (dolist (face '(region
                      secondary-selection))
        (set-face-attribute face nil :extend t)))
    (with-eval-after-load 'markdown-mode
      (dolist (face '(markdown-code-face
                      markdown-pre-face))
        (set-face-attribute face nil :extend t)))))

Spacemacs Theme

(use-package spacemacs-theme :ensure t)

Modus operandi theme

(use-package modus-themes :ensure t)

Custom faces

Common

;; (dolist (el '("test" "test1")) (message el))
;; (custom-theme-set-faces
;;  'compro-light
;;  )
(custom-set-faces
 '(org-level-1 ((t (:inherit outline-1 :extend t :height 3.0))))
 '(org-level-2 ((t (:inherit outline-2 :extend t :height 2.5))))
 '(org-level-3 ((t (:inherit outline-3 :extend t :height 2.0))))
 '(org-level-4 ((t (:inherit outline-4 :extend t :height 1.5)))))
;; light
(custom-set-faces
 '(dired-subtree-depth-1-face ((t (:background "white smoke"))))
 '(dired-subtree-depth-2-face ((t (:background "gainsboro"))))
 '(dired-subtree-depth-3-face ((t (:background "white smoke"))))
 '(dired-subtree-depth-4-face ((t (:background "gainsboro"))))
 '(dired-subtree-depth-5-face ((t (:background "white smoke"))))
 '(dired-subtree-depth-6-face ((t (:background "gainsboro"))))
 '(org-block ((t (:inherit shadow :extend t :background "white smoke"))))
 '(org-block-begin-line ((t (:inherit org-meta-line :extend t :background "gainsboro" :foreground "dark slate gray")))))
;; dark
;; (custom-set-faces
;;  '(default ((t (:inherit nil :extend nil :stipple nil :background "gray10" :foreground "#bbc0ca" :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight regular :height 98 :width normal :foundry "ADBO" :family "Source Code Pro"))))
;;  '(mode-line ((t (:background "blue" :foreground "white" :box nil))))
;;  '(org-block-begin-line ((t (:inherit org-meta-line :extend t :background "gray13" :foreground "tan4"))))
;;  '(region ((t (:extend t :background "gray25"))))
;;  '(shadow ((t (:foreground "SlateBlue1"))))
;;  '(tab-bar ((t (:inherit variable-pitch :background "gray26" :foreground "white" :height 1.2))))
;;  '(tab-bar-tab ((t (:inherit tab-bar :background "gray10" :box nil))))
;;  '(tab-bar-tab-inactive ((t (:inherit tab-bar-tab :background "gray20")))))

Page break lines

Convert “^L” characters to single lines for better readability.

(use-package page-break-lines :ensure t
  :config
  (global-page-break-lines-mode t))

Autocompletion for Emacs

vertico provides a simpler interface for completions in Emacs as compared to selectrum.

Previously I used ivy but I didn’t like its sorting mechanism. Sure, there is ivy-prescient which changes the sorting mechanism but its developed by the same author who developed selectrum who also claims that ivy is complicated by design.

(use-package orderless :ensure t
  :config
  (setq completion-styles '(orderless flex substring)
        completion-category-defaults nil
        completion-category-overrides '((file (styles . (partial-completion)))))

  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))

(use-package consult :ensure t
  :bind (("M-y" . consult-yank-pop)
         ("M-v" . consult-yank-pop)
         ("C-v" . consult-yank-pop)
         ("M-g l" . consult-line)
         ("M-g o" . consult-outline)
         ("C-x C-r" . consult-recent-file)
         ("C-x b" . consult-buffer)
         :map minibuffer-local-map
         ("C-r" . consult-history)))

(use-package consult-dir :ensure t
  :bind ("C-x d" . consult-dir)
  :init
  (with-eval-after-load 'eshell
    (defun eshell/z (&optional regexp)
      "Navigate to a previously visited directory in eshell, or to
any directory proferred by `consult-dir'.
Source: https://karthinks.com/software/jumping-directories-in-eshell/"
      (let ((eshell-dirs (delete-dups
                          (mapcar 'abbreviate-file-name
                                  (ring-elements eshell-last-dir-ring)))))
        (cond
         ((and (not regexp) (featurep 'consult-dir))
          (let* ((consult-dir--source-eshell `(:name "Eshell"
                                                     :narrow ?e
                                                     :category file
                                                     :face consult-file
                                                     :items ,eshell-dirs))
                 (consult-dir-sources (cons consult-dir--source-eshell
                                            consult-dir-sources)))
            (eshell/cd (substring-no-properties
                        (consult-dir--pick "Switch directory: ")))))
         (t (eshell/cd (if regexp (eshell-find-previous-directory regexp)
                         (completing-read "cd: " eshell-dirs)))))))))

(use-package marginalia :ensure t :after vertico
  :config
  (setq marginalia-annotators
        '(marginalia-annotators-heavy marginalia-annotators-light nil))
  (marginalia-mode +1))

(use-package embark :ensure t
  :bind (("C-S-a" . embark-act)
         ("" . embark-act)
         ("C-S-e" . embark-act-noexit)
         ("" . embark-act-noexit)
         ("C-S-b" . embark-become)
         ("" . embark-become))
  :config
  ;; which-key support
  (setq embark-action-indicator
        (lambda (map)
          (which-key--show-keymap "Embark" map nil nil 'no-paging)
          #'which-key--hide-popup-ignore-command)
        embark-become-indicator embark-action-indicator))

(use-package vertico :ensure t :defer nil

  ;; More convenient directory navigation commands
  :bind (:map vertico-map
          ("RET" . vertico-directory-enter)
          ("DEL" . vertico-directory-delete-char)
          ("M-DEL" . vertico-directory-delete-word)
          ("M-V" . vertico-multiform-vertical)
          ("M-G" . vertico-multiform-grid)
          ("M-F" . vertico-multiform-flat)
          ("M-R" . vertico-multiform-reverse)
          ("M-U" . vertico-multiform-unobtrusive))

  :init
  (setq read-file-name-completion-ignore-case t
        read-buffer-completion-ignore-case t)

  ;; Tidy shadowed file names
  :hook (rfn-eshadow-update-overlay-hook . vertico-directory-tidy)

  :config
  (setq vertico-count 18)
  (require 'vertico-mouse)
  (require 'vertico-indexed)
  (vertico-mode 1)
  (vertico-multiform-mode 1)
  (vertico-mouse-mode 1)
  (vertico-indexed-mode 1)
  (advice-add #'vertico--format-candidate :around
            (lambda (orig cand prefix suffix index _start)
              (setq cand (funcall orig cand prefix suffix index _start))
              (concat
               (if (= vertico--index index)
                   (propertize "» " 'face 'vertico-current)
                 "  ")
               cand)))
  ;; Selectrum Wiki - Minibuffer default add function
  (autoload 'ffap-guesser "ffap")
  (setq minibuffer-default-add-function
        (defun minibuffer-default-add-function+ ()
          (with-selected-window (minibuffer-selected-window)
            (delete-dups
             (delq nil
                   (list (thing-at-point 'symbol)
                         (thing-at-point 'list)
                         (ffap-guesser)
                         (thing-at-point-url-at-point))))))))

(use-package vertico-directory :after vertico :ensure nil)

Isearch alternative

ctrlf is just a simple improvement over isearch.

(use-package ctrlf :ensure t
  :config (ctrlf-mode 1))

Better M-< and M->

(use-package beginend :ensure t
  :config (beginend-global-mode))

Move text up and down easily

(use-package move-text :ensure t)

Zoom in and zoom out text

(use-package default-text-scale :ensure t
  :config (default-text-scale-mode 1))

iedit

Make a hydra. Interactive editing using iedit-mode. Similar to multiple cursors but:

  • simpler
  • smarter
  • more flexible
(use-package iedit :ensure t
  :bind ("C-c i" . iedit-mode))

wgrep

Edit grep buffers.

(use-package wgrep :ensure t :after grep)

Clang format

Format C++ buffers on save.

(use-package clang-format+ :ensure t
  :config
  (setq clang-format+-context 'buffer))

Telegram

(use-package telega :ensure t :when is-linux)

Org mode

Org mode is a note taking system which has other uses too. This configuration is written in org mode.

For syntax highlighting in PDF exports pygments python package is required.

(use-package org :ensure org-contrib
  :hook (org-mode-hook . org-superstar-mode)
  :init
  ;; see https://list.orgmode.org/87r5718ytv.fsf@sputnik.localhost
  (eval-after-load 'org-list
    '(add-hook 'org-checkbox-statistics-hook (function ndk/checkbox-list-complete)))

  (defun ndk/checkbox-list-complete ()
    (save-excursion
      (org-back-to-heading t)
      (let ((beg (point)) end)
        (end-of-line)
        (setq end (point))
        (goto-char beg)
        (if (re-search-forward "\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]" end t)
            (if (match-end 1)
                (if (equal (match-string 1) "100%")
                    ;; all done - do the state change
                    (org-todo 'done)
                  (org-todo 'todo))
              (if (and (> (match-end 2) (match-beginning 2))
                       (equal (match-string 2) (match-string 3)))
                  (org-todo 'done)
                (org-todo 'todo)))))))

  (use-package ob-async :ensure t :after ob)
  (use-package ob-restclient :ensure t :after ob)

  (use-package boxy-headings :ensure t)

  (use-package org-babel-eval-in-repl :ensure t
    :after ob
    :bind
    (:map org-mode-map
          ("C-c C-<return>" . ober-eval-block-in-repl)))

  (use-package ox-hugo :ensure t :after ox :disabled t
    :config
    (dolist (ext '("zip" "ctf"))
      (push ext org-hugo-external-file-extensions-allowed-for-copying)))

  (use-package org-superstar :ensure t
    :config
    (setq org-superstar-leading-bullet ?\s))

  (use-package org-re-reveal :ensure t :after ox)

  (add-hook 'org-mode-hook
            #'(lambda () (setq line-spacing 0.2) ;; Add more line padding for readability
                ))

  :bind
  (("C-c l" . org-store-link)
   ("C-c a" . org-agenda)
   ("C-c c" . org-capture)
   :map org-mode-map
   ([C-tab] . nil)
   ([C-backtab] . nil)
   ("M-n" . outline-next-visible-heading)
   ("C-c k" . endless/insert-key)
   ("M-p" . outline-previous-visible-heading))
  :config
  (defun compro/right-angled-triangle-with-theta ()
    (interactive)
    (insert "
\\begin{center}
\\begin{tikzpicture}
% Define the triangle vertices
\\coordinate [label=below:$A$] (A) at (0,0);
\\coordinate [label=above:$B$] (B) at (0,2);
\\coordinate [label=below:$C$] (C) at (3,0);

% Draw the triangle
\\draw (A) -- (B) -- (C) -- cycle;

% Label the sides
\\node at ($(A)!0.5!(B)$) [left] {$p$};
\\node at ($(B)!0.5!(C)$) [above] {$h$};
\\node at ($(C)!0.5!(A)$) [below] {$b$};

% Mark the right angle at B
\\draw pic[draw, angle radius=3mm, angle eccentricity=1.5] {right angle = B--A--C};
\\draw pic[draw, angle radius=5mm, \"$\\theta$\", angle eccentricity=1.5] {angle = B--C--A};
\\end{tikzpicture}
\\end{center}
"))
  (add-to-list 'org-latex-packages-alist '("" "minted"))
  (setq org-latex-listings 'minted)

  (setq org-latex-pdf-process
        '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
          "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
          "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

  ;; (define-key org-mode-map "\C-ck" #'endless/insert-key)
  (defun endless/insert-key (key)
    "Ask for a key then insert its description.
Will work on both org-mode and any mode that accepts plain html."
    (interactive "kType key sequence: ")
    (let* ((is-org-mode (derived-mode-p 'org-mode))
           (tag (if is-org-mode
                    "@@html:<kbd>%s</kbd>@@"
                  "<kbd>%s</kbd>")))
      (if (null (equal key "\r"))
          (insert
           (format tag (help-key-description key nil)))
        (insert (format tag ""))
        (forward-char (if is-org-mode -8 -6)))))

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((shell . t)
     (python . t)
     (restclient . t)
     (emacs-lisp . t)))

  (setq org-return-follows-link t
        org-agenda-diary-file "~/.org/diary.org"
        org-src-window-setup 'current-window
        org-startup-with-inline-images t
        org-image-actual-width 400
        org-hierarchical-todo-statistics nil
        org-checkbox-hierarchical-statistics nil
        org-src-preserve-indentation nil
        org-adapt-indentation t)

  (defun my-org-autodone (n-done n-not-done)
    "Switch entry to DONE when all subentries are done, to TODO otherwise."
    (let (org-log-done org-log-states)   ; turn off logging
      (org-todo (if (= n-not-done 0) "DONE" "TODO"))))
  (add-hook 'org-after-todo-statistics-hook 'my-org-autodone)
  (require 'org-tempo)
  (define-minor-mode unpackaged/org-export-html-with-useful-ids-mode
    "Attempt to export Org as HTML with useful link IDs.
Instead of random IDs like \"#orga1b2c3\", use heading titles,
made unique when necessary."
    :global t
    (if unpackaged/org-export-html-with-useful-ids-mode
        (progn
          (advice-add #'org-export-new-title-reference :override #'unpackaged/org-export-new-title-reference)
          (advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference))
      (advice-remove #'org-export-new-title-reference #'unpackaged/org-export-new-title-reference)
      (advice-remove #'org-export-get-reference #'unpackaged/org-export-get-reference)))

  (defun unpackaged/org-export-get-reference (datum info)
    "Like `org-export-get-reference', except uses heading titles instead of random numbers."
    (let ((cache (plist-get info :internal-references)))
      (or (car (rassq datum cache))
          (let* ((crossrefs (plist-get info :crossrefs))
                 (cells (org-export-search-cells datum))
                 ;; Preserve any pre-existing association between
                 ;; a search cell and a reference, i.e., when some
                 ;; previously published document referenced a location
                 ;; within current file (see
                 ;; `org-publish-resolve-external-link').
                 ;;
                 ;; However, there is no guarantee that search cells are
                 ;; unique, e.g., there might be duplicate custom ID or
                 ;; two headings with the same title in the file.
                 ;;
                 ;; As a consequence, before re-using any reference to
                 ;; an element or object, we check that it doesn't refer
                 ;; to a previous element or object.
                 (new (or (cl-some
                           (lambda (cell)
                             (let ((stored (cdr (assoc cell crossrefs))))
                               (when stored
                                 (let ((old (org-export-format-reference stored)))
                                   (and (not (assoc old cache)) stored)))))
                           cells)
                          (when (org-element-property :raw-value datum)
                            ;; Heading with a title
                            (unpackaged/org-export-new-title-reference datum cache))
                          ;; NOTE: This probably breaks some Org Export
                          ;; feature, but if it does what I need, fine.
                          (org-export-format-reference
                           (org-export-new-reference cache))))
                 (reference-string new))
            ;; Cache contains both data already associated to
            ;; a reference and in-use internal references, so as to make
            ;; unique references.
            (dolist (cell cells) (push (cons cell new) cache))
            ;; Retain a direct association between reference string and
            ;; DATUM since (1) not every object or element can be given
            ;; a search cell (2) it permits quick lookup.
            (push (cons reference-string datum) cache)
            (plist-put info :internal-references cache)
            reference-string))))

  (defun unpackaged/org-export-new-title-reference (datum cache)
    "Return new reference for DATUM that is unique in CACHE."
    (cl-macrolet
        ((inc-suffixf
           (place)
           `(progn
              (string-match (rx bos
                                (minimal-match (group (1+ anything)))
                                (optional "--" (group (1+ digit)))
                                eos)
                            ,place)
              ;; HACK: `s1' instead of a gensym.
              (-let* (((s1 suffix) (list (match-string 1 ,place)
                                         (match-string 2 ,place)))
                      (suffix (if suffix
                                  (string-to-number suffix)
                                0)))
                (setf ,place (format "%s--%s" s1 (cl-incf suffix)))))))
      (let* ((title (org-element-property :raw-value datum))
             (ref (url-hexify-string (substring-no-properties title)))
             (parent (org-element-property :parent datum)))
        (while (--any (equal ref (car it))
                      cache)
          ;; Title not unique: make it so.
          (if parent
              ;; Append ancestor title.
              (setf title (concat (org-element-property :raw-value parent)
                                  "--" title)
                    ref (url-hexify-string (substring-no-properties title))
                    parent (org-element-property :parent parent))
            ;; No more ancestors: add and increment a number.
            (inc-suffixf ref)))
        ref)))
  (defun org-generate-custom-ids-based-on-headings ()
    (interactive)
    (let ((hlist nil))
      (save-excursion
        (goto-char (point-min))
        (while (outline-next-heading)
          (let* ((old-id (plist-get (org-element--get-node-properties) :CUSTOM_ID))
                 (heading (replace-regexp-in-string "[^A-Za-z0-9]" "-" (substring-no-properties (org-get-heading t t t t))))
                 (new-id heading)  ;; (new-id (concat "h-" heading))
                 (dup (assoc heading hlist))
                 (dup-count (if dup (1+ (cdr dup)) 1)))
            (setq new-id (concat new-id (if (= dup-count 1) "" (number-to-string dup-count))))
            (unless (string-equal old-id new-id)
              (org-set-property "CUSTOM_ID" new-id))
            (setq hlist (delete dup hlist))
            (push `(,heading . ,dup-count) hlist))))))
  (fset 'org-dedent-properties
        (kmacro-lambda-form
         [?\C-s ?: ?P ?R ?O ?P ?E ?R ?T ?I ?E ?S ?: return
                ?\C-a ?\C-x ? ?\C-s ?: ?E ?N ?D ?: return
                ?\C-b ?\C-b ?\C-b ?\C-b ?\C-b
                134217848 ?k ?i ?l ?l ?- ?r ?e ?c ?t ?a ?n ?g ?l ?e return] 0 "%d"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp :tangle init.el"))

  (setq org-pretty-entities t
        org-bullets-bullet-list '(" ") ;; no bullets, needs org-bullets package
        org-ellipsis (if is-windows "..." "")
        org-hide-emphasis-markers t    ;; show actually italicized text instead of /italicized text/
        org-agenda-block-separator ""
        org-fontify-whole-heading-line t
        org-fontify-done-headline t
        org-fontify-quote-and-verse-blocks t
        org-default-notes-file "/home/compro/Dropbox/programs/notes/notes.org"
        org-todo-keywords '((sequence "TODO(t)" "inPROGRESS(i)" "|" "DONE(d)" "CANCELED(c)"))

        org-capture-templates
        '(("t" "Todo" entry (file+headline "~/org/todo.org" "Tasks")
           "** TODO %?\n  %i\n  %a")
          ("l" "Link" entry (file+headline "~/notes.org" "Links")
           "** %T %^L \n%?"))

        org-todo-keyword-faces
        '(("DONE" . (:inherit org-done :strike-through t))
          ("TODO" . (:inherit org-warning :inverse-video t))
          ("CANCELED" . (:inherit org-verbatim
                                  :box-around-text t
                                  :strike-through t))
          ("inPROGRESS" . (:foreground "orange" :inverse-video t)))))

To export to pdf the following packages need to be installed:

Arch Linux

sudo pacman -Sy texlive-core texlive-latexextra texlive-fontsextra --noconfirm

Image rotation functionality

Usage:

#+ATTR_ORG: :width 80 :rotation 90
[[file:~/Pictures/img.png]]

Note:

org-display-inline-image and org--create-inline-image have been redefined

(with-eval-after-load 'org
  ;; Function redefinition
  (defun org-display-inline-images (&optional include-linked refresh beg end)
    "Display inline images.

An inline image is a link which follows either of these
conventions:

  1. Its path is a file with an extension matching return value
     from `image-file-name-regexp' and it has no contents.

  2. Its description consists in a single link of the previous
     type.  In this case, that link must be a well-formed plain
     or angle link, i.e., it must have an explicit \"file\" type.

Equip each image with the key-map `image-map'.

When optional argument INCLUDE-LINKED is non-nil, also links with
a text description part will be inlined.  This can be nice for
a quick look at those images, but it does not reflect what
exported files will look like.

When optional argument REFRESH is non-nil, refresh existing
images between BEG and END.  This will create new image displays
only if necessary.

BEG and END define the considered part.  They default to the
buffer boundaries with possible narrowing."
    (interactive "P")
    (when (display-graphic-p)
      (unless refresh
        (org-remove-inline-images)
        (when (fboundp 'clear-image-cache) (clear-image-cache)))
      (let ((end (or end (point-max))))
        (org-with-point-at (or beg (point-min))
          (let* ((case-fold-search t)
                 (file-extension-re (image-file-name-regexp))
                 (link-abbrevs (mapcar #'car
                                       (append org-link-abbrev-alist-local
                                               org-link-abbrev-alist)))
                 ;; Check absolute, relative file names and explicit
                 ;; "file:" links.  Also check link abbreviations since
                 ;; some might expand to "file" links.
                 (file-types-re
                  (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?file:\\)"
                          (if (not link-abbrevs) ""
                            (concat "\\|" (regexp-opt link-abbrevs))))))
            (while (re-search-forward file-types-re end t)
              (let* ((link (org-element-lineage
                            (save-match-data (org-element-context))
                            '(link) t))
                     (linktype (org-element-property :type link))
                     (inner-start (match-beginning 1))
                     (path
                      (cond
                       ;; No link at point; no inline image.
                       ((not link) nil)
                       ;; File link without a description.  Also handle
                       ;; INCLUDE-LINKED here since it should have
                       ;; precedence over the next case.  I.e., if link
                       ;; contains filenames in both the path and the
                       ;; description, prioritize the path only when
                       ;; INCLUDE-LINKED is non-nil.
                       ((or (not (org-element-property :contents-begin link))
                            include-linked)
                        (and (or (equal "file" linktype)
                                 (equal "attachment" linktype))
                             (org-element-property :path link)))
                       ;; Link with a description.  Check if description
                       ;; is a filename.  Even if Org doesn't have syntax
                       ;; for those -- clickable image -- constructs, fake
                       ;; them, as in `org-export-insert-image-links'.
                       ((not inner-start) nil)
                       (t
                        (org-with-point-at inner-start
                          (and (looking-at
                                (if (char-equal ?< (char-after inner-start))
                                    org-link-angle-re
                                  org-link-plain-re))
                               ;; File name must fill the whole
                               ;; description.
                               (= (org-element-property :contents-end link)
                                  (match-end 0))
                               (match-string 2)))))))
                (when (and path (string-match-p file-extension-re path))
                  (let ((file (if (equal "attachment" linktype)
                                  (progn
                                    (require 'org-attach)
                                    (ignore-errors (org-attach-expand path)))
                                (expand-file-name path))))
                    (when (and file (file-exists-p file))
                      (let ((width (org-display-inline-image--width link))
                            (rotation (org-display-inline-image--rotation link))
                            (old (get-char-property-and-overlay
                                  (org-element-property :begin link)
                                  'org-image-overlay)))
                        (if (and (car-safe old) refresh)
                            (image-refresh (overlay-get (cdr old) 'display))
                          (let ((image (org--create-inline-image file width rotation)))
                            (when image
                              (let ((ov (make-overlay
                                         (org-element-property :begin link)
                                         (progn
                                           (goto-char
                                            (org-element-property :end link))
                                           (skip-chars-backward " \t")
                                           (point)))))
                                (overlay-put ov 'display image)
                                (overlay-put ov 'face 'default)
                                (overlay-put ov 'org-image-overlay t)
                                (overlay-put
                                 ov 'modification-hooks
                                 (list 'org-display-inline-remove-overlay))
                                (when (boundp 'image-map)
                                  (overlay-put ov 'keymap image-map))
                                (push ov org-inline-image-overlays))))))))))))))))

  ;; Function redefinition
  (defun org--create-inline-image (file width rotation)
    "Create image located at FILE, or return nil.
 WIDTH is the width of the image.  The image may not be created
 according to the value of `org-display-remote-inline-images'."
    (let* ((remote? (file-remote-p file))
           (file-or-data
            (pcase org-display-remote-inline-images
              ((guard (not remote?)) file)
              (`download (with-temp-buffer
                           (set-buffer-multibyte nil)
                           (insert-file-contents-literally file)
                           (buffer-string)))
              (`cache (let ((revert-without-query '(".")))
                        (with-current-buffer (find-file-noselect file)
                          (buffer-string))))
              (`skip nil)
              (other
               (message "Invalid value of `org-display-remote-inline-images': %S"
                        other)
               nil))))
      (when file-or-data
        (create-image file-or-data
                      (and (image-type-available-p 'imagemagick)
                           width
                           'imagemagick)
                      remote?
                      :width width
                      :rotation rotation))))

  ;; New function
  (defun org-display-inline-image--rotation (link)
    "Determine the display rotation of the image LINK, in degrees."
    ;; Apply `org-image-actual-width' specifications.
    (let* ((case-fold-search t)
           (par (org-element-lineage link '(paragraph)))
           (attr-re "^[ \t]*#\\+attr_.*?: +.*?:rotation +\\(\\S-+\\)")
           (par-end (org-element-property :post-affiliated par))
           ;; Try to find an attribute providing a :rot.
           (attr-rot
            (when (and par (org-with-point-at
                               (org-element-property :begin par)
                             (re-search-forward attr-re par-end t)))
              (match-string 1))))
      (when attr-rot (string-to-number attr-rot)))))

Rust

(use-package rust-mode :ensure t)

(use-package cargo :ensure t
  :hook (rust-mode . cargo-minor-mode))

Web mode

web-mode is a package that provides integration for web related major modes together in the same mode.

;; (use-package company-web :ensure t :after mhtml-mode)

;; (use-package ac-html-csswatcher :ensure t :after mhtml-mode)

(use-package mhtml-mode
  :when (>= emacs-major-version 26)
  :mode ("\\.vue\\'" "\\.html\\'" "\\.jsx")
  :hook (mhtml-mode-hook . sgml-electric-tag-pair-mode)
  :config
  (setq mhtml-tag-relative-indent nil)
  ;; (require 'company)                                   ; load company mode
  ;; (require 'company-web-html)                          ; load company mode html backend
  ;; ;; and/or
  ;; (require 'company-web-jade)                          ; load company mode jade backend
  ;; (require 'company-web-slim)                          ; load company mode slim backend
  ;; (require 'ac-html-csswatcher)
  ;; (company-web-csswatcher-setup)
  ;; (define-key mhtml-mode-map (kbd "C-'") 'company-web-html)
  ;; (add-hook 'mhtml-mode-hook (lambda ()
  ;;                            (set (make-local-variable 'company-backends) '(company-web-html company-files))
  ;;                            (company-mode t)))
  )

(use-package web-mode :ensure t)

Elf mode

Elf is a binary format commonly used on Linux systems.

(use-package elf-mode :ensure t)

Cmake mode

Cmake is a build system for C++ development.

(use-package cmake-mode :ensure t)

PlantUML mode

Mode for plantuml files

(use-package plantuml-mode :ensure t
  :when (locate-file "plantuml.jar" '("~/Downloads"))
  :config
  (setq plantuml-jar-path "~/Downloads/plantuml.jar"))

Typescript mode

(use-package typescript-mode :ensure t)

Treemacs - Sidebar folder view

(use-package treemacs :ensure t
  :bind (:map treemacs-mode-map
         ([mouse-1] . treemacs-single-click-expand-action))
  :config
  (treemacs-resize-icons 17)
  (setq treemacs-read-string-input 'from-minibuffer))

Python

(defun mapc-buffers (fn)
  (mapc (lambda (buffer)
          (with-current-buffer buffer
            (funcall fn)))
        (buffer-list)))

(defun comint-write-history-on-exit ()
  "Save comint history

References:
1. https://github.com/manzyuk/dotfiles/blob/130f86385f645f0a3a7ee6b31a479c6de2c5ce82/.emacs.d/init.el#L183C1-L188C62
2. https://emacs.stackexchange.com/questions/9720/savehist-the-comint-input-ring"
  (message "In write")
  (when comint-input-ring-file-name
    (unless (file-exists-p (file-name-directory comint-input-ring-file-name))
      (make-directory (file-name-directory comint-input-ring-file-name) t))
    (message (concat "Writing " comint-input-ring-file-name))
    (comint-write-input-ring)))

(add-hook 'kill-buffer-hook 'comint-write-input-ring)
(add-hook 'kill-emacs-hook (lambda () (mapc-buffers 'comint-write-input-ring)))

(use-package python
  :bind (:map
         python-mode-map
         ("TAB" . python-indent-shift-right)
         ("S-TAB" . python-indent-shift-left)
         ("<backtab>" . python-indent-shift-left)
         ("S-<iso-lefttab>" . python-indent-shift-left)

         :map
         python-ts-mode-map
         ("TAB" . python-indent-shift-right)
         ("S-TAB" . python-indent-shift-left)
         ("<backtab>" . python-indent-shift-left)
         ("S-<iso-lefttab>" . python-indent-shift-left))
  :hook (((python-mode python-ts-mode)
          . (lambda ()
              (setq-local fill-column 85
                          forward-sexp-function nil)))
         (inferior-python-mode
          .
          (lambda ()
            (setq-local
             comint-input-ring-file-name
             (concat
              cache-d
              (file-name-as-directory "python-history")
              (s-replace
               "/" "!"
               (or
                (pet-project-root)
                (buffer-file-name)
                (s-replace "*" "+" (buffer-name))))))
            (message (concat "Set " comint-input-ring-file-name))
            (when (file-exists-p comint-input-ring-file-name)
              (message (concat "Reading " comint-input-ring-file-name))
              (comint-read-input-ring)))))
  :config
  (setq python-indent-guess-indent-offset-verbose nil
        python-shell-dedicated 'project))

ruff integration using flymake

(use-package flymake-ruff :ensure t
  :hook ((python-mode-hook . flymake-mode)
         (python-mode-hook . flymake-ruff-load)))

pyright integration using eglot

(use-package eglot
  :when (>= emacs-major-version 29)
  :config
  (defun compro/python-lsp-setup-for-pyright (&rest r)
    (when-let* ((is-python (seq-contains-p '(python-mode python-ts-mode) major-mode))
                (root (expand-file-name (project-root (project-current))))
                (pyright-exe (executable-find "pyright"))
                (pyrightconfig.json (expand-file-name "pyrightconfig.json" root))
                (config-not-exists (not (f-exists-p pyrightconfig.json)))
                (config-data "")
                (break-apart
                 (lambda (cmd)
                   (when-let ((virtualenv-root-1 (string-split (shell-command-to-string cmd) "\n"))
                              (virtualenv-root-exists (= (length virtualenv-root-1) 2))
                              (virtualenv-root (car virtualenv-root-1))
                              (part1 (file-name-parent-directory virtualenv-root))
                              (part2 (car (last (file-name-split virtualenv-root)))))
                     `(,part1 . ,part2)))))
      (progn
        (if-let* ((poetry-exe (executable-find "poetry"))
                  (parts (funcall break-apart "poetry env info -p")))
            (setq config-data (json-encode (list :venvPath (car parts) :venv (cdr parts))))
          (if-let* ((pipenv-exe (executable-find "pipenv"))
                    (parts (funcall break-apart "pipenv --venv")))
              (setq config-data (json-encode (list :venvPath (car parts) :venv (cdr parts))))
            (message "compro/python-lsp-setup-for-pyright: no pipenv / poetry virtualenv found")))
        (when (not (string= config-data ""))
          (with-temp-file pyrightconfig.json (insert config-data))
          (message (concat "compro/python-lsp-setup-for-pyright: created " pyrightconfig.json))))))

  (advice-add 'eglot :before 'compro/python-lsp-setup-for-pyright))

virtualenv integration

Install dasel for pet package integration. Its available in AUR.

(use-package pet :ensure t
  :hook ((python-mode . compro/set-python-variables)
         (python-ts-mode . compro/set-python-variables))
  :init
  (defun compro/get-exe (root name)
    (when-let* ((location (concat root "/bin/" name))
                (exists (file-exists-p location)))
      location))
  (defun compro/set-python-variables ()
    (let* ((env-root (or (pet-virtualenv-root) "/usr"))
           (default-directory (or (when env-root (pet-project-root)) default-directory))
           (ipython3 (compro/get-exe env-root "ipython3"))
           (python (or
                    (compro/get-exe env-root "python3")
                    (compro/get-exe env-root "python2")
                    (compro/get-exe env-root "python")))
           (dj-root (locate-dominating-file default-directory "manage.py"))
           (manage.py (when dj-root (expand-file-name "manage.py" dj-root)))
           (default-directory (if dj-root (pet-project-root) default-directory))
           (dj-shell-plus (when dj-root (= 0 (call-process python nil nil nil manage.py "help" "shell_plus")))))
      (when manage.py
        (setq-local
         python-shell-interpreter-args
         (concat
          " "
          manage.py
          " "
          (cond
           ((and ipython3 dj-shell-plus)
            "shell_plus --ipython -- -i --simple-prompt --classic")
           (ipython3
            "shell --command \"from IPython import start_ipython; start_ipython(argv=[\'-i\', \'--simple-prompt\',\'--classic\'])\"")
           (dj-shell-plus
            "shell_plus --plain -- -i")
           (t
            "shell -i python"))))
        (setq ipython3 nil))
      (cond
       (ipython3 (setq-local
                  py-use-local-default t
                  py-shell-local-path ipython3
                  python-shell-interpreter ipython3
                  python-shell-interpreter-args "-i --simple-prompt --classic"))
       (python (setq-local
                py-use-local-default t
                py-shell-local-path python
                python-shell-interpreter python)))

      (setq-local python-shell-virtualenv-root env-root
                  lsp-pyright-venv-path env-root
                  lsp-pyright-python-executable-cmd python
                  dap-python-executable python
                  python-pytest-executable (pet-executable-find "pytest")
                  exec-path (append `(,(concat env-root "/bin")) exec-path)))

    ;; (when-let ((black-executable (pet-executable-find "black")))
    ;;   (setq-local python-black-command black-executable)
    ;;   (python-black-on-save-mode 1))

    ;; (when-let ((isort-executable (pet-executable-find "isort")))
    ;;   (setq-local python-isort-command isort-executable)
    ;;   (python-isort-on-save-mode 1))
    ))

Vterm - A better terminal emulator

(use-package vterm :ensure t :when is-linux
  :init
  (defun vterm-directory-sync ()
    "Synchronize current working directory."
    (interactive)
    (when vterm--process
      (let* ((pid (process-id vterm--process))
             (dir (file-truename (format "/proc/%d/cwd/" pid))))
        (setq default-directory dir))))
  :config
  (setq vterm-kill-buffer-on-exit t
        vterm-buffer-name-string "*vterm-%s*"
        vterm-always-compile-module t))

Tree-sitter

References:

  1. https://robbmann.io/posts/emacs-treesit-auto/
  2. https://github.com/renzmann/treesit-auto
(use-package treesit-auto :ensure t
  :when (>= emacs-major-version 29)
  :config
  (setq treesit-auto-install 'prompt)
  (global-treesit-auto-mode))

Eshell syntax highlighting

(use-package eshell-syntax-highlighting :ensure t :after esh-mode
  :config (eshell-syntax-highlighting-global-mode +1))

embrace.el - Like evil-surround

(use-package embrace :ensure t
  :bind
  ("C-," . embrace-commander))

Quelpa - Install from source

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

Ligatures

(use-package ligature :ensure nil
  :unless (or (< emacs-major-version 27) is-windows)
  :init
  (require 'quelpa)
  (when (not (quelpa--package-installed-p 'ligature))
    (quelpa
     '(ligature
       :fetcher url
       :url "https://raw.githubusercontent.com/mickeynp/ligature.el/master/ligature.el")))
  :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 Code ligatures in programming modes
  (ligature-set-ligatures
   'prog-mode
   '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
     ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
     "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
     "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
     "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
     "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
     "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
     "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
     ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
     "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
     "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
     "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
     "\\\\" "://"))
  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode t))

Diff in buffers

(use-package diff-hl :ensure t

  :hook (;; Sync with git (specifically magit) operations
         (magit-post-refresh-hook . diff-hl-magit-post-refresh)
         (magit-pre-refresh-hook . diff-hl-magit-pre-refresh)

         ;; Enable in local dired mode
         (dired-mode-hook . diff-hl-dired-mode-unless-remote))

  :config
  ;; Disable diff-hl on remote files to prevent slowness
  (setq diff-hl-disable-on-remote t)

  (global-diff-hl-mode t)
  (diff-hl-margin-mode t)
  (diff-hl-flydiff-mode t))

Whole line or region

Emacs has bad default behaviour when there is no region selected for keybindings like C-w, M-;, etc.

(use-package whole-line-or-region :ensure t
  :config (whole-line-or-region-global-mode +1))

cascading-dir-locals.el

Provides a global minor mode that changes how Emacs handles the lookup of applicable dir-locals files (.dir-locals.el): instead of starting at the directory of the visited file and moving up the directory tree only until a first dir-locals file is found, collect and apply all (!) dir-locals files found from the current directory up to the root one.

(use-package cascading-dir-locals :ensure t
  :config
  (cascading-dir-locals-mode 1))

Just mode

Alternative to GNU Make.

(use-package just-mode :ensure t)

Numpy doc generator

(use-package numpydoc :ensure t
  :bind (:map python-mode-map
              ("C-c C-n" . numpydoc-generate)))

Balancing chemical equations

https://github.com/sergiruiztrepat/chembalance

(use-package chembalance :ensure t)

Ping

(use-package eping :ensure t)

Golang

(use-package go-mode :ensure t)

The following packages are good to have:

  • go get github.com/mdempsky/gocode (For completions in Go code)
  • go get -u github.com/traefik/yaegi/cmd/yaegi (Good REPL)
  • go get github.com/motemen/gore/cmd/gore (REPL compiles everytime)

Filetree

Use M-x filetree-show-* commands to get into filetree view. Watch video for more info.

(use-package filetree :ensure t)

Flycheck in Flymake

(use-package flymake-flycheck :ensure t)

Bash completion

(use-package bash-completion :ensure t
  :config
  (bash-completion-setup))

Format buffers using apheleia

For Python formatting first install the packages using pip:

pip3 install usort black
(use-package apheleia :ensure t
  :config
  ;; (setf (alist-get 'isort apheleia-formatters)
  ;;     '("usort" "format" "-"))
  (defun compro/black ()
    (if-let* ((root (pet-virtualenv-root))
              (executable (concat root "/bin/black"))
              (exists (file-exists-p executable)))
        `(,executable "-")
      "cat"))
  (defun compro/djhtml ()
         (if-let* ((root (pet-virtualenv-root))
                   (executable (concat root "/bin/djhtml"))
                   (exists (file-exists-p executable)))
             `(,executable "-")
           "cat"))
  (setf (alist-get 'python-mode apheleia-mode-alist) '(black-custom)
        (alist-get 'black-custom apheleia-formatters) '((compro/black))
        (alist-get 'djhtml apheleia-formatters) '((compro/djhtml))
        (alist-get 'rustfmt apheleia-formatters) '("rustfmt" "--quiet" "--emit" "stdout" "--edition" "2021"))
  (apheleia-global-mode +1))

Narrow reindent

Dedent after narrowing.

(use-package narrow-reindent :ensure t
  :hook (find-file-hook . narrow-reindent-mode))

Daemons

Show all systemd services.

(use-package daemons :ensure t)

All the icons in completion candidates

(use-package all-the-icons-completion :ensure t
  :when (display-graphic-p)
  :hook (marginalia-mode-hook . all-the-icons-completion-marginalia-setup)
  :config
  (all-the-icons-completion-mode 1))

Flatten imenu

(use-package flimenu :ensure t
  :config
  (flimenu-global-mode 1))

Fix pagers in comint-mode

Disabled for now because M-x run-python doesn’t seem to handle newlines when this is switched on.

(use-package coterm :ensure t :disabled t
  :config
  (coterm-mode 1))

Mode for editing git files

This helps in editing files like .gitignore and .gitattributes.

(use-package git-modes :ensure t)

Make backups asynchronous on save

(use-package async-backup :ensure t
  :hook (after-save-hook . async-backup))

Edit subtitles

(use-package subed :ensure t
  ;; :init
  ;; ;; Disable automatic movement of point by default
  ;; (add-hook 'subed-mode-hook 'subed-disable-sync-point-to-player)
  ;; ;; Remember cursor position between sessions
  ;; (add-hook 'subed-mode-hook 'save-place-local-mode)
  ;; ;; Break lines automatically while typing
  ;; (add-hook 'subed-mode-hook 'turn-on-auto-fill)
  ;; ;; Break lines at 40 characters
  ;; (add-hook 'subed-mode-hook (lambda () (setq-local fill-column 40)))
  )

Move buffers or Swap windows

(use-package buffer-move :ensure t)

Redacted mode

To make buffers unreadable like rot13 but can’t be decrypted.

(use-package redacted :ensure t
  :init
  (add-hook 'redacted-mode-hook (lambda () (read-only-mode (if redacted-mode 1 -1)))))

Cycle at point

https://gitlab.com/ideasman42/emacs-cycle-at-point

(use-package cycle-at-point :ensure t
  :bind (("M-p" . cycle-at-point)
         ("M-n" . (lambda ()
                    (interactive)
                    (let ((current-prefix-arg '(-1)))
                      (call-interactively 'cycle-at-point))))))

Comint mime

Display images in comint and related buffers:

(use-package comint-mime :ensure t :when (display-graphic-p)
  :hook
  ((shell-mode-hook . comint-mime-setup)
   (inferior-python-mode-hook . comint-mime-setup)))

Flymake collection

(use-package flymake-collection :ensure t
  :config
  (flymake-collection-hook-setup)
  (push
   '(python-mode
     flymake-collection-mypy                      ; Always added to diagnostic functions.
     (flymake-collection-pycodestyle :disabled t) ; Never added.
     (flymake-collection-pylint                   ; Added when predicate is true.
      :predicate (lambda ()
                   (executable-find "pylint"))))
   flymake-collection-config))

Ruby

(use-package ruby-electric :ensure t
  :hook (ruby-mode-hook . ruby-electric-mode))

(use-package rbenv :ensure t
  :config
  (global-rbenv-mode)
  (rbenv-use-corresponding))

(use-package inf-ruby :ensure t
  :bind (:map inf-ruby-minor-mode-map
              ("C-c C-c" . ruby-send-buffer-and-go)))

(use-package ruby-test-mode :ensure t
  :hook (ruby-mode-hook . ruby-test-mode))

(use-package rinari :ensure t
  :config
  (global-rinari-mode))

(use-package yari :ensure t
  :hook (ruby-mode-hook . ri-bind-key)
  :init
  (defun ri-bind-key ()
    (local-set-key [f1] 'yari)))

Fancy compilation buffers

(use-package fancy-compilation :ensure t :after compile
  :config
  (fancy-compilation-mode))

repeat-mode help

(use-package repeat-help :ensure t
  :hook (repeat-mode-hook . repeat-help-mode))

yaml-pro-mode

(use-package yaml-pro :ensure t
  :hook (yaml-mode-hook . yaml-pro-mode))

Clean kill ring

(use-package clean-kill-ring :ensure t
  :config
  (setq clean-kill-ring-prevent-duplicates t)
  (clean-kill-ring-mode 1))

Eldoc box to show documentation in a popup

(use-package eldoc-box :ensure t
  :hook (prog-mode-hook . eldoc-box-hover-at-point-mode))

Editorconfig

References:

  1. https://editorconfig.org/
  2. https://github.com/editorconfig/specification
  3. https://github.com/editorconfig/editorconfig-emacs
(use-package editorconfig :ensure t
  :config
  (add-to-list 'editorconfig-indentation-alist
               '(mhtml-mode js-indent-level
                            css-indent-offset
                            sgml-basic-offset))
  (editorconfig-mode 1))

Yaml mode

(use-package yaml-mode :ensure t)

After init jobs

(set-face-attribute 'mode-line nil :box nil)
(set-face-attribute 'mode-line-inactive nil :box nil)
(when (> emacs-major-version 27)
  (set-face-attribute 'tab-bar-tab nil :box nil))
(minions-mode 1)
(setq debug-on-error  nil
      init-file-debug nil)
(compro/redownload-empty-pkgs)

;; Remove text property from text in kill-ring
(defun unpropertize-kill-ring ()
  (setq kill-ring (mapcar 'substring-no-properties kill-ring)))
(add-hook 'kill-emacs-hook 'unpropertize-kill-ring)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages