Skip to content

My personal emacs configuration. This is not a starter kit.

License

Notifications You must be signed in to change notification settings

JungleCandy/ygg-emacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

199 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Ygg-Emacs

My personal Emacs configuration

Preamble

This is not a starter kit, but maybe someone will find it useful, just as I have found othersโ€™ configuration files useful, in particular, and not exclusively:

Most of the configurations are in this .org file, which is loaded in init.el

Installation

Install Emacs

macOS

I use Emacs Plus. On macOS this can be installed through:

brew tap d12frosted/emacs-plus

brew install emacs-plus@30

Linux

Installed with the App Manager

Install Spell checking support

macOS

brew install hunspell

in ~/Library/Spelling/ download and install the required dictionaries with

get http://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_GB.aff

get http://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_GB.dic

Running (getenv "LANG") on my system shows that the installed language is en_GB so there is no need to create symlinks to the directory as some web guides have suggested.

Linux

Installed with the App Manager

Personal dictionary

In the dotfiles repo there is a file hunspell_en_GB. Create a link to this with ln -s dotfiles/hunspell_en_GB ~/.hunspell_en_GB

Clone the repo

Clone this repo to .emacs.d

git clone https://github.com/JungleCandy/ygg-emacs.git .emacs.d

Install Fonts

Iโ€™m using Neotree with icons, this requires M-x all-the-icons-install-fonts to be run.

Org-roam

If using Org-roam, make sure the directory structure is set up as defined, and pull down the content.

Licence

GPL v3, obviously

Notes

Doing this in Org-Mode As a better way to be organised. And while I am looking at it I can try and figure out what I know and what I need to know about

  • Flyspell
  • Company
  • abbrev and family
  • Projectile
  • Org Mode

Configuration

Built-Ins

Configure what is built in.

Personal Information

(setq user-full-name "Abizer Nasir"
      user-mail-address "abizern@mac.com"
      user-domain "abizern.dev")  

Built-in Configuration

;; Reduce the frequency of garbage collection
(setq gc-cons-threshold 50000000)

;; Warn when opening very large files about 100MB
(setq large-file-warning-threshold 100000000)

;; We don't need a startup message.
(setq inhibit-startup-message t)

