From Scratch to Emacs, The Adventurous Configurator's Handbook

emacs_sea.jpg

Emacs, the world-class, extensible, customisable, free/libre text editor, has stood the test of time and continues to be a powerful tool for both developers and writers alike. Its reputation for complexity belies its potential: it is an undiscovered country, a treasure chest filled with gold, jewels, and priceless artifacts. Embarking on a journey with Emacs is much like setting sail on the high seas of productivity, and in this guide, we aim to help you navigate these waters with ease.

This guide is a treasure map, of sorts, to help you chart your course in the vast ocean of Emacs functionalities. We will help you equip your Emacs ship with an arsenal of powerful tools and configurations to bolster your productivity, simplify your workflow, and make your voyage smoother. From the helm to the cannon, from the crow’s nest to the rigging, you’ll find that your Emacs ship will be ready for whatever storm the high seas might throw at you.

Whether you’re a seasoned Emacs sailor or a landlubber who’s only just beginning to learn the ropes, this guide is intended to help you tailor your Emacs experience to your needs and preferences. By the end of our journey together, you’ll have transformed your Emacs from a humble sailboat to a powerful, fully-equipped galleon, ready to tackle the toughest coding or writing challenges you might face.

So, hoist the Jolly Roger, set your course, and let’s set sail on the open sea of Emacs optimisation!

Chapter 1: Sailing the Seas

Packages: The Mariner’s Charts and Tools

Our maiden voyage begins by setting our charts and readying our tools. Just as a ship needs maps to navigate the high seas, so do we need the MELPA1 repository, our trove of packages to steer Emacs.

Add the following markings to your captain’s log (init.el), to invite MELPA onboard and ensure our tools are always shiny and ready. For every new dawn we sail, our package list shall be refreshed.

(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

(unless package-archive-contents
  (package-refresh-contents))
Pasted image 20230602100950.png

Evil Mode: Hoisting the Jolly Roger of Editing

Our ship now sails under the black flag of “Evil mode”2, an homage to the legendary seafarers of Vim, allowing us to navigate our text with precision and ease. Heave the following ropes into your init.el logbook to hoist the black flag.

(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

(unless package-archive-contents
  (package-refresh-contents))

(unless (package-installed-p 'evil)
  (package-install 'evil))

(require 'evil)
(evil-mode 1)
Pasted image 20230602101551.png

Window Size: Calibrating the Spyglass

Our ship’s porthole is our window into the vast sea of code. Currently, it’s too small, like a miser’s eyepiece. We can widen our view with the following enchantment:

(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

(unless package-archive-contents
  (package-refresh-contents))

(unless (package-installed-p 'evil)
  (package-install 'evil))

(require 'evil)
(evil-mode 1)

(add-to-list 'default-frame-alist '(height . 40))
(add-to-list 'default-frame-alist '(width . 90))

For the sake of our journey, we won’t veer too far off course, but remember, the horizon is yours to command.

Pasted image 20230602104103.png

Fonts: Inking the Captain’s Quill

Our final touch for this chapter of the voyage, we shall imbue the ship with a touch of elegance, by changing the ship’s lettering, the font of all frames. Here, we choose the scribe’s favorite, “Fira Code”.

(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

(unless package-archive-contents
  (package-refresh-contents))

(unless (

package-installed-p 'evil)
  (package-install 'evil))

(require 'evil)
(evil-mode 1)

(add-to-list 'default-frame-alist '(height . 40))
(add-to-list 'default-frame-alist '(width . 90))

(set-frame-font "Fira Code-12" nil t)
Pasted image 20230602105054.png

Tool bar and menu bar: The Ship’s Helm and Mainsail

Keep your helm clean by removing unnecessary clutter such as the default tool bar and menu bar. This can be achieved with a couple of simple commands.

(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

(unless package-archive-contents
  (package-refresh-contents))

(unless (package-installed-p 'evil)
  (package-install 'evil))

(require 'evil)
(evil-mode 1)

(add-to-list 'default-frame-alist '(height . 40))
(add-to-list 'default-frame-alist '(width . 90))

(set-frame-font "Fira Code-12" nil t)

;; Remove the menu bar and tool bar
(tool-bar-mode -1)
(menu-bar-mode -1)
Pasted image 20230602105536.png

Projectile: The Cannon of Project Navigation

Elevate your project management skills by utilising Projectile3. This tool not only helps in navigating projects but can also be customised to use dired4 for opening files in a project. By default, Projectile uses whatever method it thinks is most appropriate for opening files in a project, but we want to use dired. With this configuration, whenever you switch to a project, Projectile will open the project’s root in dired.

;; ... same as before

;; Projectile
(unless (package-installed-p 'projectile)
  (package-install 'projectile))

(require 'projectile)
(projectile-mode +1)

(setq projectile-switch-project-action 'projectile-dired)
Pasted image 20230602111209.png

Mini-frame: The Crow’s Nest of Display

Observe your Emacs environment from a higher perspective with the mini-frame. Install it and place it at the top for an optimal viewing experience.

;; Mini-frame
(unless (package-installed-p 'mini-frame)
  (package-install 'mini-frame))

(require 'mini-frame)
(mini-frame-mode 1)

(setq mini-frame-show-parameters
      '((top . 0)
        (width . 0.7)
        (left . 0.5)))
Pasted image 20230602112301.png

Ivy: The Rigging of Completion

Ivy5 is your personal assistant for completion in Emacs. Enhance its functionalities by integrating ivy-posframe6 which displays Ivy in a child frame. Here we’ll install both packages and configure them.

;; Ivy support
(unless (package-installed-p 'ivy)
  (package-install 'ivy))

(unless (package-installed-p 'ivy-posframe)
  (package-install 'ivy-posframe))


(require 'ivy)
(ivy-mode 1)

(require 'ivy-posframe)
(setq ivy-display-function #'ivy-posframe-display-at-frame-center)
(setq ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-center)))
(ivy-posframe-mode 1)
Pasted image 20230602113339.png

Doom modeline: The Jolly Roger of Status Lines

Now we install an improved modeline, which is the Doom modeline7.

;; Doom modeline
(unless (package-installed-p 'doom-modeline)
  (package-install 'doom-modeline))

(require 'doom-modeline)
(doom-modeline-mode 1)
Pasted image 20230602114216.png

Startup projects: The Voyage’s Beginnings

Always keep your projects within reach by displaying a list of recent projects every time Emacs starts. To do this, we tell it to remember your recent projects. The alien indexing method will cause Projectile to use external utilities like git to provide a speed boost. The ivy completion system will use Ivy for project selection. A function creates a new buffer called *Projectile Projects*, erases any existing content in it, inserts a list of the projects known to Projectile, and displays the buffer.

The new “Projectile” section will look like this:

;; Projectile
(unless (package-installed-p 'projectile)
  (package-install 'projectile))

(require 'projectile)

;;; Enable Projectile and configure it to remember recent projects
(projectile-mode +1)
(setq projectile-switch-project-action 'projectile-dired)
(setq projectile-indexing-method 'alien)
(setq projectile-completion-system 'ivy)

;;; Create a function to display recent projects on startup
(defun display-projectile-list-on-startup ()
  (let ((buf (get-buffer-create "*Projectile Projects*")))
    (with-current-buffer buf
      (erase-buffer)
      (dolist (project (projectile-relevant-known-projects))
        (insert project "\n")))
    (switch-to-buffer buf)))

The page is still quite bare-bones, but we’ll add more features as we go along.

Pasted image 20230602120443.png

Markdown: The Sea Chart of Formatting

Since we’re writing this page in Markdown, let’s go ahead and install a Markdown mode8. We’ll install a Markdown major mode, a TOC generation package, and a formatting package. We also need to tell Emacs to use the Markdown mode for files with the .md extension.

;; Markdown
(dolist (package '(markdown-mode markdown-toc markdownfmt))
  (unless (package-installed-p package)
    (package-install package)))

(require 'markdown-mode)
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
Pasted image 20230602122015.png

But … wait. The code blocks are ugly and do not have syntax highlighting. To fix this, we first enable global font lock mode, which will enable syntax highlighting for all major modes. Then we tell Emacs to use the markdown-fontify-code-blocks-natively function to highlight code blocks. markdown-mode should automatically use syntax highlighting for code blocks, but it depends on the major mode associated with the language used in the code block. We also have to make sure the major mode for that language is installed. For example, if you have a Python code block, you would need python-mode (actually, this also comes with Emacs).

;;; Enable font-lock globally (syntax highlighting for code blocks)
(global-font-lock-mode 1)

;; ...

(setq markdown-fontify-code-blocks-natively t)

Looks better now!

Pasted image 20230602123200.png

Scrolling conservatively: The Steady Course of Viewing

By default, Emacs scrolls by nearly “full screens” (the exact behavior is influenced by the scroll-step and scroll-conservatively variables).

To make Emacs scroll line by line, you can set the scroll-conservatively variable to a high value. You can add the following line to your init.el file:

(setq scroll-conservatively 101)

The number 101 is somewhat arbitrary, it just needs to be greater than 100. When scroll-conservatively is set to a value greater than 100, Emacs will always keep the cursor at least that many lines from the top and bottom of the window, effectively causing it to scroll one line at a time.

Speedbar: The Swift Current of Shortcuts

One of my favourite features in Doom Emacs is the speedbar. Doom Emacs uses a package named which-key to provide this and it is a package that displays available keybindings in a popup.

This package is very handy to discover available keybindings and to learn new ones. which-key-mode makes it so that whenever you start a key sequence, and then wait for a short period of time, which-key will pop up a buffer with all possible completions of the key sequence. This sequence (sometimes called the “leader key”) will be set in the config as M-SPC.

We will also use general.el, a package that provides a more convenient way to define key bindings. The first keybindings we will define is the p for a “projects” section and f to find all files in a project.

;; Speed bar
(dolist (package '(which-key general))
  (unless (package-installed-p package)
    (package-install package)))

(require 'which-key)
(which-key-mode)

(require 'general)
(general-create-definer my-leader-def
  :states '(normal visual insert emacs)
  :prefix "M-SPC"
  :non-normal-prefix "M-SPC")

(my-leader-def
  "p"  '(:ignore t :which-key "projects")
  "pf" 'projectile-find-file)

(setq which-key-idle-delay 0.5) ;; default 0.5
Pasted image 20230602135513.png

A Few Cosmetic Changes: Polishing the Captain’s Quarters

We will now do a few cosmetic changes. The theme we will use is modus-operandi9. This theme is bundled with Emacs 27, so we don’t need to install it. We will also change the font in the Markdown code blocks to the same as the main font. Additionally, we will add syntax highlighting in Markdown mode to wikilinks.

;; Font name
(defvar my-font-name "Fira Code")

(set-frame-font (concat my-font-name "-12") nil t)
(set-face-attribute 'fixed-pitch nil :family my-font-name :height 120)

;;; ...

(setq markdown-enable-wiki-links t)

;; Match Markdown font main one
(set-face-attribute 'markdown-code-face nil :family my-font-name)
(set-face-attribute 'markdown-pre-face nil :family my-font-name)

;;; ...

;; Modus operandi
(load-theme 'modus-operandi t)
Pasted image 20230602142614.png

Buffer splitting: Dividing the Ship’s Log

Something I use quite offen is splitting the window vertically or horizontally. In addition, if I’m inside a project, I want the new window to be in the same project and show me a list of files so that I can easily open one of them. To do this, we define two functions, one for vertical splitting and one for horizontal splitting and assign to keys for the speedbar. The keys will be M-SPC b s r (Buffers -> split -> right) and M-SPC b s t (Buffers -> split -> top) for vertical and horizontal splitting, respectively.

;; Buffer splitting
(defun split-right-and-list-project-files ()
  "Split the current window into two, left and right. The left one contains the current contents and the right one contains a projectile-list of all the files in the project."
  (interactive)
  (split-window-right)
  (other-window 1)
  (projectile-find-file))

(defun split-below-and-list-project-files ()
  "Split the current window into two, top and bottom. The top one contains the current contents and the bottom one contains a projectile-list of all the files in the project."
  (interactive)
  (split-window-below)
  (other-window 1)
  (projectile-find-file))

(my-leader-def
  "b"  '(:ignore t :which-key "Buffers")
  "bs" '(:ignore t :which-key "Split")
  "bsr" '(split-right-and-list-project-files :which-key "Right split")
  "bst" '(split-below-and-list-project-files :which-key "Top split"))
Pasted image 20230602174258.png

Chapter 2: The Archipelago of Languages

Rust: The Ironclad Island

To add support for Rust we use the rustic10 package. rustic provides a more comprehensive environment for Rust development essentially combinating several packages (rust-mode, cargo, flycheck, lsp-mode, etc.) providing syntax highlighting, formatting, linting, running Cargo commands and so on.

We also will use rustic-lsp-server with Rust Analyzer11 as the language server (altough RLS is also supported).

company will be added for autocompletion and rustic-lsp-server’s inlay type hints will be enabled.

rustic will have auto-formatting on save enabled.

;; Rust
(unless (package-installed-p 'rustic)
  (package-install 'rustic))

(require 'rustic)

;; use rust-analyzer as the LSP server
(setq rustic-lsp-server 'rust-analyzer) 

;;; Company
(unless (package-installed-p 'company)
  (package-install 'company))

(require 'company)
;; Enable company mode in all buffers
(add-hook 'after-init-hook 'global-company-mode)
;; Set company backends for lsp-mode
(push 'company-capf company-backends)

;;; lsp-mode
(unless (package-installed-p 'lsp-mode)
  (package-install 'lsp-mode))

(require 'lsp-mode)
;; Enable lsp-mode in rustic-mode
(add-hook 'rustic-mode-hook #'lsp)
;; Use company-capf as the completion provider
(add-hook 'lsp-mode-hook (lambda () (set (make-local-variable 'company-backends) '(company-capf))))

;; Enable rustfmt on save
(setq rustic-format-on-save t)

;; Enable inlay hints
(setq lsp-rust-analyzer-display-inlay-hints t)
(setq lsp-inlay-hints-mode t)

Pasted image 20230602210653.png
We will also add keybindings for common Rust/Cargo commands, such a build, run, test, format, check and clippy.

;;; Speedbar for Rust
(my-leader-def
  :states '(normal visual emacs)
  :keymaps 'override
  "l" '(:ignore t :which-key "Language")
  "r" '(:ignore t :which-key "Rust"))

(my-leader-def
  :states '(normal visual emacs)
  :keymaps 'override
  "l r b" '(rustic-cargo-build :which-key "Build")
  "l r r" '(rustic-cargo-run :which-key "Run")
  "l r t" '(rustic-cargo-test :which-key "Test")
  "l r f" '(rustic-cargo-fmt :which-key "Format")
  "l r c" '(rustic-cargo-check :which-key "Check")
  "l r p" '(rustic-cargo-clippy :which-key "Clippy"))
Pasted image 20230602212953.png

Python: The Serpent’s Atoll

In this section, we make Python programming in Emacs a breeze by integrating the Elpy12 package. Elpy adds a multitude of enhancements to the Python editing experience, including code navigation, automatic formatting, refactoring helpers, and more.

First, we check if Elpy is installed and install it if necessary:

(unless (package-installed-p 'elpy)
  (package-install 'elpy))
(require 'elpy)

Next, we enable Elpy for all Python buffers and configure IPython to be the default Python interpreter:

(elpy-enable)
(setq python-shell-interpreter "ipython"
      python-shell-interpreter-args "-i --simple-prompt")

We also specify the Python 3 interpreter for Elpy’s RPC and enable the Python Language Server Protocol (LSP) for Python mode:

(setq elpy-rpc-python-command "python3")
(add-hook 'python-mode-hook #'lsp-deferred)
(setq lsp-python-ms-python-executable-cmd "python3")

Flycheck13, a modern syntax checking package, is then configured to replace Elpy’s default Flymake:

(when (require 'flycheck nil t)
  (setq elpy-modules (delq 'elpy-module-flymake elpy-modules))
  (add-hook 'elpy-mode-hook 'flycheck-mode))

Our scripting journey gets even more exciting as we set sail to the land of Virtual Environments. We define a function set-python-virtualenv that automatically sets the Python virtual environment based on the Projectile project root:

(defun set-python-virtualenv ()
  "Set `python-shell-virtualenv-root' based on projectile's project root."
  (interactive)
  ... )
(add-hook 'python-mode-hook 'set-python-virtualenv)

Lastly, we bind the treasure map (which-key) with commonly used Python commands, helping us navigate the Python seas smoothly:

(my-leader-def
 :states '(normal visual emacs)
 :keymaps 'override
 "lp"  '(:ignore t :which-key "Python")
 "lpv" '(set-python-virtualenv :which-key "Set Virtualenv")
 "lpr" '(run-python :which-key "Run Python REPL")
 "lpf" '(python-format-buffer :which-key "Format buffer")
 "lpd" '(elpy-goto-definition :which-key "Go to definition"))

This marks the end of our Python setup chapter. We have set up a powerful Python development environment in Emacs, complete with syntax checking, virtual environment support, and interactive Python commands, all just a few key presses away. With these tools at hand, your Python adventure in Emacs will be a sailing pleasure!

Pasted image 20230602221240.png

Go: The Swift Current Isle

As our adventure in this programming environment continues, we delve into the rich ecosystem of Go, bringing out its full potential in Emacs.

The story begins with the installation of the go-mode14 package. If the package isn’t already part of the inventory (installed), our script takes the initiative to acquire it:

(unless (package-installed-p 'go-mode)
  (package-install 'go-mode))

Following this, we require or load the go-mode package to prepare Emacs for Go development:

(require 'go-mode)

The support crew, company mode, is called upon to enhance the user experience by providing autocompletion features globally:

(add-hook 'after-init-hook 'global-company-mode)

Next, Language Server Protocol (LSP) support is hooked into go-mode to offer a plethora of language features like linting, code navigation, and much more:

(add-hook 'go-mode-hook #'lsp-deferred)
(setq lsp-gopls-server-path (expand-file-name "~/go/bin/gopls"))
(setq lsp-enable-snippet nil)  ;; disable snippet support
(setq lsp-gopls-staticcheck t)  ;; enable gopls' staticcheck linter
(setq lsp-eldoc-render-all t)   ;; display documentation in minibuffer while typing

To keep our code neat and imports organised, gofmt (set to goimports) is triggered before every save. Additional save hooks are also set up to automatically format the buffer and organise imports on saving a Go file:

(setq gofmt-command "goimports")
(add-hook 'before-save-hook 'gofmt-before-save)
(defun lsp-go-install-save-hooks ()
  (add-hook 'before-save-hook #'lsp-format-buffer t t)
  (add-hook 'before-save-hook #'lsp-organize-imports t t))
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

The adventure wouldn’t be complete without a handy map and compass. So, we set up key bindings (using which-key) under M-SPC l g for Go:

(my-leader-def
 :states '(normal visual emacs)
 :keymaps 'override
 "lg"  '(:ignore t :which-key "Go")
 "lgf" '(gofmt :which-key "Format code")
 "lgt" '(compile :which-key "Run test")
 "lgi" '(lsp-goto-implementation :which-key "Go to implementation")
 "lgd" '(lsp-find-definition :which-key "Go to definition")
 "lgr" '(lsp-find-references :which-key "Find references")
 "lgh" '(lsp-describe-thing-at-point :which-key "Describe thing at point"))

This setup offers a more navigable and interactive environment, enabling you to journey through your Go code like an expert adventurer, efficiently uncovering the treasures of your project.

Pasted image 20230602230258.png

YAML: Charting the Indentation Ocean

 ;; First, we ensure that the yaml-mode package is installed. If not, Emacs is instructed to download and install it.
 (unless (package-installed-p 'yaml-mode)
  (package-refresh-contents)
  (package-install 'yaml-mode))

With the indispensable yaml-mode installed and readied, our vessel for navigating the YAML seas, we now link it with every YAML file we encounter during our journey.

(require 'yaml-mode)
(add-to-list 'auto-mode-alist '("\\.ya?ml\\'" . yaml-mode))

One of the quirks of our YAML sea is the importance of proper indentation. But worry not! We’ve equipped our ship with an auto-indenting feature. Whenever you press the enter key (C-m), a new line is created, and the cursor is automatically positioned with the correct indentation.

(add-hook 'yaml-mode-hook
  (lambda ()
    (define-key yaml-mode-map "\C-m" 'newline-and-indent)))

Our journey in the YAML sea often involves vast and complex structures. To make our journey manageable, we employ code folding, a valuable map that allows us to hide and reveal portions of our code as needed.

(add-hook 'yaml-mode-hook
  (lambda ()
    (hs-minor-mode 1)))

As we chart our course, we also enable company-mode to provide us with suggestions, turning our journey into a collective effort with a supportive crew.

(add-hook 'yaml-mode-hook 'company-mode)

We’re not alone on these seas. To keep us from straying off course, we enlist the help of a yamllint lookout in our crow’s nest, alerting us whenever we deviate from the ideal YAML structure. Before we embark, ensure you’ve enlisted yamllint by installing it with pip install yamllint.

;; Add YAML lint. Must have `pip install yamllint` installed.
(add-hook 'yaml-mode-hook 'flycheck-mode)

Lastly, to aid our navigation through the layers of indentation, we make use of indent-guide. Like a dotted line in the ocean, it illuminates our path, making the structure of our YAML document clear and visible.

(unless (package-installed-p 'indent-guide)
  (package-install 'indent-guide))

(require '

indent-guide)
(add-hook 'yaml-mode-hook 'indent-guide-mode)
(set-face-foreground 'indent-guide-face "lightgray")
(set-face-background 'indent-guide-face nil)
(setq indent-guide-char ":")

With these tools at our disposal, our journey through the YAML seas will be a breeze, and we’ll be able to navigate the waves of indentation with ease and confidence.

Pasted image 20230603103132.png

Chapter 3: In the Hold: The Craftsman’s Kit

Once you have set sail on the vast seas of code, it is crucial to wield the power of version control to navigate your way through the ever-changing tides of development. Git, the legendary version control system, will become your trusted companion on this adventurous journey. In this chapter, we will equip ourselves with the tools and knowledge to master Git and unlock its hidden treasures.

To harness the power of Git within Emacs, we need to gather the necessary tools. Let’s start by installing the evil-collection package, which provides Evil keybindings for various Emacs modes:

(unless (package-installed-p 'evil-collection)
  (package-install 'evil-collection))

(require 'evil-collection)

Evil Collection enhances your Emacs experience by bringing Evil keybindings to Magit, among other modes.

The heart of our Git journey lies within Magit15, the legendary Git interface for Emacs. We must install the magit package to summon its power:

(unless (package-installed-p 'magit)
  (package-install 'magit))

Magit will serve as our compass, guiding us through the treacherous waters of version control.

To ensure that our Git knowledge permeates various programming realms, we will bind the magit-refresh-status command to our modes of choice. Let’s create a list of modes and add the hook to each one:

(setq my-modes '(rust-mode-hook
                 python-mode-hook
                 go-mode-hook
                 markdown-mode-hook
                 yaml-mode-hook))

(mapc (lambda (mode)
        (add-hook mode (lambda () (add-hook 'after-save-hook 'magit-refresh-status))))
      my-modes)

By doing so, we guarantee that whenever we save a file in one of these modes, Magit will refresh its status, keeping us informed about the state of our repositories.

Our quest wouldn’t be complete without discovering the hidden repositories lurking in the shadows. With the power of Projectile, we can uncover their secrets. Let’s map over the known projects, filter out those without a .git directory, and prepare our repository list:

(setq magit-repo-dirs
      (mapcar
       (lambda (dir)
         (substring dir 0 -1))
       (cl-remove-if-not
        (lambda (project)
          (unless (file-remote-p project)
            (file-directory-p (concat project "/.git/"))))
        (projectile-relevant-known-projects))))

Now, we possess a list of repositories, each ready to be explored and conquered.

With our arsenal fully assembled, it’s time to unleash the power of Git with our custom keybindings. Using the my-leader-def macro, we can assign keybindings that will guide us through the Git seas:

(my-leader-def
 :states '(normal visual emacs)
 :keymaps 'override
 "g"  '(:ignore t :which-key "Git")
 "gs" '(magit-status :which-key "Show status")
 "gb" '(magit-branch :which-key "Show all branches")
 "gco" '(magit-checkout :which-key "Checkout branch")
 "gr" '(magit-rebase :which-key "Rebase on a branch")
 "gsq" '(magit-rebase-squash :which

-key "Squash commits")
 "gc" '(magit-commit :which-key "Commit")
 "gp" '(magit-push-popup :which-key "Push"))

These keybindings will be your guiding stars as you navigate through Git’s vast expanse, allowing you to perform actions like displaying the repository status, managing branches, checking out branches, rebasing, squashing commits, committing changes, and pushing to remote repositories.

Sometimes, even in the face of adversity, we encounter unexpected challenges. In this case, the unbound suffix ':' error arises when trying to access Evil’s evil-ex prompt. Fear not, for we shall reclaim our territory:

(with-eval-after-load 'evil-maps
  (define-key evil-motion-state-map (kbd ":") 'evil-ex))

(defun my-magit-setup ()
  (define-key evil-normal-state-local-map (kbd ":") 'evil-ex))

(add-hook 'magit-status-mode-hook 'my-magit-setup)

With these configurations, we restore the full power of Evil’s evil-ex prompt, allowing us to issue commands fearlessly.

To ensure a smooth voyage, we must tame the unseen forces that could disrupt our Git journey. One such force is the “unbound suffix <mouse movement>” error. By advising the magit-key-mode-error-message function, we can suppress this error when it’s triggered by mouse movement:

(defun ignore-mouse (ret)
  (let ((debug-on-message "unbound suffix <mouse movement>"))
    (if (called-interactively-p 'interactive)
        ret
      (ignore-errors (funcall ret)))))

(advice-add 'magit-key-mode-error-message :around 'ignore-mouse)

With this protection in place, you can navigate Magit buffers using your mouse without being interrupted by the “unbound suffix” error.

Congratulations! With these Git tools at your disposal, you are now equipped to embark on your version control adventures. May you sail through the Git seas with confidence and mastery, leaving a trail of impeccable code in your wake.

Pasted image 20230603113453.png

Kubernetes: Commanding the Container Fleet

Once upon a time, in the deep blue Emacs sea, there was a brave adventurer on a quest to tame the fearsome beast of Kubernetes. The tale begins with the collection of tools required for the daring voyage.

(unless (package-installed-p 'kubernetes)
  (package-install 'kubernetes))

(require 'kubernetes)

(with-eval-after-load 'kubernetes
  (unless (package-installed-p 'kubernetes-evil)
    (package-refresh-contents)
    (package-install 'kubernetes-evil)))

Our mariner, with the trusty map of package-installed-p, first ensured the company of the mighty warrior, kubernetes. When found missing, package-install was invoked to summon the warrior. The brave kubernetes16 was then made to pledge allegiance to the mariner. A similar course of action was taken with the wily mercenary, kubernetes-evil, ensuring a formidable party for the adventure ahead.

(setq kubernetes-parent-directories '("k8s" "manifests" "config"))

With the team assembled, the mariner set out on the mighty vessel, seeking out the treacherous terrains named k8s, manifests, and config. The name of these lands was encrypted in a secret scroll labeled kubernetes-parent-directories.

;; Function to check if a parent directory exists
(defun kubernetes-enable-mode-if-parent-directory-exists ()
  (let ((parent-dir (file-name-nondirectory (directory-file-name (file-name-directory buffer-file-name)))))
    (when (member parent-dir kubernetes-parent-directories)
      (kubernetes-mode))))

;; Add a hook to enable kubernetes-mode based on parent directory
(add-hook 'yaml-mode-hook 'kubernetes-enable-mode-if-parent-directory-exists)

With a clever device - a hook paired with a function, our mariner ensured that upon sailing through the stormy YAML sea, the presence of one of the desired lands triggered the transformation of the ship, morphing it into a kubernetes-mode leviathan, ready to face any storm.

(setq kubernetes-namespace "default")

As they ventured forth, the mariner christened their current realm as the default namespace - their home in the chaotic sea, their sanctuary amidst the turmoil.

;; Function to deploy the current buffer in Kubernetes
(defun kubernetes-deploy-buffer ()
  "Deploy the currently opened buffer using kubectl apply -f."
  (interactive)
  (let ((file (buffer-file-name)))
    (if (not (and file (file-exists-p file)))
        (message "Buffer is not visiting a file.")
      (async-shell-command (concat "kubectl apply -f " file)))))

Armed with a magical charm, the kubernetes-deploy-buffer, the mariner could summon their mighty forces into action at will. A mere invocation was enough to deploy the fleet on the battlefield.

Pasted image 20230603151916.png
;; Function to list Kubernetes namespaces and switch to selected namespace
(defun kubernetes-switch-namespace ()
  "List available namespaces and switch to selected namespace."
  (interactive)
  (let ((output-buffer (get-buffer-create "*kubernetes-namespace*")))
    (with-current-buffer output-buffer
      (erase-buffer)
      (call-process "kubectl" nil t nil "get" "namespaces"))
    (let ((namespace (completing-read "Switch to namespace: "
                                      (split-string (with-current-buffer output-buffer (buffer-string)) "\n" t))))
      (unless (string-empty-p namespace)
        (async-shell-command (concat "kubectl config set-context --current --namespace=" namespace))
        (message "Switched to namespace: %s" namespace)))))

The mariner also possessed the mystical power to switch their ship to any other namespace with the spell, kubernetes-switch-namespace. This allowed them to nimbly navigate through the whirlpools of the Kube-sea.

(my-leader-def
 :states '(normal visual emacs)
 :keymaps 'override
 "k" '(:ignore t :which-key "Kubernetes")
 "kf" '(kubernetes-forward-resource :which-key "Forward resource")
 "kb" '(kubernetes-backward-resource :which-key "Backward resource")
 "kcr" '(kubernetes-create-resource :which-key "Create resource")
 "kdel" '(kubernetes-delete-resource :which-key "Delete resource")
 "ked" '(kubernetes-describe-resource :which-key "Describe resource")
 "kex" '(kubernetes-exec-popup :which-key "Execute command in resource")
 "kl" '(kubernetes-logs-popup :which-key "View resource logs")
 "kp" '(kubernetes-pods :which-key "List pods")
 "ks" '(kubernetes-set-namespace :which-key "Set namespace")
 "kd" '(kubernetes-deploy-buffer :which-key "Deploy buffer")
 "kn" '(kubernetes-switch-namespace :which-key "Switch namespace")
 "ko" '(kubernetes-overview :which-key "Kubernetes overview"))

Lastly, a grand tapestry was woven, a guide to the whole adventure. Each spell, each charm, and each command were carefully etched onto this map, creating a comprehensive guide for our brave mariner.

This tale, fellow Emacs sailor, is one of thrill, exploration, and conquest, sailing the tumultuous Kubernetes sea. With tools and treasures abound, we traverse these waters, ready for what lies ahead. May your Emacs voyage be as fruitful and your adventures as exciting as our brave mariner’s journey.

Pasted image 20230603152241.png

Containers: The Legend of the Layered Vessel

Ahoy there, brave Emacs sailor! Let’s prepare for our new adventure in the stormy Docker seas, in search of legendary Containers, each akin to a floating island, ripe with opportunities and treasures!

(unless (package-installed-p 'docker)
  (package-install 'docker))

(require 'docker)
(add-hook 'docker-mode-hook (lambda () (docker-container-ls)))

The journey begins in our homeland, ensuring that the courageous Docker is aboard, ready to guide us through treacherous tides. With the trusty docker-mode-hook, our ship is ever-ready to unfurl its sails, revealing a tableau of Docker containers, awaiting exploration.

(defun docker-container-prompt (prompt)
  "Prompt for a Docker container name."
  (completing-read prompt (docker-container-names)))

(defun docker-container-names ()
  "Retrieve a list of Docker container names."
  (split-string (shell-command-to-string "docker ps -a --format '{{.Names}}'") "\n" t))

Our mariner conjured the spell docker-container-prompt that whispered the names of all Docker containers, calling upon them like sirens of the sea. An even more potent spell docker-container-names was forged to summon the entire list of container names, each echoing like a chant from the deep Docker sea.

(defun docker-kill-prompt ()
  "Prompt and kill a Docker container."
  (interactive)
  (let ((container-name (docker-container-prompt "Kill container:")))
    (docker-container-kill container-name)))

(defun docker-restart-prompt ()
  "Prompt and restart a Docker container."
  (interactive)
  (let ((container-name (docker-container-prompt "Restart container:")))
    (docker-container-restart container-name)))

(defun docker-start-prompt ()
  "Prompt and start a Docker container."
  (interactive)
  (let ((container-name (docker-container-prompt "Start container:")))
    (docker-container-start container-name)))

(defun docker-stop-prompt ()
  "Prompt and stop a Docker container."
  (interactive)
  (let ((container-name (docker-container-prompt "Stop container:")))
    (docker-container-stop container-name)))

(defun docker-tag-prompt ()
  "Prompt and tag a Docker container."
  (interactive)
  (let ((container-name (docker-container-prompt "Tag container:")))
    (docker-container-tag container-name)))

The mariner’s toolkit contained many powerful charms, each more wondrous than the last. One could subdue an unruly container, the docker-kill-prompt. Another breathed life into a dormant one, the docker-start-prompt. If a container became sluggish, docker-restart-prompt was used to rejuvenate it, while docker-stop-prompt could coax an overly active one into submission. Lastly, the docker-tag-prompt was used to mark the containers with unique insignia, aiding the mariner in their journeys.

(my-leader-def
  :states '(normal visual emacs)
  :keymaps 'override
  "c" '(:ignore t :which-key "Containers")
  "ci" '(docker-images :which-key "Images (New Buffer)")
  "cl" '(docker-logs :which-key "Logs (New Buffer)")
  "cn" '(docker-network :which-key "Network (New Buffer)")
  "cp" '(docker-ps :which-key "PS (New Buffer)")
  "ck" '(docker-kill-prompt :which-key "Kill (Prompt)")
  "cr" '(docker-restart-prompt :which-key "Restart (Prompt)")
  "cs" '(docker-start-prompt :which-key "Start (Prompt)")
  "cS" '(docker-stop-prompt :which-key "Stop (Prompt)")
  "ct" '(docker-tag-prompt :which-key "Tag (Prompt)")
  "ca" '(docker-attach :which-key "Attach")
  "cb" '(docker-build-buffer :which-key "Build Buffer")
  "cc" '(docker-compose :which-key "Compose")
  "ce" '(docker-exec :which-key "Exec"))

Then a grand guide was etched onto a gleaming shell, the ‘my-leader-def’. This was a guide to navigate the Docker sea with ease, with each container under the mariner’s command. Be it the birth of new containers or their demise, the mariner stood at the helm, orchestrating their symphony.

Pasted image 20230603172130.png

Such is our tale, dear sailor, of an epic adventure in the Docker sea. Now, it’s time for you to write your own legend in the annals of the Emacs sea.

Chapter 4. Taming the Kraken: An Adventure in Org-Mode

Anchors Aweigh: Setting Up Org-Mode

Arrr, step aboard the mighty vessel of knowledge, the S.S. Org-Mode! This be not your regular ship, it be a floating archive of ye thoughts, a library of knowledge, a ship to navigate the vast ocean of ideas. Let’s hoist the sails!

(unless (package-installed-p 'org)
  (package-install 'org))

We’re stocking up on our provisions, ye see. Without the trusty Org17, we’d be set adrift. Be it organising your thoughts like stowing cargo or keeping a captain’s log, it be essential.

(defvar my-org-root-directory "~/notes/org/"
  "Root directory for Org mode files.")

(setq org-directory my-org-root-directory)

In the heart of our ship, a chest - the root directory. A grand treasure map of sorts, guiding us to all our Org files.

(defun my-org-open-index-file ()
  "Open the index file for Org mode."
  (interactive)
  (find-file (expand-file-name "index.org" my-org-root-directory)))

Ahoy, the captain’s log! A simple incantation to unfurl it, my-org-open-index-file, and it’s open for us to pen our thoughts, musings, and tales.

Pasted image 20230603184929.png
(setq-local face-remapping-alist '((default (:height 1.5) variable-pitch)
                                  (header-line (:height 4.0) variable-pitch)
                                  (org-document-title (:height 1.75) org-document-title)
                                  (org-code (:height 1.55) org-code)
                                  (org-verbatim (:height 1.55) org-verbatim)
                                  (org-block (:height 1.25) org-block)
                                  (org-block-begin-line (:height 0.7) org-block)))

The captain likes his scrolls a particular way, see. A custom array, face-remapping-alist, sets the parchment just right, tuning the height of different parts like adjusting the tension in the rigging of a ship.

(unless (package-installed-p 'org-modern)
  (package-install 'org-modern))

(add-hook 'org-mode-hook #'org-modern-mode)

Nothing like a bit of modern charm on the old Org-Mode. With ‘org-modern’, our ship stays seaworthy and trendy.

(add-hook 'org-mode-hook (lambda () (org-indent-mode)))

The captain insists on keeping the ship’s log neat and tidy. Indentation be the order of the day!

(setq
 ;; Edit settings
 org-auto-align-tags nil
 org-tags-column 0
 org-catch-invisible-edits 'show-and-error
 org-special-ctrl-a/e t
 org-insert-heading-respect-content t
 ;; Org styling, hide markup etc.
 org-hide-emphasis-markers t
 org-pretty-entities t
 org-ellipsis "…"

 ;; Agenda styling
 org-agenda-tags-column 0
 org-agenda-block-separator ?─
 org-agenda-time-grid
 '((daily today require-timed)
   (800 1000 1200 1400 1600 1800 2000)
   " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
 org-agenda-current-time-string
 "⭠ now ─────────────────────────────────────────────────")

A flurry of captain’s orders followed, fine-tuning the look and feel of our ship’s logs - the visibility of emphasis markers, the style of ellipses, and more.

(setq org-modern-indent t)

(custom-set-faces
 '(org-document-title ((t (:inherit outline-1 :height 1.5)))))
(custom-set-faces
 '(org-level-1 ((t (:inherit outline-1 :height 1.1)))))

(setq org-todo-keywords
      '((sequence "TODO" "LATER" "|" "DONE")))

More customization followed, the captain being a detail-oriented fellow. Everything, from the inheritance of the document title to the task keywords, had to be shipshape.

(global-org-modern-mode)

The whole ship was adorned with the ‘org-modern’ charm, setting the tone for our long voyage.

Pasted image 20230603191306.png
(setq org-default-notes-file (expand-file-name "inbox.org" my-org-root-directory))
(setq org-capture-templates
      `(("c" "Capture" entry (file+headline org-default-notes-file "LATER")
         "* LATER %?\n\n  %i\n\n  %a")))

The captain had an ingenious system for capturing fleeting ideas. Into the ‘inbox.org’ they went, to be sorted and dealt with later.

(my-leader-def
  :states '(normal visual emacs)
  :keymaps 'override
  "o" '(:ignore t :which-key "Org Mode")
  "oi" '(my-org-open-index-file :which-key "Open Index File")
  "oc" '(org-capture :which-key "Capture"))

Lastly, the captain etched onto his map - the leader definition. It contained the quickest routes to key tasks: opening the index file and capturing thoughts. One could sail the sea of ideas with ease.

So, fellow sailor, that be our tale. The ship be prepared, the sails set. The vast sea of thoughts, ideas, and knowledge awaits us!

Pasted image 20230603192245.png