This repository

This repository (abougouffa/dotfiles) contains my configuration files for Zsh, MinEmacs, Vim, Alacritty and other Linux related stuff.

If you want to reuse some of these configurations, you will need to modify some directories and add some user specific information (usernames, passwords…)

Notice

This is the main configuration file, it contains the literal personal configuration for MinEmacs, and I use it to generate some other Linux configuration files (define aliases, environment variables, user tools, Git configuration…).

For my old deprecated Doom Emacs configuration, see dotfiles/dot_doom.d.

Intro

I’ve been using Linux exclusively since 2010, GNU Emacs was always installed on my machine, but I didn’t discover the real Emacs until 2020. In the beginning, I started my Vanilla Emacs configuration from scratch, but after a while, it becomes a mess. As a new Emacs user, I didn’t understand the in the beginning how to optimize my configuration and how to do things correctly. I discovered then Spacemacs, which made things much easier, but it was a little slow, and just after, I found the awesome Doom Emacs, which helped me progress and learn more about Emacs and Lisp, but at some point, I faced multiple problems with Doom, so I started my own configuration framework, MinEmacs.

MinEmacs is intended to be a minimal configuration framework. In the beginning, I planned to use only necessary packages. However, it is starting to grow to respond to my daily needs.

#!/bin/env bash

# NOTE: This file is generated from "config-literate.org".
# This shell script has been generated from the litterate Org configuration.
# It helps installing required tools (for Arch/Manjaro Linux) and tweak some
# system settings

MinEmacs config files

Early configuration (early-config.el)

;;; early-config.el -*- coding: utf-8-unix; lexical-binding: t; -*-

;; NOTE: This file is generated from "config-literate.org".

;; MinEmacs specific stuff
(unless minemacs-verbose
  (setq minemacs-msg-level 2)) ; print info messages

;; Disable `dashboard'
(setq +dashboard-disable t)

;; Force loading lazy packages immediately, not in idle time
;; (setq minemacs-not-lazy t)

;; Enable full screen at startup
;; (if-let ((fullscreen (assq 'fullscreen default-frame-alist)))
;;     (setcdr fullscreen 'fullboth)
;;   (push '(fullscreen . fullboth) default-frame-alist))

;; Setup a `debug-on-message' to catch a wired message!
;; (setq debug-on-message "\\(?:error in process filter: \\(?:\\(?:mu4e-warn: \\)?\\[mu4e] No message at point\\)\\)")
;; (setq debug-on-message "Package cl is deprecated")

;; (setenv "MINEMACS_IGNORE_VERSION_CHECK" "1")

Modules (modules.el)

;;; modules.el -*- coding: utf-8-unix; lexical-binding: t; -*-

;; NOTE: This file is generated from "config-literate.org".

;; This file can be used to override `minemacs-modules'
;; and `minemacs-core-modules'