;; disable the annoying bell ring
(setq ring-bell-function 'ignore)

;; A suitably wide fill-column
(set-default 'fill-column 140)

;; Show column and line number in the modeline
(setq line-number-mode t)
(setq column-number-mode t)

;; Turn off modes that look ugly.
(mapc
 (lambda (mode)
   (when (fboundp mode)
     (funcall mode -1)))
 '(menu-bar-mode tool-bar-mode scroll-bar-mode horizontal-scroll-bar-mode))

;; more useful frame title, that show either a file or a
;; buffer name (if the buffer isn't visiting a file)
(setq frame-title-format
      '((:eval (if (buffer-file-name)
                   (abbreviate-file-name (buffer-file-name))
                 "%b"))))

;; Tab-bar-mode
(tab-bar-mode 1)
(setq tab-bar-show 1)

;; Always load the newest version of a file, prevents stale compiled elisp code
(setq load-prefer-newer t)

;; Tab indentation is a curse, a historical pestilence.
;; Turn it off and let's never talk about this default again.
(set-default 'indent-tabs-mode nil)

;; Move files to trash
(setq delete-by-moving-to-trash t)

;; Automatically save buffers before launching M-x compile and friends,
;; instead of asking you if you want to save.
(setq compilation-ask-about-save nil)

;; Make the selection work like most people expect.
(delete-selection-mode t)
(transient-mark-mode t)

;; Automatically update unmodified buffers whose files have changed.
(global-auto-revert-mode t)

;; We aren't using monospace typewriters anymore
(setq sentence-end-double-space nil)

;; Since ethan-wspace takes care of this for us, we don't need it
(setq mode-require-final-newline nil)
(setq require-final-newline nil)

;; Turn off defadvice warnings during startup
(setq ad-redefinition-action 'accept)

;; use hippie-expand instead of dabbrev
(global-set-key (kbd "M-/") 'hippie-expand)

;; Always indent after a newline
(define-key global-map (kbd "RET") 'newline-and-indent)

;; A quick major mode help with discover-my-major
(define-key 'help-command (kbd "C-m") 'discover-my-major)

;; Align your code in a pretty way.
(global-set-key (kbd "C-x \\") 'align-regexp)

;; Ask for y/n confirmation instead of yes/no
(fset 'yes-or-no-p 'y-or-n-p)

;; Winner mode for layout persistence
(add-hook 'after-init-hook #'winner-mode)

;; Make sure to always use UTF-8
(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

;; Make eshell work nicely with zsh
(setq shell-file-name "/bin/zsh")
(setq explicit-zsh-args '("--interactive" "--login"))
(setq comint-process-echoes 0)

;; prevents echoes of commands in shell
(add-hook 'shell-mode-hook
          (lambda () (setq comint-process-echoes t)))

Themes

Allow switching between the Dracula Pro theme and the Tron Legacy theme

I like a bar cursor, so that that up in advance.

(setq-default cursor-type 'bar)

First, create a convenience function to setup the cursor I like with Dracula Pro theme ,#+begin_src emacs-lisp (defun ygg/setup-dracula-cursor () โ€œSet a gold bar cursor for Dracula Pro.โ€ (when (display-graphic-p) (set-face-attribute โ€˜cursor nil :background โ€œgold1โ€)))

Add the custom theme load path to the list

(add-to-list 'custom-theme-load-path
             (expand-file-name "themes" user-emacs-directory))

Create a global var as the theme:

(defvar ygg/theme 'tron
  "Theme to load at startup.
Possible values are 'dracula or 'tron. Default is 'tron.")

(use-package tron-legacy-theme
  :ensure t
  :config
  (setq tron-legacy-theme-vivid-cursor t))

Load the chosen theme

(cond
 ((eq ygg/theme 'dracula)
  (load-theme 'dracula-pro-pro :no-confirm)
  (ygg/setup-dracula-cursor))
 ((eq ygg/theme 'tron)
  (require 'tron-legacy-theme)
  (load-theme 'tron-legacy t)))

Make sure the correct cursor is loaded depending on the chosen theme

(add-hook 'after-make-frame-functions
          (lambda (frame)
            (with-selected-frame frame
              (when (eq ygg/theme 'dracula)
                (ygg/setup-dracula-cursor)))))

Make the choice of theme interactive

(defun ygg/set-theme (theme)
  "Disable the current theme and load THEME."
  (interactive
   (list (intern (completing-read "Theme: " '(dracula tron) nil t))))
  (mapc #'disable-theme custom-enabled-themes)
  (setq ygg/theme theme)
  (pcase theme
    ('dracula
     (load-theme 'dracula-pro-pro t)
     (ygg/setup-dracula-cursor))
    ('tron
     (require 'tron-legacy-theme)
     (load-theme 'tron-legacy t))))

Custom storage directories

;; Directory for support files. Create if needed.
(defvar savefile-dir (expand-file-name "savefile" user-emacs-directory)
  "The directory that stores support files.")
(unless (file-exists-p savefile-dir)
  (make-directory savefile-dir))

;; Define where to keep the autoload declarations.
(setq autoload-file (expand-file-name "loaddefs.el" savefile-dir))

;; Define where to keep user-settings, and load them.
(setq custom-file (expand-file-name "custom.el" savefile-dir))
(load custom-file 'noerror)

;; User lisp files. Create if needed.
(defvar ygg-lisp-dir (expand-file-name "lisp" user-emacs-directory)
  "The directory for user lisp files.")
(unless (file-exists-p ygg-lisp-dir)
  (make-directory ygg-lisp-dir))
;; Add the user-lisp directory to the load path.
(add-to-list 'load-path ygg-lisp-dir)

;; store all backup and autosave files in the tmp dir
(setq backup-directory-alist
      `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" ,temporary-file-directory t)))

Uniquify

Better buffer names if they clash

(require 'uniquify)
(setq
 uniquify-buffer-name-style 'forward
 uniquify-separator "/"
 uniquify-after-kill-buffer-p t     ;; rename after killing a buffer
 uniquify-ignore-buffers-re "^\\*") ;; ignore special buffers

Packages

Set up for using packages

;; Update package metadata if required
(unless package-archive-contents
  (package-refresh-contents))

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

(require 'use-package)
(setq use-package-always-ensure t)

;; For more verbose startup, uncomment the line below
;; (setq use-package-verbose t)  

Productivity and usability

Diminish

Donโ€™t clutter the modeline with global minor modes

(use-package diminish
  :ensure t)

ace-window

Easily move between windows, optimised for Dvorak layout.

M-oPut up indicators to make moving between windows easier
C-x C-oSwap windows
C-u M-oSwaps current window with selected window
C-u C-u M-oDeletes the selected window
(use-package ace-window
  :ensure t
  :bind (("M-o" . ace-window)
         ("C-x C-o" . ace-swap-window))
  :config
  (setq aw-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n)))  

avy

Quick navigation by word or character

C-;avy-goto-word-1
C-:avy-goto-char
(use-package avy
  :ensure t
  :bind (("C-;" . avy-goto-word-1)
         ("C-:" . avy-goto-char)))

company

All good IDEs have some interactivity

(use-package company
  :init (add-hook 'after-init-hook #'global-company-mode)
  :commands company-mode
  :config
  ;; Enable company-mode globally.
  (global-company-mode +1)
  ;; Except when you're in term-mode.
  (setq company-global-modes '(not term-mode))
  ;; Give Company a decent default configuration.
  (setq company-minimum-prefix-length 2
        company-selection-wrap-around t
        company-show-numbers t
        company-tooltip-align-annotations t
        company-require-match nil
        company-dabbrev-downcase nil
        company-dabbrev-ignore-case nil)
  ;; Sort completion candidates that already occur in the current
  ;; buffer at the top of the candidate list.
  (setq company-transformers '(company-sort-by-occurrence))
  ;; Show documentation where available for selected completion
  ;; after a short delay.

  (use-package company-quickhelp
    :ensure t
    :after (company)
    :config
    (setq company-quickhelp-delay 1)
    (company-quickhelp-mode 1))
  ;; Use C-\ to activate the Company autocompleter.
  ;; We invoke company-try-hard to gather completion candidates from multiple
  ;; sources if the active source isn't being very forthcoming.

  (use-package company-try-hard
    :ensure t
    :after (company)
    :bind ("C-\\" . company-try-hard)
    :config
    (bind-keys :map company-active-map
               ("C-\\" . company-try-hard)))
  :diminish company-mode)  

eshell

eshell
(use-package eshell
  :ensure t)
eshell-git-prompt
(use-package eshell-git-prompt
  :after shell
  :ensure t)
eshell-syntax-highlighting
(use-package eshell-syntax-highlighting
  :ensure t
  :config
  (eshell-syntax-highlighting-global-mode +1)
  :init
  (defface eshell-syntax-highlighting-invalid-face
    '((t :inherit diff-error))
    "Face used for invalid Eshell commands."
    :group 'eshell-syntax-highlighting))

ethan-wspace

See more at https://github.com/glasserc/ethan-wspace

C-c cto clean up a file
(use-package ethan-wspace
  :ensure t
  :commands global-ethan-wspace-mode
  :config (setq mode-require-final-newline nil)
  (global-ethan-wspace-mode 1)
  :bind ("C-c c" . ethan-wspace-clean-all)
  :diminish ethan-wspace-mode)  

expand-region

Select successively larger logical units. Works really well with multiple-cursors

C-=Select and expand by logical units
M-C-=Contract the region be logical units
(use-package expand-region
  :ensure t
  :bind (("C-=" . er/expand-region)
         ("M-C-=" . er/contract-region)))

Ivy, Swiper, Counsel

C-s, C-rSearch in project
M-xRun command
C-x C-fOpen File
C-x bSwitch buffer
C-c kSearch project with ripgrep
C-c gCounsel-git - find git tracked file
C-C rOpen recent files list
;; Ivy: lightweight completion
(use-package ivy
  :diminish
  :init
  (ivy-mode 1)
  :custom
  (ivy-use-virtual-buffers t)
  (ivy-count-format "(%d/%d) ") ;; Show current/total count
  (enable-recursive-minibuffers t)
  (ivy-initial-inputs-alist nil)) ;; Remove '^' from certain prompts

;; Swiper: better search
(use-package swiper
  :bind (("C-s" . swiper)
         ("C-r" . swiper))) ;; Optional: replace isearch backward

;; Counsel: ivy-enhanced commands
(use-package counsel
  :diminish
  :after ivy
  :bind (("M-x" . counsel-M-x)
         ("C-x C-m" . counsel-M-x)
         ("C-x C-f" . counsel-find-file)
         ("C-x b" . counsel-ibuffer)
         ("C-c k" . counsel-rg)
         ("C-c g" . counsel-git)
         ("C-c r" . counsel-recentf))
  :config
  (counsel-mode 1))
;; Nicer display
(use-package ivy-rich
  :after ivy
  :init
  (ivy-rich-mode 1))

Projectile

For working in projects, integrated with Ivy, Swiper and Counsel

C-c p fFind file in current project
C-c p sSearch project with ripgrep
C-c p gGrop
C-c p pSwitch project
C-c p xFind references
(use-package projectile
  :diminish projectile-mode
  :init (define-key global-map (kbd "C-c p") 'projectile-command-map)
  :config
  (projectile-mode 1)
  :custom
  (projectile-completion-system 'ivy)
  (projectile-project-search-path '("~/Developer" "~/Developer/Tools for building" "~/Sites")) ; No depth restriction
  (projectile-sort-order 'recentf))

Git support

Status & Navigation
Open Magit statusC-x g
Refresh statusg
Cycle sectionsTAB / S-TAB
Next / prev sectionn / p
Visit file / commitRET
Staging / Committing
Stage file / hunks
Unstage file / hunku
Commitc c
Amend last commitc a
PushP P
Pull / FetchF F
Branches / Refs
Checkout branchb b
Create branchb c
Mergem m
Rebaser r
Delete branchb k
Rename branchb m
Forge โ€“ Issues / Pull Requests
Forge dispatch menu# (from magit-status)
List issues# i
List pull requests# p
Create issueM-x forge-create-issue
Create pull requestM-x forge-create-pullreq
Visit PR for current branchM-x forge-visit-pullreq
Fetch issues / PRs (sync)M-x forge-pull
Checkout PR branchb y / M-x forge-checkout-pullreq
Discussion & Comments
Add / send commentC-c C-c
Reply to commentr (on comment)
Add emoji reaction:
View discussion timelineRET (on issue/PR)
Miscellaneous
Toggle fine-grained diffd
Edit patch inlinee
Resolve merge conflicts +C-c ^ (or Ediff)
Quit Magit & restore layoutq (winner-undo)
Bonus Commands (to M-x)
magit-statusOpen status for current repo
forge-browse-issuesShow issues in Forge buffer
forge-pullSync issues & PRs from remote forge
forge-pushPush issue / PR updates
Magit
(defun my-magit-quit-session ()
  "Quit Magit and restore previous window configuration."
  (interactive)
  (kill-buffer)
  (winner-undo))

(use-package magit
  :commands (magit-status)
  :bind (("C-x g" . magit-status))
  :config
  ;; Make magit-status open in the same window (like fullscreen)
  (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)

  ;; Bind `q` to your custom quit in magit-status
  (define-key magit-status-mode-map (kbd "q") #'my-magit-quit-session))

;; Force git-commit-mode to load early for commit messages via emacsclient
(require 'git-commit)

(add-to-list 'auto-mode-alist '("COMMIT_EDITMSG\\'" . git-commit-mode))

(add-hook 'git-commit-mode-hook
          (lambda ()
            (setq fill-column 72)
            (turn-on-auto-fill)))
Forge

For GitHub integration - set up for an authenticated gh.

(use-package forge
  :after magit)
Git + Magit Integration
KeybindingAction
C-c gFuzzy find Git-tracked files
C-c lFuzzy search Git commit log
;; Use counsel-git to find files tracked by Git
(global-set-key (kbd "C-c g") #'counsel-git)
;; Use counsel-git-log for fuzzy searching Git commits
(global-set-key (kbd "C-c l") #'counsel-git-log)
Optional: Better Ivy Sorting and Fuzzy Matching

Adds smarter sorting and global fuzzy matching for all Ivy interfaces

;; Optional: Prescient sorting for Ivy
(use-package ivy-prescient
  :after counsel
  :config
  (ivy-prescient-mode 1)
  (prescient-persist-mode 1)
  (setq counsel-M-x-sort-function #'ivy-prescient-sort-function))

;; Optional: Enable fuzzy matching in Ivy
(setq ivy-re-builders-alist
      '((swiper . ivy--regex-plus)
        (t . ivy--regex-ignore-order)))

Which Key

Aids discoverability with all these shortcuts

(use-package which-key
  :diminish
  :config (which-key-mode))

diff-hl

Show changes in a file

C-c gprefix key
C-c g =diff-hl-goto-hunk
C-c g ndiff-hl-next-hunk
C-c g pdiff-hl-previous-hunk
C-c g rdiff-hl-revert-hunk
(define-prefix-command 'my/diff-hl-prefix)
(global-set-key (kbd "C-c g") 'my/diff-hl-prefix)


(use-package diff-hl
  :ensure t
  :hook ((prog-mode . diff-hl-mode)
         (text-mode . diff-hl-mode)
         (magit-pre-refresh . diff-hl-magit-pre-refresh)
         (magit-post-refresh . diff-hl-magit-post-refresh))
  :bind (("C-c g =" . diff-hl-diff-goto-hunk)
         ("C-c g n" . diff-hl-next-hunk)
         ("C-c g p" . diff-hl-previous-hunk) 
         ("C-c g r" . diff-hl-revert-hunk))
  :config
  (diff-hl-flydiff-mode 1)
  (diff-hl-show-hunk-mouse-mode 1))

Flyspell

Spell check prose and code.

M-x ispell-word RET`ispell-word`then press `a` to accept & add word
C-c w`ispell-word`one-key shortcut
;; Hunspell setup (shared across machines)
(setq ispell-program-name        "hunspell"
      ispell-dictionary          "en_GB"
      ispell-personal-dictionary "~/.hunspell_en_GB"
      ispell-hunspell-dictionary-alist
      '(("en_GB" "[A-Za-z]" "[^A-Za-z]" "[']" nil
         ("-d" "en_GB") nil utf-8)))

(use-package flyspell
  :ensure nil              ;; builtโ€“in
  :defer 2                 ;; give Emacs 2s to start up before loading, prevents recursion during tangling
  :hook ((text-mode     . flyspell-mode)
         (org-mode      . flyspell-mode)
         (markdown-mode . flyspell-mode)
         (latex-mode    . flyspell-mode)
         (prog-mode     . flyspell-prog-mode))
  :bind (("C-c w" . ispell-word))
  :config
  ;; red wavy underline for mis-spellings
  (set-face-attribute 'flyspell-incorrect nil
                      :underline '(:style wave :color "red")))

Key-chord

Move like a ninja if I could only remember the chords

jjavy-goto-word-1Jump forward by word
jlavy-goto-lineJump by line
jkavy-goto-charโ€˜kโ€™ like navigation
jfavy-goto-subword-0Fine grained symbol jumps
xxcounsel M-xEasier to reach for than M-x
dkdockerHelp with docker
dydocker-compose-helper-menuFor docker.yml
(use-package key-chord
  :ensure t
  :custom
  (key-chord-two-keys-delay 0.2) ;; adjust to your typing rhythm
  :config
  (key-chord-mode 1)
  (key-chord-define-global "jj" 'avy-goto-word-1)
  (key-chord-define-global "jl" 'avy-goto-line)
  (key-chord-define-global "jk" 'avy-goto-char)
  (key-chord-define-global "jf" 'avy-goto-subword-0)
  (key-chord-define-global "xx" #'counsel-M-x)
  (key-chord-define-global "dk" 'docker)
  (key-chord-define-global "dy" 'docker-compose-helper-menu))

multiple-cursors

Why edit one line when you can work on many

C->mc/mark-next-like-this
C-<mc/mark-previous-like-this
C-c C-cmc/mark-all-like-this
C-S-c C-S-cmc/edit-lines
C-S-c C-S-emc/edit-ends-of-lines
C-S-c C-S-amc/edit-beginnings-of-lines
(use-package multiple-cursors
  :ensure t
  :commands multiple-cursors-mode
  :bind (("C->" . mc/mark-next-like-this)
         ("C-<" . mc/mark-previous-like-this)
         ("C-c C-<" . mc/mark-all-like-this)
         ("C-S-c C-S-c" . mc/edit-lines)
         ("C-S-c C-S-e" . mc/edit-ends-of-lines)
         ("C-S-c C-S-a" . mc/edit-beginnings-of-lines))
  :config
  (setq mc/list-file (expand-file-name ".mc-lists.el" savefile-dir)))  

neotree

<F5>neotree-toggle

Bindings only in Neotree buffer.

n, pnext-line, previos-line
<SPC>, <RET>, <TAB>Open current item if file, Toggle if directory
UGo up a directory
gRefresh
AToggle maximise window
HToggle display hidden files
QRecursively open a directory
C-c C-nCreate file (directory if filename ends with //)
C-c C-dDelete file or directory
C-c C-rRename file or directory
C-c C-cChange the root of the directory
C-c C-pCopy a file or a directory
(use-package neotree
  :ensure t
  :bind ("<f5>" . neotree-toggle)
  :custom
  (neo-theme 'icons)
  (neo-smart-open t)
  (neo-autorefresh t)
  (neo-show-hidden-files t))

(use-package all-the-icons

  :ensure t
  :defer
  :if (display-graphic-p))

(use-package all-the-icons-completion
  :ensure t
  :defer
  :hook (marginalia-mode . #'all-the-icons-completion-marginalia-setup)
  :init
  (all-the-icons-completion-mode))

rainbow-mode

Colourise names of colours in certain modes

(use-package rainbow-mode
  :ensure t
  :config
  (dolist (mode '(css-mode less-css-mode html-mode web-mode))
    (add-hook (intern (concat (symbol-name mode) "-hook"))
              (lambda () (rainbow-mode))))
  :diminish rainbow-mode)  

recentf

Recent File handling

(use-package recentf
  :ensure t
  :init
  (setq recentf-save-file (expand-file-name "recentf" savefile-dir)
        recentf-max-saved-items 100
        recentf-max-menu-items 15
        recentf-auto-cleanup 'never
        recentf-exclude
        '("\\COMMIT_EDITMSG\\'"
          ".*-autoloads\\.el\\'"
          ".*/elpa/.*"
          "/tmp/"
          "^/ssh:"
          "^/sudo:"))
  :config
  (recentf-mode 1))

savehist

Save history.

(use-package savehist
  :config
  (setq savehist-additional-variables
        ;; search entries
        '(search-ring regexp-search-ring)
        ;; save every minute
        savehist-autosave-interval 60
        ;; keep the home clean
        savehist-file (expand-file-name "savehist" savefile-dir))
  (savehist-mode +1))  

saveplace

Save point position between sessions.

(use-package saveplace
  :ensure t
  :init
  (setq save-place-file (expand-file-name ".places" savefile-dir))
  :config
  (setq-default save-place t))

smartparens

Brackets are really, really important

C-M-fMove forward across one balanced expression
C-M-bMove backward across one balanced expression
C-M-nMove forward out of one level of parentheses
C-M-dMove forward down one level of sexp
C-M-uMove backward out of one level of parentheses
C-M-pMove backward down one level of sexp
C-M-wCopy the following ARG expressions to the kill-ring (sp-copy-sexp)
M-sUnwrap the current list
M-rUnwrap the list and kill everything inside expect the next expression
C-)Slurp the following list into current by moving the closing delimiter
C-}Remove the last sexp in the current list by moving the closing delimiter
C-(Slurp the preceding sexp into the current one my moving the opening delimeter
C-{Barfs backwards
M-SSplit the list or string at point into two
M-JJoin the sexp before and after the point if they are of the same type
C-M-tTranspose the expressions around the point
(use-package smartparens
  :diminish
  :ensure t
  :init
  (require 'smartparens-config)
  :hook
  ((emacs-lisp-mode . smartparens-strict-mode)
   (lisp-mode . smartparens-strict-mode)
   (sly-mrepl-mode . smartparens-strict-mode))
  :config
  (smartparens-global-mode t)
  (show-smartparens-global-mode t)
  (setq sp-highlight-pair-overlay nil
        sp-highlight-wrap-overlay nil
        sp-highlight-wrap-tag-overlay nil)
  ;; Avoid pairing backticks in elisp strings
  (sp-local-pair 'emacs-lisp-mode "`" nil :when '(sp-in-string-p))
  :bind
  (("C-M-f" . sp-forward-sexp)
   ("C-M-b" . sp-backward-sexp)
   ("C-M-n" . sp-up-sexp)
   ("C-M-d" . sp-down-sexp)
   ("C-M-u" . sp-backward-up-sexp)
   ("C-M-p" . sp-backward-down-sexp)
   ("C-M-w" . sp-copy-sexp)
   ("M-s" . sp-splice-sexp)
   ("M-r" . sp-splice-sexp-killing-around)
   ("C-)" . sp-forward-slurp-sexp)
   ("C-}" . sp-forward-barf-sexp)
   ("C-(" . sp-backward-slurp-sexp)
   ("C-{" . sp-backward-barf-sexp)
   ("M-S" . sp-split-sexp)
   ("M-J" . sp-join-sexp)
   ("C-M-t" . sp-transpose-sexp)))

super-save

Automatically save files

(use-package super-save
  :diminish
  :ensure t
  :config
  (super-save-mode +1))  

undo-fu

A little simpler than undo tree

C-zUndo
C-S-zRedo
(use-package undo-fu
  :ensure t
  :config
  (global-unset-key (kbd "C-z"))
  (global-set-key (kbd "C-z") 'undo-fu-only-undo)
  (global-set-key (kbd "C-S-z") 'undo-fu-only-redo))  

zop-to-char

A better version of zap-to-char.

(use-package zop-to-char
  :ensure t
  :bind
  (("M-z" . zop-up-to-char)
   ("M-Z" . zop-to-char)))  

F for filepaths

(use-package f
  :ensure t)

Writing Modes

markdown-mode

Mostly the mode hooks and a couple of keybindings

M-nAdd line below
M-pAdd line above
(use-package markdown-mode
  :ensure t
  :mode ("\\.md\\'" "\\.markdown\\'")
  :hook ((markdown-mode . visual-line-mode)
         (markdown-mode . spell-fu-mode))
  :bind (:map markdown-mode-map
              ("M-n" . open-line-below)
              ("M-p" . open-line-above)))

Latex

(use-package tex
  :ensure auctex
  :hook ((LaTeX-mode . visual-line-mode)
         (LaTeX-mode . spell-fu-mode)
         (LaTeX-mode . LaTeX-math-mode)
         (LaTeX-mode . turn-on-reftex))
  :custom
  (TeX-auto-save t)
  (TeX-parse-self t)
  (TeX-save-query nil)
  (TeX-PDF-mode t)
  (TeX-source-correlate-mode t)
  (TeX-source-correlate-start-server t)
  ;; Default to latexmk
  (TeX-command-default "LatexMk")
  (TeX-command-list
   '(("LatexMk" "latexmk -pdf -interaction=nonstopmode -synctex=1 %s"
      TeX-run-TeX nil t
      :help "Run LatexMk")))
  ;; View PDFs with PDF Tools if available
  (TeX-view-program-selection
   '((output-pdf "PDF Tools"))))

PDF Viewer integration

;; Compile LaTeX to PDF by default
(setq TeX-PDF-mode t)

;; Set path to epdfinfo dynamically (important for macOS)
(let ((default-directory "~/.emacs.d/elpa/"))
  (setq pdf-info-epdfinfo-program
        (expand-file-name
         "epdfinfo"
         (car (directory-files default-directory t "pdf-tools.*" t)))))

;; Auto-revert PDF buffer after LaTeX compilation
(use-package pdf-tools
  :ensure t
  :config
  (pdf-tools-install)
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer))

Org-Mode Configuration

Support for using Org mode for writing, blogging and note-taking.

(use-package org
  :ensure t
  :after yasnippet
  :hook ((org-mode . visual-line-mode)
         (org-mode . yas-minor-mode))
  :bind (:map org-mode-map
              ("M-j" . org-metaup)
              ("M-k" . org-metadown)
              ("C-c t" . yas-next-field))
  :config
  (setq org-directory "~/Documents/Org"
        org-metadir (concat org-directory "_orgmata/")
        org-default-notes-file (concat org-directory "refile.org")
        org-archive-location (concat org-metadir "archive.org::date-tree")
        org-agenda-files '("~/Documents/Org/")
        org-startup-indented t
        org-src-tab-acts-natively t
        org-src-fontify-natively t
        org-src-preserve-indentation t
        org-edit-src-content-indentation 0))

(defun ygg/fix-org-tab-width ()
  "Ensure tab-width is 8 in Org mode buffers to satisfy parser requirements."
  (setq-local tab-width 8))

(add-hook 'org-mode-hook #'ygg/fix-org-tab-width)
Keybinding corrections
C-c !Insert an inactive timestamp (between square brackets) default behaviour
C-c fFlycheck menu command prefix
(with-eval-after-load 'flycheck
  (define-key flycheck-mode-map (kbd "C-c !") nil)) ;; remove prefix map

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c !") #'org-time-stamp-inactive))

(with-eval-after-load 'flycheck
  (define-key flycheck-mode-map (kbd "C-c f") flycheck-command-map))
Org Visual Enhancements
(use-package org-bullets
  :ensure t
  :hook (org-mode . org-bullets-mode))

(setq org-pretty-entities t)
Org Writing & Note Tools
(use-package org-cliplink
  :ensure t
  :after org
  :bind (:map org-mode-map
              ("C-c M-l" . org-cliplink)))
Org-roam

Build a cross-platform โ€œsecond brainโ€ (macOS + Linux) backed by a Git repo.

  • Repo root: ~/org-roam/
  • Notes: ~/org-roam/notes/
  • Dailies: ~/org-roam/daily/
  • Database: ~/org-roam/org-roam.db (gitignored; rebuilt locally)

Key bindings:

KeyCommandWhat it does
C-c n forg-roam-node-findFind a node; creates it if it doesnโ€™t exist.
C-c n iorg-roam-node-insertInsert a link to a node at point (create if needed).
C-c n corg-roam-captureCreate a new node using capture templates.
C-c n lorg-roam-buffer-toggleToggle backlinks/side buffer for the current node.
C-c n dorg-roam-dailies-capture-todayOpen/create todayโ€™s daily note.

Troubleshooting:

  • M-x org-roam-db-sync (rebuild DB)
  • M-x org-roam-db-clear-all then org-roam-db-sync (full reset)
(use-package org-roam
  :ensure t
  :after org
  :init
  ;; Paths
  (setq org-roam-repo-root
        (file-truename (expand-file-name "~/org-roam/")))

  (setq org-roam-directory
        (file-truename (expand-file-name "notes" org-roam-repo-root)))

  (setq org-roam-dailies-directory
        (file-truename (expand-file-name "daily" org-roam-repo-root)))

  (setq org-roam-db-location
        (file-truename (expand-file-name "org-roam.db" org-roam-repo-root)))

  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n d" . org-roam-dailies-capture-today))
  :config
  (org-roam-db-autosync-mode)

  ;; Node capture template
  (setq org-roam-capture-templates
        '(("d" "default" plain "%?"
           :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n#+date: %U\n\n")
           :unnarrowed t)))

  ;; Daily capture template
  (setq org-roam-dailies-capture-templates
        '(("d" "default" entry "* %?"
           :if-new (file+head "%<%Y-%m-%d>.org"
                              "#+title: %<%Y-%m-%d>\n\n")))))
org-roam-ui

For nice visualisation other than using backlinks

(use-package org-roam-ui
  :ensure t
  :after org-roam
  :config
  (setq org-roam-ui-sync-theme t
        org-roam-ui-follow t
        org-roam-ui-update-on-save t
        org-roam-ui-open-on-start t))
Org Agenda & GTD

Not sure if I actually use this, but leaving it in for now.

(with-eval-after-load 'org
  (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
                            (sequence "DRAFT(r)" "|" "PUBLISH(p)"))
        org-use-fast-todo-selection t
        org-log-done 'time
        org-treat-S-cursor-todo-selection-as-state-change nil))
Org and Latex
(with-eval-after-load 'ox-latex
  (add-to-list 'org-latex-classes
               '("article"
                 "\\documentclass[a4paper]{scrartcl}
  \\usepackage[utf8]{inputenc}
  \\usepackage{amsmath}
  \\usepackage{amssymb}
  \\usepackage{fullpage}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

  (add-to-list 'org-latex-classes
               '("tufte-handout"
                 "\\documentclass[a4paper]{tufte-handout}
  \\usepackage[utf8]{inputenc}
  \\usepackage{amsmath}
  \\usepackage{amssymb}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
Ox-Hugo for blogging
(use-package ox-hugo
  :ensure t
  :after ox)
Org-Drill

Spaced Repetitive learning.

org-drillStart a drill
qFinish the drill early
sSkip the current item
eEscape the drill and edit the current buffer
org-drill-resumeContinue the drill after editing

Result of recall can be scored:

0Wrong, answer unfamiliar
1Wrong answer
2Almost, but not quite correct
3Correct answer with some effort
4Correct answer with a little thought
5Correct answer, effortless
?Show help for these options
(use-package org-drill
  :ensure t
  :config
  (add-to-list 'org-modules 'org-drill)
  (setq org-drill-add-random-noise-to-intervals-p t
        org-drill-learn-fraction 0.25))
Org Global Keybindings
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)

Programming Support

Helpers

editorconfig

Be more explicit about layout

(use-package editorconfig
  :ensure t
  :config (editorconfig-mode +1))

Apply styles to emacs modes that donโ€™t pick these up automatically

(defun ygg/apply-editorconfig-style ()
  "Mimic .editorconfig indentation settings for various major modes."
  (cond
   ;; Swift โ€” 2-space soft indent
   ((derived-mode-p 'swift-mode)
    (setq indent-tabs-mode nil
          tab-width 2
          swift-indent-offset 2))

   ;; C/C++/ObjC โ€” 2-space soft indent
   ((derived-mode-p 'c-mode 'c++-mode 'objc-mode)
    (setq indent-tabs-mode nil
          tab-width 2
          c-basic-offset 2))

   ;; Python โ€” PEP 8
   ((derived-mode-p 'python-mode)
    (setq indent-tabs-mode nil
          tab-width 4
          python-indent-offset 4))

   ;; Makefile โ€” tabs required
   ((derived-mode-p 'makefile-gmake-mode)
    (setq indent-tabs-mode t
          tab-width 8))

   ;; Org โ€” strict 8-wide tabs
   ((derived-mode-p 'org-mode)
    (setq indent-tabs-mode nil
          tab-width 8))

   ;; Markdown / text โ€” allow spacey editing, usually 2
   ((derived-mode-p 'markdown-mode 'text-mode)
    (setq indent-tabs-mode nil
          tab-width 2))))

(add-hook 'org-mode-hook #'ygg/apply-editorconfig-style)
(add-hook 'org-src-mode-hook #'ygg/apply-editorconfig-style)

YASnippets

(use-package yasnippet
  :ensure t
  :init
  (progn
    (add-hook 'after-save-hook
              (lambda ()
                (when (eql major-mode 'snippet-mode)
                  (yas-reload-all))))
    (setq yas-snippet-dirs (list (f-expand "snippets" user-emacs-directory)))
    (setq yas-indent-line 'auto)
    (yas-global-mode 1))
  :mode ("\\.yasnippet" . snippet-mode))

Flycheck

(use-package flycheck
  :ensure t
  :init (global-flycheck-mode))

LSP Support

;; Global LSP configuration
(use-package lsp-mode
  :ensure t
  :hook ((swift-mode . lsp)
         (go-mode . lsp)
         (python-mode . lsp))
  :commands lsp
  :custom
  (lsp-eldoc-enable-hover nil)) ;; Let lsp-ui handle hover

(use-package lsp-ui
  :ensure t
  :hook (lsp-mode . lsp-ui-mode)
  :custom
  (lsp-ui-doc-enable t)
  (lsp-ui-doc-show-with-cursor t)
  (lsp-ui-sideline-enable t))

Swift Development

My primary language. I should be able to use it on Linux as well.

First, locate `sourcekit-lsp`:

(defun find-sourcekit-lsp ()
  (or (executable-find "sourcekit-lsp")
      (and (eq system-type 'darwin)
           (string-trim (shell-command-to-string "xcrun -f sourcekit-lsp")))
      "sourcekit-lsp"))

Enable `swift-mode` and hook it up to LSP:

(use-package swift-mode
  :ensure t
  :custom
  (swift-mode:basic-offset 2)
  :mode "\\.swift\\'"
  :interpreter "swift")

(use-package lsp-sourcekit
  :ensure t
  :after lsp-mode
  :custom
  (lsp-sourcekit-executable (find-sourcekit-lsp)))

Common-lisp

I use SLY not Slime

M-hsly-documentation-lookup
(defun lisp-program-location ()
  "Return the SBCL path based on OS."
  (cond
   ((eq system-type 'darwin) "/opt/homebrew/bin/sbcl")      ;; macOS
   ((eq system-type 'gnu/linux) "ros run")                  ;; Linux via Roswell
   (t "sbcl")))                                             ;; fallback

(use-package sly
  :ensure t
  :init
  (setq inferior-lisp-program (lisp-program-location))
  :config
  (define-key sly-prefix-map (kbd "M-h") 'sly-documentation-lookup))

Rust

Portable Rust configuration for Emacs 30.2 and environments where Rust may or may not be installed.

Useful Emacs keybindings and commands for working with Rust, rust-analyzer, Cargo, and Eglot. Safe to keep in your config even on machines without Rust.

CategoryCommand / KeybindingWhat It Does
**Editing**`TAB` / `C-M-i`Trigger completion (via rust-analyzer)
`M-.`Jump to definition
`M-,`Jump back
`C-c C-d`Show documentation / type info (Eglot)
`C-c C-r`Apply code action / fix (Eglot)
`C-M-`Indent region
(auto)Format on save (rustfmt)
**Cargo**`M-x cargo-build`Run `cargo build`
`M-x cargo-run`Run `cargo run`
`M-x cargo-test`Run `cargo test`
`M-x cargo-clean`Clean project
`M-x cargo-check`Type-check project (`cargo check`)
**Eglot**`M-x eglot`Start Eglot manually
(auto)Eglot starts automatically in Rust buffers
`M-x eglot-code-actions`Show code fixes / refactorings
`M-x eglot-reconnect`Restart Eglot
`M-x eglot-shutdown`Stop LSP server
**Rust Tools**`M-x rust-format-buffer`Format buffer using rustfmt
(auto)rustfmt runs on save
**Navigation**`M-.`Jump to definition
`M-,`Go back
`C-s` / `C-r`Search forward/backward
**Help**`C-h f`Describe function
`C-h v`Describe variable
`C-h m`Show mode help (very useful!)
;;; ----------------------
;;; Rust (tree-sitter) support
;;; ----------------------

;; Prefer the tree-sitter Rust mode
(use-package rust-ts-mode
  :ensure t
  :mode ("\\.rs\\'" . rust-ts-mode)
  :init
  ;; Use rustfmt on save (if available)
  (setq rust-format-on-save t))

;; Start Eglot only if rust-analyzer is present
(defun my/rust-eglot-if-available ()
  (when (executable-find "rust-analyzer")
    (eglot-ensure)))

(use-package eglot
  :ensure nil                     ;; Eglot is built into Emacs 30
  :hook (rust-ts-mode . my/rust-eglot-if-available))

;; Enable cargo-minor-mode only if cargo is installed
(defun my/rust-cargo-if-available ()
  (when (executable-find "cargo")
    (cargo-minor-mode 1)))

(use-package cargo
  :ensure t
  :hook (rust-ts-mode . my/rust-cargo-if-available))

Python Development

Active with venv, once I figure out how to do that. Run M-x python-activate and point it at the .venv directory, although there is now a hook to point to one at the root of a project.

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

(use-package blacken
  :ensure t
  :hook (python-mode . blacken-mode)
  :custom (blacken-line-length 88))

(use-package py-isort
  :ensure t
  :hook (before-save . py-isort-before-save))

(defun ygg/auto-pyvenv-activate ()
  "Auto-activate local .venv if present."
  (let ((root (locate-dominating-file default-directory ".venv")))
    (when root
      (pyvenv-activate (expand-file-name ".venv" root)))))

(add-hook 'python-mode-hook #'ygg/auto-pyvenv-activate)

Go Mode

(use-package go-mode
  :ensure t
  :mode "\\.go\\'"
  :hook ((before-save . gofmt-before-save)))

web-mode

C-c C-rMark the tag weโ€™re in and itโ€™s pair for renaming
(defun my-web-mode-hook ()
  (setq web-mode-markup-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-code-indent-offset 2))

(use-package web-mode
  :ensure t
  :mode (;; Want to use web-mode for HTML, not default html-mode.
         ("\\.html?\\'" . web-mode)
         ;; Add some extensions as per web-mode docs
         ("\\.phtml\\'" . web-mode)
         ("\\.tpl\\.php\\'" . web-mode)
         ("\\.[agj]sp\\'" . web-mode)
         ("\\.erb\\'" . web-mode)
         ("\\.mustache\\'" . web-mode)
         ("\\.djhtml\\'" . web-mode))
  :hook (web-mode . my-web-mode-hook)
  :config
  (setq-default web-mode-enable-auto-pairing t
                web-mode-enable-auto-closing t
                web-mode-enable-auto-quoting t
                web-mode-enable-css-colorization t
                web-mode-enable-comment-keywords t
                web-mode-enable-current-column-highlight t)
  (bind-keys :map web-mode-map
             ("C-c C-r" . 'mc/mark-sgml-tag-pair)))

json-mode

C-c <tab>Beautify
(use-package json-mode
  :ensure t
  :mode "\\.json\\'"
  :hook ((json-mode . spell-fu-mode)
         (json-mode . smartparens-strict-mode))
  :bind (:map json-mode-map
              ("C-c <tab>" . json-mode-beautify)))

Docker Support

docker.el

(use-package docker
  :ensure t
  :bind ("C-c d" . docker))

dockerfile-mode

(use-package dockerfile-mode
  :ensure t
  :mode "Dockerfile\\'")

yaml-mode

(use-package yaml-mode
  :ensure t
  :mode "\\.ya?ml\\'")

Docker Transient Menu

C-c DShow docker transient menu
(require 'transient)

(transient-define-prefix docker-transient-menu ()
  "Docker & Docker Compose Menu"
  ["Docker"
   ("p" "ps"     (lambda () (interactive) (shell-command "docker ps")))
   ("l" "logs"   (lambda () (interactive) (shell-command "docker logs -f $(docker ps -lq)")))
   ("r" "restart last" (lambda () (interactive) (shell-command "docker restart $(docker ps -lq)")))
   ("s" "stop all"     (lambda () (interactive) (shell-command "docker stop $(docker ps -q)")))
   ("c" "clean system" (lambda () (interactive) (shell-command "docker system prune -af --volumes")))]

  ["Compose"
   ("u" "compose up"     (lambda () (interactive) (shell-command "docker-compose up -d")))
   ("d" "compose down"   (lambda () (interactive) (shell-command "docker-compose down")))
   ("b" "compose build"  (lambda () (interactive) (shell-command "docker-compose build")))
   ("P" "compose ps"     (lambda () (interactive) (shell-command "docker-compose ps")))])

(global-set-key (kbd "C-c D") #'docker-transient-menu)

Docker Compose Tools

C-c C-ddocker-compose-helper-menu
(require 'transient)

(transient-define-prefix docker-compose-helper-menu ()
  "Docker Compose Utilities"
  ["YAML & Validation"
   ("v" "config validation" (lambda () (interactive)
                              (shell-command "docker-compose config")))
   ("y" "show resolved YAML" (lambda () (interactive)
                               (shell-command "docker-compose config --resolve-image-digests")))]

  ["Service Info"
   ("l" "list services" (lambda () (interactive)
                          (shell-command "docker-compose config --services")))
   ("i" "inspect service" (lambda ()
                            (interactive)
                            (let ((svc (read-shell-command "Service name: ")))
                              (shell-command (format "docker-compose config --service %s" svc)))))])

(global-set-key (kbd "C-c C-d") #'docker-compose-helper-menu)

Docker Babel Support

(org-babel-do-load-languages
 'org-babel-load-languages
 '((shell . t)))
Example: Run docker in Org-mode
docker ps --format "{{.Names}}"

Helper Functions

goto-line-with-feedback

M-g M-gShow line numbers temporarily and prompt for the line to move to
(defun goto-line-with-feedback ()
  "Show line numbers temporarily, while prompting for the line number input."
  (interactive)
  (unwind-protect
      (progn
        (display-line-numbers-mode 1)
        (call-interactively 'goto-line))
    (display-line-numbers-mode -1)))

;; Remaps goto-line so that line numbers are turned on only when needed. M-g M-g
(global-set-key [remap goto-line] 'goto-line-with-feedback)  

json-format

Pretty print JSON using the Python helper function

(defun json-format ()
  "Reformats the JSON in the region for humans."
  (interactive)
  (save-excursion
    (shell-command-on-region (mark) (point) "python -m json.tool" (buffer-name) t)))

Custom Date insertion

C-c C-d13/4/2024
C-u C-t C-d2024-04-13
C-u C-u C-d C-dTuesday, April 13, 2024
C-c C-tISO 8601 formatted date/time
;; Insert Date
;; Usage
;; - `C-c C-d` -> 13/04/2024
;; - `C-u C-c C-d` -> 2024-04-13
;; - `C-u C-u C-d C-d` -> Tuesday, April 13, 2024
(defun ygg-insert-date (prefix)
  "Insert the current date. With prefix-argument use ISO format. With two
        prefix arguments, write out the day and month name"
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%d/%m/%Y")
                 ((equal prefix '(4)) "%F")
                 ((equal prefix '(16)) "%A, %B %d, %Y")))
        (system-time-locale "en_GB"))
    (insert (format-time-string format))))

(defun ygg-insert-iso-date-time ()
  "Insert the current date in ISO format for UTC"
  (interactive)
  (insert (format-time-string "%FT%T%z" nil "UTC")))

(global-set-key (kbd "C-c C-d") 'ygg-insert-date)
(global-set-key (kbd "C-c C-t") 'ygg-insert-iso-date-time)

ygg/wrap-with

Wrapper for parentheses

(defun ygg/wrap-with (s)
  "Create a wrapper function for smartparens using S."
  `(lambda (&optional arg)
     (interactive "P")
     (sp-wrap-with-pair ,s)))  

Key Bindings

Xcode Line up/down

Not the easiest on a compact keyboard, but still useful to have.

M-S-]Move line up
M-S-[Move line down
;; Xcode binding to move line up
(defun ygg/move-line-up ()
  "Move the current line up"
  (interactive)
  (transpose-lines 1)
  (forward-line -2)
  (indent-according-to-mode))

(global-set-key (kbd "M-s-]")
                (lambda ()
                  (interactive)
                  (ygg/move-line-up)))

;; Xcode binding to move line down
(defun ygg/move-line-down ()
  "Move the current line down"
  (interactive)
  (forward-line 1)
  (transpose-lines 1)
  (forward-line -1)
  (indent-according-to-mode))

(global-set-key (kbd "M-s-[")
                (lambda ()
                  (interactive)
                  (ygg/move-line-down)))  

Better Movement

;; Move about more quickly
;; move about in steps of 5 with C-S insteard of just C-
(global-set-key (kbd "C-S-n")
                (lambda ()
                  (interactive)
                  (ignore-errors (forward-line 5))))

(global-set-key (kbd "C-S-p")
                (lambda ()
                  (interactive)
                  (ignore-errors (forward-line -5))))

(global-set-key (kbd "C-S-f")
                (lambda ()
                  (interactive)
                  (ignore-errors (forward-char 5))))

(global-set-key (kbd "C-S-b")
                (lambda ()
                  (interactive)
                  (ignore-errors (backward-char 5))))

Remove Super Keybindings (conflict prevention)

Some packages bind Super key combinations (e.g. s-g, s-r), which may cause startup errors on systems where Super is not a recognized modifier. This block disables those bindings safely.

;; Remove problematic Super keybindings after init
(add-hook 'after-init-hook
          (lambda ()
            ;; Unbind from global-map safely
            (define-key global-map (kbd "s-g") nil)
            (define-key global-map (kbd "s-r") nil)
            (define-key global-map (kbd "s-s") nil)
            (define-key global-map (kbd "s-x") nil)))

;; Also remove Super bindings from projectile-mode-map after it's loaded
(with-eval-after-load 'projectile
  (define-key projectile-mode-map (kbd "s-g") nil)
  (define-key projectile-mode-map (kbd "s-r") nil)
  (define-key projectile-mode-map (kbd "s-s") nil)
  (define-key projectile-mode-map (kbd "s-x") nil))

Final Super Key Purge (prevent s-* errors permanently)

(defun ygg/unbind-super-globally ()
  "Unbind all known s-* keys that might be bound by packages or OS overrides."
  (dolist (key '("s-g" "s-r" "s-s" "s-x" "s-i"))
    (when (stringp key)
      (ignore-errors (define-key global-map (kbd key) nil)))))

(add-hook 'after-init-hook #'ygg/unbind-super-globally)

(with-eval-after-load 'projectile
  (dolist (key '("s-g" "s-r" "s-s" "s-x" "s-i"))
    (ignore-errors (define-key projectile-mode-map (kbd key) nil))))

Set up personal keymaps

Override the package shortcuts that use the Super Key

(define-prefix-command 'my/project-map)
(global-set-key (kbd "C-c C-p") 'my/project-map)

(with-eval-after-load 'projectile
  (define-key my/project-map (kbd "f") #'projectile-find-file)
  (define-key my/project-map (kbd "s") #'projectile-ripgrep)
  (define-key my/project-map (kbd "p") #'projectile-switch-project))

Project Hydra

Hydra provides a transient menu for fast access to project tools.

Use C-c C-h to bring it up.

(use-package hydra
  :ensure t)

(defhydra hydra-project (:color blue :hint nil)
  "
๐Ÿ›  Project Tools:

_f_: Find file     _s_: Search (ripgrep)    _g_: Grep
_x_: References    _p_: Switch project      _q_: Quit
"
  ("f" counsel-projectile-find-file)
  ("s" counsel-projectile-rg)
  ("g" projectile-grep)
  ("x" projectile-find-references)
  ("p" counsel-projectile-switch-project)
  ("q" nil "Quit"))

(global-set-key (kbd "C-c C-h") 'hydra-project/body)

About

My personal emacs configuration. This is not a starter kit.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors