Emacs is a very versatile tool. I use it as:
- a text editor (of course)
- a mail client (mu4e)
- an IRC chat client (ERC)
- a git GUI (magit)
- a multilingual IDE for quick and simple work
- a special-purpose terminal emulator (vterm)
- for note-taking and organization (org-mode)
- as a superior interface for many interpreters (comint)
What’s more, I’m using a distribution of Emacs called Spacemacs, which comes with many sensible defaults and simplifies the installation and setup of packages. Because it is so feature-rich, Emacs can take an annoying time to start up. That’s why I have set up a systemd service that runs an emacs daemon on system startup. When I want to launch emacs to perform a task, I run myemacs (a custom command that wraps emacsclient), which attaches to the daemon, without the need to load the cruft each time.
For some of those use cases I mentioned above, I want to run a standalone emacs
daemon, specialized for that use case. One reason is that emacs is
single-threaded, so a hang-up in one application would render the rest of the
daemon unresponsive. Another reason is simply to have a cleaner buffer list, so
I can use ibuffer
more efficiently.
Here’s a list of standalone daemons I have configured. Each one has a systemd
service configured and thus launches on system startup. For each entry the
daemon name is shown (as passed to the --socket-name
option). Each daemon has
its own configuration file (see Configuration loading) to prevent loading of
unnecessary code.
irc
- Used as an IRC chat client
- Configuration file
~/.emacs.d/haris/irc.el
<<irc.service>>
ide
- Used as a multilingual IDE via the lsp protocol
NOTE: The functionality for this daemon is not yet configured.
emacs
- Everything else is done through this daemon. This includes basic text
editing and applications that don’t warrant a standalone daemon.
- Configuration file
~/.emacs.d/default.el
TODO: It is planned that this config file be used, but it’s not yet implemented.
The main generated user configuration file is ~/.emacs.d/haris/root.el. This
file is loaded by spacemacs and internally, it loads the other files based on
the type of the purpose of the current emacs daemon (by reading the
server-name
variable). This is the config file:
(load-file (cond ((and (boundp 'server-name) (equal server-name "irc"))
"~/.emacs.d/haris/irc.el")
("~/.emacs.d/haris/default.el")))
set -e
mkdir -p ~/.emacs.d
pushd ~/.emacs.d >/dev/null
if [ ! -d .git ]; then
git init
git remote add origin 'https://github.com/veracioux/spacemacs'
git remote set-url --push origin '[email protected]:veracioux/spacemacs'
git remote add upstream 'https://github.com/syl20bnr/spacemacs'
git checkout --recurse-submodules haris/main
fi
popd >/dev/null
ln -s ~/.haris/.spacemacs ~/.spacemacs || true
echo "Emacs will be launched now. Follow the prompts to setup spacemacs"
echo "When done, you can close the emacs window"
read -n1 -p 'Press any key to continue: '
# NOTE: In same cases I observed that the GUI won't run on initial spacemacs
# startup, so I used --no-window-system
COLORTERM=truecolor LSP_USE_PLISTS=true emacs --debug-init --no-window-system
I use a slightly customized version of the beautiful Dracula theme. Make sure you perfomed the setup in order to have the theme available.
(setq custom-theme-directory "~/.emacs.d/private/themes")
(load-theme 'dracula t)
(add-hook 'after-make-frame-functions
(defun haris/load-theme-delayed (frame)
;; Without this the theme only loads after a second frame is created
(run-with-timer 0 nil
(lambda ()
(load-theme 'dracula t)))
(remove-hook 'after-make-frame-functions #'haris/load-theme-delayed)))
I normally use use-package
for installing packages (with melpa
as the default source).
I use straight
to clone some packages that are not available on melpa
.
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
(or (bound-and-true-p straight-base-dir)
user-emacs-directory)))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(setq straight-vc-git-default-clone-depth 1)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
(require 'use-package)
;; (setq use-package-defaults (assq-delete-all ':straight use-package-defaults))
(setq use-package-always-ensure t)
(use-package focus-autosave-mode :defer t)
(use-package multi-vterm :defer t)
(use-package command-log-mode)
(straight-use-package
'(explain-pause-mode
:type git
:host github
:repo "lastquestion/explain-pause-mode"))
(use-package fontawesome :defer t)
(use-package json-mode :defer t)
(use-package counsel-jq :defer t)
(use-package fish-mode :defer t)
(use-package vimrc-mode :defer t)
(use-package sxhkdrc-mode :defer t)
(use-package i3wm-config-mode :defer t)
(use-package git-modes :defer t)
(use-package systemd :defer t)
(use-package ssh-config-mode :defer t)
(use-package crontab-mode :defer t)
;; Performance improvements
(setq native-comp-async-report-warnings-errors nil)
;; 500 MB
(setq gc-cons-threshold (* 500 1024 1024))
(setq paradox-github-token t)
(setq image-auto-resize 'fit-width)
(setq image-auto-resize-on-window-resize t)
(setq evil-want-keybinding nil)
(setq uniquify-buffer-name-style 'forward)
(setq frame-title-format "%b"
dotspacemacs-frame-title-format nil)
(eval-after-load "recentf"
(defun haris//after-load/recentf ()
(add-hook 'dired-after-readin-hook
(defun haris//add-file-to-recentf ()
(recentf-add-file default-directory)))
(add-to-list 'recentf-exclude "^/tmp/haris-pipe-")
(cl-delete-if (lambda (el) (string= el "~/.emacs.d/elpa/29.1/develop/"))
recentf-exclude)))
(setq winum-scope 'frame-local)
(add-to-list 'image-types 'svg)
;; Enable clipboard in the terminal
(use-package xclip)
;; Using xclip instead makes emacsclient hang
(setq xclip-method 'xsel)
(setq xclip-program "xsel")
(xclip-mode)
(setq warning-minimum-level :emergency)
;; Handle URLs as files
(url-handler-mode)
(add-to-list 'exec-path (expand-file-name "~/.local/bin"))
(if (not (boundp 'haris/prepended-path))
(progn
(setq haris/prepended-path "yes")
(setenv "PATH" (concat (expand-file-name "~/.local/bin") ":" (getenv "PATH")))))
;; TODO whatever this does
(add-hook 'text-mode-hook #'auto-fill-mode)
(add-hook 'prog-mode-hook #'auto-fill-mode)
;; Some packages prefer helm over ivy and don't provide a customization option.
(with-eval-after-load 'helm
(fmakunbound 'helm))
(setq fill-column 80)
(setq-default tab-width 4)
(spacemacs/toggle-visual-line-navigation-globally-on)
(set-language-environment "UTF-8")
(modify-syntax-entry ?_ "w")
(setq indent-guide-delay 0.1)
(setq browse-url-generic-program (executable-find "firefox"))
(setq dotspacemacs-search-tools '("rg" "grep"))
;; Ways to spawn shells from within Emacs
(setq shell-default-shell 'shell)
(setq terminal-here-linux-terminal-command '("alacritty")
terminal-here-mac-terminal-command '("alacritty"))
(setq shell-file-name "bash")
(setq scroll-bar-mode-explicit nil)
(setq mouse-wheel-scroll-amount '(6))
;; Fix scrolling performance
(use-package fast-scroll)
(fast-scroll-config)
(fast-scroll-mode 1)
I change the following in order to prevent Emacs from complaining about filenames being too long. The main culprit is Java, with its horrendously deep directory structures.
(make-directory "~/.emacs.d/.cache/auto-save/dist" t)
(make-directory "~/.emacs.d/.cache/auto-save/site" t)
(dolist (item auto-save-file-name-transforms)
(let ((uniquify (nthcdr 2 item)))
(if uniquify (setcar uniquify 'md5))))
(defalias 'haris//undo-tree-make-history-save-file-name/default
(symbol-function
(if (fboundp 'haris//undo-tree-make-history-save-file-name/default)
'haris//undo-tree-make-history-save-file-name/default
'undo-tree-make-history-save-file-name))
"The original implementation of the undo-tree-make-history-save-file-name
function, so I can call it in my custom override.")
(defun undo-tree-make-history-save-file-name (file)
"Create the undo history file name for FILE. This overrides the default
implementation, by making the basename of the file a hashed version of the
original path. The benefit of this is that it prevents a 'filename too long
error'."
(let ((original (haris//undo-tree-make-history-save-file-name/default file)))
(concat (file-name-directory original)
(md5 (file-name-nondirectory file))
".~undo-history~")))
Call these to test the patterns:
(make-auto-save-file-name)
(undo-tree-make-history-save-file-name (buffer-file-name))
I change this in order to prevent non-emacs tools from picking up the files, and misbehaving due to their existence.
(make-directory "~/.emacs.d/.cache/lock-file/dist" t)
(make-directory "~/.emacs.d/.cache/lock-file/site" t)
;; Set lock-file-name-transforms to auto-save-file-name-transforms,
;; but replace "auto-save" with "lock-file" in each replacement pattern.
(setq lock-file-name-transforms
(remove nil
(mapcar
(lambda (item)
(let ((item-copy (copy-tree item)))
(if (string-match "auto-save" "lock-file")
(setcar (cdr item-copy)
(string-replace "auto-save" "lock-file" (cadr item-copy))))
item-copy))
auto-save-file-name-transforms)))
(dolist (item lock-file-name-transforms)
(let ((uniquify (nthcdr 2 item)))
(if uniquify (setcar uniquify 'sha512))))
You can test the transforms by calling this:
(make-lock-file-name (buffer-file-name))
(global-auto-revert-mode t)
(setq auto-revert-verbose t
auto-revert-use-notify t
auto-revert-avoid-polling t
;; When reverting buffers whose files have been changed by emacs, it
;; seems that the interval from this variable is used, even though
;; polling was disabled above.
;; An example where this can be observed is when an org-mode code block
;; is tangled and the target file is open in another buffer.
auto-revert-interval 0.2)
(setq haris/custom-temp-buffer-name "*haris-temp*")
(setq clean-buffer-list-delay-general 1
clean-buffer-list-delay-special 1800
clean-buffer-list-kill-regexps
`(
"^haris-pipe-"
"^\\*Help\\*"
"^\\*helpful "
"^\\*ivy-occur"
"^\\*lsp-help\\*"
"^\\*lsp session\\*"
"^magit: "
"^magit-\\(log\\|diff\\|stash\\|revision\\)"
"^\\*Man "
"^\\*straight-process\\*"
"\\*which-key\\*"
"^\\*xref\\*"
;; Docker command output buffers
"^\\* docker .*\\*"
;; Docker compose command output buffers
"^\\* .* docker-compose .*\\*"
(concat "^" (regexp-quote haris/custom-temp-buffer-name))))
This enables periodic cleanup:
(when (boundp 'haris/timer/clean-buffer-list)
(cancel-timer haris/timer/clean-buffer-list)
(makunbound 'haris/timer/clean-buffer-list))
(setq haris/timer/clean-buffer-list (run-with-timer 1800 1800 #'clean-buffer-list))
(global-set-key (kbd "C-+") 'text-scale-adjust)
(global-set-key (kbd "C--") 'text-scale-adjust)
(global-set-key (kbd "C-0") (defun haris//text-scale-reset ()
(interactive)
(text-scale-set 0)))
(defun haris/stage ()
"Go to a user-specific temporary staging directory, useful as a playground."
(interactive)
(let ((dir (format "/tmp/stage-%s"
(user-login-name))))
(mkdir dir t)
(dired dir)))
(defun haris/force-kill-window (&optional window)
"Kill a window, and the frame as well if it's the last one."
(interactive)
(let ((frame (window-frame window)))
(if (eq (length (window-list frame)) 1)
(delete-frame frame)
(quit-window window))))
(defun haris/yas-minor-mode-on ()
"Like yas-minor-mode-on, but do not honor yas-dont-activate-functions"
(yas-minor-mode 1))
(defun haris/fifo (text)
"Send TEXT to the fifo I use for testing"
(interactive "sInput: ")
(with-temp-buffer
(insert text "\n")
(shell-command-on-region
(point-min)
(point-max)
"fifo")))
(define-key evil-normal-state-map (kbd "M-w") 'avy-goto-word-0)
(define-key evil-normal-state-map (kbd "M-f") 'avy-goto-char)
(setq dired-listing-switches "-al \"--time-style=+%Y-%m-%d %H:%M\"")
(setq dired-kill-when-opening-new-dired-buffer t)
(use-package dired-rsync :defer t)
(use-package dired-rsync-transient :defer t)
(straight-use-package '(dired-hacks :type git :host github :repo "Fuco1/dired-hacks"))
These are commands that I primarily intend to use interactively and directly, without binding them to any keys.
All commands defined in ./scripts.org are taken and loaded as equivalent Elisp
interactive commands. Each command is mapped to a function named
haris/script/<script-name-from-scripts.org>
. When this interactive command is
run, it opens a vterm buffer named based on the command name, and runs the
command there (without any arguments).
(funcall
(defun haris/load-commands-from-local-shell-scripts ()
"Load all local shell script commands as interactive Elisp commands."
(interactive)
(with-temp-buffer
(org-mode)
(setq-local org-use-tag-inheritance nil)
(insert-file-contents "~/.haris/scripts.org")
;; Extract all applicable script commands
(setq-local
_commands
(org-map-entries
(lambda () (let ((title (nth 4 (org-heading-components))))
(string-replace "=" "" title)))
"script" nil))
;; Create an interactive function definition for each command
(mapcar
(lambda (command)
(eval
`(defun ,(intern (format "haris/script/%s" command))
;; Arglist
(prefix-arg)
;; Docstring
,(format
"Interactive command corresponding to the custom local shell script '%s'"
command)
(interactive "P")
(let ((default-directory "~")
(command ,command)
(_vterm nil)
(run-command nil))
;; Run multi-vterm
(setq _vterm (multi-vterm))
(with-current-buffer _vterm
;; Rename the buffer based on the command name
(rename-buffer (format "*haris/script/%s*" command) t)
(setq
run-command
(eval `(lambda (&optional argstring)
(interactive ,(format "sCLI arguments: %s " ,command))
"Run the command inside the open vterm buffer"
(comint-send-string
(get-buffer-process ,_vterm)
(format "%s %s\n" ,command (or argstring ""))))))
;; Run the command
(if prefix-arg
;; With prefix arg - prompt for CLI arguments before running
(call-interactively run-command)
;; No prefix arg - run without CLI arguments
(run-with-timer 0.6 nil (eval `(lambda () (funcall ,run-command))))))))))
_commands))))
(defun haris/remove-hook ()
"Find the surrounding add-hook form and revert its effects."
(interactive)
(let* ((sexp (save-excursion
(search-backward
(if (eq (symbol-at-point) 'add-hook)
"("
"(add-hook"))
(sexp-at-point))))
(setcar sexp 'remove-hook)
(message "%s" sexp)
(eval
;; Remove DEPTH argument, if any
(cl-remove-if #'numberp sexp))))
(defun haris/insert-tab ()
(interactive)
(insert-tab))
(defun haris/describe-symbol-at-point ()
(interactive)
(let ((was-in-minibuffer (minibufferp))
(original-buffer (current-buffer)))
(helpful-symbol (helpful--symbol-at-point))
(when was-in-minibuffer (switch-to-buffer original-buffer))))
;; M-TAB in insert mode inserts a tab emulated by spaces
(define-key evil-insert-state-map (kbd "M-TAB") #'insert-tab)
;; "SPC +" will pop up eshell
(spacemacs/set-leader-keys "+" 'spacemacs/shell-pop-eshell)
;; Don't use it, plus it interferes with bindings such as forward-button
(eval-after-load "helpful"
(lambda ()
(define-key evil-normal-state-map (kbd "TAB") nil)))
;; Help bindings
(spacemacs/set-leader-keys "hdo" 'helpful-symbol)
(evil-define-key 'normal org-mode-map (kbd "C-q")
'haris/describe-symbol-at-point)
(evil-define-key 'normal emacs-lisp-mode-map (kbd "C-q")
'haris/describe-symbol-at-point)
(evil-define-key 'normal ielm-map (kbd "C-q")
'haris/describe-symbol-at-point)
(evil-define-key 'normal read--expression-map (kbd "C-q")
'haris/describe-symbol-at-point)
(evil-define-key 'normal helpful-mode-map (kbd "TAB") #'forward-button)
(spacemacs/declare-prefix "o" "custom")
Note: there is also spacemacs’ builtin SPC "
that opens a terminal in-place.
(global-set-key (kbd "M-e")
(defun haris/open-buffer-in-new-frame ()
(interactive)
(let ((buf (current-buffer)))
(select-frame (make-frame '((window-system . x))))
(switch-to-buffer buf))))
(global-set-key
(kbd "M-v")
(lambda () (interactive)
(start-process "" nil "gvim" (buffer-file-name (window-buffer)))))
(defun haris/open-emacs.org ()
(interactive)
(find-file "~/.haris/emacs.org"))
(defun haris/load-user-config ()
(interactive)
(load-file "~/.emacs.d/haris/root.el"))
(defun haris/open-dotfiles-git ()
(interactive)
(magit-status "~/.haris"))
(define-key evil-normal-state-map (kbd "SPC f e h") #'haris/open-emacs.org)
(define-key evil-normal-state-map (kbd "SPC f e H") #'haris/open-dotfiles-git)
(define-key evil-normal-state-map (kbd "SPC f e r") #'haris/load-user-config)
(defalias 'spacemacs/default-pop-shell 'spacemacs/shell-pop-multivterm)
There are some inconsistencies in the vim key bindings (vim is guilty of this as
well). For example D
deletes until end of line, but V
visually selects the whole
line. This section remaps V
to v$
and does the same for other similar cases.
Some custom keybindings are defined here as well.
(define-key evil-normal-state-map (kbd "Q") 'delete-window)
(define-key evil-motion-state-map (kbd "Q") 'delete-window)
(define-key evil-visual-state-map (kbd "v") 'evil-visual-line)
(define-key evil-normal-state-map (kbd "V") (kbd "v$"))
(setq evil-want-Y-yank-to-eol t)
(define-key evil-normal-state-map (kbd "C-a") 'evil-numbers/inc-at-pt)
(define-key evil-visual-state-map (kbd "C-a") 'evil-numbers/inc-at-pt)
(define-key evil-normal-state-map (kbd "C-x") 'evil-numbers/dec-at-pt)
(define-key evil-visual-state-map (kbd "C-x") 'evil-numbers/dec-at-pt)
(defun haris/nohighlight () (interactive) (evil-ex-call-command "" "noh" ""))
(define-key evil-normal-state-map (kbd "M-/") 'haris/nohighlight)
(define-key evil-motion-state-map (kbd "M-/") 'haris/nohighlight)
(setq dotspacemacs-distinguish-gui-tab t)
(setq ielm-dynamic-return nil)
;; Use RET to execute command even in normal mode
(evil-define-key 'normal ielm-map (kbd "RET") #'ielm-send-input)
;; Make RET in insert mode insert newline at point, unless the
;; point is at the end of the line, in which case send input.
(defun haris/ielm-insert-mode-return ()
"Insert newline at point"
(interactive)
(if (= (point)
(save-excursion
(end-of-visual-line)
(point)))
(ielm-send-input)
(ielm-return)))
(evil-define-key 'insert ielm-map (kbd "RET") #'haris/ielm-insert-mode-return)
;; Buffer map
(setq haris/buffer-prefix-map (make-sparse-keymap))
(spacemacs/set-leader-keys "ob" haris/buffer-prefix-map)
(define-key haris/buffer-prefix-map (kbd "r") #'rename-buffer)
(define-key haris/buffer-prefix-map (kbd "R") #'revert-buffer)
(define-key haris/buffer-prefix-map (kbd "c") #'clone-buffer)
(define-key haris/buffer-prefix-map (kbd "i") #'ibuffer)
;; Command log mode
(setq haris/command-log-prefix-map (make-sparse-keymap))
(spacemacs/set-leader-keys "oc" haris/command-log-prefix-map)
(define-key haris/command-log-prefix-map (kbd "l") #'haris/command-log)
;; Misc
(with-eval-after-load 'go-translate
(spacemacs/set-leader-keys "ot" #'gt-do-translate))
(with-eval-after-load 'npm-mode
(spacemacs/set-leader-keys "on" npm-mode-command-keymap))
;; Git
(setq haris/git-prefix-map (make-sparse-keymap))
(with-eval-after-load 'magit
(spacemacs/set-leader-keys "og" haris/git-prefix-map))
(define-key haris/git-prefix-map (kbd "c") 'magit-find-git-config-file)
;; Friendly descriptions
(which-key-add-key-based-replacements
"SPC o b" "Buffer manipulation"
"SPC o c" "Command log"
"SPC o c l" "Local command log"
"SPC o t" "Translate"
"SPC o n" "NPM"
"SPC o g" "Git")
<<custom-global-map>>
;; Use M-y or M-n to answer a minibuffer prompt
(defun haris/insert-into-minibuffer-and-exit (text)
(interactive)
(with-current-buffer (window-buffer (active-minibuffer-window))
(insert text)
(exit-minibuffer)))
(global-set-key (kbd "M-y")
(lambda ()
(interactive)
(haris/insert-into-minibuffer-and-exit "y")))
(global-set-key (kbd "M-n")
(lambda ()
(interactive)
(haris/insert-into-minibuffer-and-exit "n")))
(define-key comint-mode-map (kbd "M-h") (lambda ()
"Search through current history"
(interactive)
(counsel-shell-history)))
(use-package evil-quickscope)
(global-evil-quickscope-mode)
(setq evil-lookup-func (lambda () (call-interactively #'man)))
(setq evil-want-C-i-jump t)
(add-hook 'evil-insert-state-exit-hook #'company-cancel)
(setq evil-collection-setup-minibuffer t)
;; Please keep this sorted
(evil-collection-init 'bluetooth)
(evil-collection-init 'bookmark)
(evil-collection-init 'calendar)
(evil-collection-init 'comint)
(evil-collection-init 'daemons)
(evil-collection-init 'docker)
(evil-collection-init 'doc-view)
(evil-collection-init 'edbi)
(evil-collection-init 'edebug)
(evil-collection-init 'explain-pause-mode)
(evil-collection-init 'git-timemachine)
(evil-collection-init 'ibuffer)
(evil-collection-init 'info)
(evil-collection-init 'ivy)
(evil-collection-init 'man)
(evil-collection-init 'minibuffer)
(evil-collection-init 'proced)
(evil-collection-init 'tablist)
(evil-collection-init 'tabulated-list)
(evil-collection-init 'yaml-mode)
(evil-collection-init 'compilation)
(evil-collection-init 'evil-mc)
(with-eval-after-load 'evil-surround
(add-to-list 'evil-surround-pairs-alist '(?$ . ("\"$(" . ")\""))))
(defun haris/evil-define-key-both (keymap key def &rest bindings)
"evil-define-key in both normal and insert state"
;; Forward to evil-define-key, handling &rest args properly
(apply 'evil-define-key* (append `(normal ,keymap ,key ,def) bindings))
(apply 'evil-define-key* (append `(insert ,keymap ,key ,def) bindings)))
(setq projectile-require-project-root nil)
(setq projectile-auto-discover nil)
(setq projectile-track-known-projects-automatically t)
(setq projectile-git-ignored-command "git ls-files -zcoi -X=.gitignore")
(setq projectile-ignored-projects '("~/"))
(setq projectile-indexing-method 'hybrid
projectile-enable-caching t)
(with-eval-after-load 'magit
(let*
((projects (-filter
'file-directory-p
'("~/.haris/"
"~/.emacs.d/"
"~/.emacs.d/private/snippets/"
"~/.emacs.d/private/themes/dracula/"
"~/wiki/")))
(worktrees
(flatten-list
(mapcar
(lambda (proj)
(append
;; Add all worktrees of the project's repo as projects
(let ((default-directory proj))
(mapcar 'car (magit-list-worktrees))
;; In case it is not a git repo, worktrees would be nil, so add the
;; project itself
(list proj))))
projects))))
;; Add each project (+ its worktrees) to projectile-known-projects
(dolist (worktree worktrees)
(add-to-list 'projectile-known-projects worktree nil 'file-equal-p)
;; Also create a .projectile file in case the directory exists and it is not
;; a git repo
(when (and (file-exists-p worktree) (not (magit-git-repo-p worktree)))
(f-touch (f-join worktree ".projectile"))))))
;; Provides better namespacing than the default spacemacs/projectile-shell-pop
(define-key spacemacs-cmds (kbd "p '") 'multi-vterm-project)
(add-hook 'prog-mode-hook 'spacemacs/toggle-fill-column-indicator)
(eval-after-load "yaml"
(defun haris//after-load/yaml ()
(use-package yaml-pro)))
;; Use yaml-ts-mode instead of yaml-mode (it's faster), unless the mode is docker-compose-mode.
(add-hook 'yaml-mode-hook
(defun haris/replace-yaml-with-yaml-ts ()
(when (not (eq major-mode 'docker-compose-mode))
(yaml-ts-mode))))
(let ((hooks '(yaml-mode-hook yaml-ts-mode-hook))
(hook))
(dolist (hook hooks)
(add-hook hook
(lambda ()
(require 'openapi-yaml-mode)
(require 'openapi-preview)))
(add-hook hook 'spacemacs/toggle-indent-guide)
(add-hook hook #'eldoc-mode 90)
(add-hook hook #'yaml-pro-ts-mode 91)
(add-hook hook 'spacemacs/toggle-fill-column-indicator)
(add-hook hook
(lambda ()
(setq-local counsel-jq-command "yq")) 92)
(add-hook hook #'lsp 94)))
(add-hook 'yaml-pro-ts-mode-hook
(lambda ()
(setq-local lsp-enable-imenu nil)))
(straight-use-package '(openapi-yaml-mode :type git :host github :repo "magoyette/openapi-yaml-mode"))
(straight-use-package '(openapi-preview :type git :host github :repo "merrickluo/openapi-preview"))
(eval-after-load "yaml"
(defun haris//after-load/openapi ()
(require 'openapi-yaml-mode)
(require 'openapi-preview)))
(add-to-list 'auto-mode-alist '("(openapi|swagger)\\.ya?ml$" . openapi-yaml-mode))
(eval-after-load "yaml"
(lambda ()
(evil-define-key 'normal yaml-mode-map (kbd "SPC j =") 'lsp-format-buffer)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "M-n") 'yaml-pro-ts-next-subtree)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "g j") 'yaml-pro-ts-next-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c C-n") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "M-p") 'yaml-pro-ts-prev-subtree)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "g k") 'yaml-pro-ts-prev-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c C-p") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "g h") 'yaml-pro-ts-up-level)
(define-key yaml-pro-ts-mode-map (kbd "C-c C-u") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", '") 'yaml-pro-edit-ts-scalar)
(define-key yaml-pro-ts-mode-map (kbd "C-c '") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", <") 'yaml-pro-ts-unindent-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c <") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", >") 'yaml-pro-ts-indent-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c >") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", v") 'yaml-pro-ts-mark-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c @") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "M-j") 'yaml-pro-ts-move-subtree-down)
(define-key yaml-pro-ts-mode-map (kbd "s-<down>") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd "M-k") 'yaml-pro-ts-move-subtree-up)
(define-key yaml-pro-ts-mode-map (kbd "s-<up>") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", d") 'yaml-pro-kill-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c C-x C-w") nil)
(evil-define-key 'normal yaml-pro-ts-mode-map (kbd ", p") 'yaml-pro-ts-paste-subtree)
(define-key yaml-pro-ts-mode-map (kbd "C-c C-x C-y") nil)))
yaml-language-server yamllint
(defun haris/json/set-indent-level () (setq-local js-indent-level 2))
(add-hook 'json-mode-hook 'haris/json/set-indent-level)
(add-hook 'json-mode-hook 'spacemacs/toggle-indent-guide)
(add-hook 'json-mode-hook 'json-ts-mode 90)
(defalias 'jq 'counsel-jq)
(setq counsel-jq-json-buffer-mode 'json-ts-mode)
Some performance improvement settings depend on the power of your hardware. You can find them here. You can place them in your private configuration.
(with-eval-after-load 'lsp
(setq lsp-idle-delay 0.1)
(setq lsp-keep-workspace-alive nil)
;; Diagnostic mode doesn't work well with flycheck
(setq lsp-diagnostics-disabled-modes '(python-mode sh-mode))
(setq lsp-enable-on-type-formatting nil)
;; This prevents lsp from overriding my custom 'company-backends'
(setq lsp-completion-provider :none)
(setq lsp-restart 'ignore)
(setq lsp-auto-execute-action nil)
(setq lsp-inlay-hint-enable t)
;; Prevents session persistence, which can cause performance issues
(setq lsp-session-file nil)
(add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]venv$"))
(with-eval-after-load 'lsp
;; TODO: not working
(haris/evil-define-key-both lsp-mode-map (kbd "M-RET") 'lsp-execute-code-action)
(spacemacs/set-leader-keys-for-minor-mode
'lsp-mode
"FR" (defun haris/lsp-clear-workspace-folders ()
(interactive)
(mapcar (lambda (x) (lsp-workspace-folders-remove x))
(lsp-session-folders (lsp-session))))))
(setq dap-auto-show-output nil)
(setq c-default-style "bsd"
c-basic-offset 4)
(add-hook 'c-mode-hook (lambda () (setq tab-width 4)))
(add-hook 'c++-mode-hook (lambda () (setq tab-width 4)))
(defun haris/cmake-info () (interactive)
(info-display-manual "cmake")
(Info-top-node))
(defun haris/cmake-help () (interactive)
(split-window-right-and-focus)
(let ((symbol (cmake-symbol-at-point)))
(haris/cmake-info)
(Info-menu symbol)))
(evil-define-key 'normal cmake-mode-map (kbd ",hc") 'haris/cmake-info)
(evil-define-key 'normal cmake-mode-map (kbd ",hh") 'haris/cmake-help)
cmake-language-server
Spacemacs: elpy layer downloaded from here
(add-hook 'python-mode-hook (lambda () (setq tab-width 4)))
(setq python-shell-interpreter "ipython")
(setq lsp-pylsp-plugins-pylint-enabled t
lsp-pylsp-plugins-flake8-enabled nil
lsp-pyls-plugins-flake8-enabled nil
lsp-diagnostics--flycheck-enabled t)
;; elpy
(setq elpy-modules nil)
(with-eval-after-load 'lsp
(evil-define-key 'normal lsp-mode-map (kbd ",GG") 'lsp-ui-doc-glance))
python-lsp-server flake8 python-typing_extensions python-lsp-black python-pylint
pyls-isort pyls-mypy pyls-memestra autoflake importmagic epc ptvsd
(use-package mocha-snippets :ensure t)
(use-package npm-mode :ensure t)
(setq javascript-indent-level 4
js-indent-level 4
js2-basic-offset 4)
(with-eval-after-load 'js (require 'dap-node))
(with-eval-after-load 'ts (require 'dap-node))
(with-eval-after-load "npm-mode" (npm-global-mode t))
(use-package lsp-java)
(setq-default haris/java-lombok-jar-path nil)
(defun haris/modify-java-vmargs-in-lsp-java ()
(when (and lsp-mode (eq major-mode 'java-mode))
(add-to-list 'lsp-java-vmargs "-Xmx3G" t)
(add-to-list 'lsp-java-vmargs "-Xms512m" t)
(add-to-list 'lsp-java-vmargs "-XX:+UseStringDeduplication" t)
;; Add Lombok to the lsp server
(when haris/java-lombok-jar-path
(add-to-list 'lsp-java-vmargs
(format "-javaagent:%s" haris/java-lombok-jar-path) t))))
(add-hook 'lsp-before-initialize-hook #'haris/modify-java-vmargs-in-lsp-java)
(setq web-mode-markup-indent-offset 2
web-mode-css-indent-offset 2
web-mode-code-indent-offset 2
css-indent-offset 2)
vue-language-server
@angular/language-service@next typescript @angular/language-server
(use-package company-shell)
(defun haris/enable--completions-in-shell-modes ()
(setq-local company-backends haris/company-backends-sh-mode))
(add-hook 'sh-base-mode-hook #'haris/enable--completions-in-shell-modes)
(add-hook 'fish-mode-hook #'haris/enable--completions-in-shell-modes)
(straight-use-package
'(company-fish
:type git
:host github
:repo "CeleritasCelery/company-fish"))
(add-to-list 'spacemacs--indent-variable-alist '(fish-mode . fish-indent-offset))
(defun haris/rst-heading () (interactive)
(evil-execute-macro 1 "\"yyyp^v$"))
(define-key evil-normal-state-map (kbd ", H") 'haris/rst-heading)
(setq dockerfile-use-buildkit t
dockerfile-build-progress "plain")
dockerfile-language-server
(add-to-list 'auto-mode-alist '("compose.*\.ya?ml$" . docker-compose-mode))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection
'("docker-compose-langserver" "--stdio"))
:activation-fn (lsp-activate-on "yaml")
:priority 0
:server-id 'compose-ls
:major-modes '(docker-compose-mode)))
docker-compose-langserver
(setq sql-capitalize-keywords t)
(use-package protobuf-mode :ensure t)
(use-package protobuf-ts-mode :ensure t)
(setq Man-notify-method 'bully)
(defun haris/man-search () (interactive)
(swiper "^[[:space:]]+"))
(add-hook 'Man-mode-hook (lambda ()
(define-key Man-mode-map (kbd "SPC s ^") 'haris/man-search)))
Fix for empty image list (not sure if the fix works):
(use-package transient :defer t)
(use-package docker-compose-mode :defer t)
(use-package go-translate)
(setq gt-taker-prompt 'buffer)
(setq gt-langs '(en es de bs tr cs))
(setq gt-buffer-render-follow-p t)
(setq gt-polyglot-p t)
(setq gt-taker-pick nil)
(setq gt-default-translator
(gt-translator
:engines (gt-google-engine)
:render (gt-buffer-render)))
;; Perform translation
(evil-define-key 'normal gt-buffer-prompt-map (kbd ",c") (kbd "C-c C-c"))
;; Abort translation
(evil-define-key 'normal gt-buffer-prompt-map (kbd ",k") (kbd "C-c C-k"))
;; Switch translator engine
(evil-define-key 'normal gt-buffer-prompt-map (kbd ",e") (kbd "C-c C-e"))
;; Next language
(haris/evil-define-key-both gt-buffer-prompt-map (kbd "M-n") (kbd "C-c C-n"))
;; Previous language
(haris/evil-define-key-both gt-buffer-prompt-map (kbd "M-p") (kbd "C-c C-p"))
;; go-translate provides only a function for next, but not for prev
(defun haris/gt-buffer-render--cycle-prev (&optional ignore-rules)
(interactive "P")
(with-slots (target taker keep) gt-buffer-render-translator
(if (and (slot-boundp taker 'langs) (gt-functionp (oref taker langs)))
(user-error "Current taker not support cycle prev")
(let* ((curr gt-last-target)
(gt-skip-lang-rules-p ignore-rules)
(gt-ignore-target-history-p t)
(prev (gt-target taker gt-buffer-render-translator 'prev)))
(unless (equal prev curr)
(setf target prev keep t)
(gt-start gt-buffer-render-translator))))))
(add-hook
'gt-buffer-render-output-hook
(defun haris//gt-buffer-render-output-init-bindings () (interactive)
;; Without this, the following keybindings don't take effect
(evil-normal-state)
(evil-define-key 'normal gt-buffer-render-local-map
(kbd "M-n") 'gt-buffer-render--cycle-next)
(evil-define-key 'normal gt-buffer-render-local-map
(kbd "M-p") 'haris/gt-buffer-render--cycle-prev)
(evil-define-key 'normal gt-buffer-render-local-map
(kbd "q") 'haris/force-kill-window)
(evil-define-key 'normal gt-buffer-render-local-map
(kbd "?") (defun haris/gt-buffer-render-help ()
(interactive)
(describe-keymap gt-buffer-render-local-map)))))
(use-package bluetooth :defer t)
;; This is set to 't' to avoid mail syncing issues when using mbsync
(setq mu4e-change-filenames-when-moving t)
;; Refresh mail using isync every M minutes
(setq mu4e-update-interval (let ((M 4)) (* M 60)))
(setq mu4e-get-mail-command "mbsync -a")
(setq mu4e-enable-async-operations t)
;; Configure contexts
(setq mu4e-contexts
`(
,(make-mu4e-context
:name "[email protected]"
:match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "[email protected]")))
:enter-func (lambda () (message "Entering context: [email protected]"))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Haris Gusic")
(mu4e-drafts-folder . "/gmail/hgusic.pub/[Gmail]/Drafts")
(mu4e-sent-folder . "/gmail/hgusic.pub/[Gmail]/Sent Mail")
(mu4e-refile-folder . "/gmail/hgusic.pub/[Gmail]/All Mail")
(mu4e-trash-folder . "/gmail/hgusic.pub/[Gmail]/Trash")
(
mu4e-maildir-shortcuts
. (("/gmail/hgusic.pub/Inbox" . ?i)
("/gmail/hgusic.pub/[Gmail]/Sent Mail" . ?s)
("/gmail/hgusic.pub/[Gmail]/Trash" . ?t)
("/gmail/hgusic.pub/[Gmail]/Drafts" . ?d)
("/gmail/hgusic.pub/[Gmail]/All Mail" . ?a)))))
,(make-mu4e-context
:name "[email protected]"
:match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "[email protected]")))
:enter-func (lambda () (message "Entering context: [email protected]"))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Haris Gusic")
(mu4e-drafts-folder . "/gmail/harisgusic.dev/[Gmail]/Drafts")
(mu4e-sent-folder . "/gmail/harisgusic.dev/[Gmail]/Sent Mail")
(mu4e-refile-folder . "/gmail/harisgusic.dev/[Gmail]/All Mail")
(mu4e-trash-folder . "/gmail/harisgusic.dev/[Gmail]/Trash")
(
mu4e-maildir-shortcuts
. (("/gmail/harisgusic.dev/Inbox" . ?i)
("/gmail/harisgusic.dev/[Gmail]/Sent Mail" . ?s)
("/gmail/harisgusic.dev/[Gmail]/Trash" . ?t)
("/gmail/harisgusic.dev/[Gmail]/Drafts" . ?d)
("/gmail/harisgusic.dev/[Gmail]/All Mail" . ?a)))))
))
(setq mu4e-context-policy 'ask
mu4e-compose-context-policy 'ask)
(setq mu4e-org-support t)
;; Enable org mode when composing messages
(setq mu4e-org-compose-support t)
;; Show completion for From and To headers
(setq mail-user-agent 'mu4e-user-agent)
(setq message-mail-alias-type 'ecomplete)
(add-hook 'message-setup-hook 'flyspell-mode)
(use-package mu4e-alert :defer t)
(setq mu4e-enable-notifications t)
(mu4e-alert-set-default-style 'libnotify)
slack-register-team automatically connects to slack. If I add it to the slack-mode-hook hook, it never connects. Investigate
;; (add-hook
;; 'slack-mode-hook
;; (lambda ()
;; ;; Add slack teams here
;; (slack-register-team
;; :name "efektivnialtruismus"
;; :token (auth-source-pick-first-password
;; :host "efektivnialtruismus.slack.com"
;; :user "[email protected]")
;; :cookie (auth-source-pick-first-password
;; :host "efektivnialtruismus.slack.com"
;; :user "[email protected]^cookie")
;; :subscribed-channels '((main-announcements
;; main-community-events
;; main-opportunities
;; main-random
;; project-eahouse)))))
(use-package erc)
(setq erc-server "irc.libera.chat"
erc-nick "veracioux"
erc-user-full-name "Haris Gušić"
erc-track-shorten-start 8
erc-autojoin-channels-alist '(("irc.libera.chat" "#archlinux" "#Jobs" "#fossjobs"))
erc-kill-buffer-on-part t
erc-auto-query 'bury)
(add-hook 'erc-join-hook (lambda () (evil-normal-state)))
;; For some reason erc-modules is undefined
(add-to-list 'erc-modules 'notifications)
(delete 'readonly erc-modules)
(erc-services-mode 1)
(erc-update-modules)
(erc-notify-mode t)
(erc-notifications-mode t)
(defun haris/erc-quit-channel () (interactive)
(erc-part-from-channel ""))
(defun haris/euirc () (interactive)
(erc :server "irc.euirc.net" :port 6667 :nick "veracioux"))
(defun haris/erc-list-channels () (interactive)
(erc-with-server-buffer
(erc-kill-input)
(insert "/list")
(erc-send-current-line)))
(define-key erc-mode-map (kbd "C-l") 'comint-clear-buffer)
(evil-define-key 'normal erc-mode-map (kbd ",b") 'erc-switch-to-buffer)
(evil-define-key 'normal erc-mode-map (kbd ",j") 'erc-join-channel)
(evil-define-key 'normal erc-mode-map (kbd ",q") 'haris/erc-quit-channel)
(evil-define-key 'normal erc-mode-map (kbd ",l") 'haris/erc-list-channels)
(evil-define-key 'motion erc-list-menu-mode-map (kbd "RET") nil)
(evil-define-key 'normal erc-list-menu-mode-map (kbd "RET") nil)
;; TODO shadowed by evil binding, don't know how to fix
;; (evil-define-key 'normal erc-list-menu-mode-map (kbd ",j") 'erc-list-join)
(setq erc-track-faces-priority-list
'(erc-error-face
erc-notice-face
(erc-nick-default-face erc-current-nick-face)
erc-current-nick-face erc-keyword-face
(erc-nick-default-face erc-pal-face)
erc-pal-face erc-nick-msg-face erc-direct-msg-face
(erc-button erc-default-face)
(erc-nick-default-face erc-dangerous-host-face)
erc-dangerous-host-face erc-nick-default-face
(erc-nick-default-face erc-default-face)
erc-default-face erc-action-face
(erc-nick-default-face erc-fool-face)
erc-fool-face erc-input-face erc-prompt-face))
[Unit]
Description=Emacs daemon for IRC chat
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
[Service]
Type=forking
Environment=COLORTERM=truecolor
ExecStart=/usr/bin/emacs --daemon="irc"
Restart=always
TimeoutStartSec=600
TimeoutStopSec=30
StartLimitBurst=0
[Install]
WantedBy=default.target
Database viewer in Emacs.
(use-package edbi
:defer t
:config (progn
(define-key edbi:dbview-keymap (kbd "SPC") nil)
(define-key edbi:dbview-keymap (kbd "RET")
'edbi:dbview-show-tabledef-command)))
RPC::EPC::Service DBI
# For postgres support
DBD::Pg
(defun octave-write-and-source () (interactive)
(write-file (buffer-file-name))
(octave-source-file (buffer-file-name)))
(evil-define-key 'normal octave-mode-map
(kbd ",ss") 'octave-write-and-source)
(evil-define-key 'normal inferior-octave-mode-map
(kbd ",hh") 'octave-help)
(add-hook 'md4rd-mode-hook 'md4rd-indent-all-the-lines)
(setq md4rd-subs-active '(linuxquestions+linux+opensource plc))
This is a very elegant reader for reddit that uses org-mode.
(defun reddit-view-linux () (interactive)
(reddigg-view-sub "linux+linuxquestions+opensource"))
(defun reddit-view-elec () (interactive)
(reddigg-view-sub "plc+ElectricalEngineering+embedded"))
I used this mode like 2-3 times, but I’m keeping it in case I have to use it again.
(add-to-list 'load-path "/usr/share/emacs/site-lisp/maxima/")
(autoload 'maxima-mode "maxima" "Maxima mode" t)
(autoload 'imaxima "imaxima" "Frontend for maxima with Image support" t)
(autoload 'maxima "maxima" "Maxima interaction" t)
(autoload 'imath-mode "imath" "Imath mode for math formula input" t)
(setq imaxima-use-maxima-mode-flag t)
(add-to-list 'auto-mode-alist '("\\.ma[cx]\\'" . maxima-mode))
(define-minor-mode haris/minibuffer-elisp-mode
"Custom mode that allows me to use emacs-lisp-mode features in minibuffers
that evaluate elisp code.")
(add-hook 'minibuffer-setup-hook
(defun haris//minibuffer-maybe-enable-elisp-mode ()
(when (member this-command
'(eval-expression
edebug-eval-expression))
(haris/minibuffer-elisp-mode 1))))
(add-hook
'minibuffer-mode-hook
(defun haris//enable-company-in-non-completing-minibuffers ()
"Enable company-mode in buffers which don't already have some kind of
inherent completion mechanism."
(when (and (not minibuffer-completion-predicate)
(not minibuffer-completion-table))
(company-mode))))
(add-hook 'haris/minibuffer-elisp-mode-hook
(defun haris//initialize-minibuffer-elisp-mode ()
"Initialize custom haris/minibuffer-elisp-mode"
(haris/yas-minor-mode-on)
(yas-activate-extra-mode 'emacs-lisp-mode)
; (ref:company-backends-emacs-lisp-mode/usage)
(setq-local company-backends company-backends-emacs-lisp-mode)
(company-mode)))
(advice-add
#'eval-expression
:around
(defun haris/eval-expression/around (func &rest args)
"Overrides eval-expression to use active region is its initial input"
(interactive
(cons
(let ((minibuffer-setup-hook minibuffer-setup-hook))
;; If a region was selected, open the read--expression minibuffer in normal mode
(if (use-region-p)
(add-hook
'minibuffer-setup-hook
(defun haris//eval-expression-normal-state ()
;; Only do it for the eval-expression minibuffer, otherwise
;; stacked minibuffers will misbehave
(if (eq this-command 'eval-expression)
(evil-force-normal-state)))
99))
(read--expression
"Eval: "
(when (use-region-p)
(buffer-substring (region-beginning) (region-end)))))
(eval-expression-get-print-arguments current-prefix-arg)))
(apply func args)))
Note: I override the <escape>
binding for company-active-map
for this mode
specifically. I did that here, because I don’t know a better way to do it.
(define-key minibuffer-mode-map (kbd "M-P") 'counsel-minibuffer-history)
(use-package all-the-icons-ivy-rich
:ensure t
:config
(all-the-icons-ivy-rich-mode 1))
(use-package ivy-rich
:ensure t
:config
(setq ivy-rich-display-transformers-list
(append
ivy-rich-display-transformers-list
`(helpful-symbol
(:columns
((,(defun haris/helpful-symbol-transformer (symbol)
(or (counsel-describe-variable-transformer symbol)
(counsel-describe-function-transformer symbol)))
(:width 0.3))
(all-the-icons-ivy-rich-symbol-class
(:width 8 :face all-the-icons-ivy-rich-type-face))
(all-the-icons-ivy-rich-symbol-docstring
(:face all-the-icons-ivy-rich-doc-face)))))
`(helpful-callable
,(nth
(+ 1 (cl-position 'counsel-describe-function
ivy-rich-display-transformers-list))
ivy-rich-display-transformers-list))
`(helpful-variable
,(nth
(+ 1 (cl-position 'counsel-describe-variable
ivy-rich-display-transformers-list))
ivy-rich-display-transformers-list))))
(ivy-rich-mode 1))
(setq ivy-initial-inputs-alist
(append
'((helpful-function . "^")
(helpful-callable . "^")
(helpful-variable . "^")
(helpful-symbol . "^")
(devdocs-browser-open . "^")
ivy-initial-inputs-alist)))
(add-hook 'lsp-mode-hook
(lambda ()
(setq xref-show-xrefs-function 'ivy-xref-show-xrefs
xref-show-definitions-function 'ivy-xref-show-defs)))
(defun haris/ivy-insert-current ()
"Exactly the same as ivy-insert-current, but made interactive."
(interactive)
(ivy-insert-current))
(define-key ivy-minibuffer-map (kbd "<backtab>") 'haris/ivy-insert-current)
(evil-define-key 'normal ivy-minibuffer-map (kbd "C-n") 'ivy-next-line)
(evil-define-key 'normal ivy-minibuffer-map (kbd "C-p") 'ivy-previous-line)
(evil-define-key 'normal ivy-minibuffer-map (kbd "gg") 'ivy-beginning-of-buffer)
(evil-define-key 'normal ivy-minibuffer-map (kbd "G") 'ivy-end-of-buffer)
(evil-define-key 'normal ivy-minibuffer-map (kbd "C-j") 'ivy-next-line)
(define-key ivy-minibuffer-map (kbd "C-<return>") 'ivy-immediate-done)
(define-key ivy-minibuffer-map (kbd "M-P") 'ivy-reverse-i-search)
(setq imenu-max-item-length nil)
(eval-after-load "org"
(lambda ()
(use-package org-transclusion :defer t)
(use-package org-preview-html :defer t)
(use-package org-drill :defer t)
(use-package ob-restclient)
(use-package ol-man :ensure nil)
(use-package org-tempo :ensure nil)
(use-package org-modern
:config (setq org-modern-star nil))
(use-package company-org-block)))
(defun haris/org-mode-visual-fill ()
(setq visual-fill-column-width 90
visual-fill-column-center-text t)
(visual-fill-column-mode 1))
(defun haris/org-babel-goto-tangle-file ()
"Go to the file that the code block at point tangles to. If there is an
interactive prefix argument, open the final destination (production) file."
(let ((file (if current-prefix-arg
(haris/extract-tangle-final-dest)
(haris/extract-tangle-dest))))
(when file (find-file file))))
(add-hook 'org-mode-hook 'org-appear-mode)
(add-hook 'org-mode-hook 'haris/org-mode-visual-fill)
(add-hook 'org-mode-hook 'org-indent-mode)
(add-hook 'org-mode-hook 'org-transclusion-add-all)
(add-hook 'org-open-at-point-functions 'haris/org-babel-goto-tangle-file)
;; Prettiness
(setq org-indent-mode t
org-M-RET-may-split-line nil
org-ellipsis " ▾"
org-superstar-headline-bullets-list '("◉" "○" "■" "◆")
org-hide-emphasis-markers t
org-pretty-entities t
org-appear-autoentities t
org-appear-autolinks nil)
;; Misc variables
(setq org-download-screenshot-method "flameshot gui --path screenshots/%s"
org-projectile-file "TODO.org"
org-projectile-per-project-filepath "TODO.org")
(add-to-list 'org-file-apps '("\\.x?html?\\'" . "firefox %s"))
(add-to-list 'org-export-backends 'md)
;; Enable org-modern-mode
(with-eval-after-load 'org (global-org-modern-mode))
(setq org-agenda-files (append
(remove "~/wiki/index.org"
(file-expand-wildcards "~/wiki/*"))
(file-expand-wildcards "~/proj/*/*.org")
(file-expand-wildcards "~/proj/drytoe/*/*.org")))
Doesn’t get loaded correctly.
(use-package org-special-block-extras
:ensure t
:hook (org-mode . org-special-block-extras-mode))
Takes too long to load.
(use-package org-alert :defer t)
(setq org-structure-template-alist
(cl-remove-duplicates
(append (default-value 'org-structure-template-alist)
'(("el" . "src elisp")
("sh" . "src shell")
("py" . "src python")
("bash" . "src bash")
("fish" . "src fish")
("fishfun" . "src fish :tangle (haris/tangle-deps \".config/fish/functions/TODO\")")
("fishcomp" . "src fish :tangle (haris/tangle-deps \".config/fish/completions/TODO\")")
("dep" . "src shell :tangle (haris/tangle-deps \"TODO\")")
("sht" . "src shell :tangle (haris/tangle-home \"TODO\")")
("elt" . "src elisp :tangle (haris/tangle-home \"TODO\")")
("sh" . "src elisp :tangle (haris/tangle-home \"TODO\")")
("st" . "src :tangle (haris/tangle-home \"TODO\")")
("rest" . "src restclient")))
:test (lambda (a b) (string= (car a) (car b)))))
I tangle my configs from various org files into their respective destination
files. But, sometimes I perform a tangle without wanting to overwrite my live
configuration. One reason for this is that I have a (WIP) github workflow that I
use to generate the configs from my org files. That is why code blocks in my
literal configs use temporary “staging” destinations. So, whenever I run
(org-babel-tangle)
, the files are output into /tmp/tangle-<username>
or
/tmp/dependencies-<username>
(varies by code block). Then, if I want to apply
those files to my live config under ~/
, I can call (haris/tangle-dest)
.
;; (use-package ob-async :defer t)
;; There are a few custom functions I define for tangling that are in a separate
;; file, so that file can be used as a minimalistic source for boostrapping.
(load-file "~/.haris/bootstrap/tangle.el")
(add-to-list 'org-babel-load-languages '(restclient . t))
;; (add-to-list 'org-babel-load-languages '(async . t))
(add-to-list 'org-babel-load-languages '(verb . t))
(add-to-list 'org-babel-load-languages '(sql . t))
(org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages)
;; This variable is by default unbound, and so causes an error whenever a code
;; block with ':session' is evaluated
(setq org-babel-prompt-command "")
(defun haris/tangle-dest (&optional prefix-arg)
"Tangle block(s) to their final destinations. If a code block has the
temporary staging destination as their :tangle argument, it will be tangled to
the production destination under ~/ as well."
(interactive "P")
(let ((tangle-home (haris/tangle-home)))
(delete-directory tangle-home t)
(org-transclusion-add-all)
(org-babel-tangle prefix-arg)
(shell-command (concat "rsync -ru --keep-dirlinks " tangle-home " ~/"))))
(evil-define-key 'normal org-mode-map (kbd ",bT") 'haris/tangle-dest)
(add-hook 'org-src-mode-hook
(defun haris//org-src-inherit-allow-copilot ()
(setq-local haris/allow-copilot
(org-src-do-at-code-block haris/allow-copilot))
(when haris/allow-copilot
;; NOTE: Doesn't work without the timer for some reason
(run-with-timer 0.1 nil 'copilot-mode))))
(add-hook
'org-src-mode-hook
(defun haris/org-src-mode-prepare-for-lsp-mode ()
"Make the current org-src buffer support lsp-mode.
1. Create a temporary file and associate it the current org-src-mode buffer to
it by setting 'buffer-file-name'.
2. Set the projectile-project-root to be the same as the source org buffer
3. Cleanup of the temporary file is also scheduled for when the buffer is killed."
(let* ((tmpdir (make-temp-file "" t))
;; Naming the file " " is a hack that prevents an ugly
;; "<name>Edit, then exit with ..." in the org-src buffer's header
(filename (format "%s/ " tmpdir)))
;; Create a temporary association of the file with the proper major-mode
(setq-local buffer-file-name filename)
(write-region nil nil buffer-file-name)
;; Set project root
(setq-local projectile-project-root
(org-src-do-at-code-block (projectile-project-root)))
(setq lsp-auto-guess-root t)
;; Clean up
(add-hook
'kill-buffer-hook
(lambda ()
;; Delete temporary file
(delete-file buffer-file-name))
99
t)))
90)
(advice-add
'org-edit-src-save
:before
(lambda (&rest rest)
(when buffer-file-name
(write-region nil nil buffer-file-name))))
(defun haris/extract-tangle-dest ()
"Extract the tangle destination from the code block under point."
(let* ((args (nth 2 (org-babel-get-src-block-info)))
(tangle-arg (alist-get :tangle args)))
(if (and tangle-arg (not (string= "no" tangle-arg)))
tangle-arg)))
(defun haris/extract-tangle-final-dest ()
"Extract the tangle destination of the current code block. If the destination
is defined in terms of (haris/tangle-home), then the final destination under
~/ is returned."
(let* ((dest (haris/extract-tangle-dest)))
(if dest
(let* ((home-dir-re (concat "^" (regexp-quote (haris/tangle-home))))
(deps-dir-re (concat "^" (regexp-quote (haris/tangle-deps ""))))
(_file (replace-regexp-in-string home-dir-re "~/" dest)))
(replace-regexp-in-string deps-dir-re "~/" _file))
nil)))
(setq org-preview-latex-default-process 'dvisvgm)
(setq org-latex-create-formula-image-program 'dvisvgm)
(setq org-preview-latex-image-directory "/tmp/org-mode/ltximg/")
(setq org-image-actual-width 400)
(add-hook 'verb-response-body-mode-hook 'verb-toggle-show-headers)
(setq verb-json-use-mode 'json-ts-mode)
(spacemacs/set-leader-keys-for-minor-mode
'verb-response-body-mode
"rs" #'verb-show-request)
(defun haris/org-babel-restclient-split-window-fix ()
"Fixes a bug where executing a restclient code block splits the window."
(interactive)
(if (string=
(car (org-babel-get-src-block-info))
"restclient")
(delete-window)))
(add-hook 'org-babel-after-execute-hook 'haris/org-babel-restclient-split-window-fix)
;; Make org-cycle work only in evil normal state, so it doesn't interfere with
;; completion etc.
(define-key org-mode-map (kbd "TAB") nil t)
(evil-define-key 'normal org-mode-map (kbd "TAB") 'org-cycle)
(evil-define-key 'normal org-mode-map (kbd ", S") 'org-attach-screenshot)
(evil-define-key 'normal org-mode-map (kbd ", TAB") 'org-next-link)
(evil-define-key 'normal org-mode-map (kbd ", <backtab>") 'org-previous-link)
(evil-define-key 'normal org-mode-map (kbd ", i c") 'org-columns)
(evil-define-key 'normal org-mode-map (kbd ", b E") 'haris/execute-named-code-block)
(evil-define-key 'normal org-mode-map (kbd ", R") 'org-mode-restart)
(evil-define-key 'normal org-mode-map (kbd "SPC h o") 'org-info-find-node)
;; The , prefix is implied
(define-key spacemacs-org-src-mode-map (kbd "w")
'org-edit-src-save)
(define-key spacemacs-org-src-mode-map (kbd "bt")
'haris/org-src-tangle)
(define-key spacemacs-org-src-mode-map (kbd "bT")
'haris/org-src-tangle-dest)
(spacemacs/set-leader-keys-for-minor-mode 'org-mode "l" #'lsp-org)
(spacemacs/set-leader-keys-for-minor-mode 'org-src-mode "l" #'lsp)
(defun haris/execute-named-code-block ()
"Execute a named code block from the current buffer, interactively prompting
the user."
(interactive)
(save-excursion
(call-interactively 'org-babel-goto-named-src-block)
(org-babel-execute-src-block-maybe)))
(defun haris/org-src-tangle ()
"Tangle the target code block while inside the org-src buffer"
(interactive)
(org-edit-src-save)
(org-src-do-at-code-block
;; Prefix arg '(16) makes it tangle only the code blocks related to the same
;; file as the code block that is being edited in the current org-src buffer.
(org-babel-tangle '(16))))
(defun haris/org-src-tangle-dest ()
"Tangle the target code block while inside the org-src buffer"
(interactive)
(org-edit-src-save)
(org-src-do-at-code-block
;; Prefix arg '(16) makes it tangle only the code blocks related to the same
;; file as the code block that is being edited in the current org-src buffer.
(haris/tangle-dest '(16))))
(use-package git-gutter :defer t)
;; Loading it eagerly helps with proper initialization of hooks.
;; Magit is essential to my workflow so having it eagerly loaded is no tradeoff.
(use-package magit)
(setq magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1
magit-diff-refine-hunk 'all
magit-save-repository-buffers nil)
(setq magit-repository-directories
'(("~/.haris" . 0)
("~/proj" . 1)
("~/proj/drytoe" . 1)
("~" . 0)))
(setq magit-generate-buffer-name-function
(defun haris/magit-generate-buffer-name (mode &optional value)
"Simple wrapper around `magit-generate-buffer-name-default-function'
that always includes the full path to the repository."
(let ((magit-buffer-name-format
(string-replace "%t" (magit-toplevel)
magit-buffer-name-format)))
(magit-generate-buffer-name-default-function mode value))))
;; Performance improvements
(remove-hook 'server-switch-hook 'magit-commit-diff)
(remove-hook 'with-editor-filter-visit-hook 'magit-commit-diff)
(remove-hook 'magit-status-sections-hook 'magit-insert-unpushed-to-upstream-or-recent)
(define-derived-mode haris/git-commit-major-mode
text-mode
"Git commit major mode (Custom)"
"Custom major mode for editing git commit messages.")
(defun haris/git-commit/cd-to-worktree ()
"Make sure that the default-directory in git-commit-mode is the worktree."
(setq-local default-directory (magit-toplevel)))
(add-hook 'git-commit-mode-hook 'haris/git-commit/cd-to-worktree)
(setq git-commit-major-mode 'haris/git-commit-major-mode)
(defun haris/magit-fetch-to-local (remote branch args)
"Fetch a remote branch to a local branch of the same name"
(interactive
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-remote-branch "Fetch branch" remote)
(magit-fetch-arguments))))
(magit-git-fetch remote (cons (concat branch ":" branch) args)))
(add-hook 'magit-status-mode-hook
(lambda ()
(transient-append-suffix
'magit-fetch "o"
'(1 "O" "another, to local" haris/magit-fetch-to-local))))
(evil-define-key 'normal magit-section-mode-map (kbd "g h") #'magit-section-up)
(setq auth-sources '(password-store "~/.authinfo.dev.gpg" "~/.netrc.gpg"))
(with-eval-after-load 'chatgpt
(setq chatgpt-model "gpt-4-turbo-preview"))
(with-eval-after-load 'codegpt
(setq codegpt-model "gpt-3.5-turbo-instruct"))
(setq chatgpt-window-prompt "")
(setq chatgpt-display-method
(lambda (buffer-or-name)
(pop-to-buffer buffer-or-name
`((display-buffer-in-direction)
(dedicated . t)))
(spacemacs/toggle-maximize-buffer)
(chatgpt-type-response)))
(add-hook 'chatgpt-input-mode-hook
(defun haris//chatgpt-input-setup ()
(call-interactively 'evil-insert-state)))
;; ChatGPT mode
(define-key chatgpt-mode-map (kbd "q") 'quit-window)
(define-key chatgpt-mode-map (kbd "RET")
(defun haris//chatgpt-type-response ()
(interactive)
(let ((haris/chatgpt-session-buffer (current-buffer)))
(chatgpt-type-response))))
;; CodeGPT mode
(haris/evil-define-key-both codegpt-mode-map (kbd "q") 'quit-window)
;; ChatGPT input mode
(evil-define-key 'normal chatgpt-input-mode-map (kbd "q") 'kill-this-buffer)
(evil-define-key 'visual chatgpt-input-mode-map (kbd "q") 'kill-this-buffer)
(straight-use-package '(copilot
:type git
:host github
:repo "copilot-emacs/copilot.el"))
(setq copilot-indent-offset-warning-disable t)
(defvar haris/allow-copilot nil
"Whether the copilot-mode function should be allowed to run. This is a
security measure to prevent sharing of sensitive information in undesired
contexts.")
(add-hook 'after-change-major-mode-hook
(lambda ()
(when haris/allow-copilot
(copilot-mode))) 90)
(defun haris/allow-copilot (allow)
"Allow copilot if ALLOW, disallow otherwise"
(interactive (list (not haris/allow-copilot)))
(setq-local haris/allow-copilot allow)
(when (called-interactively-p 'any)
(message "%s copilot in the current buffer" (if allow
"Allowed"
"Disallowed")))
(unless allow
(copilot-mode -1)))
(with-eval-after-load 'copilot
(advice-add
'copilot-mode
:around
(defun haris//advice/copilot-mode (orig-func &rest args)
"Checks if 'haris/allow-copilot' is true before running ORIG-FUNC."
(let* ((arg (car args))
(enabling (if (eq arg 'toggle) (not copilot-mode)
(or
(not (numberp arg))
(not (< arg 0))))))
;; If interactive and copilot is disallowed, prompt the user to allow it
(if (and (not haris/allow-copilot)
(called-interactively-p 'any))
(when
(yes-or-no-p
"Copilot is disallowed. Do you want to allow it (buffer-locally)?")
(setq-local haris/allow-copilot t)))
;; If copilot is allowed and enable was requested, enable it
;; If disable is requested, disable it unconditionally
(if (or (not enabling)
haris/allow-copilot)
(progn
(apply orig-func args)
;; Notify the user if interactive
(when (called-interactively-p 'any)
(message "%s Copilot in current buffer"
(if enabling
"Enabled"
"Disabled"))))
(unless (called-interactively-p 'any)
(message
"Warning: Copilot is disallowed (haris/allow-copilot is nil in buffer %s)"
(buffer-name))))))))
(with-temp-buffer
(let ((haris/allow-copilot t))
;; Should enable copilot-mode successfully
(copilot-mode)
(cl-assert copilot-mode)
;; Should disable copilot-mode successfully
(setq haris/allow-copilot t)
(copilot-mode -1)
(cl-assert (not copilot-mode))
;; With copilot disallowed, attempt to enable should fail without error
(setq haris/allow-copilot nil)
(copilot-mode)
(cl-assert (not copilot-mode))))
(with-eval-after-load 'copilot
(define-key copilot-completion-map (kbd "<tab>") 'copilot-accept-completion)
(define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion)
(define-key copilot-completion-map (kbd "M-<backspace>") 'copilot-clear-overlay)
(define-key copilot-completion-map (kbd "M-l") 'copilot-accept-completion-by-word)
(define-key copilot-completion-map (kbd "M-j") 'copilot-accept-completion-by-line)
(define-key copilot-completion-map (kbd "M-n") 'copilot-next-completion)
(define-key copilot-completion-map (kbd "M-p") 'copilot-previous-completion)
(define-key copilot-completion-map (kbd "C-<tab>") 'copilot-panel-complete)
(spacemacs/set-leader-keys "oC" 'copilot-mode))
(add-hook 'comint-mode-hook #'smartparens-mode)
(add-hook 'vterm-mode-hook #'smartparens-mode)
(add-hook 'eshell-mode-hook #'smartparens-mode)
(defun comint-clear-buffer-goto () (interactive)
(comint-clear-buffer) (evil-goto-line))
(define-key comint-mode-map (kbd "C-l") 'comint-clear-buffer-goto)
(evil-define-key 'insert comint-mode-map (kbd "C-p") 'comint-previous-input)
(evil-define-key 'insert comint-mode-map (kbd "C-n") 'comint-next-input)
(evil-define-key 'insert comint-mode-map (kbd "C-k") 'comint-previous-prompt)
(evil-define-key 'insert comint-mode-map (kbd "C-j") 'comint-next-prompt)
(setq vterm-max-scrollback 10000)
(setq vterm-exit-functions 'delete-frame)
(defun haris/vterm-set-environment-windowid (&rest _)
(setq
vterm-environment
(list (format
"WINDOWID=%s"
(cdr (assoc 'window-id
(cadr (cadr
(current-frame-configuration)))))))))
;; Ideally, I would add haris/vterm-set-environment-windowid to a
;; vterm-before-shell-hook, if such a hook existed. But it doesn't, so...
(advice-add #'vterm :before #'haris/vterm-set-environment-windowid)
(advice-add #'multi-vterm :before #'haris/vterm-set-environment-windowid)
(advice-add #'spacemacs/shell-pop-vterm :before #'haris/vterm-set-environment-windowid)
(advice-add #'spacemacs/shell-pop-multivterm :before #'haris/vterm-set-environment-windowid)
(defalias 'haris/vterm-toggle-freeze 'vterm-copy-mode
"Freeze/unfreeze the output of the current vterm buffer")
Vterm doesn’t automatically re-wrap truncated lines when the terminal width increases. This seems to be a bug.
I wrap my shell in screen
as a workaround. NOTE: If I set vterm-shell
directly, it is overriden by the Spacemacs shell layer.
(setq shell-default-term-shell "screen fish")
I sometimes use Emacs as my terminal emulator. For that I need additional stability, so I run a separate daemon for that, which is unaffected by blockage and crashes of my default Emacs instance.
[Unit]
Description=Emacs daemon for Vterm
[Service]
Type=forking
Environment=COLORTERM=truecolor
ExecStart=/usr/bin/emacs --daemon="vterm"
Restart=always
TimeoutStartSec=600
TimeoutStopSec=30
StartLimitBurst=0
[Install]
WantedBy=default.target
(defun haris/vterm-clear-keep-scrollback ()
(interactive)
(let ((vterm-clear-scrollback-when-clearing nil))
(vterm-clear)))
(with-eval-after-load 'vterm
(haris/evil-define-key-both vterm-mode-map (kbd "C-l") #'haris/vterm-clear-keep-scrollback)
(evil-define-key 'normal vterm-mode-map (kbd "0") 'evil-collection-vterm-first-non-blank)
(evil-define-key 'normal vterm-mode-map (kbd "A") 'evil-append-line)
(evil-define-key 'normal vterm-mode-map (kbd "M-TAB") 'other-window)
(evil-define-key 'normal vterm-mode-map (kbd ",f") 'haris/vterm-toggle-freeze)
(evil-define-key 'normal vterm-copy-mode-map (kbd ",f") 'haris/vterm-toggle-freeze))
libvterm
git nodejs npm python-pyqt5 python-pyqt5-sip python-pyqtwebengine wmctrl python-pymupdf
python-epc
Note: Some variables are configured in Spacemacs layers.
(use-package company)
(use-package company-statistics)
(global-company-mode)
(setq tab-always-indent t)
(setq company-minimum-prefix-length 1)
(setq company-tooltip-align-annotations t)
(setq completion-ignore-case t
read-file-name-completion-ignore-case t
read-buffer-completion-ignore-case t)
(setq company-tempo-expand t)
;; This should ideally be 0, but a non-zero value might help with performance
(setq company-idle-delay 0.05)
(setq company-posframe-quickhelp-delay nil)
(setq company-show-quick-access 'left)
(add-hook 'company-mode-hook #'company-statistics-mode)
(setq company-statistics-file (concat spacemacs-cache-directory
"company-statistics-cache.el"))
;; Default backends for any new mode
(setq-default company-backends company-backends-text-mode)
;; Show popup even when the current text is the only candidate
(with-eval-after-load 'company
(setq company-frontends (delq 'company-pseudo-tooltip-unless-just-one-frontend company-frontends))
(add-to-list 'company-frontends 'company-pseudo-tooltip-frontend))
(setq company-dabbrev-char-regexp "\\sw\\(?:-\\sw\\)*")
(defun haris/company-disable-idle-popup ()
"Disable company idle tooltip"
(setq-local company-idle-delay nil))
(defun haris/company-enable-idle-popup ()
"Enable company idle tooltip"
(kill-local-variable 'company-idle-delay))
(defun haris/company-candidate-is-snippet-p (candidate)
(equal (get-text-property 0 'company-backend candidate)
'company-yasnippet))
(defun haris/company-sort-snippets-first (candidates)
"Sort candidates using backend company-yasnippet before other ones."
(sort candidates
(lambda (c1 c2)
(and
(haris/company-candidate-is-snippet-p c1)
(not
(haris/company-candidate-is-snippet-p c2))))))
(defun haris/company-sort-prefix-first (candidates)
"Sort CANDIDATES so that those starting with the typed prefix come first."
(let ((prefix (company-grab-symbol)))
(if prefix
(let ((prefix-regex (concat "^" (regexp-quote prefix))))
(sort candidates
(lambda (c1 c2)
(and (string-match prefix-regex c1)
(not (string-match prefix-regex c2))))))
candidates)))
(defun haris/company-toggle-tooltip ()
(interactive)
(cond
;; Tried company-tooltip-visible-p instead, but it doesn't work
((company--active-p) (company-cancel) t)
((company-manual-begin))))
(defun haris/company-sort-by-statistics-preserve-snippet-order (candidates)
"Wraps company-sort-by-statistics but preserves the relative order of snippet completions."
(let* ((old-score-calc company-statistics-score-calc)
(company-statistics-score-calc
(lambda (candidate)
(if (haris/company-candidate-is-snippet-p candidate)
99999999
(funcall old-score-calc candidate)))))
(company-sort-by-statistics candidates)))
(defun haris/init-company-backends-for-mode (mode company-backends)
"Add a hook for MODE which initializes company backends for it.
The backends are taken from the variable haris/company-backends-MODE."
(let ((mode-hook-symbol (intern (format "%s-hook" mode)))
(init-func-symbol (intern
(format "haris/init-company-backends-for-%s" mode)))
(backends-var-symbol (intern (format "haris/company-backends-%s" mode))))
(eval `(setq ,backends-var-symbol company-backends))
(defalias init-func-symbol
`(lambda ()
(setq-local company-backends ,backends-var-symbol))
(format "Initialize company-backends for %s" mode))
(add-hook mode-hook-symbol init-func-symbol 99)))
Most of these have been createdu using the default ones set up by Spacemacs auto-completion as the starting value.
(setq haris/company-backends-default
'((company-files :with company-yasnippet)
(company-capf :with company-yasnippet)
(company-semantic company-dabbrev-code company-keywords company-ispell :with company-yasnippet)
(company-dabbrev :with company-yasnippet)))
(haris/init-company-backends-for-mode
'sh-mode
'(
(company-files :with company-yasnippet)
(;; This will only take effect in fish mode
company-fish-shell
company-shell-env
company-shell
company-capf
:with company-yasnippet)
(company-semantic
company-dabbrev-code
company-keywords
company-ispell
:with company-yasnippet)
(company-dabbrev :with company-yasnippet)))
(remove-hook 'org-mode-hook #'spacemacs//init-company-backends-org-mode)
(haris/init-company-backends-for-mode
'org-mode
'((company-files :with company-yasnippet)
(company-capf :with company-yasnippet)
(company-org-block
company-semantic
company-dabbrev-code
company-keywords
company-ispell
:with company-yasnippet)
(company-dabbrev :with company-yasnippet)))
(haris/init-company-backends-for-mode
'nginx-mode
'((company-files :with company-yasnippet)
(company-nginx
company-semantic
company-dabbrev-code
company-keywords
company-ispell
:with company-yasnippet)
(company-dabbrev :with company-yasnippet)))
(haris/init-company-backends-for-mode 'fundamental-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'text-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'yaml-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'yaml-ts-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'json-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'json-ts-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'java-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'java-ts-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'conf-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'python-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'inferior-python-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'sql-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'systemd-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'haris/git-commit-major-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'typescript-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'emacs-lisp-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'lisp-data-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'dockerfile-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'docker-compose-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'markdown-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'minibuffer-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'web-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'sgml-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'nxml-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'chatgpt-input-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'sql-interactive-mode haris/company-backends-default)
(haris/init-company-backends-for-mode 'nodejs-repl-mode haris/company-backends-default)
(haris/init-company-backends-for-mode
'sql-interactive-mode
'((company-files :with company-yasnippet)
;; TODO: company-edbi seems to be useless
(company-edbi
company-semantic
company-dabbrev-code
company-keywords
company-ispell
:with company-yasnippet)
(company-dabbrev :with company-yasnippet)))
This variable is initialized too late, and we require it here. I wasn’t able to identify where exactly it is initialized by spacemacs, so I set it explicitly here.
(setq company-backends-emacs-lisp-mode haris/company-backends-default)
(setq company-transformers
'(haris/company-sort-by-statistics-preserve-snippet-order
company-sort-by-backend-importance
haris/company-sort-snippets-first
haris/company-sort-prefix-first))
(add-hook
'company-statistics-mode-hook
(lambda ()
"Remove company-sort-by-statistics element that was added automatically by company-statistics"
(setq company-transformers (delq 'company-sort-by-statistics company-transformers))))
(setq completion-styles '(basic partial-completion emacs22 initials substring flex))
(with-eval-after-load 'company-posframe
(define-key company-posframe-active-map (kbd "<f1>") nil)
(define-key company-posframe-active-map (kbd "M-h")
#'company-posframe-quickhelp-toggle))
;; Additional trigger for company
(define-key evil-insert-state-map (kbd "C-SPC") #'haris/company-toggle-tooltip)
The following overrides the escape key for company-active-map
in eval-expression
buffers. I know of no other way to make this binding take precedence over the
other maps, so I define it here.
; (ref:company-active-map-escape-override)
(define-key
company-active-map
(kbd "<escape>")
(defun haris/company-active-mode-escape-override ()
"Override the <escape> key to close the company menu instead of exiting
to normal mode in minibuffers. This prevents accidental closing of the
minibuffer."
(interactive)
(if (eq major-mode 'minibuffer-mode)
(haris/company-toggle-tooltip)
(evil-force-normal-state))))
Disabled posframe in Spacemacs Layers.
(use-package yasnippet)
(yas-reload-all)
(setq yas-alias-to-yas/prefix-p nil)
(add-hook 'git-commit-mode-hook
(defun haris/git-commit-init-yasnippet ()
(haris/yas-minor-mode-on)
(yas-activate-extra-mode 'git-commit-mode))
90)
(add-hook 'org-src-mode-hook
(defun haris/org-src-init-yasnippet ()
(haris/yas-minor-mode-on)
(yas-activate-extra-mode 'org-src-mode))
90)
(add-hook 'verb-mode-hook (lambda () (yas-activate-extra-mode 'verb-mode)))
(advice-add 'yas-tryout-snippet :after #'evil-insert-state)
;; Remove default bindings for next/prev field
(define-key yas-keymap (kbd "<tab>") nil)
(define-key yas-keymap (kbd "TAB") nil)
(define-key yas-keymap (kbd "<backtab>") nil)
(define-key yas-keymap (kbd "S-<tab>") nil)
(define-key yas-keymap (kbd "M-n") #'yas-next-field)
(define-key yas-keymap (kbd "M-p") #'yas-prev-field)
The TAB key is much abused. Keeping track of it in each minor mode separately is a nightmare. Therefore I unbind TAB in some keymaps so that it falls back to the TAB binding in the global keymap. This way, I can define the exact order of precedence of TAB-bound commands for all modes.
(defun haris/handle-TAB-in-insert-mode (&optional alert)
"Handle the TAB key exactly the way I want to."
(interactive)
(setq alert command-log-mode)
(let ((last-fun nil))
(defun _ (fun &rest rest)
"Call the function normally, and record it to the last-fun variable."
(setq last-fun (append (list fun) rest))
(apply fun rest))
(cond
;; If in an org table, try org-cycle
((when (and (fboundp 'org-at-table-p) (org-at-table-p)) (or (_ 'org-cycle) t)))
;; If company-mode is on, complete
((when (and company-mode (company-tooltip-visible-p)) (_ 'company-complete-selection)))
;; If org-mode is on, try org-cycle
((when (eq major-mode 'org-mode)
(cond ((_ 'org-cycle))
;; If there's nothing to cycle, go to next link
((_ 'org-next-link)))))
;; Default: indent
((when (eq major-mode 'minibuffer-mode) (_ 'minibuffer-complete)))
((_ 'indent-for-tab-command)))
(when alert
(alert (format "%s" last-fun)
:title "Command bound to pressed key:"))))
(define-key evil-insert-state-map (kbd "TAB") #'haris/handle-TAB-in-insert-mode)
;; Add ivy-partial-or-done to ivy-minibuffer-map in insert mode so as to not be
;; overridden by the above.
(evil-define-key 'insert ivy-minibuffer-map (kbd "TAB") #'ivy-partial-or-done)
Remove Info mode annoying keybindings.
(evil-define-key 'normal Info-mode-map (kbd "[") 'Info-prev)
(evil-define-key 'normal Info-mode-map (kbd "]") 'Info-next)
(evil-define-key 'normal Info-mode-map (kbd "C-p") 'Info-backward-node)
(evil-define-key 'normal Info-mode-map (kbd "C-n") 'Info-forward-node)
(setq ispell-program-name "aspell")
(use-package accent)
(setq accent-diacritics
'((a (á à â ä æ ã å ā))
(c (č ć ç))
(d (ď đ))
(e (é ě è ê ë ē ė ę))
(i (í î ï ī į ì))
(l (ł))
(n (ň ñ ń))
(o (ô ö ò ó œ ø ō õ))
(r (ř))
(s (š ß ś))
(t (ť))
(u (ů ú û ü ù ū))
(y (ý ÿ))
(z (ž ź ż))
;; Capital
(A (Á À Â Ä Æ Ã Å Ā))
(C (Č Ć Ç))
(D (Ď Đ))
(E (É Ě È Ê Ë Ē Ė Ę))
(I (Í Î Ï Ī Į Ì))
(L (Ł))
(N (Ň Ñ Ń))
(O (Ô Ö Ò Ó Œ Ø Ō Õ))
(R (Ř))
(S (Š Ś))
(T (Ť))
(U (Ů Ú Û Ü Ù Ū))
(Y (Ý Ÿ))
(Z (Ž Ź Ż))
;; Non-alphabetic
(< («))
(> (»))))
(define-key evil-insert-state-map (kbd "M-S-<return>") 'accent-menu)
(define-key evil-normal-state-map (kbd "M-S-<return>")
(defun haris/accent-menu-normal-mode ()
(interactive)
(let ((accent-position 'after))
(accent-menu))))
(define-key ibuffer-mode-map (kbd "j") 'evil-next-line)
(define-key ibuffer-mode-map (kbd "k") 'evil-previous-line)
(use-package currency-convert
:defer t
:init (lambda () (setq
currency-convert-exchangeratesapi-key
(string-trim (shell-command-to-string "pass show @apilayer/api-key")))))
(setq command-log-mode-auto-show nil)
;; TODO: sometimes you have to call this twice for the command-log buffer to appear
(defun haris/command-log ()
(interactive)
(let ((command-log-mode-auto-show t))
(call-interactively #'command-log-mode)))
(add-hook 'octave-mode-hook
(lambda ()
(setq comment-start "% "
comment-end "")))
(define-key evil-normal-state-map (kbd "SPC c c") 'evilnc-copy-and-comment-lines)
(setq alert-default-style 'libnotify)
(use-package daemons)
(evil-define-key 'normal daemons-mode-map (kbd "u") #'daemons-systemd-toggle-user)
(use-package company-nginx)
(add-hook 'helpful-mode-hook
(lambda ()
(setq evil-lookup-func 'helpful-at-point)))
(use-package atomic-chrome)
(setq atomic-chrome-buffer-open-style 'frame)
(atomic-chrome-start-server)
(add-hook 'gitconfig-mode-hook
(lambda () (setq-local tab-width 2)))
(evil-collection-init 'bookmark)
(straight-use-package '(nmcli-wifi :type git :host github :repo "luckysori/nmcli-wifi"))
(evil-define-key 'normal nmcli-wifi-mode-map (kbd "c") 'nmcli-wifi-connect)
(evil-define-key 'normal nmcli-wifi-mode-map (kbd "r") 'nmcli-wifi-refresh)
(with-eval-after-load 'transient
(setq transient-values-file "~/.emacs.d/transient/values.el"))
(use-package tldr :defer t)
(use-package devdocs-browser)
(with-eval-after-load 'devdocs-browser
(setq devdocs-browser-major-mode-docs-alist
(append
devdocs-browser-major-mode-docs-alist
'((typescript-mode "TypeScript")
(javascript-mode "JavaScript")
(java-mode "OpenJDK")
(org-mode "elisp")))))
(spacemacs/set-leader-keys "hbd" 'devdocs-browser-open)
This prevents Spacemacs from asking me to install missing layers. This forces me to put layers directly in dotspacemacs-configuration-layers, and keeps me from forgetting to put the layers in the versioned config.
(setq dotspacemacs-ask-for-lazy-installation nil)
Spacemacs provides the spacemacs/ediff-dotfile-and-template
function (bound to
SPC f e D
) for updating the dotfile at ~/.spacemacs when Spacemacs is updated.
But the dotfile also contains content dynamically inserted by Emacs, which I
don’t want to version. That’s why I keep the crux of my configuration in a
separate file: ~/.haris/.spacemacs. This file is loaded from ~/.spacemacs, and
should contain the following (along with any content dynamically inserted by
Emacs):
(load-file "~/.haris/.spacemacs")
;; Do not write anything past this comment. This is where Emacs will
;; auto-generate custom variable definitions.
In order to have the spacemacs/ediff-dotfile-and-template
function use my custom
file, I advise it with a custom function:
(advice-add
'spacemacs/ediff-dotfile-and-template
:around
(defun haris//advice/ediff-dotfile-and-template (oldfun &rest ignored)
"Use a different file as the diff target instead of ~/.spacemacs."
(let ((dotspacemacs-filepath (expand-file-name "~/.haris/.spacemacs")))
(call-interactively oldfun))))
The remaining code blocks in this section are tangled to ~/.spacemacs-init.el. This file is in turn loaded from ~/.spacemacs.
(setq-default
dotspacemacs-configuration-layers ; (ref:dotspacemacs-configuration-layers)
'(syntax-checking
multiple-cursors
octave
markdown
html
spacemacs-language
spacemacs-navigation
spacemacs-project
(spacemacs-editing
:variables
vim-style-enable-undo-region t)
spacemacs-editing-visual
spacemacs-org
search-engine
spell-checking
major-modes
helpful
ivy
imenu-list
(lsp
:variables
lsp-headerline-breadcrumb-enable t
lsp-ui-sideline-show-symbol t
lsp-lens-enable t)
(c-c++ :variables c-c++-backend 'lsp-clangd c-c++-enable-clang-support t)
(cmake :variables cmake-backend 'lsp cmake-enable-cmake-ide-support t)
(python :variables python-formatter 'black python-backend 'lsp)
dap
vagrant
;; elpy
;; pythonp
ipython-notebook
emacs-lisp
shell
(shell-scripts :variables shell-scripts-backend 'lsp)
javascript
typescript
java
kotlin
go
prettier
vue
(yaml :variables yaml-enable-lsp t)
csv
rust
(docker :variables docker-dockerfile-backend 'lsp)
vagrant
translate
git
lua
(org :variables
org-enable-appear-support t
org-enable-transclusion-support t
org-enable-verb-support t)
restclient
slack
;; mu4e
pass
sql
nginx
systemd
tmux
;; eaf
;; emms
debug
(auto-completion ; (ref:auto-completion)
:variables
auto-completion-enable-snippets-in-popup t
auto-completion-use-company-posframe nil ; (ref:disable-posframe)
auto-completion-return-key-behavior nil
auto-completion-tab-key-behavior 'complete
auto-completion-enable-help-tooltip 'manual
auto-completion-enable-sort-by-usage nil ;; I configure this manually
)
openai))
Packages installed with use-package
should be added here as well. Otherwise
Spacemacs would delete them every time on startup.
(setq-default
dotspacemacs-additional-packages
'(
org-fragtog
org-drill
org-ref
org-attach-screenshot
org-special-blocks
ob-ipython
yasnippet-snippets
vterm
rainbow-mode
evil-easymotion
reddigg
md4rd
pydoc
pylint
python-info
nodejs-repl
command-log-mode
org-preview-html
vimrc-mode
systemd
evil-quickscope
edbi
counsel-jq
sxhkdrc-mode
bluetooth
git-gutter
json-mode
fish-mode
currency-convert
i3wm-config-mode
docker-compose-mode
react
focus-autosave-mode
xclip
accent
sqlite3
company-shell
company-statistics))
When upgrading Spacemacs, run spacemacs/ediff-dotfile-and-template to merge any upstream changes to the dotfile.
(condition-case err
(setq openai-key
(replace-regexp-in-string
"\n$" ""
(with-temp-buffer
(insert-file-contents "~/.local/share/haris/openai-api-key.txt")
(buffer-string))))
(error (message "WARNING: %s" err)))
I use this variable to check if the config loaded correctly.
(setq haris/config-loaded-fine (current-time-string))
(when (daemonp)
(when (not (boundp 'haris//daemon-ready-notified))
(alert (format "Daemon '%s' ready" server-name)
:title "Emacs Daemon")
(setq haris//daemon-ready-notified t)))
The private config is loaded from ~/.emacs.d/haris/private.el.
(let ((file "~/.emacs.d/haris/private.el"))
(when (file-exists-p file) (load-file file)))