(setq
 ;; MinEmacs core modules
 minemacs-core-modules
 '(me-splash        ; Simple splash screen (inspired by emacs-splash)
   me-keybindings   ; general.el, which-key, hydra, ...
   me-evil          ; evil, evil-collection, evil-mc, ...
   me-core-ui       ; Theme and modeline
   me-completion)   ; vertico, marginalia, corfu, cape, consult, ...
 ;; MinEmacs modules
 minemacs-modules
 '(me-ui            ; focus, writeroom-mode, emojify, ...
   me-editor        ; yasnippet, unicode-fonts, ligature, ...
   me-extra         ; better-jumper, ...
   me-undo          ; undo-fu-session, vundo, ...
   me-multi-cursors ; iedit, evil-mc, ...
   me-vc            ; magit, forge, core-review, diff-hl, ...
   me-project       ; project, consult-project-extra, ...
   me-prog          ; tree-sitter, eglot, editorconfig, ...
   me-checkers      ; flymake, flymake-easy, gdb-mi, ...
   me-lsp           ; lsp-mode, dap-mode, consult-lsp, ...
   me-debug         ; realgud, disaster, ...
   me-lisp          ; parinfer-rust, macrostep, geiser, elisp, ...
   me-data          ; csv, yaml, toml, json, ...
   me-org           ; org, org-modern, ...
   me-notes         ; org-roam, ...
   me-email         ; mu4e, mu4e-alert, org-msg, ...
   me-lifestyle     ; awqat, ...
   me-docs          ; pdf-tools, nov, ...
   me-calendar      ; calfw, calfw-org, calfw-ical, ...
   me-latex         ; tex, auctex, reftex, ...
   me-natural-langs ; spell-fu, eglot-ltex, ...
   me-files         ; dirvish, dired, vlf, ...
   me-tools         ; vterm, tldr, docker, systemd, ...
   me-biblio        ; org-cite, citar, ...
   me-daemon        ; Emacs daemon tweaks
   me-tty           ; Emacs from terminal
   me-rss           ; elfeed, ...
   me-robot         ; Robotics stuff (ros, robot-mode, ...)
   me-embedded      ; Embedded systems (arduino, openocd, bitbake, ...)
   me-eaf           ; EAF apps (browser, jupyter, file-sender, ...)
   me-math          ; maxima, ess, ...
   me-modeling      ; OpenSCAD, ...
   me-workspaces    ; tabspaces, tab-bar, ...
   me-window        ; frame & window tweaks, ...
   me-media         ; empv, ...
   me-fun           ; xkcd, speed-type, ...
   me-binary)      ; hexl, decompile (using objdump)...
 ;; MinEmacs disabled packages
 minemacs-disabled-packages
 (append
  '(dashboard)))

User configuration (config.el)

;;; config.el -*- coding: utf-8-unix; lexical-binding: t; -*-

;; NOTE: This file is generated from "config-literate.org".

General Emacs settings

User information

;; Personal info
(setq user-full-name "Abdelhak Bougouffa"
      user-mail-address (concat "abougouffa" "@" "fedora" "project" "." "org"))

Crypto stuff

(setq-default
 ;; Encrypt files to my self by default
 epa-file-encrypt-to '("F808A020A3E1AC37"))

Bidirectional settings

This combo should speedup opening files:

(setq-default
 ;; Better support for files with long lines
 bidi-paragraph-direction 'left-to-right
 ;; Speeds redisplay, may break paranthesis rendering for bidirectional files
 bidi-inhibit-bpa t)

Directories

(defvar +biblio-notes-path (expand-file-name "~/PhD/bibliography/notes/"))
(defvar +biblio-styles-path (expand-file-name "~/Zotero/styles/"))
(defvar +biblio-storage-path (expand-file-name "~/Zotero/storage/"))
(defvar +biblio-libraries-path (expand-file-name "~/Zotero/library.bib"))

(setq org-directory "~/Dropbox/Org/"
      source-directory "~/Softwares/aur/emacs-git/src/emacs-git/")

Misc

(setq +binary-hexl-enable t
      +binary-objdump-enable t
      browse-url-chromium-program (or (executable-find "brave")
                                      (executable-find "chromium")
                                      (executable-find "chromium-browser"))
      browse-url-chrome-program browse-url-chromium-program)

Awqat

(+lazy-when! (featurep 'me-lifestyle)
  ;; Calendar settings (from `solar')
  (setq calendar-latitude 48.86
        calendar-longitude 2.35
        calendar-location-name "Paris, FR"
        calendar-time-display-form '(24-hours ":" minutes))

  (awqat-display-prayer-time-mode 1)
  (awqat-set-preset-french-muslims))

Projects

(+lazy!
 (setq +project-scan-dir-paths
       '("~/PhD/papers/"
         "~/PhD/workspace/"
         "~/PhD/workspace-no/"
         "~/PhD/workspace-no/ez-wheel/swd-starter-kit-repo/"
         "~/Projects/foss/packages/"
         "~/Projects/foss/repos/"))

 (+shutup!
  (+project-scan-for-projects)))

Package configuration

User interface

Theme & font

(setq
 minemacs-theme 'doom-one-light ; 'apropospriate-light
 minemacs-fonts
 '(:font-family "Iosevka Fixed Curly Slab"
   :font-size 13
   :variable-pitch-font-family "IBM Plex Serif" ; "Lato"
   :variable-pitch-font-size 13
   :unicode-font-family "JuliaMono")) ; Default font for Unicode chars

Writing mode

(with-eval-after-load 'me-writing-mode
  (setq +writing-mixed-pitch-enable nil
        +writing-text-scale 2.0))

Completion & IDE

LSP

Register LSP over Tramp for Python using pyls. The pyls LSP server should be installed on the distant machine for this to work.

(with-eval-after-load 'lsp-mode
  (lsp-register-client
   (make-lsp-client
    :new-connection (lsp-tramp-connection "pyls")
    :major-modes '(python-mode python-ts-mode)
    :remote? t
    :server-id 'pyls-remote)))

Natural languages

Offline dictionaries

Needs sdcv to be installed, needs also StarDict dictionaries, you can download some from here and here for french.

Spell-fu

(with-eval-after-load 'spell-fu
  (+spell-fu-register-dictionaries! "en" "fr"))

Applications

News feed (elfeed)

Set RSS news feeds

(with-eval-after-load 'elfeed
  (setq elfeed-feeds
        '(("https://arxiv.org/rss/cs.RO" robotics academic)
          ("https://interstices.info/feed" science academic)
          ("https://spectrum.ieee.org/rss/robotics/fulltext" robotics academic)
          ("https://spectrum.ieee.org/rss/aerospace/fulltext" academic aerospace)
          ("https://spectrum.ieee.org/rss/computing/fulltext" academic computing)
          ("https://spectrum.ieee.org/rss/blog/automaton/fulltext" academic automation robotics)
          ("https://www.technologyreview.com/feed" tech science)
          ("https://itsfoss.com/feed" linux foss)
          ("https://lwn.net/headlines/rss" linux foss)
          ("https://linuxhandbook.com/feed" linux foss)
          ("https://www.omgubuntu.co.uk/feed" linux foss)
          ("https://this-week-in-rust.org/rss.xml" rust prog)
          ("https://planet.emacslife.com/atom.xml" emacs prog foss)
          ("https://developers.redhat.com/blog/feed" linux foss))))

Email (mu4e)

Configuring mu4e as email client needs three parts:

  • Incoming mail configuration IMAP (using mbsync)
  • Outgoing mail configuration SMTP (using smtpmail or msmtp)
  • Email indexer and viewer (via mu and mu4e)

IMAP (mbsync)

You will need to:

  • Install mu and isync (sudo pacman -S mu isync)
  • Set up a proper configuration file for your accounts at ~/.mbsyncrc
  • Run mu init --maildir=~/Maildir --my-address=user@host1 --my-address=user@host2
  • Run mbsync -c ~/.mbsyncrc -a
  • For sending mails from mu4e, add a ~/.authinfo file, file contains a line in this format machine MAIL.DOMAIN.TLD login USER port 587 password PASSWD
  • Encrypt the ~/.authinfo file using GPG gpg -c ~/.authinfo and delete the original unencrypted file.

I use a mbsyncrc file for multi-accounts, with some hacks for Gmail accounts (to rename the [Gmail]/... folders). Here is an explained configuration example.

In the configuration file, there is a parameter named Pass which should be set to the password in plain text. Most of the examples you can find online uses this parameter, but in real life, nobody uses it, it is extremely unsafe to put the password in plain text configuration file. Instead, mbsync configuration file provides the alternative PassCmd parameter, which can be set to an arbitrary shell command which gets the password for you. You can set it for example to call the pass password manager to output the account password, or to bw command (for Bitwarden users). For me, I’m using it with Emacs' ~/.authinfo.gpg, the PassCmd in my configuration uses GPG and awk to decrypt and filter the file content to find the required account’s password. I set PassCmd to something like this:

gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp\.googlemail\.com login username@gmail\.com/ {print $NF}'

Remember the line format in the ~/.authinfo.gpg file:

machine smtp.googlemail.com login username@gmail.com port 587 password PASSWD

This PassCmd command above, decrypts the ~/.authinfo.gpg, passes it to awk to search the line containing "machine smtp.googlemail.com login username@gmail.com" and prints the last field (the last field $NF in the awk command corresponds to the password, as you can see in the line format).

The whole ~/.mbsync file should look like this:

# mbsync config file
# GLOBAL OPTIONS
BufferLimit 50mb             # Global option:   Default buffer size is 10M, too small for modern machines.
Sync All                     # Channels global: Sync everything "Pull Push New ReNew Delete Flags" (default option)
Create Both                  # Channels global: Automatically create missing mailboxes on both sides
Expunge Both                 # Channels global: Delete messages marked for deletion on both sides
CopyArrivalDate yes          # Channels global: Propagate arrival time with the messages

# SECTION (IMAP4 Accounts)
IMAPAccount work             # IMAP Account name
Host mail.host.ccc           # The host to connect to
User user@host.ccc           # Login user name
SSLVersions TLSv1.2 TLSv1.1  # Supported SSL versions
# Extract password from encrypted ~/.authinfo.gpg
# File format: "machine <SERVER> login <LOGIN> port <PORT> password <PASSWORD>"
# This uses sed to extract <PASSWORD> from line matching the account's <SERVER>
PassCmd "gpg2 -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp.domain.tld/ {print $NF}'"
AuthMechs *                  # Authentication mechanisms
SSLType IMAPS                # Protocol (STARTTLS/IMAPS)
CertificateFile /etc/ssl/certs/ca-certificates.crt
# END OF SECTION
# IMPORTANT NOTE: you need to keep the blank line after each section

# SECTION (IMAP Stores)
IMAPStore work-remote        # Remote storage name
Account work                 # Associated account
# END OF SECTION

# SECTION (Maildir Stores)
MaildirStore work-local      # Local storage (create directories with mkdir -p ~/Maildir/<ACCOUNT-NAME>)
Path ~/Maildir/work/         # The local store path
Inbox ~/Maildir/work/Inbox   # Location of the INBOX
SubFolders Verbatim          # Download all sub-folders
# END OF SECTION

# Connections specify links between remote and local folders
# they are specified using patterns, which match remote mail
# folders. Some commonly used patters include:
#
# - "*" to match everything
# - "!DIR" to exclude "DIR"
# - "DIR" to match DIR
#
# SECTION (Channels)
Channel work                 # Channel name
Far :work-remote:            # Connect remote store
Near :work-local:            # to the local one
Patterns "INBOX" "Drafts" "Sent" "Archives/*" "Spam" "Trash"
SyncState *                  # Save state in near side mailbox file ".mbsyncstate"
# END OF SECTION

# =================================================================================

IMAPAccount gmail
Host imap.gmail.com
User user@gmail.com
PassCmd "gpg2 -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp.domain.tld/ {print $NF}'"
AuthMechs LOGIN
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
Path ~/Maildir/gmail/
Inbox ~/Maildir/gmail/Inbox

# For Gmail, I like to make multiple channels, one for each remote directory
# this is a trick to rename remote "[Gmail]/mailbox" to "mailbox"
Channel gmail-inbox
Far :gmail-remote:
Near :gmail-local:
Patterns "INBOX"
SyncState *

Channel gmail-trash
Far :gmail-remote:"[Gmail]/Trash"
Near :gmail-local:"Trash"
SyncState *

Channel gmail-drafts
Far :gmail-remote:"[Gmail]/Drafts"
Near :gmail-local:"Drafts"
SyncState *

Channel gmail-sent
Far :gmail-remote:"[Gmail]/Sent Mail"
Near :gmail-local:"Sent Mail"
SyncState *

Channel gmail-all
Far :gmail-remote:"[Gmail]/All Mail"
Near :gmail-local:"All Mail"
SyncState *

Channel gmail-starred
Far :gmail-remote:"[Gmail]/Starred"
Near :gmail-local:"Starred"
SyncState *

Channel gmail-spam
Far :gmail-remote:"[Gmail]/Spam"
Near :gmail-local:"Spam"
SyncState *

# GROUPS PUT TOGETHER CHANNELS, SO THAT WE CAN INVOKE
# MBSYNC ON A GROUP TO SYNC ALL CHANNELS
#
# FOR INSTANCE: "mbsync gmail" GETS MAIL FROM
# "gmail-inbox", "gmail-sent", and "gmail-trash"
#
# SECTION (Groups)
Group gmail
Channel gmail-inbox
Channel gmail-sent
Channel gmail-trash
Channel gmail-drafts
Channel gmail-all
Channel gmail-starred
Channel gmail-spam
# END OF SECTION

SMTP (msmtp)

I was using the standard smtpmail to send mails; but recently, I’m getting problems when sending mails. I passed a whole day trying to fix mail sending for one of my accounts, at the end of the day, I got a working setup; BUT, sending the first mail always ask me about password! I need to enter the password to be able to send the mail, Emacs asks me then if I want to save it to ~/.authifo.gpg, when I confirm saving it, it got duplicated in the .authinfo.gpg file.

This seems to be a bug; I also found somewhere that smtpmail is buggy, and that msmtp seems to be a good alternative, so now I’m using a msmtp-based setup, and it works like a charm!

For this, we will need an additional configuration file, ~/.msmtprc, I configure it the same way as mbsync, specifying this time SMTP servers instead of IMAP ones. I extract the passwords from ~/.authinfo.gpg using GPG and awk, the same way we did in mbsync’s configuration.

The following is a sample file ~/.msmtprc.

# Set default values for all following accounts.
defaults
auth                    on
tls                     on
tls_starttls            on
tls_trust_file          /etc/ssl/certs/ca-certificates.crt
logfile                 ~/.msmtp.log

# Gmail
account                 gmail
auth                    plain
host                    smtp.googlemail.com
port                    587
from                    username@gmail.com
user                    username
passwordeval            "gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp.googlemail.com login .*@gmail.com/ {print $NF}'"
add_missing_date_header on

## Gmail - aliases
account                 alias-account : gmail
from                    alias@mail.com

account                 other-alias : gmail
from                    other.alias@address.org

# Work
account                 work
auth                    on
host                    smtp.domaine.tld
port                    587
from                    username@domaine.tld
user                    username
passwordeval            "gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp.domaine.tld/ {print $NF}'"
tls_nocertcheck # ignore TLS certificate errors

Mail client and indexer (mu and mu4e)

I configure my email accounts in a private file in private/mu4e-accounts.el, which will be loaded after this common part:

(with-eval-after-load 'mu4e
  ;; Custom files
  (setq mail-personal-alias-file (concat minemacs-config-dir "private/mail-aliases.mailrc")
        mu4e-icalendar-diary-file (concat org-directory "icalendar-diary.org"))

  ;; Add a unified inbox shortcut
  (add-to-list
   'mu4e-bookmarks
   '(:name "Unified inbox" :query "maildir:/.*inbox/" :key ?i) t)

  ;; Add shortcut to view spam messages
  (add-to-list
   'mu4e-bookmarks
   '(:name "Spams" :query "maildir:/.*\\(spam\\|junk\\).*/" :key ?s) t)

  ;; The `+mu4e-extras-ignore-spams-query' function is defined in
  ;; `me-mu4e-extras'.
  (with-eval-after-load 'me-mu4e-extras
    ;; Add shortcut to view yesterday's messages
    (add-to-list
     'mu4e-bookmarks
     `(:name "Yesterday's messages" :query ,(+mu4e-extras-ignore-spams-query "date:1d..today") :key ?y) t))

  ;; Load my accounts
  (+load minemacs-config-dir "private/mu4e-accounts.el")
  (+load minemacs-config-dir "private/mu4e-extra-commands.el"))

Calendar

(with-eval-after-load 'calfw-ical
  (+load minemacs-config-dir "private/calfw-sources.el"))

I like to use an MPD powered EMMS, so when I restart Emacs I do not lose my music.

EMPV

(with-eval-after-load 'empv
  (setq
   ;; Set the radio channels, you can get streams from https://www.radio-browser.info
   empv-radio-channels
   '(("El-Bahdja FM" . "http://webradio.tda.dz:8001/ElBahdja_64K.mp3")
     ("El-Chaabia" . "https://radio-dzair.net/proxy/chaabia?mp=/stream")
     ("Quran Radio" . "http://stream.radiojar.com/0tpy1h0kxtzuv")
     ("Algeria International" . "https://webradio.tda.dz/Internationale_64K.mp3")
     ("JOW Radio" . "https://str0.creacast.com/jowradio")
     ("Europe1" . "http://ais-live.cloud-services.paris:8000/europe1.mp3")
     ("France Iter" . "http://direct.franceinter.fr/live/franceinter-hifi.aac")
     ("France Info" . "http://direct.franceinfo.fr/live/franceinfo-hifi.aac")
     ("France Culture" . "http://icecast.radiofrance.fr/franceculture-hifi.aac")
     ("France Musique" . "http://icecast.radiofrance.fr/francemusique-hifi.aac")
     ("FIP" . "http://icecast.radiofrance.fr/fip-hifi.aac")
     ("Beur FM" . "http://broadcast.infomaniak.ch/beurfm-high.aac")
     ("Skyrock" . "http://icecast.skyrock.net/s/natio_mp3_128k"))
   ;; See https://docs.invidious.io/instances/
   empv-invidious-instance "https://invidious.projectsegfau.lt/api/v1"))

Programming

Tramp

(with-eval-after-load 'tramp
  (setq
   ;; Do not use a separate history file for tramp sessions (buggy!)
   tramp-histfile-override nil
   ;; Use Bash as a default remote shell
   tramp-default-remote-shell "/bin/bash"
   ;; Use bash for encoding and decoding commands on the local host
   tramp-encoding-shell "/bin/bash"))

Robot Operating System (ROS)

(with-eval-after-load 'ros
  (setq ros-workspaces
        (list
         (ros-dump-workspace
          :tramp-prefix "/docker:ros@ros-machine:"
          :workspace "~/ros_ws"
          :extends '("/opt/ros/noetic/"))
         (ros-dump-workspace
          :tramp-prefix "/docker:ros@ros-machine:"
          :workspace "~/ros2_ws"
          :extends '("/opt/ros/foxy/"))
         (ros-dump-workspace
          :tramp-prefix "/ssh:swd_sk@172.16.96.42:"
          :workspace "~/ros_ws"
          :extends '("/opt/ros/noetic/"))
         (ros-dump-workspace
          :tramp-prefix "/ssh:swd_sk@172.16.96.42:"
          :workspace "~/ros2_ws"
          :extends '("/opt/ros/foxy/")))))

Office

Org mode

Org mode tweaks

(with-eval-after-load 'org
  (setq
   ;; Let's put our Org files here
   org-directory "~/Dropbox/Org/"
   ;; Do not ask before tangling
   org-confirm-babel-evaluate nil
   ;; The last level which is still exported as a headline
   org-export-headline-levels 5
   ;; Default file for notes (for org-capture)
   org-default-notes-file (concat org-directory "inbox.org")
   +org-inbox-file (concat org-directory "inbox.org")
   +org-projects-file (concat org-directory "projects.org")
   ;; Custom todo keyword faces
   org-todo-keyword-faces
   '(("IDEA" . (:foreground "goldenrod" :weight bold))
     ("NEXT" . (:foreground "IndianRed1" :weight bold))
     ("STRT" . (:foreground "OrangeRed" :weight bold))
     ("WAIT" . (:foreground "coral" :weight bold))
     ("KILL" . (:foreground "DarkGreen" :weight bold))
     ("PROJ" . (:foreground "LimeGreen" :weight bold))
     ("HOLD" . (:foreground "orange" :weight bold)))
   ;; Custom org-capture templates, see: https://orgmode.org/manual/Capture.html
   org-capture-templates
   `(("t" "Todo" entry (file+headline ,+org-inbox-file "Inbox")
      "* TODO %?\n%i\n%a")
     ("i" "Idea" entry (file+headline ,+org-inbox-file "Ideas")
      "* IDEA %?\n%T\n%i\n%a")
     ("p" "Project note" entry (file ,+org-projects-file)
      "* %?\n%T\n%i\n%a")
     ("n" "Note" entry (file+headline ,+org-inbox-file "Notes")
      "* NOTE %?\n%T\n%i\n%a")))

  (setq org-tag-persistent-alist
        '((:startgroup . nil)
          ("home"      . ?h)
          ("research"  . ?r)
          ("work"      . ?w)
          (:endgroup   . nil)
          (:startgroup . nil)
          ("tool"      . ?o)
          ("dev"       . ?d)
          ("report"    . ?p)
          (:endgroup   . nil)
          (:startgroup . nil)
          ("easy"      . ?e)
          ("medium"    . ?m)
          ("hard"      . ?a)
          (:endgroup   . nil)
          ("urgent"    . ?u)
          ("key"       . ?k)
          ("bonus"     . ?b)
          ("ignore"    . ?i)
          ("noexport"  . ?x)))

  (setq org-tag-faces
        '(("home"     . (:foreground "goldenrod"  :weight bold))
          ("research" . (:foreground "goldenrod"  :weight bold))
          ("work"     . (:foreground "goldenrod"  :weight bold))
          ("tool"     . (:foreground "IndianRed1" :weight bold))
          ("dev"      . (:foreground "IndianRed1" :weight bold))
          ("report"   . (:foreground "IndianRed1" :weight bold))
          ("urgent"   . (:foreground "red"        :weight bold))
          ("key"      . (:foreground "red"        :weight bold))
          ("easy"     . (:foreground "green4"     :weight bold))
          ("medium"   . (:foreground "orange"     :weight bold))
          ("hard"     . (:foreground "red"        :weight bold))
          ("bonus"    . (:foreground "goldenrod"  :weight bold))
          ("ignore"   . (:foreground "Gray"       :weight bold))
          ("noexport" . (:foreground "LimeGreen"  :weight bold))))

  ;; stolen from https://github.com/yohan-pereira/.emacs#babel-config
  ;; (defun +org-confirm-babel-evaluate (lang body)
  ;;   (not (string= lang "scheme"))) ;; Don't ask for scheme

  ;; (setq org-confirm-babel-evaluate #'+org-confirm-babel-evaluate)

  (setq org-agenda-files (list +org-inbox-file
                               +org-projects-file
                               (concat org-directory "gcal-agenda.org"))
        org-agenda-deadline-faces
        '((1.001 . error)
          (1.000 . org-warning)
          (0.500 . org-upcoming-deadline)
          (0.000 . org-upcoming-distant-deadline))
        org-list-demote-modify-bullet
        '(("+"  . "-")
          ("-"  . "+")
          ("*"  . "+")
          ("1." . "a.")))

  ;; Needs to make a src_latex{\textsc{text}}?, with this hack you can write [[latex:textsc][Some text]].
  (+shutup!
   (org-add-link-type
    "latex" nil
    (lambda (path desc format)
      (cond
       ((eq format 'html)
        (format "<span class=\"%s\">%s</span>" path desc))
       ((eq format 'latex)
        (format "{\\%s{%s}}" path desc))))))

  (add-hook
   'org-mode-hook
   (defun +org--time-stamp-setup-h ()
     (setq-local
      time-stamp-active t
      time-stamp-format "%04Y-%02m-%02d"
      time-stamp-start "#\\+lastmod:[ \t]*"
      time-stamp-end "$")))

  (add-hook 'before-save-hook #'time-stamp))

Org + Hugo

(with-eval-after-load 'ox-hugo
  (setq org-hugo-auto-set-lastmod t))

Org + LaTeX

(with-eval-after-load 'ox-latex
  (add-to-list 'org-latex-packages-alist '("svgnames" "xcolor")))

Org-roam

(setq org-roam-directory "~/Dropbox/Org/slip-box/"
      org-roam-db-location (concat org-roam-directory "org-roam.db"))

(with-eval-after-load 'org-roam
  (add-to-list 'recentf-exclude org-roam-directory)

  (advice-add
   #'doom-modeline-buffer-file-name
   :around
   (defun +doom-modeline--org-roam-buffer-file-name-a (orig-fun)
     (if (string-search (expand-file-name org-roam-directory) (or buffer-file-name ""))
         (replace-regexp-in-string
          "\\(?:^\\|.*/\\)\\([0-9]\\{4\\}\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)[0-9]*-"
          (concat (nerd-icons-codicon "nf-cod-note") " (\\1-\\2-\\3) ")
          (subst-char-in-string ?_ ?  buffer-file-name))
       (funcall orig-fun)))))

Bibliography

Org-cite
(with-eval-after-load 'oc
  (setq org-cite-csl-styles-dir +biblio-styles-path
        org-cite-global-bibliography (ensure-list +biblio-libraries-path)))
Citar
(with-eval-after-load 'citar
  (setq citar-library-paths (ensure-list +biblio-storage-path)
        citar-notes-paths (ensure-list +biblio-notes-path)
        citar-bibliography (ensure-list +biblio-libraries-path)))

Text editing

Helper commands

(defun +helper--in-buffer-replace (old new)
  "Replace OLD with NEW in the current buffer."
  (save-excursion
    (goto-char (point-min))
    (let ((case-fold-search nil)
          (matches 0))
      (while (re-search-forward old nil t)
        (replace-match new)
        (cl-incf matches))
      matches)))

(defun +helper-clear-frenchy-ponctuations ()
  "Replace french ponctuations (like unsectable space) by regular ones."
  (interactive)
  (let ((chars
         '(("[\u00a0\u200b]" . "") ;; Non-breaking and zero-width spaces
           ;; Special spaces and quads
           ("[\u2000-\u200A\u202F\u205F\u3000]" . " ")
           ("[‘’‚’]" . "'")
           ("[“”„”«»]" . "\"")))
        (matches 0))
    (dolist (pair chars)
      (cl-incf matches (+helper--in-buffer-replace (car pair) (cdr pair))))
    (message "Replaced %d match%s." matches (if (> matches 1) "es" ""))))

(defun +yank-region-as-paragraph ()
  "Yank region as one paragraph. This removes new line characters between lines."
  (interactive)
  (when (use-region-p)
    (let ((text (buffer-substring-no-properties (region-beginning) (region-end))))
      (with-temp-buffer
        (insert text)
        (goto-char (point-min))
        (let ((case-fold-search nil))
          (while (re-search-forward "\n[^\n]" nil t)
            (replace-region-contents
             (- (point) 2) (- (point) 1)
             (lambda (&optional a b) " ")))
          (kill-new (buffer-string)))))))

System configuration

Mime types

Org mode files

Org mode isn’t recognized as its own mime type by default, but that can easily be changed with the following file. For system-wide changes try /usr/share/mime/packages/org.xml.

<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
  <mime-type type="text/org">
    <comment>Emacs Org-mode File</comment>
    <glob pattern="*.org"/>
    <alias type="text/org"/>
  </mime-type>
</mime-info>

What’s nice is that Papirus now has an icon for text/org. One simply needs to refresh their mime database:

update-mime-database ~/.local/share/mime

Then set Emacs as the default editor:

xdg-mime default emacs-client.desktop text/org

Registering org-protocol://

The recommended method of registering a protocol is by registering a desktop application, which seems reasonable.

[Desktop Entry]
Name=Emacs Org-Protocol
Exec=emacsclient %u
Icon=/home/hacko/.doom.d/assets/org-mode.svg
Type=Application
Terminal=false
MimeType=x-scheme-handler/org-protocol

To associate org-protocol:// links with the desktop file:

xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol

Configuring Chrome/Brave

As specified in the official documentation, we would like to invoke the org-protocol:// without confirmation. To do this, we need to add this system-wide configuration.

read -p "Do you want to set Chrome/Brave to show the 'Always open ...' checkbox, to be used with the 'org-protocol://' registration? [Y | N]: " INSTALL_CONFIRM

if [[ "$INSTALL_CONFIRM" == "Y" ]]
then
  sudo mkdir -p /etc/opt/chrome/policies/managed/

  sudo tee /etc/opt/chrome/policies/managed/external_protocol_dialog.json > /dev/null <<'EOF'
  {
  "ExternalProtocolDialogShowAlwaysOpenCheckbox": true
  }
EOF

  sudo chmod 644 /etc/opt/chrome/policies/managed/external_protocol_dialog.json
fi

Then add a bookmarklet in your browser with this code:

javascript:location.href =
    'org-protocol://roam-ref?template=r&ref='
    + encodeURIComponent(location.href)
    + '&title='
    + encodeURIComponent(document.title)
    + '&body='
    + encodeURIComponent(window.getSelection())

Git

Git diffs

Based on this gist and this article.

*.tex                         diff=tex
*.bib                         diff=bibtex
*.{c,h,c++,h++,cc,hh,cpp,hpp} diff=cpp
*.m                           diff=matlab
*.py                          diff=python
*.rb                          diff=ruby
*.php                         diff=php
*.pl                          diff=perl
*.{html,xhtml}                diff=html
*.f                           diff=fortran
*.{el,lisp,scm}               diff=lisp
*.r                           diff=rstats
*.texi*                       diff=texinfo
*.org                         diff=org
*.rs                          diff=rust

*.odt                         diff=odt
*.odp                         diff=libreoffice
*.ods                         diff=libreoffice
*.doc                         diff=doc
*.xls                         diff=xls
*.ppt                         diff=ppt
*.docx                        diff=docx
*.xlsx                        diff=xlsx
*.pptx                        diff=pptx
*.rtf                         diff=rtf

*.{png,jpg,jpeg,gif}          diff=exif

*.pdf                         diff=pdf
*.djvu                        diff=djvu
*.epub                        diff=pandoc
*.chm                         diff=tika
*.mhtml?                      diff=tika

*.{class,jar}                 diff=tika
*.{rar,7z,zip,apk}            diff=tika

Then adding some regular expressions for it to ~/.config/git/config, with some tools to view diffs on binary files.

# ====== TEXT FORMATS ======
[diff "org"]
  xfuncname = "^(\\*+ +.*)$"

[diff "lisp"]
  xfuncname = "^(\\(.*)$"

[diff "rstats"]
  xfuncname = "^([a-zA-z.]+ <- function.*)$"

[diff "texinfo"]
# from http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f=.gitattributes;h=c3b2926c78c939d94358cc63d051a70d38cfea5d;hb=HEAD
  xfuncname = "^@node[ \t][ \t]*\\([^,][^,]*\\)"

[diff "rust"]
  xfuncname = "^[ \t]*(pub|)[ \t]*((fn|struct|enum|impl|trait|mod)[^;]*)$"

# ====== BINARY FORMATS ======
[diff "pdf"]
  binary = true
# textconv = pdfinfo
# textconv = sh -c 'pdftotext "$@" -' # sudo apt install pdftotext
  textconv = sh -c 'pdftotext -layout "$0" -enc UTF-8 -nopgbrk -q -'
  cachetextconv = true

[diff "djvu"]
  binary = true
# textconv = pdfinfo
  textconv = djvutxt # yay -S djvulibre
  cachetextconv = true

[diff "odt"]
  textconv = odt2txt
# textconv = pandoc --standalone --from=odt --to=plain
  binary = true
  cachetextconv = true

[diff "doc"]
# textconv = wvText
  textconv = catdoc # yay -S catdoc
  binary = true
  cachetextconv = true

[diff "xls"]
# textconv = in2csv
# textconv = xlscat -a UTF-8
# textconv = soffice --headless --convert-to csv
  textconv = xls2csv # yay -S catdoc
  binary = true
  cachetextconv = true

[diff "ppt"]
  textconv = catppt # yay -S catdoc
  binary = true
  cachetextconv = true

[diff "docx"]
  textconv = pandoc --standalone --from=docx --to=plain
# textconv = sh -c 'docx2txt.pl "$0" -'
  binary = true
  cachetextconv = true

[diff "xlsx"]
  textconv = xlsx2csv # pip install xlsx2csv
# textconv = in2csv
# textconv = soffice --headless --convert-to csv
  binary = true
  cachetextconv = true

[diff "pptx"]
# pip install --user pptx2md (currently not wotking with Python 3.10)
# textconv = sh -c 'pptx2md --disable_image --disable_wmf -i "$0" -o ~/.cache/git/presentation.md >/dev/null && cat ~/.cache/git/presentation.md'
# Alternative hack, convert PPTX to PPT, then use the catppt tool
  textconv = sh -c 'soffice --headless --convert-to ppt --outdir /tmp "$0" && TMP_FILENAME=$(basename -- "$0") && catppt "/tmp/${TMP_FILENAME%.*}.ppt"'
  binary = true
  cachetextconv = true

[diff "rtf"]
  textconv = unrtf --text # yay -S unrtf
  binary = true
  cachetextconv = true

[diff "epub"]
  textconv = pandoc --standalone --from=epub --to=plain
  binary = true
  cachetextconv = true

[diff "tika"]
  textconv = tika --config=~/.local/share/tika/tika-conf.xml --text
  binary = true
  cachetextconv = true

[diff "libreoffice"]
  textconv = soffice --cat
  binary = true
  cachetextconv = true

[diff "exif"]
  binary = true
  textconv = exiftool # sudo apt install perl-image-exiftool

Extensions

This tool is taken from https://gist.github.com/nottrobin/5758221. It is attributed to David Underhill. It is a script to permanently delete files/folders from a Git repository. To use it, cd to your repository’s root, then run the script with a list of paths you want to delete, e.g., git-prune-files path1 path2

#!/usr/bin/env bash
set -o errexit

if [ $# -eq 0 ]; then
    echo "Usage: git prune-files <path1> [<path2> ...]" 1>&2
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository" 1>&2
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all && git gc --aggressive --prune

Apache Tika App wrapper

Apache Tika is a content detection and analysis framework. It detects and extracts metadata and text from over a thousand different file types. We will be using the Tika App in command-line mode to show some meaningful diff information for some binary files.

First, let’s add a custom script to run tika-app:

#!/bin/sh
APACHE_TIKA_JAR="$HOME/.local/share/tika/tika-app.jar"

if [ -f "${APACHE_TIKA_JAR}" ]
then
  exec java -Dfile.encoding=UTF-8 -jar "${APACHE_TIKA_JAR}" "$@" 2>/dev/null
else
  echo "JAR file not found at ${APACHE_TIKA_JAR}"
fi

Add tika’s installation instructions to the setup.sh file.

update_apache_tika () {
  TIKA_JAR_PATH="$HOME/.local/share/tika"

  if [ ! -d "${TIKA_JAR_PATH}" ]
  then
    mkdir -p "${TIKA_JAR_PATH}"
  fi

  TIKA_BASE_URL=https://archive.apache.org/dist/tika/
  TIKA_JAR_LINK="${TIKA_JAR_PATH}/tika-app.jar"

  echo -n "Checking for new Apache Tika App version... "

  # Get the lastest version
  TIKA_VERSION=$(
    curl -s "${TIKA_BASE_URL}" | # Get the page
    pandoc -f html -t plain | # Convert HTML page to plain text.
    awk '/([0-9]+\.)+[0-1]\// {print substr($1, 0, length($1)-1)}' | # Get the versions directories (pattern: X.X.X/)
    sort -rV | # Sort versions, the newest first
    head -n 1 # Get the first (newest) version
  )

  if [ -z "${TIKA_VERSION}" ]
  then
    echo "Failed, check your internet connection."
    exit 1
  fi

  echo "Lastest version is ${TIKA_VERSION}"

  TIKA_JAR="${TIKA_JAR_PATH}/tika-app-${TIKA_VERSION}.jar"
  TIKA_JAR_URL="${TIKA_BASE_URL}${TIKA_VERSION}/tika-app-${TIKA_VERSION}.jar"

  if [ ! -f "${TIKA_JAR}" ]
  then
    echo "New version available!"
    read -p "Do you want to download Apache Tika App v${TIKA_VERSION}? [Y | N]: " INSTALL_CONFIRM
    if [[ "$INSTALL_CONFIRM" == "Y" ]]
    then
      curl -o "${TIKA_JAR}" "${TIKA_JAR_URL}" && echo "Apache Tika App v${TIKA_VERSION} downloaded successfully"
    fi
  else
    echo "Apache Tika App is up-to-date, version ${TIKA_VERSION} already downloaded to '${TIKA_JAR}'"
  fi

  # Check the existance of the symbolic link
  if [ -L "${TIKA_JAR_LINK}" ]
  then
    unlink "${TIKA_JAR_LINK}"
  fi

  # Create a symbolic link to the installed version
  ln -s "${TIKA_JAR}" "${TIKA_JAR_LINK}"
}

update_apache_tika;

When it detects that Tesseract is installed, Tika App will try to extract text from some file types. For some reason, it tries to use Tesseract with some compressed files like *.bz2, *.apk… etc. I would like to disable this feature by exporting an XML config file which will be used when launching the Tika App (using --config=<tika-config.xml>).

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <parsers>
    <parser class="org.apache.tika.parser.DefaultParser">
      <parser-exclude class="org.apache.tika.parser.ocr.TesseractOCRParser"/>
    </parser>
  </parsers>
</properties>

Command-line wrapper

A wrapper around emacsclient:

  • Accepting stdin by putting it in a temporary file and immediately opening it.
  • Guessing that the tty is a good idea when $DISPLAY is unset (relevant with SSH sessions, among other things).
  • With a whiff of 24-bit color support, sets TERM variable to a terminfo that (probably) announces 24-bit color support.
  • Changes GUI emacsclient instances to be non-blocking by default (--no-wait), and instead take a flag to suppress this behavior (-w).

I would use sh, but using arrays for argument manipulation is just too convenient, so I’ll raise the requirement to bash. Since arrays are the only ’extra’ compared to sh, other shells like ksh etc. should work too.

#!/usr/bin/env bash
force_tty=false
force_wait=false
stdin_mode=""

args=()

usage () {
  echo -e "Usage: e [-t] [-m MODE] [OPTIONS] FILE [-]

Emacs client convenience wrapper.

Options:
-h, --help            Show this message
-t, -nw, --tty        Force terminal mode
-w, --wait            Don't supply --no-wait to graphical emacsclient
-                     Take stdin (when last argument)
-m MODE, --mode MODE  Mode to open stdin with
-mm, --maximized      Start Emacs client in maximized window

Run emacsclient --help to see help for the emacsclient."
}

while :
do
  case "$1" in
    -t | -nw | --tty)
      force_tty=true
      shift ;;
    -w | --wait)
      force_wait=true
      shift ;;
    -m | --mode)
      stdin_mode=" ($2-mode)"
      shift 2 ;;
    -mm | --maximized)
        args+=("--frame-parameters='(fullscreen . maximized)")
        shift ;;
    -h | --help)
      usage
      exit 0 ;;
    --*=*)
      set -- "$@" "${1%%=*}" "${1#*=}"
      shift ;;
    *)
      [ "$#" = 0 ] && break
      args+=("$1")
      shift ;;
  esac
done

if [ ! "${#args[*]}" = 0 ] && [ "${args[-1]}" = "-" ]
then
  unset 'args[-1]'
  TMP="$(mktemp /tmp/emacsstdin-XXX)"
  cat > "$TMP"
  args+=(--eval "(let ((b (generate-new-buffer \"*stdin*\"))) (switch-to-buffer b) (insert-file-contents \"$TMP\") (delete-file \"$TMP\")${stdin_mode})")
fi

if [ -z "$DISPLAY" ] || $force_tty
then
  # detect terminals with sneaky 24-bit support
  if { [ "$COLORTERM" = truecolor ] || [ "$COLORTERM" = 24bit ]; } \
    && [ "$(tput colors 2>/dev/null)" -lt 257 ]
  then
    if echo "$TERM" | grep -q "^\w\+-[0-9]"
    then
      termstub="${TERM%%-*}"
    else
      termstub="${TERM#*-}"
    fi

    if infocmp "$termstub-direct" >/dev/null 2>&1
    then
      TERM="$termstub-direct"
    else
      TERM="xterm-direct"
    fi # should be fairly safe
  fi

  emacsclient --tty -create-frame --alternate-editor="/usr/bin/emacs" "${args[@]}"
else
  if ! $force_wait
  then
    args+=(--no-wait)
  fi

  emacsclient -create-frame --alternate-editor="/usr/bin/emacs" "${args[@]}"
fi
Useful aliases

Now, to set an alias to use e with magit, and then for maximum laziness we can set aliases for the terminal-forced variants.

# -*- mode: sh; -*-

# Aliases to run emacs+magit
alias magit='e --eval "(progn (magit-status) (delete-other-windows))"'
alias magitt='e -t --eval "(progn (magit-status) (delete-other-windows))"'

# Aliases to run emacs+mu4e
alias emu='e --eval "(progn (=mu4e) (delete-other-windows))"'
alias emut='e -t --eval "(progn (=mu4e) (delete-other-windows))"'

And this to launch Emacs in terminal mode et, I use this as a default $EDITOR

#!/usr/bin/env bash
e -t "$@"

And ev for use with $VISUAL:

#!/usr/bin/env bash
e -w "$@"
export EDITOR="$HOME/.local/bin/et"
export VISUAL="$HOME/.local/bin/ev"

AppImage

Install/update the appimageupdatetool.AppImage tool:

update_appimageupdatetool () {
  TOOL_NAME=appimageupdatetool
  MACHINE_ARCH=$(uname -m)
  APPIMAGE_UPDATE_TOOL_PATH="$HOME/.local/bin/${TOOL_NAME}"
  APPIMAGE_UPDATE_TOOL_URL="https://github.com/AppImage/AppImageUpdate/releases/download/continuous/${TOOL_NAME}-${MACHINE_ARCH}.AppImage"

  if [ -f "${APPIMAGE_UPDATE_TOOL_PATH}" ] && "$APPIMAGE_UPDATE_TOOL_PATH" -j "${APPIMAGE_UPDATE_TOOL_PATH}" 2&>/dev/null
  then
    echo "${TOOL_NAME} already up to date"
  else
    if [ -f "${APPIMAGE_UPDATE_TOOL_PATH}" ]
    then
      echo "Update available, downloading latest ${MACHINE_ARCH} version to ${APPIMAGE_UPDATE_TOOL_PATH}"
      mv "${APPIMAGE_UPDATE_TOOL_PATH}" "${APPIMAGE_UPDATE_TOOL_PATH}.backup"
    else
      echo "${TOOL_NAME} not found, downloading latest ${MACHINE_ARCH} version to ${APPIMAGE_UPDATE_TOOL_PATH}"
    fi
    wget -O "${APPIMAGE_UPDATE_TOOL_PATH}" "${APPIMAGE_UPDATE_TOOL_URL}" && # 2&>/dev/null
        echo "Downloaded ${TOOL_NAME}-${MACHINE_ARCH}.AppImage" &&
        [ -f "${APPIMAGE_UPDATE_TOOL_PATH}.backup" ] &&
        rm "${APPIMAGE_UPDATE_TOOL_PATH}.backup"
    chmod a+x "${APPIMAGE_UPDATE_TOOL_PATH}"
  fi
}

update_appimageupdatetool;

Oh-my-Zsh

Path

Path to your oh-my-zsh installation.

export ZSH="$HOME/.oh-my-zsh"

Themes and customization

Set name of the theme to load, if set to "random", it will load a random theme each time oh-my-zsh is loaded, in which case, to know which specific one was loaded, run: echo $RANDOM_THEME See github.com/ohmyzsh/ohmyzsh/wiki/Themes.

# Typewritten customizations
TYPEWRITTEN_RELATIVE_PATH="adaptive"
TYPEWRITTEN_CURSOR="underscore"

ZSH_THEME="typewritten/typewritten"

# Set list of themes to pick from when loading at random
# Setting this variable when ZSH_THEME=random will cause zsh to load
# a theme from this variable instead of looking in $ZSH/themes/
# If set to an empty array, this variable will have no effect.
# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" )

Behavior

# Uncomment the following line to use case-sensitive completion.
# CASE_SENSITIVE="true"

# Uncomment the following line to use hyphen-insensitive completion.
# Case-sensitive completion must be off. _ and - will be interchangeable.
# HYPHEN_INSENSITIVE="true"

# Uncomment the following line to disable bi-weekly auto-update checks.
# DISABLE_AUTO_UPDATE="true"

# Uncomment the following line to automatically update without prompting.
DISABLE_UPDATE_PROMPT="true"

# Uncomment the following line to change how often to auto-update (in days).
export UPDATE_ZSH_DAYS=3

# Uncomment the following line if pasting URLs and other text is messed up.
# DISABLE_MAGIC_FUNCTIONS="true"

# Uncomment the following line to disable colors in ls.
# DISABLE_LS_COLORS="true"

# Uncomment the following line to disable auto-setting terminal title.
# DISABLE_AUTO_TITLE="true"

# Uncomment the following line to enable command auto-correction.
# ENABLE_CORRECTION="true"

# Uncomment the following line to display red dots whilst waiting for completion.
# COMPLETION_WAITING_DOTS="true"

# Uncomment the following line if you want to disable marking untracked files
# under VCS as dirty. This makes repository status check for large repositories
# much, much faster.
# DISABLE_UNTRACKED_FILES_DIRTY="true"

# Uncomment the following line if you want to change the command execution time
# stamp shown in the history command output.
# You can set one of the optional three formats:
# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
# or set a custom format using the strftime function format specifications,
# see 'man strftime' for details.
# HIST_STAMPS="mm/dd/yyyy"

Plugins

# Would you like to use another custom folder than $ZSH/custom?
ZSH_CUSTOM=$HOME/.config/my_ohmyzsh_customizations

# Which plugins would you like to load?
# Standard plugins can be found in $ZSH/plugins/
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
# Example format: plugins=(rails git textmate ruby lighthouse)
# Add wisely, as too many plugins slow down shell startup.
plugins=(
  zsh-autosuggestions
  zsh-navigation-tools
  zsh-interactive-cd
  archlinux
  ssh-agent
  sudo
  docker
  systemd
  tmux
  python
  pip
  rust
  repo
  cp
  rsync
  ripgrep
  fzf
  fd
  z
)

Bootstrap Oh-my-Zsh

source $ZSH/oh-my-zsh.sh

Aliases

# Aliases
alias zshconfig="vim ~/.zshrc"
alias ohmyzsh="ranger $ZSH"

Zsh user configuration

pbcopy and pbpaste

I like to define MacOS-like commands (pbcopy and pbpaste) to copy and paste in terminal (from stdin, to stdout). The pbcopy and pbpaste are defined using either xclip or xsel, you would need to install these tools, otherwise we wouldn’t define the aliases.

# Define aliases to 'pbcopy' and 'pbpaste'
if command -v xclip &> /dev/null
then
  # Define aliases using xclip
  alias pbcopy='xclip -selection clipboard'
  alias pbpaste='xclip -selection clipboard -o'
elif command -v xsel &> /dev/null
then
  # Define aliases using xsel
  alias pbcopy='xsel --clipboard --input'
  alias pbpaste='xsel --clipboard --output'
fi

netpaste

Define a netpaste command to paste to a Pastebin server.

alias netpaste='curl -F file=@- 0x0.st' # OR 'curl -F f:1=<- ix.io '

Sudo GUI!

And then define gsuon and gsuoff aliases to run graphical apps from terminal with root permissions, this requires xhost.

# To run GUI apps from terminal with root permissions
if command -v xhost &> /dev/null
then
  alias gsuon='xhost si:localuser:root'
  alias gsuoff='xhost -si:localuser:root'
fi

Neovim

Use Neovim instead of VIM to provide vi and vim commands.

# NeoVim
if command -v nvim &> /dev/null
then
  alias vim="nvim"
  alias vi="nvim"
fi

ESP-IDF

Add some aliases to work with the ESP-IDF framework.

if [ -d "$HOME/Softwares/src/esp-idf/" ]
then
  alias esp-prepare-env='source $HOME/Softwares/src/esp-idf/export.sh'
  alias esp-update='echo "Updating ESP-IDF framework..." && cd $HOME/src/esp-idf && git pull --all && echo "Updated successfully"'
else
  alias esp-prepare-env='echo "esp-idf repo not found. You can clone the esp-idf repo using git clone https://github.com/espressif/esp-idf.git"'
  alias esp-update=esp-prepare-env
fi

CLI wttrin client

Define an alias to get weather information for my city:

alias wttrin='curl wttr.in/$WTTRIN_CITY'
alias wttrin2='curl v2.wttr.in/$WTTRIN_CITY'

Minicom

Enable Meta key and colors in minicom:

export MINICOM='-m -c on'

Rust

Define Rust sources path, and add packages installed from cargo to the PATH.

export RUST_SRC_PATH=$HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/
export PATH=$PATH:$HOME/.cargo/bin

I’m using the AUR package clang-format-static-bin, which provide multiple versions of Clang-format, I use it with some work projects requiring a specific version of Clang-format.

Clang-format

[ -d /opt/clang-format-static ] && export PATH=$PATH:/opt/clang-format-static

CMake

Add my manually built libraries to CMake and PATH.

export CMAKE_PREFIX_PATH=$HOME/Softwares/src/install
export PATH=$PATH:$HOME/Softwares/src/install/bin

Node

Set NPM installation path to local:

NPM_PACKAGES="${HOME}/.npm-packages"

# Export NPM bin path
export PATH="$PATH:$NPM_PACKAGES/bin"

# Preserve MANPATH if you already defined it somewhere in your config.
# Otherwise, fall back to `manpath` so we can inherit from `/etc/manpath`.
export MANPATH="${MANPATH-$(manpath)}:$NPM_PACKAGES/share/man"

# Tell Node about these packages
export NODE_PATH="$NPM_PACKAGES/lib/node_modules:$NODE_PATH"

Tell NPM to use this directory for its global package installs by adding this in ~/.npmrc:

prefix = ~/.npm-packages

Some useful stuff (fzf, opam, Doom Emacs…)

tmux

I like to use tmux by default, even on my local sessions, I like to start a tmux in a default session on the first time I launch a terminal, and then, attach any other terminal to this default session:

# If not running inside Emacs (via vterm/eshell...)
if [ -z $INSIDE_EMACS ]
then
  if command -v tmux &> /dev/null && [ -z "$TMUX" ]
  then
    tmux attach -t default || tmux new -s default
  fi
fi

Other stuff

# You may need to manually set your language environment
# export LANG=en_US.UTF-8

# Preferred editor for local and remote sessions
# if [[ -n $SSH_CONNECTION ]]; then
#   export EDITOR='vim'
# else
#   export EDITOR='mvim'
# fi

# Compilation flags
# export ARCHFLAGS="-arch x86_64"

# FZF
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

# OPAM configuration
[[ ! -r $HOME/.opam/opam-init/init.zsh ]] || source $HOME/.opam/opam-init/init.zsh > /dev/null 2> /dev/null

# Add ~/.config/emacs/bin to path (for DOOM Emacs stuff)
export PATH=$PATH:$HOME/.config/emacs/bin

Define some environment variables.

source ~/.zshrc_private

Load my bitwarden-cli session, exported to BW_SESSION.

[ -f ~/.bitwarden-session ] && source ~/.bitwarden-session

Rust format

For Rust code base, the file $HOME/.rustfmt.toml contains the global format settings, I like to set it to:

# Rust edition 2018
edition = "2018"

# Use Unix style newlines, with 2 spaces tabulation.
newline_style = "Unix"
tab_spaces = 2
hard_tabs = false

# Make one line functions in a single line
fn_single_line = true

# Format strings
format_strings = true

# Increase the max line width
max_width = 120

# Merge nested imports
merge_imports = true

# Enum and Struct alignement
enum_discrim_align_threshold = 20
struct_field_align_threshold = 20

# Reorder impl items: type > const > macros > methods.
reorder_impl_items = true

# Comments and documentation formating
wrap_comments = true
normalize_comments = true
normalize_doc_attributes = true
format_code_in_doc_comments = true
report_fixme = "Always"
todo = "Always"

eCryptfs

Unlock and mount script

#!/bin/sh -e
# This script mounts a user's confidential private folder
#
# Original by Michael Halcrow, IBM
# Extracted to a stand-alone script by Dustin Kirkland <kirkland@ubuntu.com>
# Modified by: Abdelhak Bougouffa <abougouffa@fedoraproject.org>
#
# This script:
#  * interactively prompts for a user's wrapping passphrase (defaults to their
#    login passphrase)
#  * checks it for validity
#  * unwraps a users mount passphrase with their supplied wrapping passphrase
#  * inserts the mount passphrase into the keyring
#  * and mounts a user's encrypted private folder

PRIVATE_DIR="Private"
PW_ATTEMPTS=3
MESSAGE=`gettext "Enter your login passphrase:"`

if [ -f $HOME/.ecryptfs/wrapping-independent ]
then
  # use a wrapping passphrase different from the login passphrase
  MESSAGE=`gettext "Enter your wrapping passphrase:"`
fi

WRAPPED_PASSPHRASE_FILE="$HOME/.ecryptfs/wrapped-passphrase"
MOUNT_PASSPHRASE_SIG_FILE="$HOME/.ecryptfs/$PRIVATE_DIR.sig"

# First, silently try to perform the mount, which would succeed if the appropriate
# key is available in the keyring
if /sbin/mount.ecryptfs_private >/dev/null 2>&1
then
  exit 0
fi

# Otherwise, interactively prompt for the user's password
if [ -f "$WRAPPED_PASSPHRASE_FILE" -a -f "$MOUNT_PASSPHRASE_SIG_FILE" ]
then
  tries=0

  while [ $tries -lt $PW_ATTEMPTS ]
  do
    LOGINPASS=`zenity --password --title "eCryptFS: $MESSAGE"`
    if [ $(wc -l < "$MOUNT_PASSPHRASE_SIG_FILE") = "1" ]
    then
      # No filename encryption; only insert fek
      if printf "%s\0" "$LOGINPASS" | ecryptfs-unwrap-passphrase "$WRAPPED_PASSPHRASE_FILE" - | ecryptfs-add-passphrase -
      then
        break
      else
        zenity --error --title "eCryptfs" --text "Error: Your passphrase is incorrect"
        tries=$(($tries + 1))
        continue
      fi
    else
      if printf "%s\0" "$LOGINPASS" | ecryptfs-insert-wrapped-passphrase-into-keyring "$WRAPPED_PASSPHRASE_FILE" -
      then
        break
      else
        zenity --error --title "eCryptfs" --text "Error: Your passphrase is incorrect"
        tries=$(($tries + 1))
        continue
      fi
    fi
  done

  if [ $tries -ge $PW_ATTEMPTS ]
  then
    zenity --error --title "eCryptfs" --text "Too many incorrect password attempts, exiting"
    exit 1
  fi

  /sbin/mount.ecryptfs_private
else
  zenity --error --title "eCryptfs" --text "Encrypted private directory is not setup properly"
  exit 1
fi

if grep -qs "$HOME/.Private $PWD ecryptfs " /proc/mounts 2>/dev/null; then
  zenity --info --title "eCryptfs" --text "Your private directory has been mounted."
fi

dolphin "$HOME/Private"
exit 0

Desktop integration

[Desktop Entry]
Type=Application
Version=1.0
Name=eCryptfs Unlock Private Directory
Icon=unlock
Exec=/home/hacko/.ecryptfs/ecryptfs-mount-private-gui
Terminal=False

GDB

Early init

I like to disable the initial message (containing copyright info and other stuff), the right way to do this is either by starting gdb with -q option, or (since GDB v11 I think), by setting in ~/.gdbearlyinit.

# GDB early init file
# Abdelhak Bougouffa (c) 2022

# Disable showing the initial message
set startup-quietly

Init

GDB loads $HOME/.gdbinit at startup, I like to define some default options in this file, this is a WIP, but it won’t evolve too much, as it is recommended to keep the .gdbinit clean and simple. For the moment, it does just enable pretty printing, and defines the c and n commands to wrap continue and next with a post refresh, which is helpful with the annoying TUI when the program outputs to the stdout.

# GDB init file
# Abdelhak Bougouffa (c) 2022

# Save history
set history save on
set history filename ~/.gdb_history
set history remove-duplicates 2048

# When debugging my apps, debug information of system libraries
# aren't that important
set debuginfod enabled off

# Set pretty print
set print pretty on

# I hate stepping into system libraries when I'm debugging my
# crappy stuff, so lets add system headers to skipped files
skip pending on
python
import os

# Add paths here, they will be explored recursivly
LIB_PATHS = ["/usr/include" "/usr/local/include"]

for lib_path in LIB_PATHS:
  for root, dirs, files in os.walk(lib_path):
    for file in files:
      cmd = f"skip file {os.path.join(root, file)}"
      gdb.execute(cmd, True, to_string=True)
end
skip enable

skip pending on
guile
<<gdb-init-guile>>
end
skip enable

# This fixes the annoying ncurses TUI gliches and saves typing C-l each time to refresh the screen
define cc
  continue
  refresh
end

define nn
  next
  refresh
end

GnuPG

I add this to my ~/.gnupg/gpg-agent.conf, to set the time-to-live to one day.

# Do not ask me about entered passwords for 24h (during the same session)
default-cache-ttl 86400
max-cache-ttl 86400

# As I'm using KDE, use Qt based pinentry tool instead of default GTK+
pinentry-program /usr/bin/pinentry-qt

# Allow pinentry in Emacs minibuffer (combined with epg-pinentry-mode)
allow-loopback-pinentry
allow-emacs-pinentry

OCR This

This creates a script named ocrthis that can be bound to OS shortcut. When triggered:

  • it shows a selection tool to take a partial screenshot,
  • it runs tesseract to extract the text,
  • and then, it copies it to clipboard.
#!/bin/bash

IMG=$(mktemp -u --suffix=".png")
scrot -s "$IMG" -q 100
mogrify -modulate 100,0 -resize 400% "$IMG"
tesseract "$IMG" - -l eng 2> /dev/null | xsel -ib

Slack

This script is called at system startup.

#!/bin/bash

WEEK_DAY=$(date +%u)
HOUR=$(date +%H)
SLACK=$(which slack)

if [ ! "$WEEK_DAY" == "6" ] && [ ! "$WEEK_DAY" == "7" ] && [ "$HOUR" -gt 7 ] && [ "$HOUR" -lt 18 ] ; then
  $SLACK -u %U
else
  echo "It is not work time!"
fi

Arch Linux packages

Here, we install Arch packages

check_and_install_pkg() {
    PKG_NAME="$1"
    if ! pacman -Qiq "${PKG_NAME}" &>/dev/null; then
        echo "Package ${PKG_NAME} is missing, installing it using yay"
        yay -S "${PKG_NAME}"
    fi
}

PKGS_LIST=(
    git ripgrep fd gnupg fzf the_silver_searcher
    ttf-ibm-plex ttf-fira-code ttf-roboto-mono ttf-overpass ttf-lato ttf-input
    ttf-cascadia-code ttf-jetbrains-mono ttf-fantasque-sans-mono
    ttc-iosevka ttf-iosevka-nerd ttc-iosevka-slab ttc-iosevka-curly
    ttc-iosevka-curly-slab ttc-iosevka-etoile ttc-iosevka-ss09
    ccls cppcheck clang gcc gdb lldb valgrind rr openocd
    sbcl cmucl clisp chez-scheme mit-scheme chibi-scheme chicken
    vls vlang rustup semgrep-bin
    mu isync msmtp xsel xorg-xhost
    mpc mpv mpd vlc yt-dlp
    maxima fricas octave scilab-bin graphviz jupyterlab jupyter-notebook r
    djvulibre catdoc unrtf perl-image-exiftool wkhtmltopdf
    chezmoi neovim repo ecryptfs-utils
    pandoc hugo inkscape imagemagick
    aspell aspell-en aspell-fr aspell-ar grammalecte language-tool ltex-ls-bin
    libvterm brave zotero bitwarden-cli binutils
    poppler ffmpegthumbnailer mediainfo imagemagick tar unzip
)

for PKG in "${PKGS_LIST[@]}"; do
    check_and_install_pkg "$PKG"
done

KDE Plasma

On KDE, there is a good support for HiDPI displays, however, I faced annoying small icons in some contexts (for example, a right click on desktop). This can be fixed by setting PLASMA_USE_QT_SCALING=1 before starting KDE Plasma. KDE sources the files with .sh extension found on ~/.config/plasma-workspace/env, so let’s create ours.

export PLASMA_USE_QT_SCALING=1