4177 lines
128 KiB
Org Mode
4177 lines
128 KiB
Org Mode
#+title: Emacs configuration
|
||
#+author: Aryadev Chavali
|
||
#+description: My Emacs configuration
|
||
#+property: header-args:emacs-lisp :tangle config.el :comments link :results none
|
||
#+startup: noindent
|
||
#+options: toc:t num:t
|
||
#+latex_header:\usepackage[margin=1.0in]{geometry}
|
||
#+latex_class: article
|
||
#+latex_class_options: [a4paper,12pt]
|
||
|
||
* Introduction
|
||
Welcome to my Emacs configuration. This thing is quite big, but a lot
|
||
of it has been "write and forget" i.e. I've only needed to configure
|
||
it once. Sections tagged =WAIT= are currently unused, usually with
|
||
some reasoning given.
|
||
|
||
Some sections border on blog posts justifying why I think they're good
|
||
applications or giving some greater reasoning about my specific
|
||
configuration of a package. If you don't really want that, you may
|
||
tangle this file and just read the source code.
|
||
* Basics
|
||
Firstly, set full name and mail address. This is used in encryption
|
||
and mailing.
|
||
#+begin_src emacs-lisp
|
||
(setq user-full-name "Aryadev Chavali"
|
||
user-mail-address "aryadev@aryadevchavali.com")
|
||
#+end_src
|
||
|
||
Let's set all yes or no questions to single letter responses.
|
||
#+begin_src emacs-lisp
|
||
(fset 'yes-or-no-p 'y-or-n-p)
|
||
#+end_src
|
||
|
||
Set the encoding to UTF-8-Unix by default.
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:demand t
|
||
:init
|
||
(setq-default buffer-file-coding-system 'utf-8-unix
|
||
save-buffer-coding-system 'utf-8-unix))
|
||
#+end_src
|
||
|
||
Setup automatic saving for files (in case of system failure) and
|
||
auto-revert-mode (which refreshes the buffer on changes to the
|
||
underlying file). Along with that, set the custom-file (which holds
|
||
temporary customisation) in the etc folder.
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:demand t
|
||
:init
|
||
(setq backup-directory-alist `(("." . ,(no-littering-expand-var-file-name "saves/")))
|
||
global-auto-revert-non-file-buffers t
|
||
auto-revert-verbose nil)
|
||
:config
|
||
(global-auto-revert-mode 1))
|
||
#+end_src
|
||
* Custom functionality
|
||
Functions that don't require a packages to work other than Emacs,
|
||
which means I can define them early. These are used much later in the
|
||
config.
|
||
** WAIT Toggle buffer
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Like VSCode's toggling feature for just the terminal but now for
|
||
any buffer of choice, as long as I can generate it via a command.
|
||
|
||
2024-04-23: Don't need this anymore due to
|
||
~switch-to-buffer-obey-display-actions~.
|
||
#+begin_src emacs-lisp
|
||
(with-eval-after-load "window"
|
||
(defmacro +oreo/create-toggle-function (func-name buf-name
|
||
buf-create
|
||
&optional accept-numeric)
|
||
"Generate a function named FUNC-NAME that toggles the buffer with
|
||
name BUF-NAME, using BUF-CREATE to generate it if buffer BUF-NAME
|
||
does not exist already.
|
||
|
||
BUF-NAME cannot be a regexp, it must be a fixed name.
|
||
|
||
ACCEPT-NUMERIC modifies the function to allow numeric arguments
|
||
via C-u. Mostly used in Eshell."
|
||
(let ((interactive-arg
|
||
(if accept-numeric '(interactive "p") '(interactive)))
|
||
(arguments
|
||
(if accept-numeric '(&optional arg) nil))
|
||
(buffer-name (if accept-numeric
|
||
`(if (= arg 1)
|
||
,buf-name
|
||
(concat ,buf-name "<" (int-to-string arg) ">"))
|
||
buf-name))
|
||
(buffer-create (if accept-numeric
|
||
`(if (= arg 1)
|
||
(,buf-create)
|
||
(,buf-create arg))
|
||
`(,buf-create))))
|
||
`(defun ,func-name ,arguments
|
||
,interactive-arg
|
||
(let* ((buffer (or (get-buffer ,buffer-name)
|
||
,buffer-create))
|
||
(displayed (get-buffer-window buffer)))
|
||
(if displayed
|
||
(delete-window displayed)
|
||
(display-buffer buffer)
|
||
(select-window (get-buffer-window buffer))))))))
|
||
#+end_src
|
||
** Auto-run command after-save-hook
|
||
Define a macro which creates hooks into the ~after-save-hook~. On
|
||
certain ~conditions~ being met, ~to-run~ is evaluated.
|
||
#+begin_src emacs-lisp
|
||
(use-package simple
|
||
:defer t
|
||
:config
|
||
(defmacro +oreo/create-auto-save (conditions &rest to-run)
|
||
"Create a hook for after saves, where (on CONDITIONS being met)
|
||
TO-RUN is evaluated. "
|
||
`(add-hook 'after-save-hook #'(lambda ()
|
||
(interactive)
|
||
(when ,conditions
|
||
,@to-run)))))
|
||
#+end_src
|
||
** Procedure
|
||
A ~lambda~ which takes no arguments is a procedure. This macro
|
||
generates procedures, with the parameters of the macro being the body
|
||
of the procedure. It returns it in quoted form, as that is the most
|
||
common use of this macro.
|
||
|
||
(You may notice ~proc~ is used where the return value is irrelevant).
|
||
#+begin_src emacs-lisp
|
||
(defmacro proc (&rest BODY)
|
||
"For a given list of forms BODY, return a quoted 0 argument
|
||
lambda."
|
||
`(quote (lambda nil ,@BODY)))
|
||
#+end_src
|
||
** System specificity
|
||
A macro that acts as a switch case on ~(system-name)~ which allows the
|
||
writing of system specific code. For me this is for my desktop and
|
||
laptop, particularly for font sizes. Though there may be an easier
|
||
solution than this, this seems simple enough.
|
||
#+begin_src emacs-lisp
|
||
(defmacro +oreo/sys-name-cond (&rest pairs)
|
||
"Switch case on result of function `system-name'.
|
||
|
||
Each pair in PAIRS is typed as: (string . (forms...)) where the
|
||
string represents the system name to test, and forms being the
|
||
consequence if true."
|
||
`(cond
|
||
,@(mapcar #'(lambda (pair)
|
||
;; (str . forms..) -> ((string= str (system-name))
|
||
;; forms...)
|
||
(let ((name (car pair))
|
||
(body (cdr pair)))
|
||
`((string= ,name (system-name)) ,@body)))
|
||
pairs)))
|
||
#+end_src
|
||
|
||
In [[file:early-init.el][early-init.el]] I set the number of
|
||
native-workers to 4, which isn't necessarily optimal when
|
||
loading/compiling the rest of this file depending on the machine I
|
||
use:
|
||
- On my laptop (=spiderboy=) I'd prefer to have it use 2-3 threads so
|
||
I can actually use the rest of the laptop while waiting for
|
||
compilation
|
||
- On my desktop (=oldboy=) I'd prefer to use 4-6 threads as I can
|
||
afford more, so I can get a faster load up.
|
||
#+begin_src emacs-lisp
|
||
(+oreo/sys-name-cond
|
||
("spiderboy"
|
||
(setq native-comp-async-jobs-number 3))
|
||
("oldboy"
|
||
(setq native-comp-async-jobs-number 6)))
|
||
#+end_src
|
||
** Clean buffer list
|
||
Instead of cleaning my buffer list manually, selectively preserving
|
||
some fixed set of buffers, this function does it for me. Preserves
|
||
any buffers in ~+oreo/keep-buffer~ and kills the rest.
|
||
#+begin_src emacs-lisp
|
||
(defconst +oreo/keep-buffers
|
||
(list "config.org" "*scratch*"
|
||
"*dashboard*" "*Messages*"
|
||
"*Warnings*" "*eshell*")
|
||
"List of buffer names to preserve.")
|
||
|
||
(defun +oreo/clean-buffer-list ()
|
||
"Kill all buffers except any with names in +oreo/keep-buffers."
|
||
(interactive)
|
||
(mapcar #'(lambda (buf)
|
||
(if (not (member (buffer-name buf) +oreo/keep-buffers))
|
||
(kill-buffer buf)))
|
||
(buffer-list)))
|
||
#+end_src
|
||
* Aesthetics
|
||
General look and feel of Emacs (mostly disabling stuff I don't like).
|
||
** Themes
|
||
*** Dark theme
|
||
My preferred dark theme is my own "personal-primary" theme which is
|
||
stored in the Emacs lisp folder (look at
|
||
[[file:elisp/personal-primary-theme.el][this file]]). It tries to use
|
||
the primary colours for everything, leading to a colour -> meaning
|
||
relation.
|
||
|
||
I have an older version of this theme that uses a homogeneous colour
|
||
scheme ([[file:elisp/personal-theme.el][this file]])
|
||
#+begin_src emacs-lisp
|
||
(use-package custom
|
||
:demand t
|
||
:init
|
||
(setq custom-theme-directory (concat user-emacs-directory "elisp/"))
|
||
:config
|
||
(load-theme 'personal-primary t))
|
||
#+end_src
|
||
*** Light theme
|
||
I'm not very good at designing light themes as I don't really use
|
||
them. However they are necessary in high light situations where a
|
||
dark mode would strain the eyes too much. So I built a custom theme
|
||
on top of the default Emacs theme, "personal-light" (look at
|
||
[[file:elisp/personal-light-theme.el][this file]]).
|
||
|
||
I don't use it by default but I may need to switch between light and
|
||
dark easily, so here's a command to switch between them.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package custom
|
||
:defer t
|
||
:commands +oreo/switch-theme
|
||
:init
|
||
(defvar +oreo/theme 'dark)
|
||
:config
|
||
(defun +oreo/switch-theme ()
|
||
(interactive)
|
||
(cond
|
||
((eq +oreo/theme 'dark)
|
||
(mapc #'disable-theme custom-enabled-themes)
|
||
(load-theme 'personal-light t)
|
||
(setq +oreo/theme 'light))
|
||
((eq +oreo/theme 'light)
|
||
(mapc #'disable-theme custom-enabled-themes)
|
||
(load-theme 'personal-primary t)
|
||
(setq +oreo/theme 'dark))))
|
||
)
|
||
#+end_src
|
||
** Font size
|
||
Set font size to 140 if on my desktop (oldboy) or 175 if on my laptop
|
||
(spiderboy).
|
||
#+begin_src emacs-lisp
|
||
(use-package faces
|
||
:defer t
|
||
:config
|
||
(+oreo/sys-name-cond
|
||
("spiderboy" (set-face-attribute 'default nil :height 145))
|
||
("oldboy" (set-face-attribute 'default nil :height 140))))
|
||
#+end_src
|
||
** Startup screen
|
||
The default startup screen is quite bad in all honesty. While for a
|
||
first time user it can be very helpful in running the tutorial and
|
||
finding out more about Emacs, for someone who's already configured it
|
||
there isn't much point.
|
||
|
||
The scratch buffer is an interaction buffer made when Emacs is first
|
||
started, as a way to quickly prototype Emacs Lisp code. When startup
|
||
screen is disabled, this buffer is the first thing presented on boot
|
||
for Emacs. So we can use it to store some useful information.
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:defer t
|
||
:init
|
||
(setq
|
||
inhibit-startup-screen t
|
||
initial-major-mode 'fundamental-mode
|
||
initial-scratch-message ""
|
||
ring-bell-function 'ignore)
|
||
:config
|
||
(add-hook
|
||
'emacs-startup-hook
|
||
(proc
|
||
(with-current-buffer "*scratch*"
|
||
(goto-char (point-max))
|
||
(insert
|
||
(format
|
||
"Emacs v%s - %s\n"
|
||
emacs-version (emacs-init-time)))))))
|
||
#+end_src
|
||
** Blinking cursor
|
||
Turn on blinking cursor (helps with seeing if Emacs is hanging or not).
|
||
|
||
2021-03-15: Turn off blinking-cursor-mode as [[*Hl-line][hl-line]] is better.
|
||
#+begin_src emacs-lisp
|
||
(use-package frame
|
||
:defer t
|
||
:init
|
||
(setq blink-cursor-delay 0.2)
|
||
:config
|
||
(blink-cursor-mode 0))
|
||
#+end_src
|
||
** Fringes
|
||
Turning off borders in my window manager was a good idea, so turn off
|
||
the borders for Emacs.
|
||
#+begin_src emacs-lisp
|
||
(use-package fringe
|
||
:defer t
|
||
:config
|
||
(fringe-mode 0))
|
||
#+end_src
|
||
** Mode line
|
||
A mode line in an editor can provide a LOT of information, or very
|
||
little. I customised the Emacs modeline to give me a bit of info,
|
||
~telephone-line~ to give me a lot.
|
||
|
||
Currently I use the default mode line with some customisation;
|
||
simplicity is above all.
|
||
|
||
#+begin_src emacs-lisp
|
||
(setq-default
|
||
mode-line-format
|
||
'("%l:%c " ;; Line and column
|
||
"%p[" ;; %into file
|
||
(:eval (with-eval-after-load "evil" ;; Evil state
|
||
(upcase
|
||
(substring
|
||
(format "%s" (if (bound-and-true-p evil-state)
|
||
evil-state
|
||
" "))
|
||
0 1))))
|
||
"] "
|
||
"%+"
|
||
"%b"
|
||
"(" ;; Buffer name
|
||
(:eval (format "%s" major-mode))
|
||
") "
|
||
"%I " ;; file size
|
||
(:eval (if (project-current)
|
||
(project-name (project-current))))
|
||
(vc-mode vc-mode) ;; git branch
|
||
" "
|
||
(:eval
|
||
(with-eval-after-load "eglot"
|
||
(if eglot--managed-mode
|
||
(eglot--mode-line-format))))
|
||
mode-line-misc-info
|
||
mode-line-end-spaces))
|
||
#+end_src
|
||
** Mouse
|
||
Who uses a mouse? 🤮
|
||
#+begin_src emacs-lisp
|
||
(setq-default use-file-dialog nil)
|
||
#+end_src
|
||
* Core packages
|
||
For my core packages, whose configuration doesn't change much anyway,
|
||
** General
|
||
General provides a great solution for binding keys. It has evil and
|
||
use-package support so it fits nicely into configuration. In this
|
||
case, I define a "definer" for the "LEADER" keys. Leader is bound to
|
||
~SPC~ and it's functionally equivalent to the doom/spacemacs leader.
|
||
Local leader is bound to ~SPC ,~ and it's similar to doom/spacemacs
|
||
leader but doesn't try to fully assimilate the local-leader map,
|
||
instead just picking stuff I think is useful. This forces me to learn
|
||
only as many bindings as I find necessary; no more, no less.
|
||
|
||
I also define prefix leaders for differing applications. These are
|
||
quite self explanatory by their name and provide a nice way to
|
||
visualise all bindings under a specific heading just by searching the
|
||
code.
|
||
#+begin_src emacs-lisp
|
||
(use-package general
|
||
:straight t
|
||
:demand t
|
||
:config
|
||
;; General which key definitions for leaders
|
||
(general-def
|
||
:states '(normal motion)
|
||
"SPC" 'nil
|
||
"\\" '(nil :which-key "Local leader")
|
||
"SPC a" '(nil :which-key "Applications")
|
||
"SPC b" '(nil :which-key "Buffers")
|
||
"SPC c" '(nil :which-key "Code")
|
||
"SPC d" '(nil :which-key "Directories")
|
||
"SPC f" '(nil :which-key "Files")
|
||
"SPC i" '(nil :which-key "Insert")
|
||
"SPC m" '(nil :which-key "Modes")
|
||
"SPC r" '(nil :which-key "Tabs")
|
||
"SPC s" '(nil :which-key "Search")
|
||
"SPC t" '(nil :which-key "Shell")
|
||
"SPC q" '(nil :which-key "Quit/Literate"))
|
||
|
||
(general-create-definer leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC")
|
||
|
||
(general-create-definer local-leader
|
||
:states '(normal motion)
|
||
:prefix "\\")
|
||
|
||
(general-create-definer code-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC c")
|
||
|
||
(general-create-definer file-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC f")
|
||
|
||
(general-create-definer shell-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC t")
|
||
|
||
(general-create-definer tab-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC r")
|
||
|
||
(general-create-definer mode-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC m")
|
||
|
||
(general-create-definer app-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC a")
|
||
|
||
(general-create-definer search-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC s")
|
||
|
||
(general-create-definer buffer-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC b")
|
||
|
||
(general-create-definer quit-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC q")
|
||
|
||
(general-create-definer insert-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC i")
|
||
|
||
(general-create-definer dir-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC d")
|
||
|
||
(general-create-definer general-nmmap
|
||
:states '(normal motion))
|
||
|
||
(defalias 'nmmap #'general-nmmap)
|
||
|
||
(general-evil-setup t))
|
||
#+end_src
|
||
*** Some binds for Emacs
|
||
Some bindings that I couldn't fit elsewhere easily.
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:after general
|
||
:general
|
||
("C-x d" #'delete-frame)
|
||
|
||
(nmmap
|
||
"M-;" #'eval-expression
|
||
"g=" #'align-regexp
|
||
"C--" #'text-scale-decrease
|
||
"C-=" #'text-scale-increase
|
||
"C-+" #'text-scale-adjust)
|
||
|
||
(leader
|
||
"SPC" '(execute-extended-command :which-key "M-x")
|
||
"p" `(,project-prefix-map :which-key "Project")
|
||
"'" '(browse-url-emacs :which-key "Download URL to Emacs")
|
||
":" `(,(proc (interactive) (switch-to-buffer "*scratch*"))
|
||
:which-key "Switch to *scratch*")
|
||
"!" '(async-shell-command :which-key "Async shell command")
|
||
"h" '(help-command :which-key "Help"))
|
||
|
||
(mode-leader
|
||
"T" #'+oreo/switch-theme)
|
||
|
||
(code-leader
|
||
"F" `(,(proc (interactive) (find-file "~/Code/"))
|
||
:which-key "Open ~/Code/"))
|
||
|
||
(file-leader
|
||
"f" #'find-file
|
||
"F" #'find-file-other-window
|
||
"t" #'find-file-other-tab
|
||
"s" #'save-buffer)
|
||
|
||
(buffer-leader
|
||
"b" #'switch-to-buffer
|
||
"d" #'kill-current-buffer
|
||
"K" #'kill-buffer
|
||
"j" #'next-buffer
|
||
"k" #'previous-buffer
|
||
"D" '(+oreo/clean-buffer-list :which-key "Kill most buffers"))
|
||
|
||
(quit-leader
|
||
"q" #'save-buffers-kill-terminal
|
||
"c" #'+literate/compile-config
|
||
"C" #'+literate/clean-config
|
||
"l" #'+literate/load-config)
|
||
|
||
(search-leader "i" #'imenu))
|
||
#+end_src
|
||
** Evil
|
||
My editor journey started off with Vim rather than Emacs, so my brain
|
||
has imprinted on its style. Thankfully Emacs is super extensible so
|
||
there exists a package (more of a supreme system) for porting Vim's
|
||
modal editing style to Emacs, called Evil (Emacs Vi Layer).
|
||
|
||
However there are a lot of packages in Vim that provide greater
|
||
functionality, for example 'vim-surround'. Emacs, by default, has
|
||
these capabilities but there are further packages which integrate them
|
||
into Evil.
|
||
*** Evil core
|
||
Setup the evil package, with some opinionated keybindings:
|
||
- Switch ~evil-upcase~ and ~evil-downcase~ because I use ~evil-upcase~
|
||
more
|
||
- Switch ~evil-goto-mark~ and ~evil-goto-mark-line~ as I'd rather have
|
||
the global one closer to the home row
|
||
- Use 'T' character as an action for transposing objects
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:demand t
|
||
:straight t
|
||
:general
|
||
(leader
|
||
"w" '(evil-window-map :which-key "Window")
|
||
"wT" #'window-swap-states
|
||
"wd" #'evil-window-delete)
|
||
|
||
(nmmap
|
||
"K" #'man
|
||
"TAB" #'evil-jump-item
|
||
"r" #'evil-replace-state
|
||
"zC" #'hs-hide-level
|
||
"zO" #'hs-show-all
|
||
"'" #'evil-goto-mark
|
||
"`" #'evil-goto-mark-line
|
||
"C-w" #'evil-window-map
|
||
"gu" #'evil-upcase
|
||
"gU" #'evil-downcase
|
||
"T" nil)
|
||
|
||
(nmmap
|
||
:infix "T"
|
||
"w" #'transpose-words
|
||
"c" #'transpose-chars
|
||
"s" #'transpose-sentences
|
||
"p" #'transpose-paragraphs
|
||
"e" #'transpose-sexps
|
||
"l" #'transpose-lines)
|
||
:init
|
||
(setq evil-want-keybinding nil
|
||
evil-split-window-below t
|
||
evil-vsplit-window-right t
|
||
evil-want-abbrev-expand-on-insert-exit t
|
||
evil-undo-system #'undo-tree)
|
||
:config
|
||
(evil-mode))
|
||
#+end_src
|
||
*** Evil surround
|
||
Evil surround is a port for vim-surround.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-surround
|
||
:after evil
|
||
:straight t
|
||
:config
|
||
(global-evil-surround-mode))
|
||
#+end_src
|
||
*** Evil commentary
|
||
Allows generalised commenting of objects easily.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-commentary
|
||
:after evil
|
||
:straight t
|
||
:config
|
||
(evil-commentary-mode))
|
||
#+end_src
|
||
*** Evil multi cursor
|
||
Setup for multi cursors in Evil mode. Don't let evil-mc setup it's own
|
||
keymap because it uses 'gr' as its prefix, which I don't like.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-mc
|
||
:after evil
|
||
:straight t
|
||
:init
|
||
(defvar evil-mc-key-map (make-sparse-keymap))
|
||
:general
|
||
(nmap
|
||
:infix "gz"
|
||
"q" #'evil-mc-undo-all-cursors
|
||
"d" #'evil-mc-make-and-goto-next-match
|
||
"j" #'evil-mc-make-cursor-move-next-line
|
||
"k" #'evil-mc-make-cursor-move-prev-line
|
||
"j" #'evil-mc-make-cursor-move-next-line
|
||
"m" #'evil-mc-make-all-cursors
|
||
"z" #'evil-mc-make-cursor-here
|
||
"r" #'evil-mc-resume-cursors
|
||
"s" #'evil-mc-pause-cursors
|
||
"u" #'evil-mc-undo-last-added-cursor)
|
||
:config
|
||
(global-evil-mc-mode))
|
||
;; (evil-mc-define-vars)
|
||
;; (evil-mc-initialize-vars)
|
||
;; (add-hook 'evil-mc-before-cursors-created #'evil-mc-pause-incompatible-modes)
|
||
;; (add-hook 'evil-mc-before-cursors-created #'evil-mc-initialize-active-state)
|
||
;; (add-hook 'evil-mc-after-cursors-deleted #'evil-mc-teardown-active-state)
|
||
;; (add-hook 'evil-mc-after-cursors-deleted #'evil-mc-resume-incompatible-modes)
|
||
;; (advice-add #'evil-mc-initialize-hooks :override #'ignore)
|
||
;; (advice-add #'evil-mc-teardown-hooks :override #'evil-mc-initialize-vars)
|
||
;; (advice-add #'evil-mc-initialize-active-state :before #'turn-on-evil-mc-mode)
|
||
;; (advice-add #'evil-mc-teardown-active-state :after #'turn-off-evil-mc-mode)
|
||
;; (add-hook 'evil-insert-state-entry-hook #'evil-mc-resume-cursors)
|
||
#+end_src
|
||
|
||
*** Evil collection
|
||
Provides a community based set of keybindings for most modes in
|
||
Emacs. I don't necessarily like all my modes having these bindings
|
||
though, as I may disagree with some. So I use it in a mode to mode basis.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-collection
|
||
:straight t
|
||
:after evil)
|
||
#+end_src
|
||
*** Evil number
|
||
Increment/decrement a number at point like Vim does, but use bindings
|
||
that don't conflict with Emacs default.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-numbers
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"+" #'evil-numbers/inc-at-pt
|
||
"-" #'evil-numbers/dec-at-pt))
|
||
#+end_src
|
||
** Completion
|
||
Emacs is a text based interface. Completion is its bread and butter
|
||
in providing good user experience. By default Emacs provides
|
||
'completions-list' which produces a buffer of options which can be
|
||
searched and selected. We can take this further though!
|
||
|
||
Ido and Icomplete are packages distributed with Emacs to provide
|
||
greater completion interfaces. They utilise the minibuffer to create
|
||
a more interactive experience, allowing incremental searches and
|
||
option selection.
|
||
|
||
Ivy and Helm provide more modern interfaces, though Helm is quite
|
||
heavy. Ivy, on the other hand, provides an interface similar to Ido
|
||
with less clutter and better customisation options.
|
||
*** Ivy
|
||
Ivy is a completion framework for Emacs, and my preferred one. It has
|
||
a great set of features with little to no pain with setting up.
|
||
**** Ivy Core
|
||
Setup for ivy, in preparation for counsel. Turn on ivy-mode just
|
||
after init.
|
||
|
||
Setup vim-like bindings for the minibuffer ("M-(j|k)" for down|up the
|
||
selection list).
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy
|
||
:demand t
|
||
:straight t
|
||
:general
|
||
(general-def
|
||
:keymaps 'ivy-minibuffer-map
|
||
"C-j" #'ivy-yank-symbol
|
||
"M-j" #'ivy-next-line-or-history
|
||
"M-k" #'ivy-previous-line-or-history
|
||
"C-SPC" #'ivy-occur)
|
||
(general-def
|
||
:keymaps 'ivy-switch-buffer-map
|
||
"M-j" #'ivy-next-line-or-history
|
||
"M-k" #'ivy-previous-line-or-history)
|
||
(nmap
|
||
:keymaps '(ivy-occur-mode-map ivy-occur-grep-mode-map)
|
||
"RET" #'ivy-occur-press-and-switch
|
||
"J" #'ivy-occur-press
|
||
"gr" #'ivy-occur-revert-buffer
|
||
"q" #'quit-window
|
||
"D" #'ivy-occur-delete-candidate
|
||
"W" #'ivy-wgrep-change-to-wgrep-mode
|
||
"{" #'compilation-previous-file
|
||
"}" #'compilation-next-file)
|
||
:init
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'ivy-occur-mode 'normal)
|
||
(evil-set-initial-state 'ivy-occur-grep-mode 'normal))
|
||
(setq ivy-height 10
|
||
ivy-wrap t
|
||
ivy-fixed-height-minibuffer t
|
||
ivy-use-virtual-buffers nil
|
||
ivy-virtual-abbreviate 'full
|
||
ivy-on-del-error-function #'ignore
|
||
ivy-use-selectable-prompt t)
|
||
:config
|
||
(ivy-mode 1)
|
||
(require 'counsel nil t))
|
||
#+end_src
|
||
**** Counsel
|
||
Setup for counsel. Load after ivy and helpful.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(search-leader
|
||
"s" #'counsel-grep-or-swiper
|
||
"R" #'counsel-rg)
|
||
(file-leader
|
||
"r" #'counsel-recentf)
|
||
(insert-leader
|
||
"c" #'counsel-unicode-char)
|
||
(general-def
|
||
[remap describe-bindings] #'counsel-descbinds
|
||
[remap load-theme] #'counsel-load-theme)
|
||
:config
|
||
(setq ivy-initial-inputs-alist '((org-insert-link . "^"))
|
||
counsel-describe-function-function #'helpful-callable
|
||
counsel-describe-variable-function #'helpful-variable
|
||
counsel-grep-swiper-limit 1500000
|
||
ivy-re-builders-alist '((swiper . ivy--regex-plus)
|
||
(counsel-grep-or-swiper . ivy--regex-plus)
|
||
(counsel-rg . ivy--regex-plus)
|
||
(t . ivy--regex-ignore-order)))
|
||
(counsel-mode 1))
|
||
#+end_src
|
||
**** WAIT Ivy posframe
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
This makes ivy minibuffer windows use child frames.
|
||
Very nice eyecandy, but can get kinda annoying.
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-posframe
|
||
:hook (ivy-mode-hook . ivy-posframe-mode)
|
||
:straight t
|
||
:init
|
||
(setq ivy-posframe-parameters
|
||
'((left-fringe . 0)
|
||
(right-fringe . 0)
|
||
(background-color . "grey7")))
|
||
|
||
(setq ivy-posframe-display-functions-alist
|
||
'((t . ivy-posframe-display-at-window-center))))
|
||
#+end_src
|
||
**** WAIT Counsel etags
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Counsel etags allows me to search generated tag files for tags. I
|
||
already have a function defined to generate the tags, so it's just
|
||
searching them which I find to be a bit of a hassle, and where this
|
||
package comes in.
|
||
|
||
This has been replaced by [[*xref][xref]] which is inbuilt.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel-etags
|
||
:after counsel
|
||
:general
|
||
(search-leader
|
||
"t" #'counsel-etags-find-tag))
|
||
#+end_src
|
||
*** WAIT Ido
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Ido is a very old completion package that still works great to this
|
||
day. Though it is limited in its scope (and may thus be called a
|
||
completion add-on rather than a full on framework), it is still a very
|
||
powerful package. With the use of ido-completing-read+, it may be used
|
||
similarly to a fully fledged completion framework.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ido
|
||
:demand t
|
||
:general
|
||
(general-def
|
||
:keymaps '(ido-buffer-completion-map
|
||
ido-file-completion-map
|
||
ido-file-dir-completion-map
|
||
ido-common-completion-map)
|
||
(kbd "M-j") #'ido-next-match
|
||
(kbd "M-k") #'ido-prev-match
|
||
(kbd "C-x o") #'evil-window-up)
|
||
:init
|
||
(setq ido-decorations
|
||
(list "{" "}" " \n" " ..." "[" "]" " [No match]" " [Matched]"
|
||
" [Not readable]" " [Too big]" " [Confirm]")
|
||
completion-styles '(flex partial-completion intials emacs22))
|
||
(setq-default ido-enable-flex-matching t
|
||
ido-enable-dot-prefix t
|
||
ido-enable-regexp nil)
|
||
(with-eval-after-load "magit"
|
||
(setq magit-completing-read-function 'magit-ido-completing-read))
|
||
:config
|
||
(ido-mode)
|
||
(ido-everywhere))
|
||
#+end_src
|
||
**** Ido ubiquitous
|
||
Ido completing-read+ is a package that extends the ido package to work
|
||
with more text based functions.
|
||
#+begin_src emacs-lisp
|
||
(use-package ido-completing-read+
|
||
:after ido
|
||
:config
|
||
(ido-ubiquitous-mode +1))
|
||
#+end_src
|
||
*** Amx
|
||
Amx is a fork of Smex that works to enhance the
|
||
execute-extended-command interface. It also provides support for ido
|
||
or ivy (though I'm likely to use ido here) and allows you to switch
|
||
between them.
|
||
|
||
It provides a lot of niceties such as presenting the key bind when
|
||
looking for a command.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package amx
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq amx-backend 'ivy)
|
||
:config
|
||
(amx-mode))
|
||
#+end_src
|
||
*** Orderless
|
||
Orderless sorting method for completion, probably one of the best
|
||
things ever.
|
||
#+begin_src emacs-lisp
|
||
(use-package orderless
|
||
:straight t
|
||
:after (ivy ido)
|
||
:config
|
||
(setf (alist-get t ivy-re-builders-alist) 'orderless-ivy-re-builder))
|
||
#+end_src
|
||
*** Completions-list
|
||
In case I ever use the completions list, some basic commands to look
|
||
around.
|
||
#+begin_src emacs-lisp
|
||
(use-package simple
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'completion-list-mode-map
|
||
"l" #'next-completion
|
||
"h" #'previous-completion
|
||
"ESC" #'delete-completion-window
|
||
"q" #'quit-window
|
||
"RET" #'choose-completion)
|
||
:config
|
||
(with-eval-after-load "evil"
|
||
(setq evil-emacs-state-modes (cl-remove-if
|
||
#'(lambda (x) (eq 'completions-list-mode x))
|
||
evil-emacs-state-modes))
|
||
(add-to-list 'evil-normal-state-modes 'completions-list-mode)))
|
||
#+end_src
|
||
*** Company
|
||
Company is the auto complete system I use. I don't like having heavy
|
||
setups for company as it only makes it slower to use. In this case,
|
||
just setup some evil binds for company.
|
||
#+begin_src emacs-lisp
|
||
(use-package company
|
||
:defer t
|
||
:straight t
|
||
:hook
|
||
(prog-mode-hook . company-mode)
|
||
(eshell-mode-hook . company-mode)
|
||
:general
|
||
(imap
|
||
"C-SPC" #'company-complete
|
||
"M-j" #'company-select-next
|
||
"M-k" #'company-select-previous))
|
||
#+end_src
|
||
** Pretty symbols
|
||
Prettify symbols mode allows for users to declare 'symbols' that
|
||
replace text within certain modes. Though this may seem like useless
|
||
eye candy, it has aided my comprehension and speed of recognition
|
||
(recognising symbols is easier than words).
|
||
|
||
Essentially a use-package keyword which makes declaring pretty symbols
|
||
for language modes incredibly easy. Checkout my [[C/C++][C/C++]] configuration
|
||
for an example.
|
||
#+begin_src emacs-lisp
|
||
(use-package prog-mode
|
||
:demand t
|
||
:init
|
||
(setq prettify-symbols-unprettify-at-point t)
|
||
:config
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':pretty)
|
||
(defun use-package-normalize/:pretty (_name-symbol _keyword args)
|
||
args)
|
||
|
||
(defun use-package-handler/:pretty (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
(let ((mode (car arg))
|
||
(rest (cdr arg)))
|
||
`(add-hook
|
||
',mode
|
||
#'(lambda nil
|
||
(setq prettify-symbols-alist ',rest)
|
||
(prettify-symbols-mode)))))
|
||
args)))))
|
||
#+end_src
|
||
|
||
Here's a collection of keywords and possible associated symbols for
|
||
any prog language of choice. Mostly for reference and copying.
|
||
#+begin_example
|
||
("null" . "Ø")
|
||
("list" . "ℓ")
|
||
("string" . "𝕊")
|
||
("true" . "⊤")
|
||
("false" . "⊥")
|
||
("char" . "ℂ")
|
||
("int" . "ℤ")
|
||
("float" . "ℝ")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("return" . "⟼")
|
||
("print" . "ℙ")
|
||
("lambda" . "λ")
|
||
#+end_example
|
||
** Window management
|
||
Emacs' default window management is quite bad, eating other windows on
|
||
a whim and not particularly caring for the current window setup.
|
||
Thankfully you can change this via the ~display-buffer-alist~ which
|
||
matches buffer names with how the window for the buffer should be
|
||
displayed. I add a use-package keyword to make ~display-buffer-alist~
|
||
records within a use-package call.
|
||
|
||
I have no idea whether it's optimal AT ALL, but it works for me.
|
||
|
||
2024-04-23: Found this option ~switch-to-buffer-obey-display-actions~
|
||
which makes manual buffer switches obey the same constraints via
|
||
~display-buffer-alist~ as creating the buffer automatically.
|
||
#+begin_src emacs-lisp
|
||
(use-package window
|
||
:demand t
|
||
:general
|
||
:init
|
||
(setq switch-to-buffer-obey-display-actions t)
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':display)
|
||
(defun use-package-normalize/:display (_name-symbol _keyword args)
|
||
args)
|
||
|
||
(defun use-package-handler/:display (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
`(add-to-list 'display-buffer-alist
|
||
',arg))
|
||
args)))))
|
||
#+end_src
|
||
*** Some display records
|
||
Using the ~:display~ keyword, setup up some ~display-buffer-alist~
|
||
records. This is mostly for packages that aren't really configured
|
||
(like [[info:woman][woman]]) or packages that were configured before
|
||
(like [[*Ivy][Ivy]]).
|
||
#+begin_src emacs-lisp
|
||
(use-package window
|
||
:defer t
|
||
:display
|
||
("\\*Process List\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
|
||
("\\*\\(Ido \\)?Completions\\*"
|
||
(display-buffer-in-side-window)
|
||
(window-height . 0.25)
|
||
(side . bottom))
|
||
|
||
("\\*ivy-occur.*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
|
||
("\\*Async Shell Command\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25)))
|
||
#+end_src
|
||
** Tabs
|
||
Tabs in vscode are just like buffers in Emacs but way slower and
|
||
harder to use. Tabs in Emacs are essentially window layouts, similar
|
||
to instances in Tmux. With this setup I can use tabs quite
|
||
effectively.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package tab-bar
|
||
:defer t
|
||
:init
|
||
(setq tab-bar-show 1)
|
||
:config
|
||
(tab-bar-mode)
|
||
:general
|
||
(tab-leader
|
||
"t" #'tab-switch
|
||
"j" #'tab-next
|
||
"k" #'tab-previous
|
||
"h" #'tab-move-to
|
||
"l" #'tab-move
|
||
"n" #'tab-new
|
||
"c" #'tab-close
|
||
"d" #'tab-close
|
||
"f" #'tab-detach
|
||
"w" #'tab-window-detach
|
||
"r" #'tab-rename)
|
||
(mode-leader
|
||
"t" #'toggle-tab-bar-mode-from-frame))
|
||
#+end_src
|
||
** Auto typing
|
||
Snippets are a pretty nice way of automatically inserting code. Emacs
|
||
provides a ton of packages by default to do this, but there are great
|
||
packages to install as well.
|
||
|
||
Abbrevs and skeletons make up a popular solution within Emacs default.
|
||
Abbrevs are for simple expressions wherein the only input is the key,
|
||
and the output is some Elisp function. They provide a lot of inbuilt
|
||
functionality and are quite useful. Skeletons, on the other hand, are
|
||
for higher level insertions
|
||
|
||
The popular external solution is Yasnippet. Yasnippet is a great
|
||
package for snippets, which I use heavily in programming and org-mode.
|
||
I setup here the global mode for yasnippet and a collection of
|
||
snippets for ease of use.
|
||
*** Abbrevs
|
||
Just define a few abbrevs for various date-time operations. Also
|
||
define a macro that will assume a function for the expansion, helping
|
||
with abstracting a few things away.
|
||
#+begin_src emacs-lisp
|
||
(use-package abbrev
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . abbrev-mode)
|
||
(text-mode-hook . abbrev-mode)
|
||
:init
|
||
(defmacro +abbrev/define-abbrevs (abbrev-table &rest abbrevs)
|
||
`(progn
|
||
,@(mapcar #'(lambda (abbrev)
|
||
`(define-abbrev
|
||
,abbrev-table
|
||
,(car abbrev)
|
||
""
|
||
(proc (insert ,(cadr abbrev)))))
|
||
abbrevs)))
|
||
(setq save-abbrevs nil)
|
||
:config
|
||
(+abbrev/define-abbrevs
|
||
global-abbrev-table
|
||
("sdate"
|
||
(format-time-string "%Y-%m-%d" (current-time)))
|
||
("stime"
|
||
(format-time-string "%H:%M:%S" (current-time)))
|
||
("sday"
|
||
(format-time-string "%A" (current-time)))
|
||
("smon"
|
||
(format-time-string "%B" (current-time)))))
|
||
#+end_src
|
||
*** WAIT Skeletons
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Defines a macro for generating a skeleton + abbrev for a given mode.
|
||
Doesn't sanitise inputs because I assume callers are /rational/ actors
|
||
who would *only* use this for their top level Emacs config.
|
||
|
||
Honestly didn't find much use for this currently, so disabled.
|
||
#+begin_src emacs-lisp
|
||
(use-package skeleton
|
||
:after abbrev
|
||
:config
|
||
(defmacro +autotyping/gen-skeleton-abbrev (mode abbrev &rest skeleton)
|
||
(let* ((table (intern (concat (symbol-name mode) "-abbrev-table")))
|
||
(skeleton-name (intern (concat "+skeleton/" (symbol-name mode) "/" abbrev))))
|
||
`(progn
|
||
(define-skeleton
|
||
,skeleton-name
|
||
""
|
||
,@skeleton)
|
||
(define-abbrev ,table
|
||
,abbrev
|
||
""
|
||
',skeleton-name)))))
|
||
#+end_src
|
||
*** Auto insert
|
||
Allows inserting text immediately upon creating a new buffer with a
|
||
given name. Supports skeletons for inserting text. To make it easier
|
||
for later systems to define their own auto inserts, I define a
|
||
~use-package~ keyword ~auto-insert~ which allows one to define an
|
||
entry for ~auto-insert-alist~.
|
||
#+begin_src emacs-lisp
|
||
(use-package autoinsert
|
||
:demand t
|
||
:hook (emacs-startup-hook . auto-insert-mode)
|
||
:config
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':auto-insert)
|
||
(defun use-package-normalize/:auto-insert (_name-symbol _keyword args)
|
||
args)
|
||
(defun use-package-handler/:auto-insert (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
`(add-to-list
|
||
'auto-insert-alist
|
||
',arg))
|
||
args)))))
|
||
#+end_src
|
||
*** Yasnippet
|
||
Look at the snippets [[file:../.config/yasnippet/snippets/][folder]]
|
||
for all snippets I've got.
|
||
#+begin_src emacs-lisp
|
||
(use-package yasnippet
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . yas-minor-mode)
|
||
(text-mode-hook . yas-minor-mode)
|
||
:general
|
||
(insert-leader
|
||
"i" #'yas-insert-snippet)
|
||
:config
|
||
(yas-load-directory (no-littering-expand-etc-file-name "yasnippet/snippets")))
|
||
#+end_src
|
||
*** WAIT Hydra
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Hydra is a great package by =abo-abo= (yes the same guy who made ivy
|
||
and swiper) and I hope to use it later on in the config. There are
|
||
two use-package declarations here: one for ~hydra~ itself, and the
|
||
other for ~use-package-hydra~ which provides the keyword ~:hydra~ in
|
||
use-package declarations.
|
||
#+begin_src emacs-lisp
|
||
(use-package hydra
|
||
:straight t)
|
||
|
||
(use-package use-package-hydra
|
||
:straight t)
|
||
#+end_src
|
||
* Small packages
|
||
** Info
|
||
Info is GNU's attempt at better man pages. Most Emacs packages have
|
||
info pages so I'd like nice navigation options.
|
||
#+begin_src emacs-lisp
|
||
(use-package info
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'Info-mode-map
|
||
"h" #'evil-backward-char
|
||
"k" #'evil-previous-line
|
||
"l" #'evil-forward-char
|
||
"H" #'Info-history-back
|
||
"L" #'Info-history-forward
|
||
"RET" #'Info-follow-nearest-node))
|
||
#+end_src
|
||
** Display line numbers
|
||
I don't really like line numbers, I find them similar to
|
||
[[*Fringes][fringes]] as useless space, but at least it provides some
|
||
information. Sometimes it can help with doing repeated commands so a
|
||
toggle option is necessary.
|
||
#+begin_src emacs-lisp
|
||
(use-package display-line-numbers
|
||
:defer t
|
||
:commands display-line-numbers-mode
|
||
:general
|
||
(mode-leader
|
||
"l" #'display-line-numbers-mode)
|
||
:init
|
||
(setq-default display-line-numbers-type 'relative))
|
||
#+end_src
|
||
** WAIT esup
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
I used to be able to just use
|
||
[[file:elisp/profiler-dotemacs.el][profile-dotemacs.el]], when my
|
||
Emacs config was smaller, but now it tells me very little information
|
||
about where my setup is inefficient due to the literate config. Just
|
||
found this ~esup~ thing and it works perfectly, exactly how I would
|
||
prefer getting this kind of information. It runs an external Emacs
|
||
instance and collects information from it, so it doesn't require
|
||
restarting Emacs to profile, and I can compile my configuration in my
|
||
current instance to test it immediately.
|
||
|
||
2023-10-16: Unless I'm doing some optimisations or tests, I don't
|
||
really need this in my config at all times. Enable when needed.
|
||
#+begin_src emacs-lisp
|
||
(use-package esup
|
||
:straight t
|
||
:defer t)
|
||
#+end_src
|
||
** Hl-line
|
||
Highlights the current line.
|
||
#+begin_src emacs-lisp
|
||
(use-package hl-line
|
||
:straight t
|
||
:defer t
|
||
:hook (text-mode-hook . hl-line-mode)
|
||
:hook (prog-mode-hook . hl-line-mode))
|
||
#+end_src
|
||
** Recentf
|
||
Recentf provides a method of keeping track of recently opened files.
|
||
#+begin_src emacs-lisp
|
||
(use-package recentf
|
||
:defer t
|
||
:hook (emacs-startup-hook . recentf-mode))
|
||
#+end_src
|
||
** Avy
|
||
Setup avy with leader. As I use ~avy-goto-char-timer~ a lot, use the
|
||
~C-s~ bind which replaces isearch. Switch isearch to M-s in case I
|
||
need to use it.
|
||
#+begin_src emacs-lisp
|
||
(use-package avy
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'override
|
||
"C-s" #'avy-goto-char-timer
|
||
"M-s" #'isearch-forward
|
||
"gp" #'avy-move-region
|
||
"gl" #'avy-goto-line
|
||
"gw" #'avy-goto-word-1))
|
||
#+end_src
|
||
** Ace window
|
||
Though evil provides a great many features in terms of window
|
||
management, ace window can provide some nicer chords for higher
|
||
management of windows (closing, switching, etc).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ace-window
|
||
:straight t
|
||
:defer t
|
||
:custom
|
||
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
|
||
:general
|
||
(nmmap
|
||
[remap evil-window-next] #'ace-window))
|
||
#+end_src
|
||
** Ace link
|
||
Avy-style link following!
|
||
#+begin_src emacs-lisp
|
||
(use-package ace-link
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'override
|
||
"gL" #'ace-link))
|
||
#+end_src
|
||
** Helpful
|
||
Helpful provides a modernised interface for some common help
|
||
commands. I replace ~describe-function~, ~describe-variable~ and
|
||
~describe-key~ by their helpful counterparts.
|
||
#+begin_src emacs-lisp
|
||
(use-package helpful
|
||
:straight t
|
||
:defer t
|
||
:commands (helpful-callable helpful-variable)
|
||
:general
|
||
(general-def
|
||
[remap describe-function] #'helpful-callable
|
||
[remap describe-variable] #'helpful-variable
|
||
[remap describe-key] #'helpful-key)
|
||
:display
|
||
("\\*helpful.*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.25))
|
||
:config
|
||
(evil-define-key 'normal helpful-mode-map "q" #'quit-window))
|
||
#+end_src
|
||
** Which-key
|
||
Which key uses the minibuffer when performing a keybind to provide
|
||
possible options for the next key.
|
||
#+begin_src emacs-lisp
|
||
(use-package which-key
|
||
:straight t
|
||
:defer t
|
||
:config
|
||
(which-key-mode))
|
||
#+end_src
|
||
** (Rip)grep
|
||
Grep is a great piece of software, a necessary tool in any Linux
|
||
user's inventory. By default Emacs has a family of functions to use
|
||
grep, presenting results in a ~compilation~ style. ~grep~ searches
|
||
files, ~rgrep~ searches in a directory using the ~find~ program and
|
||
~zgrep~ searches archives. This is a great solution for a general
|
||
computer environment; essentially all Linux installs will have ~grep~
|
||
and ~find~ installed.
|
||
|
||
Ripgrep is a Rust program that attempts to perform better than grep,
|
||
and it actually does. This is because of a set of optimisations, such
|
||
as checking the =.gitignore= to exclude certain files from being
|
||
searched. The ripgrep package provides utilities to ripgrep projects
|
||
and files for strings. Though [[*Ivy][ivy]] comes with
|
||
~counsel-rg~, it uses Ivy's completion framework rather than the
|
||
~compilation~ style buffers, which sometimes proves very useful.
|
||
|
||
Of course, this requires installing the rg binary which is available
|
||
in most repositories nowadays.
|
||
*** Grep
|
||
I have no use for standard 'grep'; ~counsel-swiper~ does the same
|
||
thing faster and within Emacs lisp. ~rgrep~ is useful though.
|
||
#+begin_src emacs-lisp
|
||
(use-package grep
|
||
:defer t
|
||
:display
|
||
("^\\*grep.*"
|
||
(display-buffer-at-bottom display-buffer-reuse-window)
|
||
(window-height . 0.35)
|
||
(reusable-frames . t))
|
||
:general
|
||
(search-leader
|
||
"d" #'rgrep)
|
||
(nmmap
|
||
:keymaps 'grep-mode-map
|
||
"0" #'evil-beginning-of-line
|
||
"q" #'quit-window
|
||
"i" #'wgrep-change-to-wgrep-mode
|
||
"c" #'recompile)
|
||
(nmmap
|
||
:keymaps 'wgrep-mode-map
|
||
"q" #'evil-record-macro
|
||
"ZZ" #'wgrep-finish-edit
|
||
"ZQ" #'wgrep-abort-changes)
|
||
:config
|
||
;; Without this wgrep doesn't work properly
|
||
(evil-set-initial-state 'grep-mode 'normal))
|
||
#+end_src
|
||
*** rg
|
||
#+begin_src emacs-lisp
|
||
(use-package rg
|
||
:straight t
|
||
:defer t
|
||
:display
|
||
("^\\*\\*ripgrep\\*\\*"
|
||
(display-buffer-at-bottom display-buffer-reuse-window)
|
||
(window-height . 0.35)
|
||
(reusable-frames . t))
|
||
:general
|
||
(search-leader
|
||
"r" #'rg)
|
||
(nmmap
|
||
:keymaps 'rg-mode-map
|
||
"c" #'rg-recompile
|
||
"C" #'rg-rerun-toggle-case
|
||
"]]" #'rg-next-file
|
||
"[[" #'rg-prev-file
|
||
"q" #'quit-window
|
||
"i" #'wgrep-change-to-wgrep-mode)
|
||
:init
|
||
(setq rg-group-result t
|
||
rg-hide-command t
|
||
rg-show-columns nil
|
||
rg-show-header t
|
||
rg-custom-type-aliases nil
|
||
rg-default-alias-fallback "all"
|
||
rg-buffer-name "*ripgrep*")
|
||
:config
|
||
(evil-set-initial-state 'rg-mode 'normal))
|
||
#+end_src
|
||
** Olivetti
|
||
Olivetti provides a focus mode for Emacs, which makes it look a bit
|
||
nicer with fringes. I also define ~+olivetti-mode~ which will
|
||
remember and clear up any window configurations on the frame, then
|
||
when turned off will reinsert them - provides a nice way to quickly
|
||
focus on a buffer.
|
||
#+begin_src emacs-lisp
|
||
(use-package olivetti
|
||
:straight t
|
||
:defer t
|
||
:commands (+olivetti-mode)
|
||
:general
|
||
(mode-leader
|
||
"o" #'+olivetti-mode)
|
||
:init
|
||
(setq-default olivetti-body-width 0.6)
|
||
(setq olivetti-style 'fancy)
|
||
(add-hook 'olivetti-mode-on-hook
|
||
(proc (interactive) (text-scale-increase 1)))
|
||
(add-hook 'olivetti-mode-off-hook
|
||
(proc (interactive) (text-scale-decrease 1)))
|
||
:config
|
||
(defun +olivetti-mode ()
|
||
(interactive)
|
||
(if (not olivetti-mode)
|
||
(progn
|
||
(window-configuration-to-register 1)
|
||
(delete-other-windows)
|
||
(olivetti-mode t))
|
||
(jump-to-register 1)
|
||
(olivetti-mode 0))))
|
||
#+end_src
|
||
*** Presentation mode
|
||
A simple presentation system using org-mode and olivetti.
|
||
#+begin_src emacs-lisp
|
||
(use-package olivetti
|
||
:defer t
|
||
:config
|
||
(defun +presentation/prev-slide ()
|
||
(interactive)
|
||
(when presentation-mode
|
||
(widen)
|
||
(outline-previous-visible-heading 1)
|
||
(end-of-line)
|
||
(if (org-fold-folded-p)
|
||
(org-cycle))
|
||
(org-narrow-to-subtree)))
|
||
(defun +presentation/next-slide ()
|
||
(interactive)
|
||
(when presentation-mode
|
||
(widen)
|
||
(outline-next-visible-heading 1)
|
||
(end-of-line)
|
||
(if (org-fold-folded-p)
|
||
(org-cycle))
|
||
(org-narrow-to-subtree)))
|
||
(defvar presentation-mode-map (make-sparse-keymap))
|
||
(define-minor-mode presentation-mode
|
||
"When in org-mode, use each heading like a slide!"
|
||
:lighter nil
|
||
:keymap presentation-mode-map
|
||
(cond
|
||
(presentation-mode
|
||
(olivetti-mode t)
|
||
(outline-show-heading)
|
||
(org-narrow-to-subtree))
|
||
(t
|
||
(olivetti-mode -1)
|
||
(widen))))
|
||
:general
|
||
(leader
|
||
:states 'normal
|
||
:keymaps 'presentation-mode-map
|
||
"j" #'+presentation/next-slide
|
||
"k" #'+presentation/prev-slide)
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
"P" #'presentation-mode))
|
||
#+end_src
|
||
** All the Icons
|
||
Nice set of icons with a great user interface to manage them.
|
||
#+begin_src emacs-lisp
|
||
(use-package all-the-icons
|
||
:straight t
|
||
:defer t
|
||
:commands (all-the-icons-insert)
|
||
:general
|
||
(insert-leader
|
||
"e" #'all-the-icons-insert))
|
||
#+end_src
|
||
** Hide mode line
|
||
Custom minor mode to toggle the mode line. Check it out at
|
||
[[file:elisp/hide-mode-line.el][elisp/hide-mode-line.el]].
|
||
#+begin_src emacs-lisp
|
||
(use-package hide-mode-line
|
||
:load-path "elisp/"
|
||
:defer t
|
||
:general
|
||
(mode-leader
|
||
"m" #'hide-mode-line-mode))
|
||
#+end_src
|
||
** Save place
|
||
Saves current place in a buffer permanently, so on revisiting the file
|
||
(even in a different Emacs instance) you go back to the place you were
|
||
at last.
|
||
#+begin_src emacs-lisp
|
||
(use-package saveplace
|
||
:defer t
|
||
:config
|
||
(save-place-mode))
|
||
#+end_src
|
||
** Rot13
|
||
ROT13 encoding is a pretty simple cipher; fun to make decoders and
|
||
encoders for. Emacs has default support for it, to the point where it
|
||
can display files with the encoding without changing the underlying
|
||
text. That's what this is mainly for.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package rot13
|
||
:defer t
|
||
:general
|
||
(mode-leader
|
||
"r" #'toggle-rot13-mode))
|
||
#+end_src
|
||
** Licensing
|
||
Loads [[file:elisp/license.el][license.el]] for inserting licenses.
|
||
Licenses are important for distribution and attribution to be defined
|
||
clearly.
|
||
#+begin_src emacs-lisp
|
||
(use-package license
|
||
:defer t
|
||
:load-path "elisp/"
|
||
:general
|
||
(insert-leader
|
||
"l" #'+license/insert-copyright-notice
|
||
"L" #'+license/insert-complete-license))
|
||
#+end_src
|
||
** Memory-report
|
||
New feature of Emacs-29, gives a rough report of memory usage with
|
||
some details. Useful to know on a long Emacs instance what could be
|
||
eating up memory.
|
||
#+begin_src emacs-lisp
|
||
(use-package memory-report
|
||
:defer t
|
||
:general
|
||
(leader
|
||
"qm" #'memory-report))
|
||
#+end_src
|
||
** Save minibuffer history
|
||
#+begin_src emacs-lisp
|
||
(use-package savehist
|
||
:defer t
|
||
:config
|
||
(savehist-mode t))
|
||
#+end_src
|
||
** Drag Stuff
|
||
#+begin_src emacs-lisp
|
||
(use-package drag-stuff
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"C-M-h" #'drag-stuff-left
|
||
"C-M-j" #'drag-stuff-down
|
||
"C-M-k" #'drag-stuff-up
|
||
"C-M-l" #'drag-stuff-right))
|
||
#+end_src
|
||
** Searching git directories efficiently
|
||
Using [[file:elisp/search.el][search.el]] I can search a set of
|
||
directories particularly efficiently.
|
||
#+begin_src emacs-lisp
|
||
(use-package search
|
||
:defer t
|
||
:load-path "elisp/"
|
||
:general
|
||
(file-leader
|
||
"P" #'+search/find-file
|
||
"S" #'+search/search-all))
|
||
#+end_src
|
||
** Bookmarks
|
||
I maintain a bookmarks file at =~/Text/bookmarks.org=. I would like
|
||
the ability to construct new bookmarks and open bookmarks. They may
|
||
be either articles I want to read, useful information documents or
|
||
just straight up youtube videos. This
|
||
[[file:elisp/bookmark.el][library]] does the appropriate dispatching
|
||
and work for me.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-bookmark
|
||
:load-path "elisp/"
|
||
:general
|
||
(file-leader
|
||
"b" #'+bookmark/open-bookmark))
|
||
#+end_src
|
||
* Applications
|
||
Emacs is basically an operating system whose primary datatype is text.
|
||
Applications are interfaces/environments which serve a variety of
|
||
purposes, but provide a lot of capability.
|
||
** WAIT Dashboard
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Dashboard creates a custom dashboard for Emacs that replaces the
|
||
initial startup screen in default Emacs. It has a lot of customising
|
||
options.
|
||
|
||
Unfortunately not that useful, many things are easier to invoke
|
||
directly such as recent files or project changing.
|
||
#+begin_src emacs-lisp
|
||
(use-package dashboard
|
||
:straight t
|
||
:demand t
|
||
:general
|
||
(app-leader
|
||
"b" #'dashboard-refresh-buffer)
|
||
(:states '(normal motion emacs)
|
||
:keymaps 'dashboard-mode-map
|
||
"q" (proc (interactive) (kill-this-buffer)))
|
||
(nmmap
|
||
:keymaps 'dashboard-mode-map
|
||
"r" #'dashboard-jump-to-recent-files
|
||
"p" #'dashboard-jump-to-projects
|
||
"}" #'dashboard-next-section
|
||
"{" #'dashboard-previous-section)
|
||
:init
|
||
(setq initial-buffer-choice nil
|
||
dashboard-banner-logo-title "Oreomacs"
|
||
dashboard-center-content t
|
||
dashboard-set-init-info t
|
||
dashboard-startup-banner (no-littering-expand-etc-file-name "dashboard/logo.png")
|
||
dashboard-set-footer t
|
||
dashboard-set-navigator t
|
||
dashboard-items '((projects . 5)
|
||
(recents . 5))
|
||
dashboard-footer-messages (list
|
||
"Collecting parentheses..."
|
||
"Linking 'coffee_machine.o'..."
|
||
"Uploading ip to hacker named 4chan..."
|
||
"Dividing by zero..."
|
||
"Solving 3-sat..."
|
||
"Obtaining your health record..."
|
||
(format "Recompiling Emacs for the %dth time..." (random 1000))
|
||
"Escaping the cycle of samsara..."))
|
||
:config
|
||
(dashboard-setup-startup-hook))
|
||
#+end_src
|
||
** EWW
|
||
Emacs Web Wowser is the inbuilt text based web browser for Emacs. It
|
||
can render images and basic CSS styles but doesn't have a JavaScript
|
||
engine, which makes sense as it's primarily a text interface.
|
||
#+begin_src emacs-lisp
|
||
(use-package eww
|
||
:defer t
|
||
:general
|
||
(app-leader
|
||
"w" #'eww)
|
||
(nmmap
|
||
:keymaps 'eww-mode-map
|
||
"w" #'evil-forward-word-begin
|
||
"Y" #'shr-probe-and-copy-url)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-eww-setup)))
|
||
#+end_src
|
||
** Calendar
|
||
Calendar is a simple inbuilt application that helps with date
|
||
functionalities. I add functionality to copy dates from the calendar
|
||
to the kill ring and bind it to "Y".
|
||
#+begin_src emacs-lisp
|
||
(use-package calendar
|
||
:defer t
|
||
:commands (+calendar/copy-date +calendar/toggle-calendar)
|
||
:display
|
||
("\\*Calendar\\*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.17))
|
||
:general
|
||
(nmmap
|
||
:keymaps 'calendar-mode-map
|
||
"Y" #'+calendar/copy-date)
|
||
(app-leader
|
||
"d" #'calendar)
|
||
:config
|
||
(defun +calendar/copy-date ()
|
||
"Copy date under cursor into kill ring."
|
||
(interactive)
|
||
(if (use-region-p)
|
||
(call-interactively #'kill-ring-save)
|
||
(let ((date (calendar-cursor-to-date)))
|
||
(when date
|
||
(setq date (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
|
||
(kill-new (format-time-string "%Y-%m-%d" date)))))))
|
||
#+end_src
|
||
** Mail
|
||
Mail is a funny thing; most people use it just for business or
|
||
advertising and it's come out of use in terms of personal
|
||
communication in the west for the most part (largely due to "social"
|
||
media applications). However, this isn't true for the open source and
|
||
free software movement who heavily use mail for communication.
|
||
|
||
Integrating mail into Emacs helps as I can send source code and
|
||
integrate it into my workflow just a bit better.
|
||
*** Notmuch
|
||
#+begin_src emacs-lisp
|
||
(use-package notmuch
|
||
:straight t
|
||
:defer t
|
||
:commands (notmuch +mail/flag-thread)
|
||
:general
|
||
(app-leader "m" #'notmuch)
|
||
(nmap
|
||
:keymaps 'notmuch-search-mode-map
|
||
"f" #'+mail/flag-thread)
|
||
:init
|
||
(defconst +mail/signature "---------------\nAryadev Chavali")
|
||
(defconst +mail/local-dir (no-littering-expand-var-file-name "mail/"))
|
||
(setq notmuch-show-logo nil
|
||
notmuch-search-oldest-first nil
|
||
notmuch-hello-sections '(notmuch-hello-insert-saved-searches
|
||
notmuch-hello-insert-alltags
|
||
notmuch-hello-insert-recent-searches)
|
||
notmuch-archive-tags '("-inbox" "-unread" "+archive")
|
||
mail-signature +mail/signature
|
||
mail-default-directory +mail/local-dir
|
||
mail-source-directory +mail/local-dir
|
||
message-signature +mail/signature
|
||
message-auto-save-directory +mail/local-dir
|
||
message-directory +mail/local-dir)
|
||
|
||
(defun +mail/sync-mail ()
|
||
"Sync mail via mbsync."
|
||
(interactive)
|
||
(start-process-shell-command "" nil "mbsync -a"))
|
||
(defun +mail/trash-junk ()
|
||
"Delete any mail in junk"
|
||
(interactive)
|
||
(start-process-shell-command "" nil "notmuch search --output=files --format=text0 tag:deleted tag:spam tag:trash tag:junk | xargs -r0 rm"))
|
||
:config
|
||
(defun +mail/flag-thread (&optional unflag beg end)
|
||
(interactive (cons current-prefix-arg (notmuch-interactive-region)))
|
||
(notmuch-search-tag
|
||
(notmuch-tag-change-list '("-inbox" "+flagged") unflag) beg end)
|
||
(when (eq beg end)
|
||
(notmuch-search-next-thread)))
|
||
|
||
(advice-add #'notmuch-poll-and-refresh-this-buffer :after
|
||
#'+mail/trash-junk)
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-notmuch-setup)))
|
||
#+end_src
|
||
*** Smtpmail
|
||
#+begin_src emacs-lisp
|
||
(use-package smtpmail
|
||
:defer t
|
||
:commands mail-send
|
||
:init
|
||
(setq-default
|
||
smtpmail-smtp-server "mail.aryadevchavali.com"
|
||
smtpmail-smtp-user "aryadev"
|
||
smtpmail-smtp-service 587
|
||
smtpmail-stream-type 'starttls
|
||
send-mail-function #'smtpmail-send-it
|
||
message-send-mail-function #'smtpmail-send-it))
|
||
#+end_src
|
||
** Dired
|
||
Setup for dired. Make dired-hide-details-mode the default mode when
|
||
using dired-mode, as it removes the clutter. Setup evil collection
|
||
for dired (even though dired doesn't really conflict with evil, there
|
||
are some corners I'd like to adjust).
|
||
#+begin_src emacs-lisp
|
||
(use-package dired
|
||
:demand t
|
||
:commands (dired find-dired)
|
||
:hook
|
||
(dired-mode-hook . auto-revert-mode)
|
||
(dired-mode-hook . dired-hide-details-mode)
|
||
:init
|
||
(setq-default dired-listing-switches "-AFBlu --group-directories-first"
|
||
dired-omit-files "^\\."
|
||
dired-dwim-target t
|
||
image-dired-external-viewer "nsxiv")
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-dired-setup))
|
||
:general
|
||
(nmmap
|
||
:keymaps 'dired-mode-map
|
||
"SPC" nil
|
||
"SPC ," nil
|
||
"T" #'dired-create-empty-file
|
||
"H" #'dired-up-directory
|
||
"L" #'dired-find-file)
|
||
(dir-leader
|
||
"f" #'find-dired
|
||
"d" #'dired
|
||
"D" #'dired-other-window
|
||
"i" #'image-dired
|
||
"p" `(,(proc (interactive)
|
||
(dired "~/Text/PDFs/"))
|
||
:which-key "Open PDFs"))
|
||
(local-leader
|
||
:keymaps 'dired-mode-map
|
||
"i" #'dired-maybe-insert-subdir
|
||
"I" #'+dired/insert-all-subdirectories
|
||
"k" #'dired-prev-subdir
|
||
"j" #'dired-next-subdir
|
||
"K" #'dired-kill-subdir
|
||
"m" #'dired-mark-files-regexp
|
||
"u" #'dired-undo)
|
||
(nmmap
|
||
:keymaps 'image-dired-thumbnail-mode-map
|
||
"h" #'image-dired-backward-image
|
||
"l" #'image-dired-forward-image
|
||
"j" #'image-dired-next-line
|
||
"k" #'image-dired-previous-line
|
||
"H" #'image-dired-display-previous
|
||
"L" #'image-dired-display-next
|
||
"RET" #'image-dired-display-this
|
||
"m" #'image-dired-mark-thumb-original-file
|
||
"q" #'quit-window)
|
||
:config
|
||
(add-to-list 'dired-guess-shell-alist-user '("\\.pdf\\'" "zathura"))
|
||
(defun +dired/insert-all-subdirectories ()
|
||
"Insert all subdirectories currently viewable."
|
||
(interactive)
|
||
(dired-mark-directories nil)
|
||
(mapc #'dired-insert-subdir (dired-get-marked-files))
|
||
(dired-unmark-all-marks)))
|
||
#+end_src
|
||
*** fd-dired
|
||
Uses fd for finding file results in a directory: ~find-dired~ ->
|
||
~fd-dired~.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package fd-dired
|
||
:straight t
|
||
:after dired
|
||
:general
|
||
(dir-leader
|
||
"g" #'fd-dired))
|
||
#+end_src
|
||
*** wdired
|
||
Similar to [[*(Rip)grep][wgrep]] =wdired= provides
|
||
the ability to use Emacs motions and editing on file names. This
|
||
makes stuff like mass renaming and other file management tasks way
|
||
easier than even using the mark based system.
|
||
#+begin_src emacs-lisp
|
||
(use-package wdired
|
||
:straight t
|
||
:after dired
|
||
:general
|
||
(nmmap
|
||
:keymaps 'dired-mode-map
|
||
"W" #'wdired-change-to-wdired-mode)
|
||
(nmmap
|
||
:keymaps 'wdired-mode-map
|
||
"ZZ" #'wdired-finish-edit
|
||
"ZQ" #'wdired-abort-changes))
|
||
#+end_src
|
||
** WAIT Xwidget
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Xwidget is a package which allows for the insertion of arbitrary
|
||
xwidgets into Emacs through buffers. It must be compiled into Emacs
|
||
so you might need to customise your install. One of its premier uses
|
||
is in navigating the web which it provides through the function
|
||
~xwidget-webkit-browse-url~. This renders a fully functional web
|
||
browser within Emacs.
|
||
|
||
Though I am not to keen on using Emacs to browse the web /via/ xwidget
|
||
(EWW does a good job on its own), I am very interested in its
|
||
capability to render pages with JavaScript, as it may come of use when
|
||
doing web development. I can see the results of work very quickly
|
||
without switching windows all within Emacs.
|
||
|
||
2023-10-20: Disabled as it didn't seem to work, and honestly wasn't
|
||
that useful.
|
||
*** Xwidget Core
|
||
#+begin_src emacs-lisp
|
||
(use-package xwidget
|
||
:general
|
||
(app-leader
|
||
"u" #'xwidget-webkit-browse-url)
|
||
(nmmap
|
||
:keymaps 'xwidget-webkit-mode-map
|
||
"q" #'quit-window
|
||
"h" #'xwidget-webkit-scroll-backward
|
||
"j" #'xwidget-webkit-scroll-up
|
||
"k" #'xwidget-webkit-scroll-down
|
||
"l" #'xwidget-webkit-scroll-forward
|
||
"+" #'xwidget-webkit-zoom-in
|
||
"-" #'xwidget-webkit-zoom-out
|
||
(kbd "C-f") #'xwidget-webkit-scroll-up
|
||
(kbd "C-b") #'xwidget-webkit-scroll-down
|
||
"H" #'xwidget-webkit-back
|
||
"L" #'xwidget-webkit-forward
|
||
"gu" #'xwidget-webkit-browse-url
|
||
"gr" #'xwidget-webkit-reload
|
||
"gg" #'xwidget-webkit-scroll-top
|
||
"G" #'xwidget-webkit-scroll-bottom))
|
||
#+end_src
|
||
*** Xwidget Extensions
|
||
Define a function ~+xwidget/render-file~ that reads a file name and
|
||
presents it in an xwidget. If the current file is an HTML file, ask
|
||
if user wants to open current file. Bind it to ~aU~ in the leader.
|
||
|
||
Also define a function ~+xwidget/search-query~ that first asks the
|
||
user what search engine they want to use
|
||
([[https://duckduckgo.com][Duck Duck Go]] and
|
||
[[https://devdocs.io][DevDocs]] currently) then asks for a query,
|
||
which it parses then presents in an xwidget window. Bind to ~as~ in
|
||
the leader.
|
||
#+begin_src emacs-lisp
|
||
(use-package xwidget
|
||
:commands (+xwidget/render-file +xwidget/search)
|
||
:general
|
||
(app-leader
|
||
"U" #'+xwidget/render-file
|
||
"s" #'+xwidget/search)
|
||
:config
|
||
(setenv "WEBKIT_FORCE_SANDBOX" "0")
|
||
(defun +xwidget/render-file (&optional FORCE)
|
||
"Find file (or use current file) and render in xwidget."
|
||
(interactive)
|
||
(cond
|
||
((and (not FORCE) (or (string= (replace-regexp-in-string ".*.html"
|
||
"html" (buffer-name)) "html")
|
||
(eq major-mode 'web-mode)
|
||
(eq major-mode 'html-mode))) ; If in html file
|
||
(if (y-or-n-p "Open current file?: ") ; Maybe they want to open a separate file
|
||
(xwidget-webkit-browse-url (format "file://%s" (buffer-file-name)))
|
||
(+xwidget/render-file t))) ; recurse and open file via prompt
|
||
(t
|
||
(xwidget-webkit-browse-url
|
||
(format "file://%s" (read-file-name "Enter file to open: "))))))
|
||
|
||
(defun +xwidget/search ()
|
||
"Run a search query on some search engine and display in
|
||
xwidget."
|
||
(interactive)
|
||
(let* ((engine (completing-read "Engine: " '("duckduckgo.com" "devdocs.io") nil t))
|
||
(query-raw (read-string "Enter query: "))
|
||
(query
|
||
(cond
|
||
((string= engine "duckduckgo.com") query-raw)
|
||
((string= engine "devdocs.io") (concat "_ " query-raw)))))
|
||
(xwidget-webkit-browse-url (concat "https://" engine "/?q=" query)))))
|
||
#+end_src
|
||
** Eshell
|
||
*** Why Eshell?
|
||
Eshell is an integrated shell environment for Emacs, written in Emacs
|
||
Lisp. I argue that it is the best shell/command interpreter to use in
|
||
Emacs.
|
||
|
||
Eshell is unlike the alternatives in Emacs as it's a /shell/ first,
|
||
not a terminal emulator. It has the ability to spoof some aspects of a
|
||
terminal emulator (through the shell parser), but it is NOT a terminal
|
||
emulator.
|
||
|
||
The killer benefits of eshell (which would appeal to Emacs users) are
|
||
a direct result of eshell being written in Emacs lisp:
|
||
- incredible integration with Emacs utilities (such as ~dired~,
|
||
~find-file~, any read functions, etc)
|
||
- very extensible, easy to write new commands which leverage Emacs
|
||
commands as well as external utilities
|
||
- agnostic of platform: "eshell/cd" will call the underlying change
|
||
directory function for you, so commands will (usually) mean the same
|
||
thing regardless of platform
|
||
- this means as long as Emacs can run on an operating system, one
|
||
may run eshell
|
||
|
||
However, my favourite feature of eshell is the set of evaluators that
|
||
run on command input. Some of the benefits listed above come as a
|
||
result of this powerful feature. These evaluators are described below.
|
||
|
||
Lisp evaluator: works on braced expressions, evaluating them as Lisp
|
||
expressions (e.g. ~(message "Hello, World!\n")~). Any returned
|
||
objects are printed. This makes eshell a LISP REPL!
|
||
|
||
External evaluator: works within curly braces, evaluating them via
|
||
some external shell process (like sh) (e.g. ~{echo "Hello,
|
||
world!\n"}~). This makes eshell a (kinda dumb) terminal emulator!
|
||
|
||
The alias evaluator is the top level evaluator. It is the main
|
||
evaluator for each expression given to eshell. When given an
|
||
expression it tries to evaluate it by testing against these conditions:
|
||
- it's an alias defined by the user or in the ~eshell/~ namespace of
|
||
functions (simplest evaluator)
|
||
- it's some form of lisp expression (lisp evaluator)
|
||
- it's an external command (bash evaluator)
|
||
Essentially, you get the best of both Emacs and external shell
|
||
programs *ALL WITHIN* Emacs for free.
|
||
*** Eshell functionality
|
||
Bind some evil-like movements for easy shell usage, and a toggle
|
||
function to pull up the eshell quickly.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:general
|
||
(shell-leader
|
||
"t" #'eshell)
|
||
:init
|
||
(add-hook
|
||
'eshell-mode-hook
|
||
(proc
|
||
(interactive)
|
||
(general-def
|
||
:states '(normal insert)
|
||
:keymaps 'eshell-mode-map
|
||
"M-j" #'eshell-next-matching-input-from-input
|
||
"M-k" #'eshell-previous-matching-input-from-input)
|
||
(local-leader
|
||
:keymaps 'eshell-mode-map
|
||
"c" (proc (interactive) (eshell/clear)
|
||
(recenter))
|
||
"k" #'eshell-kill-process))))
|
||
#+end_src
|
||
*** Eshell pretty symbols and display
|
||
Pretty symbols and a display record.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:pretty
|
||
(eshell-mode-hook
|
||
("lambda" . "λ")
|
||
("numberp" . "ℤ")
|
||
("t" . "⊨")
|
||
("nil" . "Ø"))
|
||
:display
|
||
("\\*e?shell\\*" ; for general shells as well
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.33)))
|
||
#+end_src
|
||
*** Eshell variables and aliases
|
||
Set some sane defaults, a banner and a prompt. The prompt checks for
|
||
a git repo in the current directory and provides some extra
|
||
information in that case (in particular, branch name and if there any
|
||
changes that haven't been committed).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:config
|
||
(defun +eshell/--git-get-remote-status ()
|
||
(let* ((branch-status (split-string
|
||
(shell-command-to-string "git status | grep 'Your branch is'")))
|
||
(status (nth 3 branch-status))
|
||
(diff (cl-position "by" branch-status :test #'string=)))
|
||
(if (null diff)
|
||
(propertize "=" 'font-lock-face '(:foreground "green"))
|
||
(let ((n (nth (+ 1 diff) branch-status)))
|
||
(concat
|
||
(cond
|
||
((string= status "ahead")
|
||
(propertize "→ " 'font-lock-face '(:foreground "dodger blue")))
|
||
((string= status "behind")
|
||
(propertize "← " 'font-lock-face '(:foreground "orange red"))))
|
||
n)))))
|
||
|
||
(defun +eshell/--git-get-change-status ()
|
||
(let ((changed-files (- (length (split-string (shell-command-to-string "git status -s" ) "\n")) 1)))
|
||
(if (= changed-files 0)
|
||
(propertize "✓" 'font-lock-face '(:foreground "green"))
|
||
(propertize (number-to-string changed-files) 'font-lock-face '(:foreground "red")))))
|
||
|
||
(defun +eshell/get-git-properties ()
|
||
(let ((git-branch (shell-command-to-string "git branch")))
|
||
(if (or (string= git-branch "")
|
||
(not (string= "*" (substring git-branch 0 1))))
|
||
""
|
||
(format
|
||
"(%s<%s>[%s])"
|
||
(nth 2 (split-string git-branch "\n\\|\\*\\| "))
|
||
(+eshell/--git-get-change-status)
|
||
(+eshell/--git-get-remote-status)))))
|
||
|
||
(defun +eshell/prompt-function ()
|
||
(let ((git (+eshell/get-git-properties)))
|
||
(mapconcat
|
||
(lambda (item)
|
||
(if (listp item)
|
||
(propertize (car item)
|
||
'read-only t
|
||
'font-lock-face (cdr item)
|
||
'front-sticky '(font-lock-face read-only)
|
||
'rear-nonsticky '(font-lock-face read-only))
|
||
item))
|
||
(list
|
||
'("[")
|
||
`(,(abbreviate-file-name (eshell/pwd)) :foreground "LimeGreen")
|
||
'("]")
|
||
(if (string= git "")
|
||
""
|
||
(concat "-" git ""))
|
||
"\n"
|
||
`(,(format-time-string "[%H:%M:%S]") :foreground "purple")
|
||
"\n"
|
||
'("𝜆> " :foreground "DeepSkyBlue")))))
|
||
|
||
(defun +eshell/banner-message ()
|
||
(concat (shell-command-to-string "~/.local/scripts/cowfortune")
|
||
"\n"))
|
||
|
||
(setq eshell-cmpl-ignore-case t
|
||
eshell-cd-on-directory t
|
||
eshell-banner-message '(+eshell/banner-message)
|
||
eshell-highlight-prompt nil
|
||
eshell-prompt-function #'+eshell/prompt-function
|
||
eshell-prompt-regexp "^𝜆> "))
|
||
#+end_src
|
||
*** Eshell change directory quickly
|
||
Add ~eshell/goto~, which is actually a command accessible from within
|
||
eshell (this is because ~eshell/*~ creates an accessible function
|
||
within eshell with name ~*~). ~eshell/goto~ makes it easier to change
|
||
directories by using Emacs' find-file interface (which is much faster
|
||
than ~cd ..; ls -l~).
|
||
|
||
~eshell/goto~ is a better ~cd~ for eshell. However it is really just
|
||
a plaster over a bigger issue for my workflow; many times I want
|
||
eshell to be present in the current directory of the buffer I am
|
||
using. So here's also a command for opening eshell with the current
|
||
directory.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:general
|
||
(leader
|
||
"T" #'+eshell/current-buffer)
|
||
:config
|
||
(defun eshell/goto (&rest args)
|
||
"Use `read-directory-name' to change directories."
|
||
(eshell/cd (list (read-directory-name "Directory?: "))))
|
||
|
||
(defun eshell/project-root (&rest args)
|
||
"Change to directory `project-root'"
|
||
(if (project-current)
|
||
(eshell/cd (list (project-root (project-current))))
|
||
(eshell/echo (format "[%s]: No project in current directory"
|
||
(propertize "Error" 'font-lock-face '(:foreground "red"))))))
|
||
|
||
(defun +eshell/current-buffer ()
|
||
(interactive)
|
||
(let ((dir (if buffer-file-name
|
||
(file-name-directory buffer-file-name)
|
||
default-directory))
|
||
(buf (eshell)))
|
||
(if dir
|
||
(with-current-buffer buf
|
||
(eshell/cd dir)
|
||
(eshell-send-input))
|
||
(message "Could not switch eshell: buffer is not real file")))))
|
||
#+end_src
|
||
** WAIT Elfeed
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Elfeed is the perfect RSS feed reader, integrated into Emacs
|
||
perfectly. I've got a set of feeds that I use for a large variety of
|
||
stuff, mostly media and entertainment. I've also bound "<leader> ar"
|
||
to elfeed for loading the system.
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed
|
||
:general
|
||
(app-leader "r" #'elfeed)
|
||
(nmmap
|
||
:keymaps 'elfeed-search-mode-map
|
||
"gr" #'elfeed-update
|
||
"s" #'elfeed-search-live-filter
|
||
"<return>" #'elfeed-search-show-entry)
|
||
:init
|
||
(setq elfeed-db-directory (no-littering-expand-var-file-name "elfeed/"))
|
||
|
||
(setq +rss/feed-urls
|
||
'(("Arch Linux"
|
||
"https://www.archlinux.org/feeds/news/"
|
||
News Technology)
|
||
("The Onion"
|
||
"https://www.theonion.com/rss"
|
||
Social)
|
||
("Protesilaos Stavrou"
|
||
"https://www.youtube.com/@protesilaos"
|
||
YouTube Technology)
|
||
("Tsoding Daily"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCrqM0Ym_NbK1fqeQG2VIohg"
|
||
YouTube Technology)
|
||
("Tsoding"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCrqM0Ym_NbK1fqeQG2VIohg"
|
||
YouTube Technology)
|
||
("Nexpo"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCpFFItkfZz1qz5PpHpqzYBw"
|
||
YouTube Stories)
|
||
("3B1B"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCYO_jab_esuFRV4b17AJtAw"
|
||
YouTube)
|
||
("Fredrik Knusden"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCbWcXB0PoqOsAvAdfzWMf0w"
|
||
YouTube Stories)
|
||
("Barely Sociable"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UC9PIn6-XuRKZ5HmYeu46AIw"
|
||
YouTube Stories)
|
||
("Atrocity Guide"
|
||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCn8OYopT9e8tng-CGEWzfmw"
|
||
YouTube Stories)
|
||
("Hacker News"
|
||
"https://news.ycombinator.com/rss"
|
||
Social News Technology)
|
||
("Hacker Factor"
|
||
"https://www.hackerfactor.com/blog/index.php?/feeds/index.rss2"
|
||
Social)))
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-elfeed-setup))
|
||
|
||
(setq elfeed-feeds (cl-map 'list #'(lambda (item)
|
||
(append (list (nth 1 item)) (cdr (cdr item))))
|
||
+rss/feed-urls))
|
||
|
||
(advice-add 'elfeed-search-show-entry :after #'+elfeed/dispatch-entry)
|
||
|
||
(defun +elfeed/dispatch-entry (entry)
|
||
"Process each type of entry differently.
|
||
e.g., you may want to open HN entries in eww."
|
||
(let ((url (elfeed-entry-link entry)))
|
||
(pcase url
|
||
((pred (string-match-p "https\\:\\/\\/www.youtube.com\\/watch"))
|
||
(mpv-play-url url))
|
||
(_ (eww url))))))
|
||
#+end_src
|
||
** Magit
|
||
Magit is *the* git porcelain for Emacs, which perfectly encapsulates
|
||
the git cli. In this case I just need to setup the bindings for it.
|
||
As magit will definitely load after evil (as it must be run by a
|
||
binding, and evil will load after init), I can use evil-collection
|
||
freely. Also, define an auto insert for commit messages so that I
|
||
don't need to write everything myself.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package magit
|
||
:straight t
|
||
:defer t
|
||
:display
|
||
("magit:.*"
|
||
(display-buffer-same-window)
|
||
(inhibit-duplicate-buffer . t))
|
||
("magit-diff:.*"
|
||
(display-buffer-below-selected))
|
||
("magit-log:.*"
|
||
(display-buffer-same-window))
|
||
:general
|
||
(leader
|
||
"g" '(magit-dispatch :which-key "Magit"))
|
||
(code-leader
|
||
"b" #'magit-blame)
|
||
:auto-insert
|
||
(("COMMIT_EDITMSG" . "Commit skeleton")
|
||
""
|
||
"(" (read-string "Enter feature/module: ") ")"
|
||
(read-string "Enter simple description: ") "\n\n")
|
||
:init
|
||
(setq vc-follow-symlinks t
|
||
magit-blame-echo-style 'lines
|
||
magit-copy-revision-abbreviated t)
|
||
:config
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'magit-status-mode 'motion))
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-magit-setup)))
|
||
#+end_src
|
||
** IBuffer
|
||
IBuffer is the dired of buffers: providing the ability to mark
|
||
buffers, mass rename/delete and just observe stuff.
|
||
#+begin_src emacs-lisp
|
||
(use-package ibuffer
|
||
:defer t
|
||
:general
|
||
(buffer-leader
|
||
"i" #'ibuffer)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-ibuffer-setup)))
|
||
#+end_src
|
||
** Proced
|
||
Emacs has two systems for process management:
|
||
+ proced: a general 'top' like interface which allows general
|
||
management of linux processes
|
||
+ list-processes: a specific Emacs based system that lists processes
|
||
spawned by Emacs (similar to a top for Emacs specifically)
|
||
|
||
Core proced config, just a few bindings and evil collection setup.
|
||
#+begin_src emacs-lisp
|
||
(use-package proced
|
||
:defer t
|
||
:general
|
||
(app-leader
|
||
"p" #'proced)
|
||
(nmap
|
||
:keymaps 'proced-mode-map
|
||
"za" #'proced-toggle-auto-update)
|
||
:display
|
||
("\\*Proced\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:init
|
||
(setq proced-auto-update-interval 0.5)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-proced-setup)))
|
||
#+end_src
|
||
** Calculator
|
||
Surprise, surprise Emacs comes with a calculator.
|
||
|
||
Greater surprise, this thing is over powered. It can perform the
|
||
following (and more):
|
||
- Matrix calculations
|
||
- Generalised calculus operations
|
||
- Equation solvers for n-degree multi-variable polynomials
|
||
- Embedded mode (check below)!
|
||
|
||
~calc-mode~ is a calculator system within Emacs that provides a
|
||
diverse array of mathematical operations. It uses reverse polish
|
||
notation to do calculations (though there is a standard infix
|
||
algebraic notation mode).
|
||
|
||
Embedded mode allows computation with the current buffer as the echo
|
||
area. This basically means I can compute stuff within a buffer
|
||
without invoking calc directly: $1 + 2\rightarrow_{\text{calc-embed}} 3$.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package calc
|
||
:defer t
|
||
:display
|
||
("*Calculator*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.18))
|
||
:general
|
||
(app-leader
|
||
"c" #'calc-dispatch)
|
||
(mode-leader
|
||
"c" #'calc-embedded)
|
||
:init
|
||
(setq calc-algebraic-mode t)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-calc-setup)))
|
||
#+end_src
|
||
*** WAIT Calctex
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
~calc-mode~ also has a 3rd party package called ~calctex~. It renders
|
||
mathematical expressions within calc as if they were rendered in TeX.
|
||
You can also copy the expressions in their TeX forms, which is pretty
|
||
useful when writing a paper. I've set a very specific lock on this
|
||
repository as it's got quite a messy work-tree and this commit seems to
|
||
work for me given the various TeX utilities installed via Arch.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package calctex
|
||
:after calc
|
||
:straight (calctex :type git :host github :repo "johnbcoughlin/calctex")
|
||
:hook (calc-mode-hook . calctex-mode))
|
||
#+end_src
|
||
** WAIT Ledger
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package ledger-mode
|
||
:defer t)
|
||
|
||
(use-package evil-ledger
|
||
:after ledger-mode)
|
||
#+end_src
|
||
** Zone
|
||
Of course Emacs has a cool screensaver software.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package zone-matrix
|
||
:straight t
|
||
:defer t
|
||
:commands (zone)
|
||
:general
|
||
(leader
|
||
"z" #'zone)
|
||
:init
|
||
(setq zone-programs
|
||
[zone-pgm-drip
|
||
zone-pgm-drip-fretfully
|
||
zone-pgm-martini-swan-dive
|
||
zone-pgm-stress
|
||
zone-pgm-random-life]))
|
||
#+end_src
|
||
** (Wo)man
|
||
Man pages are the user manuals for most software on Linux. Really
|
||
useful when writing code for Un*x systems, though they can be very
|
||
verbose.
|
||
|
||
2023-08-17: `Man-notify-method' is the reason the `:display' record
|
||
doesn't work here. I think it's to do with how Man pages are rendered
|
||
or something, but very annoying as it's a break from standards!
|
||
#+begin_src emacs-lisp
|
||
(use-package man
|
||
:defer t
|
||
:init
|
||
(setq Man-notify-method 'pushy)
|
||
:display
|
||
("^\\*Man.*"
|
||
(display-buffer-reuse-mode-window display-buffer-same-window))
|
||
:general
|
||
(file-leader
|
||
"m" #'man) ;; kinda like "find man page"
|
||
(nmmap
|
||
:keymaps 'Man-mode-map
|
||
"RET" #'man-follow))
|
||
#+end_src
|
||
** WAIT gif-screencast
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Little application that uses =gifsicle= to make essentially videos of
|
||
Emacs. Useful for demonstrating features.
|
||
#+begin_src emacs-lisp
|
||
(use-package gif-screencast
|
||
:straight t
|
||
:general
|
||
(app-leader
|
||
"x" #'gif-screencast-start-or-stop)
|
||
:init
|
||
(setq gif-screencast-output-directory (expand-file-name "~/Media/emacs/")))
|
||
#+end_src
|
||
** Image-mode
|
||
Image mode, for viewing images. Supports tons of formats, easy to use
|
||
and integrates slickly into image-dired. Of course,
|
||
#+begin_src emacs-lisp
|
||
(use-package image-mode
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'image-mode-map
|
||
"+" #'image-increase-size
|
||
"-" #'image-decrease-size
|
||
"p" #'image-animate
|
||
"P" #'image-animate-set-speed
|
||
"h" #'image-backward-hscroll
|
||
"j" #'image-next-line
|
||
"k" #'image-previous-line
|
||
"l" #'image-forward-hscroll))
|
||
#+end_src
|
||
** WAIT ERC
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package erc
|
||
:defer t
|
||
:init
|
||
(setq erc-server "irc.libera.chat"
|
||
erc-nick "oreodave"
|
||
erc-buffer-display "current"))
|
||
#+end_src
|
||
** WAIT MPV
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Basically a porcelain over mpv via the IPC interface.
|
||
#+begin_src emacs-lisp
|
||
(use-package mpv
|
||
:defer t
|
||
:straight t
|
||
:config
|
||
(with-eval-after-load "org"
|
||
(defun org-mpv-complete-link (&optional arg)
|
||
(replace-regexp-in-string
|
||
"file:" "mpv:"
|
||
(org-link-complete-file arg)
|
||
t t))
|
||
(org-link-set-parameters "mpv"
|
||
:follow #'mpv-play :complete #'org-mpv-complete-link)))
|
||
#+end_src
|
||
* Text modes
|
||
Standard packages and configurations for text-mode and its derived
|
||
modes.
|
||
** Flyspell
|
||
Flyspell allows me to quickly spell check text documents. I use
|
||
flyspell primarily in org mode, as that is my preferred prose writing
|
||
software, but I also need it in commit messages and so on. So
|
||
flyspell-mode should be hooked to text-mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package flyspell
|
||
:straight t
|
||
:defer t
|
||
:hook (text-mode-hook . flyspell-mode)
|
||
:general
|
||
(nmmap
|
||
:keymaps 'text-mode-map
|
||
(kbd "M-C") #'flyspell-correct-word-before-point
|
||
(kbd "M-c") #'flyspell-auto-correct-word)
|
||
(mode-leader
|
||
"s" #'flyspell-mode))
|
||
#+end_src
|
||
** Undo tree
|
||
Undo tree sits on top of the incredible Emacs undo capabilities.
|
||
Provides a nice visual for edits and a great way to produce branches
|
||
of edits. Also allows saving of undo trees, which makes Emacs a quasi
|
||
version control system in and of itself! The only extra necessary
|
||
would be describing changes...
|
||
#+begin_src emacs-lisp
|
||
(use-package undo-tree
|
||
:demand t
|
||
:straight t
|
||
:general
|
||
(leader
|
||
"u" #'undo-tree-visualize)
|
||
:init
|
||
(setq undo-tree-auto-save-history t
|
||
undo-tree-history-directory-alist backup-directory-alist)
|
||
:config
|
||
(global-undo-tree-mode))
|
||
#+end_src
|
||
** Whitespace
|
||
Deleting whitespace, highlighting when going beyond the 80th character
|
||
limit, all good stuff. I don't want to highlight whitespace for
|
||
general mode categories (Lisp shouldn't really have an 80 character
|
||
limit), so set it for specific modes need the help.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package whitespace
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"M--" #'whitespace-cleanup)
|
||
(mode-leader
|
||
"w" #'whitespace-mode)
|
||
:hook
|
||
(before-save-hook . whitespace-cleanup)
|
||
(c-mode-hook . whitespace-mode)
|
||
(c++-mode-hook . whitespace-mode)
|
||
(haskell-mode-hook . whitespace-mode)
|
||
(python-mode-hook . whitespace-mode)
|
||
(org-mode-hook . whitespace-mode)
|
||
(text-mode-hook . whitespace-mode)
|
||
:init
|
||
(setq whitespace-style '(face empty lines-tail
|
||
spaces tabs tab-mark
|
||
trailing newline)
|
||
whitespace-line-column 80))
|
||
#+end_src
|
||
** Set auto-fill-mode for all text-modes
|
||
Auto fill mode automatically newlines text on 80 characters, which
|
||
looks nice and integrates well with Evil's sentence and paragraph text
|
||
objects.
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'text-mode-hook #'auto-fill-mode)
|
||
#+end_src
|
||
** Show-paren-mode
|
||
Show parenthesis for Emacs
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'prog-mode-hook #'show-paren-mode)
|
||
#+end_src
|
||
** Smartparens
|
||
Smartparens is a smarter electric-parens, it's much more aware of
|
||
context and easier to use.
|
||
#+begin_src emacs-lisp
|
||
(use-package smartparens
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . smartparens-mode)
|
||
(text-mode-hook . smartparens-mode)
|
||
:config
|
||
(setq sp-highlight-pair-overlay nil
|
||
sp-highlight-wrap-overlay t
|
||
sp-highlight-wrap-tag-overlay t)
|
||
|
||
(let ((unless-list '(sp-point-before-word-p
|
||
sp-point-after-word-p
|
||
sp-point-before-same-p)))
|
||
(sp-pair "'" nil :unless unless-list)
|
||
(sp-pair "\"" nil :unless unless-list))
|
||
(sp-local-pair sp-lisp-modes "(" ")" :unless '(:rem sp-point-before-same-p))
|
||
(require 'smartparens-config))
|
||
#+end_src
|
||
** Thesaurus
|
||
=le-thesaurus= is a great extension for quickly searching up words for
|
||
synonyms or antonyms. I may need it anywhere so I bind it to all
|
||
keymaps. Same with dictionary searching.
|
||
#+begin_src emacs-lisp
|
||
(use-package le-thesaurus
|
||
:straight t
|
||
:defer t
|
||
:display
|
||
("\\*Dictionary\\*"
|
||
(display-buffer-reuse-window display-buffer-same-window)
|
||
(reusable-frames . t))
|
||
:init
|
||
(setq dictionary-server "dict.org")
|
||
:general
|
||
(search-leader
|
||
:infix "w"
|
||
"s" #'le-thesaurus-get-synonyms
|
||
"a" #'le-thesaurus-get-antonyms
|
||
"d" #'dictionary-search))
|
||
#+end_src
|
||
* Programming packages
|
||
Packages that help with programming in general, providing IDE like
|
||
capabilities.
|
||
** Eldoc
|
||
Eldoc presents documentation to the user upon placing ones cursor upon
|
||
any symbol. This is very useful when programming as it:
|
||
- presents the arguments of functions while writing calls for them
|
||
- presents typing and documentation of variables
|
||
|
||
Eldoc box makes the help buffer a hovering box instead of printing it
|
||
in the minibuffer. A lot cleaner.
|
||
#+begin_src emacs-lisp
|
||
(use-package eldoc
|
||
:defer t
|
||
:hook (prog-mode-hook . eldoc-mode)
|
||
:init
|
||
(global-eldoc-mode 1)
|
||
:general
|
||
(leader
|
||
"h>" #'eldoc-doc-buffer))
|
||
|
||
(use-package eldoc-box
|
||
:straight t
|
||
:defer t
|
||
:hook (eldoc-mode-hook . eldoc-box-hover-mode)
|
||
:init
|
||
(setq eldoc-box-position-function #'eldoc-box--default-upper-corner-position-function
|
||
eldoc-box-clear-with-C-g t)
|
||
:general
|
||
(leader
|
||
"h." #'eldoc-box-help-at-point))
|
||
#+end_src
|
||
** Flycheck
|
||
Flycheck is the checking system for Emacs. I don't necessarily like
|
||
having all my code checked all the time, so I haven't added a hook to
|
||
prog-mode as it would be better for me to decide when I want checking
|
||
and when I don't.
|
||
|
||
I've added it to C/C++ mode because I use them regularly and flycheck
|
||
has very little overhead to work there.
|
||
#+begin_src emacs-lisp
|
||
(use-package flycheck
|
||
:straight t
|
||
:defer t
|
||
:commands (flycheck-mode flycheck-list-errors)
|
||
:hook
|
||
(c-mode-hook . flycheck-mode)
|
||
(c++-mode-hook . flycheck-mode)
|
||
:general
|
||
(mode-leader
|
||
"f" #'flycheck-mode)
|
||
(code-leader
|
||
"x" #'flycheck-list-errors
|
||
"J" #'flycheck-next-error
|
||
"K" #'flycheck-previous-error)
|
||
:display
|
||
("\\*Flycheck.*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:init
|
||
(setq-default flycheck-check-syntax-automatically '(save new-line mode-enabled))
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-flycheck-setup)))
|
||
#+end_src
|
||
** Eglot
|
||
Eglot is package to communicate with LSP servers for better
|
||
programming capabilities. Interactions with a server provide results
|
||
to the client, done through JSON.
|
||
|
||
NOTE: Emacs 28.1 comes with better JSON parsing, which makes Eglot
|
||
much faster.
|
||
|
||
2023-03-26: I've found Eglot to be useful sometimes, but many of the
|
||
projects I work on don't require a heavy server setup to efficiently
|
||
edit and check for errors; Emacs provides a lot of functionality. So
|
||
by default I've disabled it, using =M-x eglot= to startup the LSP
|
||
server when I need it.
|
||
#+begin_src emacs-lisp
|
||
(use-package eglot
|
||
:defer t
|
||
:general
|
||
(code-leader
|
||
:keymaps 'eglot-mode-map
|
||
"f" #'eglot-format
|
||
"a" #'eglot-code-actions
|
||
"r" #'eglot-rename
|
||
"R" #'eglot-reconnect)
|
||
:init
|
||
(setq eglot-stay-out-of '(flymake))
|
||
:config
|
||
(add-to-list 'eglot-server-programs '((c++-mode c-mode) "clangd")))
|
||
#+end_src
|
||
*** Flycheck-Eglot
|
||
By default Eglot uses the integrated flymake package for error
|
||
reporting. I don't mind flymake, and I think an integrated solution
|
||
which doesn't rely on external packages is always a great idea.
|
||
However, I just personally prefer flycheck and it's become part of my
|
||
mental model when programming. So here's a package which will
|
||
integrate flycheck into Eglot's error reporting.
|
||
|
||
(Funny but also kind of depressing is this issue in Eglot where
|
||
someone requested this integration, which caused a bit of a flame war.
|
||
People are stupid.
|
||
[[https://github.com/joaotavora/eglot/issues/42][no opinion on
|
||
flymake]])
|
||
#+begin_src emacs-lisp
|
||
(use-package flycheck-eglot
|
||
:straight t
|
||
:after (flycheck eglot)
|
||
:hook (eglot-managed-mode-hook . flycheck-eglot-mode))
|
||
#+end_src
|
||
** Indentation
|
||
By default, turn off tabs and set the tab width to two.
|
||
#+begin_src emacs-lisp
|
||
(setq-default indent-tabs-mode nil
|
||
tab-width 2)
|
||
#+end_src
|
||
|
||
However, if necessary later, define a function that may activate tabs locally.
|
||
#+begin_src emacs-lisp
|
||
(defun +oreo/use-tabs ()
|
||
(interactive)
|
||
(setq-local indent-tabs-mode t))
|
||
#+end_src
|
||
** Highlight todo items
|
||
TODO items are highlighted in org-mode, but not necessarily in every
|
||
mode. This minor mode highlights all TODO like items via a list of
|
||
strings to match. It also configures faces to use when highlighting.
|
||
I hook it to prog-mode.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package hl-todo
|
||
:straight t
|
||
:after prog-mode
|
||
:hook (prog-mode-hook . hl-todo-mode)
|
||
:init
|
||
(setq hl-todo-keyword-faces
|
||
'(("TODO" . "#E50000")
|
||
("WIP" . "#ffa500")
|
||
("NOTE" . "#00CC00")
|
||
("FIXME" . "#d02090"))))
|
||
#+end_src
|
||
** Hide-show mode
|
||
Turn on ~hs-minor-mode~ for all prog-mode. This provides folds for
|
||
free.
|
||
#+begin_src emacs-lisp
|
||
(use-package hideshow
|
||
:defer t
|
||
:hook (prog-mode-hook . hs-minor-mode))
|
||
#+end_src
|
||
** Aggressive indenting
|
||
Essentially my dream editing experience: when I type stuff in, try and
|
||
indent it for me on the fly. Just checkout the
|
||
[[https://github.com/Malabarba/aggressive-indent-mode][page]], any
|
||
description I give won't do it justice.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package aggressive-indent
|
||
:straight t
|
||
:demand t
|
||
:config
|
||
(add-to-list 'aggressive-indent-excluded-modes
|
||
'c-mode)
|
||
(add-to-list 'aggressive-indent-excluded-modes
|
||
'c++-mode)
|
||
(add-to-list 'aggressive-indent-excluded-modes
|
||
'cc-mode)
|
||
(global-aggressive-indent-mode))
|
||
#+end_src
|
||
** Compilation
|
||
Colourising the compilation buffer so ANSI colour codes get computed.
|
||
#+begin_src emacs-lisp
|
||
(use-package compile
|
||
:defer t
|
||
:general
|
||
(code-leader
|
||
"j" #'next-error
|
||
"k" #'previous-error
|
||
"c" #'compile
|
||
"C" #'recompile)
|
||
(nmmap
|
||
:keymaps 'compilation-mode-map
|
||
"c" #'recompile)
|
||
(general-def
|
||
:keymaps 'compilation-mode-map
|
||
"g" nil) ;; by default this is recompile
|
||
:display
|
||
("\\*compilation\\*"
|
||
(display-buffer-reuse-window display-buffer-at-bottom)
|
||
(reusable-frames . t)
|
||
(window-height . 0.25))
|
||
:config
|
||
(defun +compile/colourise ()
|
||
"Colourise the emacs compilation buffer."
|
||
(interactive)
|
||
(let ((inhibit-read-only t))
|
||
(ansi-color-apply-on-region (point-min) (point-max))))
|
||
(add-hook 'compilation-filter-hook #'+compile/colourise))
|
||
#+end_src
|
||
** xref
|
||
Find definitions, references and general objects using tags without
|
||
external packages. Provided by default in Emacs and just requires a
|
||
way of generating a =TAGS= file for your project. Helps with minimal
|
||
setups for programming without heavier packages like [[*Eglot][Eglot]].
|
||
#+begin_src emacs-lisp
|
||
(use-package xref
|
||
:defer t
|
||
:display
|
||
("\\*xref\\*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.25))
|
||
:general
|
||
(code-leader
|
||
"t" '(nil :which-key "Tags"))
|
||
(code-leader
|
||
:infix "t"
|
||
"t" #'xref-find-apropos
|
||
"d" #'xref-find-definitions
|
||
"r" #'xref-find-references)
|
||
(nmmap
|
||
:keymaps 'xref--xref-buffer-mode-map
|
||
"RET" #'xref-goto-xref
|
||
"J" #'xref-next-line
|
||
"K" #'xref-prev-line
|
||
"g" #'xref-revert-buffer
|
||
"q" #'quit-window))
|
||
#+end_src
|
||
** Project.el
|
||
An inbuilt solution for creating and managing projects that doesn't
|
||
require a dependency. Where possible we should try to use Emacs
|
||
defaults (admittedly this is a philosophy I've only recently adopted)
|
||
so when setting up a new computer it takes a bit less time.
|
||
|
||
Here I write a TAGS command, mimicking projectile's one, so I can
|
||
quickly generate them in C/C++ projects.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package project
|
||
:defer t
|
||
:general
|
||
(general-def
|
||
:keymaps 'project-prefix-map
|
||
"R" #'+project/generate-tags)
|
||
:config
|
||
(defun +project/generate-tags ()
|
||
(interactive)
|
||
(let ((project (project-current)))
|
||
(if (not project)
|
||
(message "+project/generate-tags: Not in project.")
|
||
(let ((tags-file (concat (project-root project) "TAGS"))
|
||
(files (format "%s" (project-files project))))
|
||
(set-process-sentinel
|
||
(start-process-shell-command
|
||
"PROJECT-GENERATE-TAGS"
|
||
"*gen-tags*"
|
||
(format "ctags -Re -f %s %s"
|
||
tags-file
|
||
(substring files 1 (- (length files) 1))))
|
||
(lambda (p event)
|
||
(when (string= event "finished\n")
|
||
(message "Finished generating tags!")))))))))
|
||
#+end_src
|
||
** WAIT Projectile
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Projectile is a project management package which integrates with Emacs
|
||
very well. It essentially provides alternative Emacs commands scoped
|
||
to the current 'project', based on differing signs that a directory is
|
||
a 'project'.
|
||
#+begin_src emacs-lisp
|
||
(use-package projectile
|
||
:hook (emacs-startup-hook . projectile-mode)
|
||
:general
|
||
(general-def
|
||
:keymaps 'projectile-command-map
|
||
"t" #'projectile-test-project
|
||
"r" #'projectile-run-project
|
||
"q" #'projectile-replace-regexp)
|
||
(leader
|
||
"p" '(projectile-command-map :which-key "Projectile"))
|
||
:init
|
||
(setq projectile-tags-command "ctags -Re -f \"%s\" %s \"%s\""
|
||
projectile-enable-caching t))
|
||
#+end_src
|
||
*** Counsel projectile
|
||
Counsel integration for projectile commands, very nice.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel-projectile
|
||
:after (projectile counsel)
|
||
:config
|
||
(counsel-projectile-mode +1))
|
||
#+end_src
|
||
** devdocs
|
||
#+begin_src emacs-lisp
|
||
(use-package devdocs
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"K" #'devdocs-lookup))
|
||
#+end_src
|
||
* Org mode
|
||
2023-03-30: finally decided to give org mode its own section.
|
||
|
||
Org is, at its most basic, a markup language. Files use the ".org"
|
||
extension and use =org-mode= to write text, with the ability to export
|
||
to a few formats, all within Emacs. Some other features include:
|
||
+ A complete spreadsheet system, with formulas (including
|
||
[[*Calculator][calc-mode]] integration)
|
||
+ Evaluation of code blocks, even using the results of them in exports
|
||
(to, say, a $\LaTeX$ or HTML document)
|
||
+ This includes exporting code blocks to a code file. All the
|
||
emacs-lisp code blocks in this file are compiled to =config.el=
|
||
([[file:elisp/literate.el][literate]])
|
||
+ Complete calendar/todo system with deadlines, scheduling and
|
||
repeaters
|
||
+ Export to a variety of formats or make your own export engine using
|
||
the org AST!
|
||
+ Writing $\LaTeX$ inline, with the ability to render the fragments on
|
||
demand
|
||
** Org Essentials
|
||
Org has a ton of settings to tweak, which change your experience quite
|
||
a bit. Here are mine, but this took a lot of just reading other
|
||
people's configurations and testing. I don't do a good job of
|
||
explaining how this works in all honesty, but it works well for me so
|
||
I'm not very bothered.
|
||
|
||
+ By default =~/Text= is my directory for text files. I actually have
|
||
a repository that manages this directory for agenda files and other
|
||
documents
|
||
+ Indentation in file should not be allowed, i.e. text indentation, as
|
||
that forces other editors to read it a certain way as well. It's
|
||
obtrusive hence it's off.
|
||
+ Org startup indented is on by default as most documents do benefit
|
||
from the indentation, but I do turn it off for some files via
|
||
~#+startup:noindent~
|
||
+ When opening an org document there can be a lot of headings, so I
|
||
set folding to just content
|
||
+ Org documents can also have a lot of latex previews, which make
|
||
opening some after a while a massive hassle. If I want to see the
|
||
preview, I'll do it myself, so turn it off.
|
||
+ Org manages windowing itself, to some extent, so I set those options
|
||
to be as unobtrusive as possible
|
||
+ Load languages I use in =src= blocks in org-mode (Emacs-lisp for
|
||
this configuration, C and Python)
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq org-directory "~/Text"
|
||
org-adapt-indentation nil
|
||
org-indent-mode nil
|
||
org-startup-indented t
|
||
org-startup-folded 'content
|
||
org-startup-with-latex-preview nil
|
||
org-imenu-depth 10
|
||
org-src-window-setup 'current-window
|
||
org-indirect-buffer-display 'current-window
|
||
org-link-frame-setup '((vm . vm-visit-folder-other-frame)
|
||
(vm-imap . vm-visit-imap-folder-other-frame)
|
||
(file . find-file))
|
||
org-babel-load-languages '((emacs-lisp . t)
|
||
(lisp . t)
|
||
(shell . t))))
|
||
#+end_src
|
||
** Org Latex
|
||
Org mode has deep integration with latex, can export to PDF and even
|
||
display latex fragments in the document directly. I setup the
|
||
pdf-process, code listing options via minted and the format options
|
||
for latex fragments.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq org-format-latex-options
|
||
'(:foreground default :background default :scale 2
|
||
:html-foreground "Black" :html-background "Transparent"
|
||
:html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\["))
|
||
org-latex-src-block-backend 'minted
|
||
org-latex-minted-langs '((emacs-lisp "common-lisp")
|
||
(ledger "text")
|
||
(cc "c++")
|
||
(cperl "perl")
|
||
(shell-script "bash")
|
||
(caml "ocaml"))
|
||
org-latex-packages-alist '(("" "minted"))
|
||
org-latex-pdf-process
|
||
(list (concat "latexmk -f -bibtex -pdf "
|
||
"-shell-escape -%latex -interaction=nonstopmode "
|
||
"-output-directory=%o %f"))
|
||
org-latex-minted-options
|
||
'(("style" "colorful")
|
||
("linenos")
|
||
("frame" "single")
|
||
("mathescape")
|
||
("fontfamily" "courier")
|
||
("samepage" "false")
|
||
("breaklines" "true")
|
||
("breakanywhere" "true"))))
|
||
#+end_src
|
||
** Org Core Variables
|
||
Tons of variables for org-mode, including a ton of latex ones. Can't
|
||
really explain because it sets up quite a lot of local stuff. Also I
|
||
copy pasted the majority of this, tweaking it till it felt good. Doom
|
||
Emacs was very helpful here.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq org-edit-src-content-indentation 0
|
||
org-goto-interface 'outline
|
||
org-imenu-depth 10
|
||
org-export-backends '(ascii html latex odt icalendar)
|
||
org-eldoc-breadcrumb-separator " → "
|
||
org-enforce-todo-dependencies t
|
||
org-fontify-quote-and-verse-blocks t
|
||
org-fontify-whole-heading-line t
|
||
org-footnote-auto-label t
|
||
org-hide-leading-stars t
|
||
org-hide-emphasis-markers nil
|
||
org-image-actual-width nil
|
||
org-priority-faces '((?A . error) (?B . warning) (?C . success))
|
||
org-link-descriptive nil
|
||
org-tags-column 0
|
||
org-todo-keywords
|
||
'((sequence "TODO" "WIP" "DONE")
|
||
(sequence "PROJ" "WAIT" "COMPLETE"))
|
||
org-use-sub-superscripts '{}))
|
||
#+end_src
|
||
** Org Core Functionality
|
||
Hooks, prettify-symbols and records for auto insertion.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(org-mode-hook . prettify-symbols-mode)
|
||
:display
|
||
("\\*Org Src.*"
|
||
(display-buffer-same-window))
|
||
:pretty
|
||
(org-mode-hook
|
||
("#+begin_src" . "≫")
|
||
("#+end_src" . "≪"))
|
||
:auto-insert
|
||
(("\\.org\\'" . "Org skeleton")
|
||
"Enter title: "
|
||
"#+title: " str | (buffer-file-name) "\n"
|
||
"#+author: " (read-string "Enter author: ") | user-full-name "\n"
|
||
"#+description: " (read-string "Enter description: ") | "Description" "\n"
|
||
"#+date: " (format-time-string "%Y-%m-%d" (current-time)) "\n"
|
||
"* " _))
|
||
#+end_src
|
||
** Org Core Bindings
|
||
Some bindings for org mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(file-leader
|
||
"l" #'org-store-link
|
||
"i" #'org-insert-last-stored-link)
|
||
(code-leader
|
||
:keymaps 'emacs-lisp-mode-map
|
||
"D" #'org-babel-detangle)
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
"l" '(nil :which-key "Links")
|
||
"'" '(nil :which-key "Tables")
|
||
"c" '(nil :which-key "Clocks")
|
||
"r" #'org-refile
|
||
"d" #'org-date-from-calendar
|
||
"t" #'org-todo
|
||
"," #'org-priority
|
||
"T" #'org-babel-tangle
|
||
"i" #'org-insert-structure-template
|
||
"p" #'org-latex-preview
|
||
"s" #'org-property-action
|
||
"e" #'org-export-dispatch
|
||
"o" #'org-edit-special)
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "l"
|
||
"i" #'org-insert-link
|
||
"l" #'org-open-at-point
|
||
"f" #'org-footnote-action)
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "'"
|
||
"a" #'org-table-align
|
||
"c" #'org-table-create
|
||
"f" #'org-table-edit-formulas
|
||
"t" #'org-table-toggle-coordinate-overlays
|
||
"s" #'org-table-sum
|
||
"e" #'org-table-calc-current-TBLFM
|
||
"E" #'org-table-eval-formula))
|
||
#+end_src
|
||
** Searching org files
|
||
The default ~imenu~ support for Org-mode is god-awful. ~Imenu~ for
|
||
org-mode should show me a list of headings and provide a
|
||
completing-read interface to search them.
|
||
|
||
[[*Counsel][Counsel]] has me covered for this as I can just provide it
|
||
a regex as an initial prompt to narrow the candidates down to just the
|
||
headings then let the user go from there. I use ~swiper~ when
|
||
considering just the local file (a la ~imenu~) and ~counsel-rg~ to
|
||
search multiple org-files.
|
||
|
||
The cherry on top is ~+org/search-config-headings~ which searches the
|
||
org files in ~user-emacs-directory~ and provides the headings for
|
||
them. This allows me to search my configuration pretty quickly.
|
||
#+begin_src emacs-lisp
|
||
(with-eval-after-load "counsel"
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:config
|
||
(defun +org/swiper-goto ()
|
||
(interactive)
|
||
(counsel-grep-or-swiper "^\\* "))
|
||
|
||
(defun +org/search-headings ()
|
||
"Searches directory (of buffer) for org headings via counsel-rg"
|
||
(interactive)
|
||
(counsel-rg "^\\* " (file-name-directory (buffer-file-name))))
|
||
|
||
(defun +org/search-config-headings ()
|
||
"Searches config.org for org headings via +org/swiper-goto"
|
||
(interactive)
|
||
(with-current-buffer (find-file (concat user-emacs-directory "config.org"))
|
||
(+org/swiper-goto)))
|
||
|
||
:general
|
||
(file-leader
|
||
"p" #'+org/search-config-headings)
|
||
(search-leader
|
||
:keymaps 'org-mode-map
|
||
"I" #'+org/search-headings)
|
||
(nmmap
|
||
:keymaps 'org-mode-map
|
||
[remap imenu] #'+org/swiper-goto)))
|
||
#+end_src
|
||
** Org Agenda
|
||
Org agenda provides a nice viewing for schedules. With org mode it's
|
||
a very tidy way to manage your time.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-agenda
|
||
:after org
|
||
:init
|
||
(defconst +org/agenda-root "~/Text"
|
||
"Root directory for all agenda files")
|
||
(setq org-agenda-files (list (expand-file-name +org/agenda-root))
|
||
org-agenda-window-setup 'current-window
|
||
org-agenda-skip-deadline-prewarning-if-scheduled t
|
||
org-agenda-skip-scheduled-if-done t
|
||
org-agenda-skip-deadline-if-done t
|
||
org-agenda-start-with-entry-text-mode nil)
|
||
:config
|
||
(evil-set-initial-state 'org-agenda-mode 'normal)
|
||
:general
|
||
(file-leader
|
||
"a" `(,(proc (interactive)
|
||
(find-file (completing-read "Enter directory: " org-agenda-files nil t)))
|
||
:which-key "Open agenda directory"))
|
||
|
||
(app-leader
|
||
"a" #'org-agenda)
|
||
|
||
(nmmap
|
||
:keymaps 'org-agenda-mode-map
|
||
"zd" #'org-agenda-day-view
|
||
"zw" #'org-agenda-week-view
|
||
"zm" #'org-agenda-month-view
|
||
"gd" #'org-agenda-goto-date
|
||
"RET" #'org-agenda-switch-to
|
||
"J" #'org-agenda-later
|
||
"K" #'org-agenda-earlier
|
||
"t" #'org-agenda-todo
|
||
"." #'org-agenda-goto-today
|
||
"," #'org-agenda-goto-date
|
||
"q" #'org-agenda-quit
|
||
"r" #'org-agenda-redo))
|
||
#+end_src
|
||
** Org capture
|
||
2024-04-24: I actually need to clean this up, in particular explain
|
||
what org-capture does.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-capture
|
||
:after org
|
||
:init
|
||
(setq
|
||
org-capture-templates
|
||
'(("t" "Todo" entry
|
||
(file "")
|
||
"* TODO %?
|
||
%T
|
||
%a")
|
||
("b" "Bookmark" entry
|
||
(file "bookmarks.org")
|
||
"* TODO %? :bookmark:
|
||
%a"))
|
||
org-default-notes-file (concat org-directory "/todo.org"))
|
||
:general
|
||
(file-leader
|
||
"w" #'org-capture))
|
||
#+end_src
|
||
** Org clock-in
|
||
Org provides a nice timekeeping system that allows for managing how
|
||
much time is taken per task. It even has an extensive reporting
|
||
system to see how much time you spend on specific tasks or overall.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-clock
|
||
:after org
|
||
:init
|
||
(defvar +org/clock-out-toggle-report nil
|
||
"Non-nil means update the first clock report in the file every
|
||
time a clock out occurs.")
|
||
:config
|
||
(advice-add #'org-clock-out
|
||
:after
|
||
(proc (interactive)
|
||
(if +org/clock-out-toggle-report
|
||
(org-clock-report t))))
|
||
:general
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "c"
|
||
"d" #'org-clock-display
|
||
"c" #'org-clock-in
|
||
"o" #'org-clock-out
|
||
"r" #'org-clock-report
|
||
"t" (proc (interactive)
|
||
(setq-local +org/clock-out-toggle-report
|
||
(not +org/clock-out-toggle-report)))))
|
||
#+end_src
|
||
** Org compile to PDF on save
|
||
If ~+org/compile-to-pdf-on-save-p~ is non-nil, then compile to
|
||
\(\LaTeX\) and run an async process to compile it to a PDF. Doesn't
|
||
make Emacs hang (like ~org-latex-export-to-pdf~) and doesn't randomly
|
||
crash (like the async handler for org-export). Works really well with
|
||
~pdf-view-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:defer t
|
||
:init
|
||
(defvar +org/compile-to-pdf-on-save-p
|
||
nil
|
||
"Non-nil to activate compile functionality.")
|
||
:general
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
"C" (proc (interactive)
|
||
(if (+org/compile-to-pdf-on-save-f)
|
||
(setq-local +org/compile-to-pdf-on-save-p nil)
|
||
(setq-local +org/compile-to-pdf-on-save-p t))))
|
||
:config
|
||
(+oreo/create-auto-save
|
||
(and (eq major-mode 'org-mode) +org/compile-to-pdf-on-save-p)
|
||
(start-process-shell-command "" "*pdflatex*" (concat "pdflatex -shell-escape "
|
||
(org-latex-export-to-latex)))))
|
||
#+end_src
|
||
** WAIT Org ref
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
For bibliographic stuff in $\LaTeX$ export.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-ref
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq bibtex-files '("~/Text/bibliography.bib")
|
||
bibtex-completion-bibliography '("~/Text/bibliography.bib")
|
||
bibtex-completion-additional-search-fields '(keywords)))
|
||
#+end_src
|
||
*** Org ref ivy integration
|
||
Org ref requires ivy-bibtex to work properly with ivy, so we need to
|
||
set that up as well
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-bibtex
|
||
:straight t
|
||
:after org-ref
|
||
:config
|
||
(require 'org-ref-ivy))
|
||
#+end_src
|
||
** Org message
|
||
Org message allows for the use of org mode when composing mails,
|
||
generating HTML multipart emails. This integrates the WYSIWYG
|
||
experience with mail in Emacs while also providing powerful text
|
||
features with basically no learning curve (as long as you've already
|
||
learnt the basics of org).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-msg
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(message-mode-hook . org-msg-mode)
|
||
(notmuch-message-mode-hook . org-msg-mode)
|
||
:config
|
||
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t tex:dvipng"
|
||
org-msg-greeting-name-limit 3)
|
||
|
||
(add-to-list 'org-msg-enforce-css
|
||
'(img latex-fragment-inline
|
||
((transform . ,(format "translateY(-1px) scale(%.3f)"
|
||
(/ 1.0 (if (boundp 'preview-scale)
|
||
preview-scale 1.4))))
|
||
(margin . "0 -0.35em")))))
|
||
#+end_src
|
||
** Org for evil
|
||
Evil org for some nice bindings.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-org
|
||
:straight t
|
||
:defer t
|
||
:hook (org-mode-hook . evil-org-mode)
|
||
:general
|
||
(nmmap
|
||
:keymaps 'org-mode-map
|
||
"TAB" #'org-cycle))
|
||
#+end_src
|
||
** Org reveal
|
||
Org reveal allows one to export org files as HTML presentations via
|
||
reveal.js. Pretty nifty and it's easy to use.
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-reveal
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js"
|
||
org-reveal-theme "sky"))
|
||
#+end_src
|
||
** WAIT Org fragtog
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Toggle latex fragments in org mode so you get fancy maths symbols. I
|
||
use latex a bit in org mode as it is the premier way of getting
|
||
mathematical symbols rendered, but org mode > latex.
|
||
|
||
Delimited environments are aplenty, escaped brackets and dollar signs
|
||
are my favourite. Here's a snippet:
|
||
$\int_{-\infty}^{\infty}e^{-x^2}dx = \sqrt{\pi}$.
|
||
|
||
[2023-09-10 Sun] Emacs 29 complains constantly about this, probably
|
||
because this isn't implemented that well. Regardless it wasn't that
|
||
necessary anyway, just a nice feature to have.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-fragtog
|
||
:hook (org-mode-hook . org-fragtog-mode))
|
||
#+end_src
|
||
** Org superstar
|
||
Org superstar adds unicode symbols for headers, much better than the
|
||
default asterisks.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-superstar
|
||
:straight t
|
||
:defer t
|
||
:hook (org-mode-hook . org-superstar-mode))
|
||
#+end_src
|
||
* Languages
|
||
For a variety of (programming) languages Emacs comes with default
|
||
modes but this configures them as well as pulls any modes Emacs
|
||
doesn't come with.
|
||
** Makefile
|
||
Defines an auto-insert for Makefiles. Assumes C but it's very easy to
|
||
change it for C++.
|
||
#+begin_src emacs-lisp
|
||
(use-package make-mode
|
||
:defer t
|
||
:auto-insert
|
||
(("[mM]akefile\\'" . "Makefile skeleton")
|
||
""
|
||
"CC=gcc
|
||
GFLAGS=-Wall -Wextra -Werror -Wswitch-enum -std=c11
|
||
DFLAGS=-ggdb -fsanitize=address -fsanitize=undefined
|
||
RFLAGS=-O3
|
||
ifdef RELEASE
|
||
CFLAGS=$(GFLAGS) $(RFLAGS)
|
||
else
|
||
CFLAGS=$(GFLAGS) $(DFLAGS)
|
||
endif
|
||
LIBS=
|
||
|
||
ARGS=
|
||
OUT=main.out
|
||
|
||
SRC=src
|
||
DIST=build
|
||
CODE=$(addprefix $(SRC)/, ) # add source files here
|
||
OBJECTS=$(CODE:$(SRC)/%.c=$(DIST)/%.o)
|
||
DEPDIR:=$(DIST)/dependencies
|
||
DEPFLAGS=-MT $@ -MMD -MP -MF
|
||
DEPS:=$(CODE:$(SRC)/%.c=$(DEPDIR):%.d) $(DEPDIR)/main.d
|
||
|
||
.PHONY: all
|
||
all: $(OUT)
|
||
|
||
$(OUT): $(DIST)/$(OUT)
|
||
|
||
$(DIST)/$(OUT): $(OBJECTS) $(DIST)/main.o | $(DIST)
|
||
$(CC) $(CFLAGS) $^ -o $@ $(LIBS)
|
||
|
||
$(DIST)/%.o: $(SRC)/%.c | $(DIST) $(DEPDIR)
|
||
$(CC) $(CFLAGS) $(DEPFLAGS) $(DEPDIR)/$*.d -c $< -o $@ $(LIBS)
|
||
|
||
.PHONY: run
|
||
run: $(DIST)/$(OUT)
|
||
./$^ $(ARGS)
|
||
|
||
.PHONY:
|
||
clean:
|
||
rm -rfv $(DIST)/*
|
||
|
||
$(DIST):
|
||
mkdir -p $(DIST)
|
||
|
||
$(DEPDIR):
|
||
mkdir -p $(DEPDIR)
|
||
|
||
-include $(DEPS)
|
||
"
|
||
_))
|
||
#+end_src
|
||
** PDF
|
||
I use PDFs mostly for reading reports or papers. Though Emacs isn't
|
||
my preferred application for viewing PDFs (I highly recommend
|
||
[[https://pwmt.org/projects/zathura/][Zathura]]), similar to most
|
||
things with Emacs, having a PDF viewer builtin can be a very useful
|
||
asset.
|
||
|
||
For example if I were editing an org document which I was eventually
|
||
compiling into a PDF, my workflow would be much smoother with a PDF
|
||
viewer within Emacs that I can open on another pane.
|
||
*** WAIT PDF tools
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
~pdf-tools~ provides the necessary functionality for viewing PDFs.
|
||
There is no proper PDF viewing without this package.
|
||
~evil-collection~ provides a setup for this mode, so use that.
|
||
#+begin_src emacs-lisp
|
||
(use-package pdf-tools
|
||
:mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode)
|
||
:straight t
|
||
:display
|
||
("^.*pdf$"
|
||
(display-buffer-same-window)
|
||
(inhibit-duplicate-buffer . t))
|
||
:config
|
||
(pdf-tools-install-noverify)
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-pdf-setup)))
|
||
#+end_src
|
||
*** WAIT PDF grep
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
PDF grep is a Linux tool that allows for searches against the text
|
||
inside of PDFs similar to standard grep. This cannot be performed by
|
||
standard grep due to how PDFs are encoded; they are not a clear text
|
||
format.
|
||
#+begin_src emacs-lisp
|
||
(use-package pdfgrep
|
||
:after pdf-tools
|
||
:hook (pdf-view-mode-hook . pdfgrep-mode)
|
||
:general
|
||
(nmap
|
||
:keymaps 'pdf-view-mode-map
|
||
"M-g" #'pdfgrep))
|
||
#+end_src
|
||
** WAIT SQL
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
The default SQL package provides support for connecting to common
|
||
database types (sqlite, mysql, etc) for auto completion and query
|
||
execution. I don't use SQL currently but whenever I need it it's
|
||
there.
|
||
#+begin_src emacs-lisp
|
||
(use-package sql
|
||
:defer t
|
||
:init
|
||
(setq sql-display-sqli-buffer-function nil))
|
||
#+end_src
|
||
** WAIT Ada
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Check out [[file:elisp/ada-mode.el][ada-mode]], my custom ~ada-mode~
|
||
that replaces the default one. This mode just colourises stuff, and
|
||
uses eglot and a language server to do the hard work.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ada-mode
|
||
:load-path "elisp/"
|
||
:defer t
|
||
:config
|
||
(with-eval-after-load "eglot"
|
||
(add-hook 'ada-mode-hook #'eglot)))
|
||
#+end_src
|
||
** NHexl
|
||
Hexl-mode is the inbuilt package within Emacs to edit hex and binary
|
||
format buffers. There are a few problems with hexl-mode though,
|
||
including an annoying prompt on /revert-buffer/.
|
||
|
||
Thus, nhexl-mode! It comes with a few other improvements. Check out
|
||
the [[https://elpa.gnu.org/packages/nhexl-mode.html][page]] yourself.
|
||
#+begin_src emacs-lisp
|
||
(use-package nhexl-mode
|
||
:straight t
|
||
:defer t
|
||
:mode ("\\.bin" "\\.out"))
|
||
#+end_src
|
||
** C/C++
|
||
Setup for C and C++ modes, using Emacs' default package: cc-mode.
|
||
*** cc-mode
|
||
Tons of stuff, namely:
|
||
+ ~auto-fill-mode~ for 80 char limit
|
||
+ Some keybindings to make evil statement movement is easy
|
||
+ Lots of pretty symbols
|
||
+ Indenting options and a nice (for me) code style for C (though
|
||
aggressive indent screws with this a bit)
|
||
+ Auto inserts to get a C file going
|
||
#+begin_src emacs-lisp
|
||
(use-package cc-mode
|
||
:defer t
|
||
:hook
|
||
(c-mode-hook . auto-fill-mode)
|
||
(c++-mode-hook . auto-fill-mode)
|
||
:general
|
||
(:keymaps '(c-mode-map c++-mode-map)
|
||
:states '(normal motion visual)
|
||
"(" #'c-beginning-of-statement
|
||
")" #'c-end-of-statement)
|
||
:pretty
|
||
(c-mode-hook
|
||
("puts" . "φ")
|
||
("fputs" . "ϕ")
|
||
("printf" . "ω")
|
||
("fprintf" . "Ω")
|
||
("NULL" . "Ø")
|
||
("true" . "⊨")
|
||
("false" . "⊭")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("return" . "⟼"))
|
||
(c++-mode-hook
|
||
("nullptr" . "Ø")
|
||
("string" . "𝕊")
|
||
("vector" . "ℓ")
|
||
("puts" . "φ")
|
||
("fputs" . "ϕ")
|
||
("printf" . "ω")
|
||
("fprintf" . "Ω")
|
||
("NULL" . "Ø")
|
||
("true" . "⊨")
|
||
("false" . "⊭")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("return" . "⟼"))
|
||
:init
|
||
(setq-default c-basic-offset 2)
|
||
(setq-default c-auto-newline nil)
|
||
(setq-default c-default-style '((other . "user")))
|
||
(defun +cc/copyright-notice ()
|
||
(let* ((lines (split-string (+license/copyright-notice) "\n"))
|
||
(copyright-line (car lines))
|
||
(rest (cdr lines)))
|
||
(concat
|
||
"* "
|
||
copyright-line
|
||
"\n"
|
||
(mapconcat
|
||
#'(lambda (x)
|
||
(if (string= x "")
|
||
""
|
||
(concat " * " x)))
|
||
rest
|
||
"\n"))))
|
||
|
||
:auto-insert
|
||
(("\\.c\\'" . "C skeleton")
|
||
""
|
||
"/" (+cc/copyright-notice) "\n\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * Description: " _ "\n"
|
||
" */\n"
|
||
"\n")
|
||
(("\\.cpp\\'" "C++ skeleton")
|
||
""
|
||
"/" (+cc/copyright-notice) "\n\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * Description: " _ "\n"
|
||
" */\n"
|
||
"\n")
|
||
(("\\.\\([Hh]\\|hh\\|hpp\\|hxx\\|h\\+\\+\\)\\'" . "C / C++ header")
|
||
(replace-regexp-in-string "[^A-Z0-9]" "_"
|
||
(string-replace "+" "P"
|
||
(upcase
|
||
(file-name-nondirectory buffer-file-name))))
|
||
"/" (+cc/copyright-notice) "\n\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * Description: " _ "\n"
|
||
" */\n\n"
|
||
"#ifndef " str n "#define " str "\n\n" "\n\n#endif")
|
||
:config
|
||
(c-add-style
|
||
"user"
|
||
'((c-basic-offset . 2)
|
||
(c-comment-only-line-offset . 0)
|
||
(c-hanging-braces-alist (brace-list-open)
|
||
(brace-entry-open)
|
||
(substatement-open after)
|
||
(block-close . c-snug-do-while)
|
||
(arglist-cont-nonempty))
|
||
(c-cleanup-list brace-else-brace)
|
||
(c-offsets-alist
|
||
(statement-block-intro . +)
|
||
(substatement-open . 0)
|
||
(access-label . -)
|
||
(inline-open . 0)
|
||
(label . 0)
|
||
(statement-cont . +)))))
|
||
#+end_src
|
||
*** Clang format
|
||
Clang format comes inbuilt with clang, so download that before using
|
||
this. Formats C/C++ files depending on a format (checkout the Clang
|
||
format [[file:~/Dotfiles/ClangFormat/.clang-format][config file]] in
|
||
my dotfiles).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package clang-format
|
||
:load-path "/usr/share/clang/"
|
||
:defer t
|
||
:after cc-mode
|
||
:commands (+code/clang-format-region-or-buffer
|
||
clang-format-mode)
|
||
;;; 2024-04-24: disabled as it's annoying on projects where a
|
||
;;; .clang-format isn't defined. Furthermore, does it make sense
|
||
;;; for *every* file you open and edit to have a format function run
|
||
;;; right after? seems a bit slow.
|
||
;; :hook
|
||
;; (c-mode-hook . clang-format-mode)
|
||
;; (c++-mode-hook . clang-format-mode)
|
||
:general
|
||
(code-leader
|
||
:keymaps '(c-mode-map c++-mode-map)
|
||
"f" #'+code/clang-format-region-or-buffer)
|
||
:config
|
||
(define-minor-mode clang-format-mode
|
||
"On save formats the current buffer via clang-format."
|
||
:lighter nil
|
||
(let ((save-func (proc (interactive)
|
||
(clang-format-buffer))))
|
||
(if clang-format-mode
|
||
(add-hook 'after-save-hook save-func nil t)
|
||
(remove-hook 'after-save-hook save-func t))))
|
||
(defun +code/clang-format-region-or-buffer ()
|
||
(interactive)
|
||
(if (mark)
|
||
(clang-format-region (region-beginning) (region-end))
|
||
(clang-format-buffer))))
|
||
#+end_src
|
||
*** cc org babel
|
||
To ensure org-babel executes language blocks of C/C++, I need to load
|
||
it as an option in ~org-babel-load-languages~.
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:after cc-mode
|
||
:init
|
||
(org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
'((C . t))))
|
||
#+end_src
|
||
** WAIT D
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
D is a systems level programming language with C-style syntax. I
|
||
think it has some interesting ideas such as a toggleable garbage
|
||
collector. Here I just install the D-mode package, enable ~org-babel~
|
||
execution of d-mode blocks and alias ~D-mode~ with ~d-mode~.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package d-mode
|
||
:defer t
|
||
:straight t
|
||
:config
|
||
(fset 'D-mode 'd-mode)
|
||
(with-eval-after-load "org-mode"
|
||
(setf (alist-get 'd org-babel-load-languages) t)))
|
||
#+end_src
|
||
** Rust
|
||
#+begin_src emacs-lisp
|
||
(use-package rust-mode
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(code-leader
|
||
:keymaps 'rust-mode-map
|
||
"f" #'rust-format-buffer)
|
||
(local-leader
|
||
:keymaps 'rust-mode-map
|
||
"c" #'rust-run-clippy)
|
||
:init
|
||
(setq rust-format-on-save t)
|
||
(with-eval-after-load "eglot"
|
||
(add-to-list 'eglot-server-programs '(rust-mode "rust-analyzer"))))
|
||
#+end_src
|
||
** WAIT Racket
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
A scheme with lots of stuff inside it. Using it for a language design
|
||
book so it's useful to have some Emacs binds for it.
|
||
#+begin_src emacs-lisp
|
||
(use-package racket-mode
|
||
:straight t
|
||
:hook (racket-mode-hook . racket-xp-mode)
|
||
:display
|
||
("\\*Racket.*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:init
|
||
(setq racket-documentation-search-location 'local)
|
||
:general
|
||
(nmap
|
||
:keymaps 'racket-describe-mode-map
|
||
"q" #'quit-window)
|
||
(nmap
|
||
:keymaps 'racket-mode-map
|
||
"gr" #'racket-eval-last-sexp)
|
||
(local-leader
|
||
:keymaps '(racket-mode-map racket-repl-mode-map)
|
||
"d" #'racket-repl-describe)
|
||
(local-leader
|
||
:keymaps 'racket-mode-map
|
||
"r" #'racket-run
|
||
"i" #'racket-repl
|
||
"e" #'racket-send-definition
|
||
"sr" #'racket-send-region
|
||
"sd" #'racket-send-definition))
|
||
#+end_src
|
||
** WAIT CSharp
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Haven't used C# in a while, but Emacs is alright for it with
|
||
omnisharp.
|
||
#+begin_src emacs-lisp
|
||
(use-package csharp-mode
|
||
:defer t
|
||
:pretty
|
||
(csharp-mode-hook
|
||
("null" . "∅")
|
||
("string" . "𝕊")
|
||
("List" . "ℓ")
|
||
("WriteLine" . "φ")
|
||
("Write" . "ω")
|
||
("true" . "⊨")
|
||
("false" . "⊭")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("return" . "⟼")))
|
||
#+end_src
|
||
** WAIT Java
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
I kinda dislike Java, but if necessary I will code in it. Just setup
|
||
a style and some pretty symbols. You can use LSP to get cooler
|
||
features to be fair.
|
||
#+begin_src emacs-lisp
|
||
(use-package ob-java
|
||
:defer t
|
||
:pretty
|
||
(java-mode-hook
|
||
("println" . "φ")
|
||
("printf" . "ω")
|
||
("null" . "Ø")
|
||
("true" . "⊨")
|
||
("false" . "⊭")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("return" . "⟼"))
|
||
:config
|
||
(with-eval-after-load "cc-mode"
|
||
(c-add-style
|
||
"java"
|
||
'((c-basic-offset . 4)
|
||
(c-comment-only-line-offset 0 . 0)
|
||
(c-offsets-alist
|
||
(inline-open . 0)
|
||
(topmost-intro-cont . +)
|
||
(statement-block-intro . +)
|
||
(knr-argdecl-intro . 5)
|
||
(substatement-open . 0)
|
||
(substatement-label . +)
|
||
(label . +)
|
||
(statement-case-open . +)
|
||
(statement-cont . +)
|
||
(arglist-intro . c-lineup-arglist-intro-after-paren)
|
||
(arglist-close . c-lineup-arglist)
|
||
(brace-list-intro first c-lineup-2nd-brace-entry-in-arglist c-lineup-class-decl-init-+ +)
|
||
(access-label . 0)
|
||
(inher-cont . c-lineup-java-inher)
|
||
(func-decl-cont . c-lineup-java-throws))))
|
||
(add-to-list 'c-default-style '(java-mode . "java")))
|
||
|
||
(with-eval-after-load "abbrev"
|
||
(define-abbrev-table 'java-mode-abbrev-table nil)
|
||
(add-hook 'java-mode-hook
|
||
(proc (setq-local local-abbrev-table java-mode-abbrev-table)))))
|
||
#+end_src
|
||
** Haskell
|
||
Haskell is a static lazy functional programming language (what a
|
||
mouthful). It's quite a beautiful language and really learning it will
|
||
change the way you think about programming. However, my preferred
|
||
functional language is still unfortunately Lisp so no extra brownie
|
||
points there.
|
||
|
||
Here I configure the REPL for Haskell via the
|
||
~haskell-interactive-mode~. I also load my custom package
|
||
[[file:elisp/haskell-multiedit.el][haskell-multiedit]] which allows a
|
||
user to create temporary ~haskell-mode~ buffers that, upon completion,
|
||
will run in the REPL. Even easier than making your own buffer.
|
||
#+begin_src emacs-lisp
|
||
(use-package haskell-mode
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(haskell-mode-hook . haskell-indentation-mode)
|
||
(haskell-mode-hook . interactive-haskell-mode)
|
||
:custom
|
||
(haskell-interactive-prompt "[λ] ")
|
||
(haskell-interactive-prompt-cont "{λ} ")
|
||
(haskell-interactive-popup-errors nil)
|
||
(haskell-stylish-on-save nil)
|
||
(haskell-process-type 'auto)
|
||
:general
|
||
(shell-leader
|
||
"h" #'haskell-interactive-bring)
|
||
(local-leader
|
||
:keymaps 'haskell-mode-map
|
||
"l" #'haskell-process-load-or-reload
|
||
"t" #'haskell-process-do-type)
|
||
(local-leader
|
||
:keymaps 'haskell-interactive-mode-map
|
||
"c" #'haskell-interactive-mode-clear)
|
||
(imap
|
||
:keymaps 'haskell-interactive-mode-map
|
||
"M-k" #'haskell-interactive-mode-history-previous
|
||
"M-j" #'haskell-interactive-mode-history-next)
|
||
:display
|
||
("\\*haskell.**\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:config
|
||
(load (concat user-emacs-directory "elisp/haskell-multiedit.el")))
|
||
#+end_src
|
||
** Python
|
||
Works well for python. If you have ~pyls~ it should be on your path, so
|
||
just run eglot if you need. But an LSP server is not necessary for a
|
||
lot of my time in python. Here I also setup org-babel for python
|
||
source code blocks.
|
||
#+begin_src emacs-lisp
|
||
(use-package python
|
||
:defer t
|
||
:pretty
|
||
(python-mode-hook
|
||
("None" . "Ø")
|
||
("list" . "ℓ")
|
||
("List" . "ℓ")
|
||
("str" . "𝕊")
|
||
("True" . "⊨")
|
||
("False" . "⊭")
|
||
("!" . "¬")
|
||
("&&" . "∧")
|
||
("||" . "∨")
|
||
("for" . "∀")
|
||
("print" . "φ")
|
||
("lambda" . "λ")
|
||
("return" . "⟼")
|
||
("yield" . "⟻"))
|
||
:init
|
||
(setq python-indent-offset 4)
|
||
:config
|
||
(with-eval-after-load "org-mode"
|
||
(setf (alist-get 'python org-babel-load-languages) t)))
|
||
#+end_src
|
||
*** Python shell
|
||
Setup for python shell, including a toggle option
|
||
#+begin_src emacs-lisp
|
||
(use-package python
|
||
:defer t
|
||
:commands +python/toggle-repl
|
||
:general
|
||
(shell-leader
|
||
"p" #'run-python)
|
||
:display
|
||
("\\*Python\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25)))
|
||
#+end_src
|
||
** YAML
|
||
YAML is a data language which is useful for config files.
|
||
#+begin_src emacs-lisp
|
||
(use-package yaml-mode
|
||
:straight t)
|
||
#+end_src
|
||
** HTML/CSS/JS
|
||
Firstly, web mode for consistent colouring of syntax.
|
||
#+begin_src emacs-lisp
|
||
(use-package web-mode
|
||
:straight t
|
||
:defer t
|
||
:mode ("\\.html" . web-mode)
|
||
:mode ("\\.js" . web-mode)
|
||
:mode ("\\.css" . web-mode)
|
||
:custom
|
||
((web-mode-code-indent-offset 2)
|
||
(web-mode-markup-indent-offset 2)
|
||
(web-mode-css-indent-offset 2)))
|
||
#+end_src
|
||
*** Emmet
|
||
Emmet for super speed code writing.
|
||
#+begin_src emacs-lisp
|
||
(use-package emmet-mode
|
||
:straight t
|
||
:hook (web-mode-hook . emmet-mode)
|
||
:general
|
||
(imap
|
||
:keymaps 'emmet-mode-keymap
|
||
"TAB" #'emmet-expand-line
|
||
"M-j" #'emmet-next-edit-point
|
||
"M-k" #'emmet-prev-edit-point))
|
||
#+end_src
|
||
*** HTML Auto insert
|
||
#+begin_src emacs-lisp
|
||
(use-package web-mode
|
||
:defer t
|
||
:auto-insert
|
||
(("\\.html\\'" . "HTML Skeleton")
|
||
""
|
||
"<!doctype html>
|
||
<html lang=''>
|
||
<head>
|
||
<meta charset='utf-8'>
|
||
<meta http-equiv='x-ua-compatible' content='ie=edge'>
|
||
<title>"(read-string "Enter title: ") | """</title>
|
||
<meta name='description' content='" (read-string "Enter description: ") | "" "'>
|
||
<meta name='author' content='"user-full-name"'/>
|
||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||
|
||
<link rel='apple-touch-icon' href='/apple-touch-icon.png'>
|
||
<link rel='shortcut icon' href='/favicon.ico'/>
|
||
<!-- Place favicon.ico in the root directory -->
|
||
|
||
</head>
|
||
<body>
|
||
<!--[if lt IE 8]>
|
||
<p class='browserupgrade'>
|
||
You are using an <strong>outdated</strong> browser. Please
|
||
<a href='http://browsehappy.com/'>upgrade your browser</a> to improve
|
||
your experience.
|
||
</p>
|
||
<![endif]-->
|
||
"
|
||
_
|
||
" </body>
|
||
</html>"))
|
||
#+end_src
|
||
*** WAIT Typescript
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
A child language of javascript which compiles to it.
|
||
#+begin_src emacs-lisp
|
||
(use-package typescript-mode
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq typescript-indent-level 2))
|
||
#+end_src
|
||
** Common Lisp
|
||
Common Lisp is a dialect of Lisp, the most /common/ one around. Emacs
|
||
comes with builtin Lisp support of course, but a REPL would be nice.
|
||
|
||
*** WAIT Sly
|
||
Enter /SLY/. Sly is a fork of /SLIME/ and is *mandatory* for lisp
|
||
development on Emacs.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package sly
|
||
:defer t
|
||
:straight t
|
||
:init
|
||
(setq inferior-lisp-program "sbcl")
|
||
:display
|
||
("\\*sly-db"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.5))
|
||
("\\*sly-"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:config
|
||
(evil-set-initial-state 'sly-db-mode 'emacs)
|
||
(with-eval-after-load "org"
|
||
(setq-default org-babel-lisp-eval-fn #'sly-eval))
|
||
(with-eval-after-load "company"
|
||
(add-hook 'sly-mrepl-hook #'company-mode))
|
||
:general
|
||
(shell-leader
|
||
"s" #'sly-mrepl)
|
||
(nmap
|
||
:keymaps '(lisp-mode-map sly-mrepl-mode-map)
|
||
"gr" #'sly-eval-buffer
|
||
"gd" #'sly-edit-definition
|
||
"gR" #'sly-who-calls)
|
||
(local-leader
|
||
:keymaps '(lisp-mode-map sly-mrepl-mode-map)
|
||
"s" #'+shell/toggle-sly
|
||
"c" #'sly-compile-file
|
||
"a" #'sly-apropos
|
||
"d" #'sly-describe-symbol
|
||
"D" #'sly-documentation-lookup
|
||
"S" #'sly-mrepl-sync
|
||
"E" #'sly-eval-defun)
|
||
(local-leader
|
||
:keymaps 'lisp-mode-map
|
||
:infix "e"
|
||
"b" #'sly-eval-buffer
|
||
"e" #'sly-eval-last-expression
|
||
"f" #'sly-eval-defun
|
||
"r" #'sly-eval-region)
|
||
(nmap
|
||
:keymaps 'sly-inspector-mode-map
|
||
"q" #'sly-inspector-quit))
|
||
#+end_src
|
||
*** Emacs lisp
|
||
#+begin_src emacs-lisp
|
||
(use-package elisp-mode
|
||
:defer t
|
||
:pretty
|
||
(lisp-mode-hook
|
||
("lambda" . "λ")
|
||
("t" . "⊨")
|
||
("nil" . "Ø")
|
||
("and" . "∧")
|
||
("or" . "∨")
|
||
("defun" . "ƒ")
|
||
("for" . "∀")
|
||
("mapc" . "∀")
|
||
("mapcar" . "∀"))
|
||
(emacs-lisp-mode-hook
|
||
("lambda" . "λ")
|
||
("t" . "⊨")
|
||
("nil" . "Ø")
|
||
("and" . "∧")
|
||
("or" . "∨")
|
||
("defun" . "ƒ")
|
||
("for" . "∀")
|
||
("mapc" . "∀")
|
||
("mapcar" . "∀"))
|
||
:general
|
||
(:states '(normal motion visual)
|
||
:keymaps '(emacs-lisp-mode-map lisp-mode-map)
|
||
")" #'sp-next-sexp
|
||
"(" #'sp-previous-sexp)
|
||
(nmmap
|
||
:keymaps '(emacs-lisp-mode-map lisp-interaction-mode-map)
|
||
"gr" #'eval-last-sexp)
|
||
(vmap
|
||
:keymaps '(emacs-lisp-mode-map lisp-interaction-mode-map)
|
||
"gr" #'eval-region))
|
||
#+end_src
|
||
*** WIP Hydra like Lispy
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
A [[*Hydra][Hydra]] which uses the ~Lispy~ package (by
|
||
abo-abo) to create a set of motions that allow movement around a lisp
|
||
file easily.
|
||
|
||
2024-04-18: Still working on this, quite rough around the edges.
|
||
#+begin_src emacs-lisp
|
||
(use-package lispy
|
||
:after (lisp-mode elisp-mode)
|
||
:hydra
|
||
(hydra-lispy
|
||
nil "Move around quickly in Lisp"
|
||
("h" #'lispy-left)
|
||
("j" ("t" #'lispy-teleport)
|
||
#'lispy-down)
|
||
("k" #'lispy-up)
|
||
("l" #'lispy-right)
|
||
("d" #'lispy-different)
|
||
("u" #'lispy-flow)
|
||
("o" #'lispy-oneline)
|
||
("m" #'lispy-multiline)
|
||
("N" #'lispy-narrow)
|
||
("W" #'lispy-widen)
|
||
("c" #'lispy-clone)
|
||
("fp" #'lispy-ace-paren)
|
||
("fs" #'lispy-ace-symbol :exit t)
|
||
("H" #'lispy-slurp)
|
||
("L" #'lispy-barf)
|
||
("M-h" #'lispy-move-left)
|
||
("M-j" #'lispy-move-down)
|
||
("M-k" #'lispy-move-up)
|
||
("M-l" #'lispy-move-right)
|
||
("C-g" nil))
|
||
:general
|
||
(nmmap
|
||
:keymaps '(emacs-lisp-mode-map lisp-mode-map)
|
||
"." #'hydra-lispy/body))
|
||
#+end_src
|
||
*** Lisp indent function
|
||
Add a new lisp indent function which indents newline lists more
|
||
appropriately.
|
||
#+begin_src emacs-lisp
|
||
(use-package lisp-mode
|
||
:defer t
|
||
:config
|
||
(defun +oreo/lisp-indent-function (indent-point state)
|
||
(let ((normal-indent (current-column))
|
||
(orig-point (point)))
|
||
(goto-char (1+ (elt state 1)))
|
||
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
|
||
(cond
|
||
;; car of form doesn't seem to be a symbol, or is a keyword
|
||
((and (elt state 2)
|
||
(or (not (looking-at "\\sw\\|\\s_"))
|
||
(looking-at ":")))
|
||
(if (not (> (save-excursion (forward-line 1) (point))
|
||
calculate-lisp-indent-last-sexp))
|
||
(progn (goto-char calculate-lisp-indent-last-sexp)
|
||
(beginning-of-line)
|
||
(parse-partial-sexp (point)
|
||
calculate-lisp-indent-last-sexp 0 t)))
|
||
;; Indent under the list or under the first sexp on the same
|
||
;; line as calculate-lisp-indent-last-sexp. Note that first
|
||
;; thing on that line has to be complete sexp since we are
|
||
;; inside the innermost containing sexp.
|
||
(backward-prefix-chars)
|
||
(current-column))
|
||
((and (save-excursion
|
||
(goto-char indent-point)
|
||
(skip-syntax-forward " ")
|
||
(not (looking-at ":")))
|
||
(save-excursion
|
||
(goto-char orig-point)
|
||
(looking-at ":")))
|
||
(save-excursion
|
||
(goto-char (+ 2 (elt state 1)))
|
||
(current-column)))
|
||
(t
|
||
(let ((function (buffer-substring (point)
|
||
(progn (forward-sexp 1) (point))))
|
||
method)
|
||
(setq method (or (function-get (intern-soft function)
|
||
'lisp-indent-function)
|
||
(get (intern-soft function) 'lisp-indent-hook)))
|
||
(cond ((or (eq method 'defun)
|
||
(and (null method)
|
||
(> (length function) 3)
|
||
(string-match "\\`def" function)))
|
||
(lisp-indent-defform state indent-point))
|
||
((integerp method)
|
||
(lisp-indent-specform method state
|
||
indent-point normal-indent))
|
||
(method
|
||
(funcall method indent-point state))))))))
|
||
(setq-default lisp-indent-function #'+oreo/lisp-indent-function))
|
||
#+end_src
|