;; -*- lexical-binding: t -*-
All my private functions have the prefix +my
.
If I extend a module I will prefix it with the +module
.
Furthermore the naming schema follows these rules:
+my:variable
: Variable+my/function
: A private function+my|function
: An interactive function
Most of the code here will follow a functional mindset using dash.el and s.el. I will prefer those functions over built in functions if they are nicer to use.
I often use my helper function (template) for template strings.
;; -*- no-byte-compile: t; -*-
Since flet
was deprecated, I’m using this for now.
Pretty much only used in Expand snippet by name.
(package! noflet)
(package! udiskie :recipe (:host gitlab :repo "floscr/udiskie.el"))
Manage systemd from emacs
(package! daemons)
Evil Plugin provides some nice addons for Evil Mode
(package! evil-plugin :recipe (:host github :repo "tarao/evil-plugins"))
(package! evil-replace-with-register)
(package! evil-text-objects-javascript :recipe (:host github :repo "urbint/evil-text-objects-javascript"))
(package! narrow-indirect :recipe (:host github :repo "emacsmirror/narrow-indirect"))
(package! rainbow-mode)
(package! visual-fill-column)
Move to ivy candidate with C-'
(package! ivy-avy)
(package! doom-themes
:recipe (:host github :repo "floscr/emacs-doom-themes" :files ("*.el" "themes/*.el"))
:pin nil)
(package! atomic-chrome)
(package! dired-narrow)
(package! dired-filter)
(package! dired-subtree)
(package! edbi)
(package! cheat-sh)
(package! proced-narrow)
(package! transmission)
Symex for editing emacs lisp by structure.
(package! symex)
(package! beancount :recipe (:host github :repo "cnsunyour/beancount.el"))
(package! indium)
(package! impatient-mode)
(package! eslintd-fix)
(package! js-import :recipe (:host github :repo "floscr/js-import"))
(package! jest :recipe (:host github :repo "floscr/emacs-jest"))
(package! graphql)
Show changes in current branch
(package! git-lens)
Quickly download and browser repositories from github
(package! elescope)
(package! nov)
Inline Calculation
(package! literate-calc-mode)
(package! doom-snippets :ignore t)
(package! my-doom-snippets
:recipe (:host github
:repo "floscr/doom-snippets"
:files ("*.el" "*")))
Continuous events are broken in the current calfw source, also it seems it isn’t maintained anymore. This Commit fixes the behavior.
(package! calfw :recipe (:host github :repo "floscr/emacs-calfw") :pin "e3d04c253230ed0692f161f527d4e42686060f62")
(package! calfw-org :recipe (:host github :repo "floscr/emacs-calfw") :pin "e3d04c253230ed0692f161f527d4e42686060f62")
(package! calfw-ical :pin "e3d04c253230ed0692f161f527d4e42686060f62")
(package! calfw-cal :disable t)
(package! org-gcal :disable t)
(package! lsp-ui :disable t)
(package! merlin-eldoc :disable t)
Config files that I don’t want to share with the world. They will be stored in here:
(defvar +my:secrets-config-file nil
"My private config file.")
(setq +my:secrets-config-file "~/.config/secrets.el")
And I will load them on system start:
(defun +my/load-secrets-config ()
(-some->> +my:secrets-config-file
(-id-when 'file-exists-p)
(load-library)))
(+my/load-secrets-config)
Set it to 32 MiB
.
(setq doom-gc-cons-threshold 33554432)
(setq +lookup-provider-url-alist
'(("DuckDuckGo" "https://duckduckgo.com/?q=%s")
("DuckDuckGo Lucky" "https://duckduckgo.com/?q=\\%s")
("Github Code" "https://github.com/search?search&q=%s&type=Code")
("Code grepp.app" "https://grep.app/search?q=%S")
("Google" "https://google.com/search?q=%s")
("Google images" "https://google.com/images?q=%s")
("Google maps" "https://maps.google.com/maps?q=%s")
("NPM" "https://npmjs.com/search?q=%s")
("Hoogle" "https://www.haskell.org/hoogle/?hoogle=%s")
("Project Gutenberg" "http://www.gutenberg.org/ebooks/search/?query=%s")
("Explain Shell" "https://explainshell.com/explain?cmd=%s")
("StackOverflow" "https://stackoverflow.com/search?q=%s")
("Github" "https://github.com/search?ref=simplesearch&q=%s")
("Youtube" "https://youtube.com/results?aq=f&oq=&search_query=%s")
("Wolfram alpha" "https://wolframalpha.com/input/?i=%s")
("Wikipedia" "https://wikipedia.org/search-redirect.php?language=en&go=Go&search=%s")))
Whether evil actions like cw
are undone in several steps.
This is sometimes annoying, as it might need you to press u
multiple times.
But I prefer the fine grained control, as I’m often staying longer in insert mode,
and don’t want one single undo action for the whole “session”.
(setq evil-want-fine-undo t)
(setq
trash-directory "~/.Trash/"
delete-by-moving-to-trash t)
(setq tags-revert-without-query 1)
Variables that I want to safely set from .dir-locals
files.
(put '+file-templates-dir 'safe-local-variable #'stringp)
(defcustom downloads-dir "~/Downloads/"
"Downloads Directory"
:type 'string)
(defcustom screenshot-dir "~/Pictures/Screenshots"
"Screenshots Directory"
:type 'string)
Never blink the cursor, it’s too distracting.
(blink-cursor-mode -1)
(setq blink-matching-paren nil)
(setq visible-cursor nil)
(setq uniquify-buffer-name-style 'forward)
(setq bookmark-save-flag nil)
;; Can't set to nil as bookmarks are still set up to hooks
;; Instead I'll keep it in this file which will be trashed on every reboot
(setq bookmark-file "/tmp/emacs-bookmarks-file")
Why does this not work out of the box?
(pcase (getenv "BROWSER")
("chromium" (setq browse-url-browser-function 'browse-url-chromium))
("firefox" (setq browse-url-browser-function 'browse-url-firefox)))
(add-to-list 'auth-sources "~/.config/gnupg/.authinfo.gpg")
Fix a paragraph that was formatted to a fill column.
(defun +my|unfill-paragraph ()
"Fix a paragraph that was formatted to a fill column."
(interactive)
(let ((fill-column (point-max)))
(fill-paragraph nil)))
Take me to the literate source file when using find-function
etc.
(defadvice! +org|try-org-babel-tangle-jump-to-org (&optional arg1)
:after '(find-function
find-variable)
(ignore-errors
(org-babel-tangle-jump-to-org)))
Change and reset line-spacing for all buffers.
(defvar +ui-default-line-spacing line-spacing)
(defvar +ui-default-line-spacing-increment 1)
(defvar +ui-big-line-spacing-increment 10)
(defun +ui/set-line-spacing (&optional increment)
"Set the line spacing
When no line spacing is given is the default-line-spacing"
(setq-default line-spacing (+ (or increment +ui-default-line-spacing-increment) line-spacing)))
(defun +ui|reset-line-spacing ()
(interactive)
(setq-default line-spacing +ui-default-line-spacing))
(defun +ui|increase-line-spacing ()
(interactive)
(+ui/set-line-spacing))
(defun +ui|decrease-line-spacing ()
(interactive)
(+ui/set-line-spacing (- +ui-default-line-spacing-increment)))
(defun +ui|increase-line-spacing-big ()
(interactive)
(+ui/set-line-spacing +ui-big-line-spacing-increment))
(defun +ui|decrease-line-spacing-big ()
(interactive)
(+ui/set-line-spacing (- +ui-big-line-spacing-increment)))
(evil-define-key 'normal 'global (kbd "]z") #'+line-spacing/step/body)
;;;###autoload (autoload '+common-lisp/macrostep/body "lang/common-lisp/autoload/hydras" nil nil)
(defhydra +line-spacing/step (:exit nil :hint nil :foreign-keys run :color pink)
"
Macro Expansion
^^Definitions ^^Compiler Notes ^^Stickers
^^^^^^─────────────────────────────────────────────────────────────────────────────────────
[_r_] Reset
[_]_] Expand
[_[_] Collapse
[_}_] Expand Big
[_{_] Collapse Big
"
("r" +ui|reset-line-spacing)
("]" +ui|increase-line-spacing)
("[" +ui|decrease-line-spacing)
("}" +ui|increase-line-spacing-big)
("{" +ui|decrease-line-spacing-big)
("q" nil "cancel" :color blue))
Expand visual region using a hydra.
Double press v
to enable.
(defhydra hydra-expand-region ()
"region: "
("f" er/mark-defun "defun")
("v" er/expand-region "expand")
("V" er/contract-region "contract"))
(evil-define-key 'visual 'global (kbd "v") #'hydra-expand-region/body)
Edit registers with +evil-edit-register|counsel
.
Mostly used to edit the macro registers q
.
(defvar +evil-edit-register:register "")
(defvar +evil-edit-register-mode-map (make-sparse-keymap))
(define-minor-mode +evil-edit-register-mode
"Edit evil register and save it back to the register."
:keymap +evil-edit-register-mode-map)
(map! :map +evil-edit-register-mode-map
"C-c C-c" #'+evil-edit-register|save-and-exit
"C-c C-k" #'kill-buffer-and-window)
(defun +evil-edit-register|save-and-exit (&optional arg)
"Save the buffer content back to the register register"
(interactive)
(evil-set-register
(string-to-char +evil-edit-register:register)
(buffer-substring-no-properties (point-min) (point-max)))
(kill-buffer-and-window))
(defun +evil-edit-register|counsel (register-string)
"Edit evil register in register"
(require 'noflet)
(interactive (noflet ((counsel-evil-registers-action (x) x))
(list (counsel-evil-registers))))
(-when-let* ((register-string (substring-no-properties register-string))
(buffer (generate-new-buffer (t! "*Evil Register Edit: <<register-string>>*")))
((_ reg register) (s-match "^\\[\\(.\\)\\]: \\(.*\\)$" register-string)))
(pop-to-buffer buffer)
(with-current-buffer buffer
(+evil-edit-register-mode 1)
(setq-local +evil-edit-register:register reg)
(setq header-line-format "Edit, then exit with 'C-c C-c', abort with 'C-c C-k'.")
(save-excursion
(insert register)))))
(setq +zen-text-scale 1.5)
(let ((height 140)
(size 23))
(setq doom-variable-pitch-font (font-spec :family "IBM Plex Sans" :size size)
doom-serif-font (font-spec :family "IBM Plex Sans" :size size)))
(add-hook! '(window-setup-hook after-make-frame-functions)
(defun +ui/init-frame-ui (&optional frame)
"Re-enable menu-bar-lines in GUI frames."
(when-let (frame (or frame (selected-frame)))
(when (display-graphic-p frame)
(set-frame-parameter frame 'internal-border-width 15)))))
Resize the window font size etc according to the system. This will be disabled in terminal mode.
(when (display-graphic-p) (+ui|adjust-ui-to-display))
Toggle between a light and a dark theme.
Bound to SPC t t
.
(defconst light-theme 'doom-one)
(defconst dark-theme 'doom-one-light)
(defun +doom|toggle-theme ()
"Toggle between light and dark themes."
(interactive)
(cond ((eq doom-theme dark-theme)
(message "Toggling to light-theme: %s" light-theme)
(setq doom-theme light-theme)
(doom/reload-theme))
((eq doom-theme light-theme)
(message "Toggling to dark-theme: %s" dark-theme)
(setq doom-theme dark-theme)
(doom/reload-theme))
(t (message "Toggling theme is not possible. Theme is not currently light-theme (%s) or dark-theme (%s)." light-theme dark-theme))))
(add-hook 'doom-load-theme-hook #'*doom-themes-custom-set-faces)
(defun *doom-themes-custom-set-faces ()
(set-face-attribute 'fringe nil
:foreground (face-background 'default)
:background (face-background 'default))
(custom-set-faces!
Remove the rainbow colors from dired.
'(diredfl-read-priv :foreground "#80899E")
'(diredfl-write-priv :foreground "#80899E")
'(diredfl-exec-priv :foreground "#80899E")
'(diredfl-other-priv :foreground "#80899E")
'(all-the-icons-dired-dir-face :foreground "#80899E")
'(diredfl-dir-priv :foreground "#282C34")
'(diredfl-k-modified :foreground "#FF8E90")
'(diredfl-number :foreground "#80899E")
'(diredfl-date-time :foreground "#49505F")
`(diredfl-dir-name :foreground "#2DADF2")
Switch the highlight.
'(mu4e-highlight-face :inherit mu4e-unread-face)
Remove the ugly grey background
'(org-column :background nil)
))
(add-hook! '(diff-hl-margin-minor-mode-hook)
(progn
(set-face-attribute 'smerge-refined-added nil
:foreground (doom-blend "#98be65" "#3e493d" 0.15)
:background (doom-lighten "#98bb5d" 0.2))
(set-face-attribute 'smerge-refined-removed nil
:foreground (doom-blend "#ff6c6b" "#4f343a" 0.2)
:background (doom-lighten "#ff6c6b" 0.1))))
(add-hook! '(magit-status-mode-hook magit-diff-mode-hook)
(progn
(set-face-attribute 'diff-refine-added nil
:foreground (doom-blend "#98be65" "#3e493d" 0.15)
:background (doom-lighten "#98bb5d" 0.2))
(set-face-attribute 'diff-refine-removed nil
:foreground (doom-blend "#ff6c6b" "#4f343a" 0.2)
:background (doom-lighten "#ff6c6b" 0.1))))
Start scrolling X lines before the end of a screen. Disable for certain modes (terminal & ivy) where the window is to small.
(setq scroll-conservatively 10)
(setq scroll-margin 10)
(add-hook 'term-mode-hook (cmd! (setq-local scroll-margin 0)))
(add-hook 'ivy-mode-hook (cmd! (setq-local scroll-margin 0)))
(set-popup-rules!
'(("^\\*Org Agenda" :side right :size 0.55 :select t :modeline t :ttl nil :quit nil)
("^\\*Org Src" :ignore t)
("^\\*Org QL View: \\(Work \\)?Projects*" :side right :size 0.55 :select t :modeline t :ttl nil :quit nil)
("^\\*PDF-Occur*" :side right :size 0.5 :select t :modeline t)
("^\\*WoMan " :side right :size 0.5 :select t :modeline t :ttl nil :quit nil)
("^\\*helm" :vslot -100 :size 0.32 :ttl nil)
("^\\*helpful command" :side right :size 0.5 :select t :modeline t :ttl nil :quit nil)
("^\\*nodejs" :side right :size 0.55 :select t :modeline t :ttl nil :quit nil)
("^\\*jest" :select nil)
("^\\*projectile-files-errors" :ignore t)
("^\\*elfeed-entry" :modeline t :ttl nil)))
(setq-default fill-column 110)
(setq fill-column 110)
(setq visual-fill-column-width fill-column)
(setq visual-fill-column-center-text t
visual-fill-column-width fill-column)
(setq-hook! 'prog-mode-hook show-trailing-whitespace nil)
Draw the underline at the bottom of the text, not at the end of line-spacing.
(setq x-underline-at-descent-line nil)
A navigation system for lisp languages that I’m comfortable with,
like evil but with a more sexp
based navigation.
Alternatives I’ve used:
- countvajhula/symex.el My main inspiration. Too tree based for my taste, I after have to think about the code structure before I can execute actions.
(set-frame-parameter nil 'internal-border-width 15)
(defvar +ly-mode-map (make-sparse-keymap))
(define-minor-mode +ly-mode
"Open org headlines in an indirect window buffer."
:keymap +ly-mode-map)
(map! :map +ly-mode-map
:n "gj" #'evil-next-line
:n "gk" #'evil-previous-line
:n "M-j" #'+ly|forward-line
:n "M-k" #'+ly|backward-line
:n "M-u" #'+ly|up-atom
:n "M-f" #'+ly|forward-atom
:n "M-b" #'+ly|backward-atom
:n "M-l" #'+ly|forward-brace
:n "M-h" #'+ly|backward-brace
:n "M-," #' symex-shift-backward
:n "M-." #'symex-shift-forward
:n "M-o" #'symex-open-line-after
:ni "<C-return>" #'+ly|add-atom
:n "M-RET" (cmd!
(parinfer-toggle-mode)
(parinfer-lispy:newline)
(parinfer-toggle-mode))
:n "M-Y" (cmd!
(symex-yank)
(symex-paste-after)
(symex-insert-newline)))
;; HACK: map not working intially
(defun +ly/activate-mode-hook ()
(require 'symex)
(evil-insert-state)
(evil-normal-state))
;; Mappings only get applied after reinserting normal mode
(add-hook '+ly-mode-hook #'+ly/activate-mode-hook)
(setq +ly:brace-regexp "([^() ]")
(setq +ly:atom-regexp "[^() ]")
(defun +ly|forward-atom ()
"Go to next atom."
(interactive)
(require 'symex)
(symex-traverse-forward)
(while (or (eq ?\( (char-after)))
(symex-traverse-forward)))
(defun +ly|backward-atom ()
"Go to next atom."
(interactive)
(require 'symex)
(symex-traverse-backward)
(while (or (eq ?\( (char-after)))
(symex-traverse-backward)))
(defun +ly|forward-brace ()
"Go to next sexp brace."
(interactive)
(require 'symex)
(symex-go-up)
(search-forward-regexp +ly:brace-regexp nil t)
(backward-char 2))
(defun +ly|backward-brace ()
"Go to previous sexp brace."
(interactive)
(require 'symex)
(unless (eq ?\( (char-after))
(symex-go-down))
(search-backward-regexp +ly:brace-regexp nil t))
(defun +ly|up-atom ()
"Go up the tree"
(interactive)
(require 'symex)
(symex-go-down))
(defun +ly|forward-line ()
"Function docstring"
(interactive)
(next-line 1)
(goto-char (point-at-bol))
(search-forward-regexp "\s*[^\s]" nil t)
(backward-char 1))
(defun +ly|backward-line ()
"Function docstring"
(interactive)
(previous-line 1)
(goto-char (point-at-bol))
(search-forward-regexp "\s*[^\s]" nil t)
(backward-char 1))
(defun +ly/inside-string? ()
"Returns non-nil if inside string, else nil.
Result depends on syntax table's string quote character."
(nth 3 (syntax-ppss)))
(defun +ly|add-atom (&optional same-line?)
"Add atom in the current sexp. If the cursor is under a string match the type and insert another string."
(interactive "P")
(let ((was-parinfer-list-mode (eq 'paren parinfer--mode)))
(up-list 1 t)
(unless same-line?
(parinfer-lispy:newline))
(when same-line? (insert " "))
(cond ((+ly/inside-string?)
(insert (t! "\"\""))
(backward-char 1)))
(evil-insert-state)))
(defhydra +ly|hydra ()
"Ly Navigation"
("f" (+ly|forward-brace) "Forward ()" :column "Navigate")
("b" (+ly|backward-brace) "Backward ()")
("f" (+ly|forward-brace) "forward ()" :column "Navigate")
("b" (+ly|backward-brace) "Backward ()")
("j" (+ly|forward-line) "Next Line")
("k" (+ly|backward-line) "Backward Line"))
Adds minor mode for editing indented source code in an indirect buffer, with the indentation reset to 0. Saving and committing keeps the indentation in the source buffer.
(defvar-local +indirect-indent 0)
(defvar +indirect-indent-mode-map (make-sparse-keymap))
(define-minor-mode +indirect-indent-mode
"Editing indented source code without the indent in an indirect buffer."
:keymap +indirect-indent-mode-map)
(map! :map +indirect-indent-mode-map
:gni "s-s" #'edit-indirect-save)
(add-hook! +indirect-indent-mode
(defun +indirect-indent/init-hook ()
(setq header-line-format
"Edit, then exit with 'C-c C-c', abort with 'C-c C-k'.")))
(advice-add #'edit-indirect-commit :before #'+indirect-indent/restore-indentation)
(advice-add #'edit-indirect-save :after #'+indirect-indent/remove-indentation)
(advice-add #'edit-indirect-save :before #'+indirect-indent/restore-indentation)
(defun +indirect-indent/restore-indentation ()
(when (and (bound-and-true-p +indirect-indent-mode)
(not (eq +indirect-indent 0)))
(without-undo
(indent-rigidly (point-min) (point-max) (+ +indirect-indent)))))
(defun +indirect-indent/remove-indentation ()
(when (and (bound-and-true-p +indirect-indent-mode)
(not (eq +indirect-indent 0)))
(without-undo
(indent-rigidly (point-min) (point-max) (- +indirect-indent)))))
(defun +indirect-indent|edit (beg end &optional with-mode)
"Edit script in an indirect buffer."
(interactive)
(edit-indirect-region beg end t)
(let ((indent (indent-rigidly--current-indentation (point-min) (point-max))))
(unless (eq indent 0)
(without-undo
(indent-rigidly (point-min) (point-max) (- indent)))
;; Local variables get undone when calling a mode
;; So we have to define the major mode before
(funcall with-mode)
(+indirect-indent-mode 1)
(setq +indirect-indent indent))))
(defvar +docs:dir nil
"The main directory to store the docs in.")
(defvar +docs:index-file nil
"The main file to store all scans.")
(setq +docs:dir (f-expand "~/Documents/Scans"))
(setq +docs:index-file (f-join +docs:dir "Scans.org"))
(defun +docs/add-entry ()
"Adds an entry in the main index file."
(interactive)
(find-file (f-expand +docs:index-file))
(goto-char (point-max))
(newline)
(goto-char (point-max))
(yas/insert-by-name "Scan Template")
(+org|add-created-property))
(defun +docs/get-attachment-path ()
"Get the attachment path from the current entry."
(require 'org-ml)
(->>
(org-ml-parse-this-headline)
(org-ml-headline-get-node-property "DOCUMENT")
(+org/link-url-or-original)
(s-replace-regexp "^attachment:" "")))
(defun +docs/join-attachment-pdfs (original add)
"Foo"
(shell-command-to-string (t! "concat-pdfs <<original>> <<add>>")))
(defun +docs|scan-document ()
"Scan a document and attach it to a new entry in the main docs file."
(interactive)
(require 'org-download)
(let ((org-scan-path (f-expand +docs:index-file)))
(+docs/add-entry)
(let ((marker (point))
(org-attach-id-dir "./.attach")
(org-attach-directory "./.attach")
(org-attach-dir-relative t))
(deferred:$
(deferred:process "scan")
(deferred:nextc it
`(lambda (x)
(save-excursion
(find-file ,org-scan-path)
(goto-char ,marker)
(let* ((old-path
(->> x
(s-split "\n")
(-butlast)
(-last-item)))
(new-path (expand-file-name (org-download--fullname old-path))))
(progn
(copy-file old-path new-path)
(org-set-property "DOCUMENT" (funcall org-download-link-format-function new-path)))))))))))
(defun +docs|scan-document-append ()
"Scan a document and attach it to a new entry in the main docs file."
(interactive)
(require 'org-download)
(let ((org-scan-path (f-expand +docs:index-file)))
(let ((marker (point))
(org-attach-id-dir "./.attach")
(org-attach-directory "./.attach")
(org-attach-dir-relative t))
(deferred:$
(deferred:process "scan")
(deferred:nextc it
`(lambda (x)
(save-excursion
(find-file ,org-scan-path)
(goto-char ,marker)
(let* ((old-path
(->> x
(s-split "\n")
(-butlast)
(-last-item))))
(+docs/join-attachment-pdfs (f-join +docs:dir (+docs/get-attachment-path)) old-path)
(-log "Merging scanned documents finished.")))))))))
(defun +docs|dired-attach-document ()
"Function docstring"
(interactive)
(let ((old-path (->> (dired-file-name-at-point)
(f-expand))))))
(after! js2-mode
:config
(setq
flycheck-javascript-eslint-executable (executable-find "eslint_d")
flycheck-disabled-checkers '(javascript-jshint javascript))
(add-hook 'js2-mode-hook #'eslintd-fix-mode))
(defun +js/match-const-function-name (line)
"Matches a line to the word after the declaration"
(nth 2 (s-match
"\\(const\\|let\\|class\\)\s\\(.+?\\)\s"
line)))
(defun +js/const-function-at-point ()
"Returns the current function name at the current line"
(+js/match-const-function-name (thing-at-point 'line t)))
(defun js2r-export-default ()
"Exports the current declaration at the end of the file"
(interactive)
(save-excursion
(let* ((name (+js/const-function-at-point)))
(goto-char (point-max))
(insert "\n")
(insert (template "export default <<name>>;")))))
Extract the const
under the cursor into a new file.
(defun js2r-extract-const-to-file ()
"Extracts function to external file"
(interactive)
(let* ((name (+js/const-function-at-point))
(path (concat "./" name ".js")))
(evil-digit-argument-or-evil-beginning-of-line)
(js2r-kill)
(f-write-text "" 'utf-8 path)
(find-file path)
(yank)))
Generate a file index in the current file for every other file in the current directory.
(defun +js/index-file-names (&optional dir)
"Get filenames from current buffers directory."
(let ((fs (directory-files (or dir default-directory) nil ".*\\.js")))
(mapcar 'f-no-ext
(remove "index.js" fs))))
(defun +js|generate-index (&optional dir ignore-list)
"Generate an index import file for files in directory.
Pass DIR for directory, falls back to default-directory
Pass IGNORE-LIST for a list of files "
(interactive)
(erase-buffer)
(let* ((dir (or dir default-directory))
(fs (-->
(+js/index-file-names dir)
(if ignore-list
(--remove (-contains? ignore-list it) it)
it))))
(message "%s" fs)
(mapc (lambda (f) (insert "import " f " from './" f "';\n")) fs)
(insert "\n")
(insert "export default {\n")
(mapc (lambda (f) (insert " " f ",\n")) fs)
(insert "};")))
Converts an expression into a template string.
Example:
When you would call the function on the foo
inside the console.log,
It would wrap it like this console.log(`${foo}`)
.
const foo = 'Foo'
console.log(`${foo}`)
(defun +js|convert-sexp-to-template-string ()
"Wrap sexp into a template string"
(interactive)
(kill-sexp)
(insert (concat "`${" (substring-no-properties (car kill-ring)) "}`"))
(pop kill-ring))
(defvar +rjsx:closing-tag-regexp nil)
(setq +rjsx:closing-tag-regexp "[^=]\\\\?>")
(defun +rjsx|split-join-tag ()
"Split or join the xml attributes list in the current tag."
(interactive)
(if-let ((closing-tag (save-excursion
(rjsx-jump-opening-tag)
(re-search-forward +rjsx:closing-tag-regexp (point-at-eol) t))))
(save-excursion
(rjsx-jump-opening-tag)
(evil-forward-WORD-begin 1)
(while (< (point) closing-tag)
(newline-and-indent)
(evil-forward-WORD-begin 1))
(previous-line)
(re-search-forward +rjsx:closing-tag-regexp (point-at-eol) t)
(backward-char 1)
(newline-and-indent))
(save-excursion
(rjsx-jump-opening-tag)
(while (not (re-search-forward +rjsx:closing-tag-regexp (point-at-eol) t))
(next-line)
(join-line))
(search-forward-regexp +rjsx:closing-tag-regexp (point-at-eol) t)
(backward-char 2)
(when (= (char-after) ?\s)
(delete-char 1)))))
Stolen from the vscode extension: dannyconnell/vscode-split-html-attributes I don’t want to translate this to elisp, and js works as well.
const useSpacesForTabs = true;
const tabSize = 4;
const sortOrder = [];
const closingBracketOnNewLine = true;
let input = process.argv[2]
// count the number of lines initally selected
let lineCount = input.split('\n').length
let lineText = input
// get the initial white space at the start of the line
let initialWhitespaceRegex = /\s*/i
let initialWhitespace = lineText.match(initialWhitespaceRegex)[0]
// get the opening tag
let openingTagRegex = /^[^\s]+/
let openingTag = input.match(openingTagRegex)[0]
// remove opening tag and trim
input = input.replace(openingTagRegex, '')
input = input.trim()
// get the ending bracket (if it's a "/>")
let endingBracket = ''
if (input.endsWith('/>')) {
endingBracket = '/>'
}
else {
endingBracket = '>'
}
// remove ending bracket and trim
if (endingBracket == '/>') {
input = input.replace('/>', '')
}
else {
input = input.substring(0, input.length - 1)
}
input = input.trim()
// create the indentation string
let indentationString
if (useSpacesForTabs == false) {
indentationString = '\t'
}
else {
indentationString = ' '.repeat(tabSize)
}
// regex to select all spaces that aren't within quotes
let spacesRegex = /\s+(?=([^"]*"[^"]*")*[^"]*$)/g
// get attributes into an array
let attributesString = input.replace(spacesRegex, '\n')
let attributesArray = attributesString.split('\n')
// sort the attributes array
let sortedAttributesArray = []
if (sortOrder.length) {
// loop through sortOrder array
sortOrder.forEach(search => {
// loop through attributesArray
let itemsToMove = []
attributesArray.forEach((item, index) => {
if (item.match(search)) {
itemsToMove.push(index)
// attributesArray.splice(index, 1)
}
})
// move matched items from attributesArray to sortedAttributesArray (and sort them)
let tempMatchedItems = []
itemsToMove.forEach(indexItem => {
tempMatchedItems.push(attributesArray[indexItem])
})
tempMatchedItems.sort()
sortedAttributesArray.push(...tempMatchedItems)
// remove matched items from attributesArray
for (var i = itemsToMove.length - 1; i >= 0; --i) {
attributesArray.splice(itemsToMove[i], 1)
}
})
// sort remaining attributes and add to sortedAttributesArray
attributesArray.sort()
sortedAttributesArray.push(...attributesArray)
}
else {
sortedAttributesArray = attributesArray
}
// add the opening tag
let result = openingTag
// set the join character based on number of lines initially selected
// (newLine if one line, space if more)
let joinCharacter = lineCount > 1 ? ' ' : '\n'
// if there are no attributes, set joinCharacter to ''
if (sortedAttributesArray.length == 1 && sortedAttributesArray[0] == '') {
joinCharacter = ''
}
// add the sorted attributes to the textSplit string
if (lineCount > 1) {
sortedAttributesArray.forEach(item => {
result += joinCharacter + item
})
}
else {
sortedAttributesArray.forEach(item => {
result += joinCharacter + initialWhitespace + indentationString + item
})
}
// configure ending bracket (new line or not new line)
if (lineCount > 1) {
if (endingBracket == '/>') {
endingBracket = ' ' + endingBracket
}
}
else {
if (closingBracketOnNewLine) {
endingBracket = '\n' + initialWhitespace + endingBracket
}
else if (endingBracket == '/>') {
endingBracket = ' ' + endingBracket
}
}
// add the ending bracket
result = result + endingBracket
console.log(result)
(defun +rjsx/split-join ()
"Function docstring"
(interactive)
(let ((input
(->>
(buffer-substring-no-properties (region-beginning) (region-end))
(s-replace "\n" "\\n"))))
(->>
(shell-command-to-string (t! "node <<doom-private-dir>>/.cache/split-join-tag.js $'<<input>>'"))
(insert))))
Converts self closing JSX tags to closing tags.
<Foo />
-> <Foo>|</Foo>
(defun +rjsx|expand-insert-self-closing-tag ()
"Opens the current tag at any position of the cursor and starts insert mode"
(interactive)
(search-forward "/>")
(evil-backward-char)
(call-interactively #'delete-backward-char)
(call-interactively #'rjsx-electric-gt)
(newline)
(call-interactively #'evil-indent-line)
(call-interactively #'evil-open-above))
(defun +js|extract-props ()
"Extract props object under the cursor."
(interactive)
(save-excursion
(let* ((point-start (search-backward "{"))
(point-end (search-forward "}"))
(text (buffer-substring-no-properties point-start point-end)))
(delete-region point-start point-end)
(insert "props")
(evil-open-below 1)
(insert (template "const <<text>> = props;"))
(search-backward "}")
(js2r-expand-node-at-point)))
(evil-normal-state))
Remove the js
extension for company-files
.
(defun company-js-files (command &optional arg &rest ignored)
"Company complete path. Remove extension after completion"
(interactive (list 'interactive))
(require 'company)
(cl-case command
(interactive (company-begin-backend 'company-js-files))
(prefix (company-files--grab-existing-name))
(candidates (company-files--complete arg))
(location (cons (dired-noselect
(file-name-directory (directory-file-name arg))) 1))
(post-completion (when (s-matches? "\.js$" arg) (delete-backward-char 3)))
(sorted t)
(no-cache t)))
(map! :map js2-mode-map
:i "C-x C-f" #'company-js-files)
(defun +js/import-file (file)
(let ((cursor-postion (point))
(filename (f-no-ext file)))
(insert (template "import from '<<filename>>';"))
(goto-char cursor-postion)
(forward-char 7)
(evil-insert-state)))
(defun +js|ivy-import-file (&optional action)
(interactive)
(let* ((local-files
(-->
(-concat (list find-program) counsel-file-jump-args)
(string-join it " ")
shell-command-to-string
split-string))
(node-packages
(-->
(concat "jq -r '.dependencies | keys | .[]' " (concat (projectile-project-root) "package.json"))
shell-command-to-string
split-string))
(imports (append local-files node-packages)))
(ivy-read "Import file " imports :action (or action '+js/import-file))))
(defun js2r-ternary-switch-statements ()
"Switch expressions in a ternary."
(interactive)
(js2r--guard)
(js2r--wait-for-parse
(save-excursion
(let* ((ternary (js2r--closest 'js2-cond-node-p))
(test-expr (js2-node-string (js2-cond-node-test-expr ternary)))
(true-expr (js2-node-string (js2-cond-node-true-expr ternary)))
(false-expr (js2-node-string (js2-cond-node-false-expr ternary)))
(stmt (js2-node-parent-stmt ternary))
(stmt-pre (buffer-substring (js2-node-abs-pos stmt) (js2-node-abs-pos ternary)))
(stmt-post (s-trim (buffer-substring (js2-node-abs-end ternary) (js2-node-abs-end stmt))))
(beg (js2-node-abs-pos stmt)))
(goto-char beg)
(delete-char (js2-node-len stmt))
(insert "return " test-expr)
(newline)
(insert "? " false-expr)
(newline)
(insert ": " true-expr ";")
(indent-region beg (point))))))
(defun +js|eslint-fix-ignore-error ()
"Adds an ignore with the current flycheck error."
(interactive)
(if-let ((error-id (flycheck-copy-errors-as-kill (point) #'flycheck-error-id)))
(save-excursion
(previous-line)
(end-of-line)
(newline-and-indent)
(insert (template "// eslint-disable-next-line <<error-id>>")))))
Go to a package from the node_modules directory.
(defun +javascript/find-npm-package-goto (package)
"Go to directory by package name"
(-some->> (f-join (projectile-project-root) "node_modules")
(-id-when #'f-exists?)
(-f-join package)
(find-file)))
(defun +javascript|find-npm-package ()
"Find package in node_modules directory."
(interactive)
(-when-let* ((json (-some->> (f-join (projectile-project-root) "package.json")
(-id-when #'f-exists?)
(json-read-file)))
((&alist 'dependencies dependencies
'devDependencies devDependencies) json)
(packages (->> (-concat dependencies devDependencies)
(-map #'car))))
(ivy-read "Go to package directory: " packages
:action #'+javascript/find-npm-package-goto)))
(map!
:after js2-mode
:map js2-mode-map
:desc "Goto parent function" :n "gh" (cmd! (js2-beginning-of-defun))
:localleader
:desc "Goto NPM Package" "m" #'+javascript|find-npm-package
(:prefix-map ("c" . "Create")
:desc "Import File" "i" #'js-import))
(map!
:after rjsx-mode
:map rjsx-mode-map
:localleader
(:desc "Open Self-Closing Tag" :n ">" #'+rjsx|expand-insert-self-closing-tag)
(:desc "Open Self-Closing Tag" :n "<" #'rjsx-rename-tag-at-point))
Adds text objects for functions in javascript.
So you can press daf
to delete a function.
(add-hook! js-mode
(require 'evil-text-objects-javascript)
(evil-text-objects-javascript/install))
Overwrite evil-text-objects-javascript
to also accepts methods.
I mainly changed the function marking helper.
- (call-interactively #'mark-defun)
+ (call-interactively #'js2-mark-defun)
(after! evil-text-objects-javascript
(evil-define-text-object
evil-inner-javascript-function (count &optional beg end type)
"inner text object for all javascript functions."
(call-interactively #'js2-mark-defun)
(narrow-to-region (region-beginning) (region-end))
(goto-char (point-min))
(let* ((beg (save-excursion
(search-forward "(")
(backward-char)
(evil-jump-item)
(search-forward-regexp "[({]")
(point)))
(end (save-excursion
(goto-char beg)
(evil-jump-item)
(point))))
(evil-range beg end type)))
(evil-define-text-object
evil-outer-javascript-function (count &optional beg end type)
"Outer text object for all Javascript functions."
(call-interactively #'js2-mark-defun)
(narrow-to-region (region-beginning) (region-end))
(goto-char (point-min))
(let* ((beg (save-excursion
(when (looking-at "[[:space:]]")
(evil-forward-word-begin))
(point)))
(end (save-excursion
(goto-char beg)
(search-forward "(")
(backward-char)
(evil-jump-item)
(search-forward-regexp "[({]")
(evil-jump-item)
(forward-char)
(if (save-excursion
(progn
(forward-char)
(when (looking-at ",") (point))))
(point)))))
(evil-range beg end type))))
(def-project-mode! +dotfiles-nim-compile-mode
:modes '(nim-mode)
:when (string= (doom-project-root) "/etc/dotfiles/")
:on-load
(setq-local nim-compile-default-command '("c" "-r" "--verbosity:0" "--hint[Processing]:off" "--excessiveStackTrace:on" "--outdir:../dst")))
(put 'js-import-alias-map 'safe-local-variable (lambda (x) t))
Dont add packages inside ~~.emacs.d~ to projectile, as I often browse the source for a package,
but I dont want them added to my projectile-known-projects
.
(use-package! projectile
:init
(setq projectile-ignored-projects '("~/"
"/tmp"
"~/.emacs.d/.local/straight/repos/")))
(use-package! projectile
:init
(setq projectile-project-search-path '("~/Code/Dotfiles"
"~/Code/Repositories"
"~/Code/Projects"
"~/Code/Tools"
"~/Code/Meisterlabs"
"~/Code/Meisterlabs/mindmeister-web/.worktrees"))
:config
;; Auto discover when running switch project for the first time
(add-transient-hook! 'counsel-projectile-switch-project
(projectile-cleanup-known-projects)
(projectile-discover-projects-in-search-path)))
(use-package elescope
:commands (elescope-checkout)
:config
(setq elescope-root-folder "~/Code/Repositories")
:init
(defalias '+git|clone 'elescope-checkout))
(use-package daemons
:commands (daemons))
(use-package! writegood-mode
:hook 'nil)
(defun +spell-fu/activate-writegood-mode-hook ()
"Toggle writegood mode with spell-fu mode."
(cond
(spell-fu-mode (writegood-mode 1))
(t (writegood-mode -1))))
(add-hook! 'spell-fu-mode-hook :after #'+spell-fu/activate-writegood-mode-hook)
(use-package cheat-sh
:commands (cheat-sh)
:config
(setq cheat-sh-topic-mode-map
'((awk-mode . "awk")
(c++-mode . "cpp")
(c-mode . "c")
(clojure-mode . "clojure")
(clojurescript-mode . "clojure")
(dockerfile-mode . "docker")
(emacs-lisp-mode . "elisp")
(fish-mode . "fish")
(go-mode . "go")
(haskell-mode . "haskell")
(hy-mode . "hy")
(java-mode . "java")
(js-jsx-mode . "javascript")
(js-mode . "javascript")
(js-mode . "js")
(lisp-interaction-mode . "elisp")
(lisp-mode . "lisp")
(objc-mode . "objectivec")
(pike-mode . "pike")
(powershell-mode . "powershell")
(python-mode . "python")
(rust-mode . "rust")
(sh-mode . "bash")
(nim-mode . "nim"))))
(defun cheat-sh (thing)
"Look up THING on cheat.sh and display the result."
(interactive (list (cheat-sh-read "Lookup: ")))
(let ((result (cheat-sh-get thing)))
(if result
(let ((temp-buffer-window-setup-hook
(cons 'cheat-sh-mode-setup temp-buffer-window-show-hook)))
(with-temp-buffer-window "*cheat.sh*" nil 'help-window-setup
(with-current-buffer "*cheat.sh*"
(+cheat-sh/set-mode thing))
(princ result)))
(error "Can't find anything for %s on cheat.sh" thing))))
(defun cheat-sh-mode-setup ()
"Set up the cheat mode for the current buffer."
(setq buffer-read-only nil))
(defun +cheat-sh/set-mode (x)
(-when-let* ((lang
(-some->> x
(s-split "/")
(nth 0)))
(mode (--find (string= lang (cdr it)) cheat-sh-topic-mode-map))
(mode (car mode)))
(funcall mode)))
Add nix-shell headers to the bridge
#! /usr/bin/env nix-shell
#! nix-shell -i perl -p mysql -p perlPackages.DBDmysql perlPackages.RPCEPCService perlPackages.DBDPg perlPackages.DBI
use strict;
use RPC::EPC::Service;
use Data::Dumper;
use DBI;
our $dbh = undef;
our $sth = undef;
sub edbi_connect {
my ($args) = @_;
my ($data_source,$username,$auth) = @$args;
# No input _probably_ means "no password" rather than empty string
$auth = undef if($auth eq "");
if ($dbh) {
eval {
$dbh->disconnect();
}
}
our $dbh = DBI->connect($data_source, $username, $auth)
or die("Could not connect to database:
Data Source ($data_source)
User Name: ($username):
DBI error: ($DBI::errstr)
");
return $dbh->get_info(18);
}
sub edbi_do {
return undef unless $dbh;
my ($args) = @_;
my ($sql, $params) = @$args;
my $rows = $dbh->do($sql, undef, @$params);
return $rows;
}
sub edbi_select_all {
return undef unless $dbh;
my ($args) = @_;
my ($sql, $params) = @$args;
my $rows = $dbh->selectall_arrayref($sql, undef, @$params); return $rows;
}
sub edbi_prepare {
return undef unless $dbh;
$sth->finish() if $sth;
my ($sql) = @_;
our $sth = $dbh->prepare($sql) or return undef;
return 'sth';
}
sub edbi_execute {
return undef unless $sth;
my ($params) = @_;
return $sth->execute(@$params) or return undef;
}
sub edbi_fetch_columns {
return undef unless $sth;
return $sth->{NAME};
}
sub edbi_fetch {
return undef unless $sth;
my ($num) = @_;
if ($num eq undef) {
return $sth->fetchall_arrayref();
} else {
my $ret = [];
for (my $i = 0; $i < $num; $i++) {
my $row = $sth->fetchrow_arrayref();
last if $row eq undef;
push @$ret, [@$row];
}
return $ret;
}
}
sub edbi_auto_commit {
return undef unless $dbh;
my ($flag) = @_;
if ($flag eq "true") {
$dbh->{AutoCommit} = 1;
return 1;
} else {
$dbh->{AutoCommit} = 0;
return 0;
}
}
sub edbi_commit {
return undef unless $dbh;
$dbh->commit();
return 1;
}
sub edbi_rollback {
return undef unless $dbh;
$dbh->rollback();
return 1;
}
sub edbi_disconnect {
return undef unless $dbh;
$dbh->disconnect();
return 1;
}
sub edbi_status {
return undef unless $dbh;
return [$dbh->err, $dbh->errstr, $dbh->state];
}
sub edbi_type_info_all {
return undef unless $dbh;
my $ret = $dbh->type_info_all;
print STDERR Dumper $ret;
return $dbh->type_info_all;
}
sub edbi_table_info {
return undef unless $dbh;
eval {
$sth->finish() if $sth;
};
my ($args) = @_;
my ($catalog, $schema, $table, $type) = @$args;
$sth = $dbh->table_info( $catalog, $schema, $table, $type );
return [$sth->{NAME}, $sth->fetchall_arrayref()];
}
sub edbi_column_info {
return undef unless $dbh;
eval {
$sth->finish() if $sth;
};
my ($args) = @_;
my ($catalog, $schema, $table, $column) = @$args;
$sth = $dbh->column_info( $catalog, $schema, $table, $column );
return [[],[]] unless $sth;
return [$sth->{NAME}, $sth->fetchall_arrayref()];
}
sub edbi_primary_key_info {
return undef unless $dbh;
eval {
$sth->finish() if $sth;
};
my ($args) = @_;
my ($catalog, $schema, $table) = @$args;
$sth = $dbh->primary_key_info( $catalog, $schema, $table );
return undef unless $sth;
return [$sth->{NAME}, $sth->fetchall_arrayref()];
}
sub edbi_foreign_key_info {
return undef unless $dbh;
eval {
$sth->finish() if $sth;
};
my ($args) = @_;
my ($pkcatalog, $pkschema, $pktable, $fkcatalog, $fkschema, $fktable) = @$args;
$sth = $dbh->foreign_key_info( $pkcatalog, $pkschema, $pktable,
$fkcatalog, $fkschema, $fktable );
return undef unless $sth;
return [$sth->{NAME}, $sth->fetchall_arrayref()];
}
sub main {
my $methods =
{
'connect' => [\&edbi_connect,"data_source, username, auth", ""],
'do' => [\&edbi_do, "sql, params", ""],
'select-all' => [\&edbi_select_all, "sql, params", ""],
'prepare' => [\&edbi_prepare, "sql", ""],
'execute' => [\&edbi_execute, "params", ""],
'fetch-columns' => [\&edbi_fetch_columns, "", ""],
'fetch' => [\&edbi_fetch, "[number]", ""],
'auto-commit' => [\&edbi_auto_commit, "false/true", ""],
'commit' => [\&edbi_commit, "", ""],
'rollback' => [\&edbi_rollback, "", ""],
'disconnect' => [\&edbi_disconnect, "", ""],
'status' => [\&edbi_status, "", ""],
'type-info-all' => [\&edbi_type_info_all, "", ""],
'table-info' => [\&edbi_table_info, "catalog, schema, table, type", ""],
'column-info' => [\&edbi_column_info, "catalog, schema, table, column", ""],
'primary-key-info' => [\&edbi_primary_key_info, "catalog, schema, table", ""],
'foreign-key-info' => [\&edbi_foreign_key_info, "pk_catalog, pk_schema, pk_table, fk_catalog, fk_schema, fk_table", ""],
};
my $server = RPC::EPC::Service->new(0, $methods);
$server->start;
}
main;
(after! edbi
(setq edbi:driver-info
(list
"nix-shell" "-p"
"perl"
"-p" "perlPackages.DBI"
"-p" "perlPackages.RPCEPCService"
"-p" "perlPackages.DBDPg"
"-p" "perlPackages.DBDmysql"
"--run"
(f-join doom-private-dir "edbi-bridge.pl"))))
Since it has the regex matching .com
it’s enabled for all my password files,
which I name after the matching url.
(setq auto-mode-alist (rassq-delete-all 'dcl-mode auto-mode-alist))
(setq
+bookmarks:main
'(((file . "~/.config/doom/modules/private/work/config.org")
(name . "Config: Work")
(goto . "* Configuration")
(goto-bol . t)
(disable-relocation . t))
((file . "~/.config/doom/autoload.org")
(name . "Config: Autoloads"))
((file . "~/.config/doom/modules/private/org/config.org")
(name . "Config: Org"))
((file . "~/.config/doom/config.org")
(name . "Bookmarks: Main")
(goto . "*** Main Bookmarks")
(goto-bol . t))
((fn . (lambda () (find-file (getenv "HISTFILE"))))
(name . "Shell History")
(goto-bol . t))
((file . "~/.config/doom/modules/private/work/config.org")
(name . "Bookmarks: Work")
(goto . "*** Bookmarks")
(goto-bol . t))
((file . "/etc/dotfiles/modules/desktop/common.nix")
(name . "Mimeapps")
(goto . "### Mimeapps")
(goto-bol . t))
((file . "~/.config/weechat/xfer")
(name . "Weechat Xfer"))
((file . "/run/media/floscr/tolino")
(name . "Tolino"))
((fn . org-capture-goto-last-stored)
(name . "Capture: Last Stored Marker"))))
(defun +bookmarks|main ()
(interactive)
(+bookmarks (-concat +bookmarks:main (+bookmarks/local/list))))
(use-package! transmission
:commands (transmission)
:config
(map! :map transmission-files-mode-map
:n "<C-return>" (cmd! (->> (transmission-files-file-at-point)
(f-parent)
(dired)))))
(map!
:map pdf-occur-buffer-mode-map
:gn [tab] (cmd! (pdf-occur-goto-occurrence t)))
(add-hook! 'emacs-lisp-mode-hook :append
(setq flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
(add-hook! 'nim-mode-hook :append
(setq flycheck-disabled-checkers '(nim-nimsuggest))
(flycheck-select-checker 'nim))
The setup function
(defun +writeroom/writeroom-setup ()
(if writeroom-mode
(+writeroom/writeroom-load)
(+writeroom/writeroom-unload)))
(defun +writeroom/writeroom-load ()
(setq-local line-spacing 0.4)
(setq-local +writeroom:faceremaps
(list
(face-remap-add-relative
'org-link `(:foreground ,(face-attribute 'default :foreground)
:underline ,(face-attribute 'default :foreground)
:weight normal))
(face-remap-add-relative
'default `(:foreground ,(doom-blend
(face-attribute 'default :background)
(face-attribute 'default :foreground)
0.1))))))
(defun +writeroom/writeroom-unload ()
(kill-local-variable 'line-spacing)
(mapc #'face-remap-remove-relative +writeroom:faceremaps)
(kill-local-variable '+writeroom:faceremaps))
and the hooks:
(add-hook! 'writeroom-mode-hook #'+writeroom/writeroom-setup)
(use-package! beancount
:defer t
:mode
("\\.bean\\(?:count\\)?\\'" . beancount-mode)
:config
(setq beancount-accounts-files
(directory-files "~/Documents/Beancount"
'full
(rx ".beancount.gpg" eos))))
(defun +beancount/collect-accounts ()
"Collect accounts from beancount files."
(beancount-collect beancount-account-regexp 0 beancount-accounts-files))
(defvar +beancount:income-accounts nil
"Prefixes for expense accounts.")
(setq +beancount:income-accounts '("Assets" "Liabilities"))
(defun +beancount/collect-expense-accounts (accounts)
"Collect accounts from list ACCOUNTS."
(-filter (lambda (x) (--find (s-starts-with? it x) +beancount:income-accounts)) accounts))
(defun +beancount/date-string ()
"Function docstring"
(format-time-string "%Y-%m-%d"))
(defun +beancount|open-main ()
"Open the main beancount-accounts-files file."
(interactive)
(find-file (car beancount-accounts-files)))
(defun +beancount|balance ()
"Show the current balances."
(interactive)
(let ((compilation-read-command nil))
(beancount--run "bean-report"
(file-relative-name buffer-file-name) "bal")))
Manually add an expense to the Cash Book.
(defun +beancount|add-expense ()
"Add expense to the bottom of the file."
(interactive)
(and-let* ((accounts (+beancount/collect-accounts))
(from (ivy-read "From: " (+beancount/collect-expense-accounts accounts)))
(to (ivy-read "To: " accounts))
(subject (read-string "Subject: "))
(amount (read-number "Amount: ")))
(goto-char (point-max))
(newline 1)
(insert (template "<<(+beancount/date-string)>> * \"<<subject>>\"
<<from>> -<<amount>> EUR
<<to>> <<amount>> EUR
"))
(beancount-align-to-previous-number)))
(map! :map beancount-mode-map
:localleader
:n "a" #'+beancount|add-expense
:n "c" #'beancount-check
:n "q" #'beancount-query
:n "b" #'+beancount|balance)
(defun +rss|open ()
(interactive)
(unless (get-buffer "*elfeed-search*")
(setq elfeed-search-filter +rss:default-search-filter))
(elfeed)
(+rss|hydra/body))
(defun +rss|search-unread ()
"Show elfeed articles tagged with unread"
(interactive)
(elfeed-search-set-filter "@6-months-ago +unread"))
Open the current entry:
- With the browser
- Or if it’s a youtube feed, open with mpv
(defun +rss|visit-dwim ()
"Either open the current entry in eww or play it in mpv."
(interactive)
(message "")
(let ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :single)))
(patterns +rss:mpv-patterns))
(while (and patterns (not (string-match (car +rss:mpv-patterns) (elfeed-entry-link entry))))
(setq patterns (cdr patterns)))
(if patterns
(+rss|play-with-mpv)
(if (eq major-mode 'elfeed-search-mode)
(elfeed-search-browse-url)
(elfeed-show-visit)))))
(defun +rss|open-eww ()
"Open the current entry with eww."
(interactive)
(add-hook 'eww-after-render-hook 'eww-readable nil t)
(let ((buffer (save-window-excursion
(eww (elfeed-entry-link elfeed-show-entry))
(current-buffer))))
(switch-to-buffer buffer)))
(defun +rss|elfeed-wrap ()
"Enhances an elfeed entry's readability by wrapping it to a width of
`fill-column' and centering it with `visual-fill-column-mode'."
(let ((inhibit-read-only t)
(inhibit-modification-hooks t))
(setq-local truncate-lines nil)
(setq-local shr-width 100)
(setq-local +zen-text-scale 0.9)
(mixed-pitch-mode)
(writeroom-mode)
(set-buffer-modified-p nil)))
(add-hook 'elfeed-show-mode-hook #'+rss|elfeed-wrap)
Play youtube entries via MPV
(defvar +rss:mpv-patterns
'("youtu\\.?be")
"List of regexp to match against elfeed entry link to know
whether to use mpv to visit the link.")
(defun +rss|play-with-mpv ()
"Play entry link with mpv."
(interactive)
(let ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :single)))
(url (elfeed-entry-link entry)))
(+mpv/play-external-url url)))
(defun +rss/stringify-entry (entry)
(let ((title (->> (elfeed-entry-title entry)
;; Remove braces, they're just confusing to org links
(s-replace "[" "")
(s-replace "]" "")))
(url (elfeed-entry-link entry))
(author (->> (elfeed-meta entry :authors)
car
((lambda (x) (plist-get x :name)))))
(tags (-some--> (elfeed-entry-tags entry)
(-remove-item 'unread it)
(-map #'symbol-name it)
(s-join ":" it)
(s-wrap it ":" ":"))))
(template "[[<<url>>][<<author>> - <<title>>]] <<(when tags tags)>>")))
(defun +rss|capture ()
"Capture current entry of elfeed."
(interactive)
(let* ((entry (if (eq major-mode 'elfeed-show-mode)
elfeed-show-entry
(elfeed-search-selected :single)))
(org-entry (+rss/stringify-entry entry)))
(kill-new org-entry)
(if (s-contains? org-entry "youtube.com")
(org-capture nil "ew")
(org-capture nil "er"))))
;; (map!
;; :after elfeed
;; :map (elfeed-show-mode-map elfeed-show-mode-map)
;; :local
;; :gn "Co" #'+rss|capture)
When I save the elfeed org-mode file I want to automatically update the feed list. Update function taken from elfeed package.
(add-hook 'after-save-hook (defun +org-efleed/update-feeds ()
(when (and (eq major-mode 'org-mode)
(string= buffer-file-name (car rmh-elfeed-org-files)))
(require 'elfeed)
(rmh-elfeed-org-process rmh-elfeed-org-files rmh-elfeed-org-tree-id))))
(defun +rss/toggle-filter (tag)
"Toggle elfeed filter TAG or append to filters if non-existent."
(->> (or (-when-let* (((_ modi) (s-match (t! "\\_<\\([+-]\\)<<tag>>\\_>") elfeed-search-filter))
(new-modi (if (string= modi "+") "-" "+")))
(s-replace-regexp (t! "\\_<\\<<modi>><<tag>>\\_>") (t! "<<new-modi>><<tag>>") elfeed-search-filter))
(s-append (t! " +<<tag>>") elfeed-search-filter))
(--tap (progn (setq elfeed-search-filter it)
(elfeed-search-update--force)
(-log (t! "Filter changed to: <<it>>"))))))
(defun +rss|counsel-filter (&optional arg)
"Toggles a filter in elfeed."
(interactive "P")
(ivy-read (t! "Filter: <<elfeed-search-filter>>") (elfeed-db-get-all-tags)
:action #'+rss/toggle-filter
:caller #'+rss|counsel-filter))
(defhydra +rss|hydra ()
"Elfeed"
("j" (evil-next-line) "Next item" :column "Navigate")
("k" (evil-previous-line) "Previous item")
("t" (+rss|counsel-filter) "Tags" :column "Filter")
("0" (elfeed-search-set-filter "@2-weeksago +unread") "Unread" :column "Favorites")
("f" (elfeed-search-set-filter "@2-weeksago +unread -YOUTUBE -REDDIT") "Feeds")
("y" (elfeed-search-set-filter "@2-weeksago +unread +YOUTUBE") "Youtube")
("e" (elfeed-search-set-filter "@2-weeksago +unread +REDDIT =emacs") "Reddit: Emacs")
("n" (elfeed-search-set-filter "@2-weeksago +unread +REDDIT =nixos") "Reddit: Nixos")
("U" (elfeed-update) "Update" :column "actions")
("u" (elfeed-search-untag-all-unread) "Mark as unread")
("s" (elfeed-db-save) "Save DB")
("v" (+rss|visit-dwim) "Visit")
("q" nil "Quit" :color blue))
(setq rmh-elfeed-org-files (list (+org/expand-org-file-name "Elfeed/Elfeed.org")))
These don’t work if you have a big line-height.
(setq +rss-enable-sliced-images nil)
(setq +rss:default-search-filter "@2-week-ago +unread -YOUTUBE")
(setq-default elfeed-search-filter +rss:default-search-filter)
(map!
:after elfeed
:map elfeed-search-mode-map
:gn "r" #'elfeed-update
(:prefix-map ("g" . "Go")
:desc "Youtube" :gn "y" (cmd! (elfeed-search-set-filter "@2-week-ago +YOUTUBE +unread"))
:desc "Normal" :gn "n" (cmd! (elfeed-search-set-filter "@2-week-ago -YOUTUBE +unread"))
:desc "Month" :gn "m" (cmd! (elfeed-search-set-filter "@1-month-ago +unread"))))
(map!
:after elfeed
:map elfeed-search-mode-map
:gn "r" #'elfeed-update)
(map! :map (elfeed-search-mode-map elfeed-show-mode-map)
:localleader
:desc "Filter Hydra" "f" #'+rss|hydra/body
:desc "Capture" "c" #'+rss|capture
:desc "Open Eww" "o" #'+rss|open-eww
:desc "Visit" "v" #'+rss|visit-dwim
(:prefix-map ("s" . "Search")
:desc "Unread" "u" #'+rss|search-unread))
(after! circe
(set-irc-server! "chat.freenode.net"
`(:tls t
:port 6697
:nick "floscr"
:sasl-username ,(+pass-get-user "Irc/freenode.net")
:sasl-password (lambda (&rest _) (+pass-get-secret "Irc/freenode.net"))
:channels ("#emacs" "#nixos"))))
(use-package! literate-calc-mode
:commands (literate-calc-mode literate-calc-minor-mode))
(defun +calendar|personal (&rest args)
(interactive)
(let ((org-agenda-skip-function '(+org/agenda-skip-without-match "-WORK")))
(call-interactively #'=calendar)))
(defun +calendar|just-family (&rest args)
(interactive)
(let ((org-agenda-skip-function '(+org/agenda-skip-without-match "+FAMILY|+BIRTHDAY")))
(call-interactively #'=calendar)))
(defun +calendar|birthdays (&rest args)
(interactive)
(let ((org-agenda-files `(,(+org/expand-org-file-name "Main/contacts.org")))
(org-agenda-skip-function '(+org/agenda-skip-without-match "+BIRTHDAY")))
(call-interactively #'=calendar)))
(defun +calendar|no-family (&rest args)
(interactive)
(let ((org-agenda-skip-function '(+org/agenda-skip-without-match "-FAMILY-WORK")))
(call-interactively #'=calendar)))
(defun +calendar|work (&rest args)
(interactive)
(let ((org-agenda-skip-function '(+org/agenda-skip-without-match "+WORK")))
(call-interactively #'=calendar)))
Emacs comes with a lot of custom calendars that I don’t want. This is mostly copied from How to set up a german emacs calendar.
(after! calfw
:config
(setq general-holidays
'((holiday-fixed 1 1 "New Years")
(holiday-fixed 5 1 "1st Mai"))))
(after! calfw
:config
(setq austrian-holidays
`((holiday-fixed 1 6 "Heilige drei Könige")
(holiday-fixed 5 1 "Staatsfeiertag")
(holiday-fixed 8 15 "Mariä Himmelfahrt")
(holiday-fixed 10 26 "Nationalfeiertag")
(holiday-fixed 11 1 "Allerheiligen")
(holiday-fixed 12 8 "Mariä Empfängnis")
(holiday-fixed 12 24 "Weihnachten")
(holiday-fixed 12 25 "Christtag")
(holiday-fixed 12 26 "Stefanitag")
;; variable
(holiday-easter-etc -2 "Karfreitag")
(holiday-easter-etc 0 "Ostersonntag")
(holiday-easter-etc 1 "Ostermontag")
(holiday-easter-etc 39 "Christi Himmelfahrt")
(holiday-easter-etc 49 "Pfingstsonntag")
(holiday-easter-etc 50 "Pfingstmontag")
(holiday-easter-etc 60 "Fronleichnam"))))
(after! calfw
:config
(setq calendar-holidays
(append
general-holidays
austrian-holidays
holiday-solar-holidays)))
(after! calfw
:config
(setq calendar-week-start-day 1)
(setq calendar-time-display-form
'(24-hours ":" minutes (and time-zone (concat " (" time-zone ")"))))
(setq calendar-abbrev-length 2))
(setq math-additional-units '((GB "1024 * MiB" "Giga Byte")
(MB "1024 * KiB" "Mega Byte")
(KB "1024 * B" "Kilo Byte")
(B nil "Byte")))
Fix for comint mode, throwing an error when pressing enter in the middle of the line.
error in process filter: End of buffer
(map!
:after comint
:map comint-mode-map
:ni "RET" (cmd! (comint-send-input nil t))
:n "<C-backspace>" #'comint-clear-buffer)
(setq company-transformers '(company-sort-by-occurrence)
company-idle-delay 0.5)
Complete a whole line with all lines from buffers matching the current major-mode.
(defun +company:buffer-list-with-modes (modes)
"Get all buffers that match MODES"
(--filter
(with-current-buffer it (-contains? (doom-enlist modes) major-mode))
(buffer-list)))
(defun +company:buffer-list-with-major-mode ()
"Get all buffers matching the current major-mode
Has built in aliases"
(let ((javascript-modes (list 'rjsx-mode 'js2-mode)))
(pcase major-mode
('rjsx-mode
(+company:buffer-list-with-modes javascript-modes))
('js2-mode
(+company:buffer-list-with-modes javascript-modes))
(_
(+company:buffer-list-with-modes major-mode)))))
(defun +company/whole-lines-all-buffers (command &optional arg &rest ignored)
"`company-mode' completion backend that completes whole-lines, akin to vim's
C-x C-l."
(interactive (list 'interactive))
(require 'company)
(pcase command
(`interactive (company-begin-backend '+company/whole-lines-all-buffers))
(`prefix (company-grab-line "^[\t\s]*\\(.+\\)" 1))
(`candidates
(all-completions
arg
(funcall (-compose
#'-uniq
#'-flatten
(lambda (xs)
(--map (with-current-buffer it
(split-string
(replace-regexp-in-string
"^[\t\s]+" ""
(buffer-substring-no-properties (point-min) (point-max)))
"\\(\r\n\\|[\n\r]\\)" t)) xs)))
(+company:buffer-list-with-major-mode))))))
Bindings
(map!
(:prefix "C-x"
:i "C-l" #'+company/whole-lines-all-buffers
:i "C-." #'+company/whole-lines))
(use-package! dired
:init
(setq dired-omit-files "^\\.?#\\|^\\.$\\|^\\.\\.$\\|\\.DS_Store$\\|flycheck_config.el"))
(after! async
(dired-async-mode 1))
(use-package! dired-x
:after dired
:config
(setq dired-omit-files
(concat dired-omit-files
;; Reason Compiled Files
"\\|\\.bs.js$")))
Automatically revert dired buffers.
(add-hook 'dired-mode-hook 'auto-revert-mode)
(after! dired
(defadvice dired-mark-read-file-name (after rv:dired-create-dir-when-needed (prompt dir op-symbol arg files &optional default) activate)
(when (member op-symbol '(copy move))
(let ((directory-name (if (< 1 (length files))
ad-return-value
(file-name-directory ad-return-value))))
(when (and (not (file-directory-p directory-name))
(y-or-n-p (format "directory %s doesn't exist, create it?" directory-name)))
(make-directory directory-name t))))))
Adds +dired-default-listing-switches
to reset the value.
(defconst +dired-default-listing-switches
"-ahl -t --group-directories-first")
(setq dired-listing-switches +dired-default-listing-switches)
(defun +dired|paste-dwim ()
"Paste data in the current directory."
(interactive)
(let ((file (read-string "Filename: "))
(last-clip-type (->> (shell-command-to-string "greenclip print")
(s-split "\n")
(-first-item))))
(unless (string= "" file)
(cond
((s-matches? "^image\\/png" last-clip-type)
(shell-command-to-string (template "xclip -selection clipboard -t image/png -o > <<file>>")))))
(dired-revert)))
(defun +dired|kill-dired-buffers ()
"Kills all dired buffers
Dired creates a buffer for every directory which it visits
Which is fine since you can easily switch between visited buffers
But at some time I want to purge those buffers"
(interactive)
(mapc (lambda (buffer)
(when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
(kill-buffer buffer)))
(buffer-list)))
(map! :after dired
:map dired-mode-map
:n "q" (cmd! (kill-buffer))
:n "Q" #'+dired|kill-dired-buffers)
Stay in normal mode when switching to wdired
(defun +dired|change-to-wdired-mode ()
"Simple forward to wdired-change-to-wdired-mode, but staying in normal mode."
(interactive)
(wdired-change-to-wdired-mode)
(evil-normal-state))
Map \
to change to wdired mode, like text mode in magit buffers.
(map!
:after dired
:map dired-mode-map
:n "\\" #'+dired|change-to-wdired-mode)
I’ve set dired-dwim-target
to t
, so it uses the other window as the target destination.
I undo this option with the interactive prefix argument, which can be accessed via SPC ucopy R/U
.
(defun +dired/dired-target-from-prefix (fn)
(let ((dired-dwim-target (if (eq (prefix-numeric-value current-prefix-arg) 4) ;; Single C-u
nil
dired-dwim-target)))
(call-interactively fn)))
(defun +dired|dired-do-copy (&optional arg)
(interactive "P")
(+dired/dired-target-from-prefix #'dired-do-copy))
(defun +dired|dired-do-rename (&optional arg)
(interactive "P")
(+dired/dired-target-from-prefix #'dired-do-rename))
(map! :after dired
:map dired-mode-map
:n "R" #'+dired|dired-do-rename
:n "C" #'+dired|dired-do-copy)
(defun dired-get-size ()
(interactive)
(let ((files (dired-get-marked-files)))
(with-temp-buffer
(apply 'call-process "du" nil t nil "-sch" files)
(message
"Size of all marked files: %s"
(progn (re-search-backward "\\(^[0-9.,]+[A-Za-z]+\\).*total$") (match-string 1))))))
(defun +dired/mouse-open-externally (event)
"Open marked dired file(s) at point with an external application. Open directories normally"
(interactive "e")
(+dired/mouse-open-externally event))
(defun +dired/mouse-open-externally (event &optional find-file-func find-dir-func)
"In Dired, visit the file or directory name you click on.
The optional arguments FIND-FILE-FUNC and FIND-DIR-FUNC specify
functions to visit the file and directory, respectively. If
omitted or nil, these arguments default to `find-file' and `dired',
respectively."
(interactive "e")
(or find-file-func (setq find-file-func '+dired|open-externally))
(or find-dir-func (setq find-dir-func 'dired))
(let (window pos file)
(save-excursion
(setq window (posn-window (event-end event))
pos (posn-point (event-end event)))
(if (not (windowp window))
(error "No file chosen"))
(set-buffer (window-buffer window))
(goto-char pos)
(setq file (dired-get-file-for-visit))
(if (file-directory-p file)
(or (and (cdr dired-subdir-alist)
(dired-goto-subdir file))
(progn
(select-window window (funcall find-dir-func file))))
(select-window window)
(funcall find-file-func)))))
(defun +dired|open-externally (&optional file)
"Open marked dired file(s) at point with an external application."
(interactive)
(let ((file-list (or file (dired-get-marked-files)))
(process-connection-type nil))
(--map (+my/nohup-shell-command "xdg-open" (s-wrap (expand-file-name it) "\"")) file-list)))
When you toggle the sorting via dired-sort-toggle-or-edit
the cursor stays at the current file, which is very disorienting.
With this function the cursor stays on the current line.
save-excursion
does not work in this function, it just throws to the top of the buffer.
The dired-line jumps at the end are used to jump the filename again.
(defun +dired|toggle-sort (&optional arg)
"Change sorting but stay on the same line."
(interactive "P")
(let ((pos (point)))
(cond
((eq arg 3)
(setq dired-listing-switches (symbol-value '+dired-default-listing-switches))
(setq dired-actual-switches (symbol-value '+dired-default-listing-switches))
(dired-sort-set-mode-line)
(revert-buffer))
((eq arg 2)
(setq dired-listing-switches
(read-string "Global ls switches (must contain -l): " dired-listing-switches))
(setq dired-actual-switches (symbol-value 'dired-listing-switches))
(dired-sort-set-mode-line)
(revert-buffer))
(arg
(setq dired-actual-switches
(read-string "Buffer ls switches (must contain -l): " dired-actual-switches)))
(t (dired-sort-toggle-or-edit)))
(goto-char pos)
(dired-next-line 1)
(dired-previous-line 1)))
(defun +dired|convert-image (&optional arg)
"Convert image files to other formats."
(interactive "P")
(assert (executable-find "convert") nil "Install imagemagick")
(let* ((dst-fpath)
(src-fpath)
(src-ext)
(last-ext)
(dst-ext))
(mapc
(lambda (fpath)
(setq src-fpath fpath)
(setq src-ext (downcase (file-name-extension src-fpath)))
(when (or (null dst-ext)
(not (string-equal dst-ext last-ext)))
(setq dst-ext (completing-read "to format: "
(seq-remove (lambda (format)
(string-equal format src-ext))
'("jpg" "png")))))
(setq last-ext dst-ext)
(setq dst-fpath (format "%s.%s" (file-name-sans-extension src-fpath) dst-ext))
(message "convert %s to %s ..." (file-name-nondirectory dst-fpath) dst-ext)
(set-process-sentinel
(start-process "convert"
(generate-new-buffer (format "*convert %s*" (file-name-nondirectory src-fpath)))
"convert" src-fpath dst-fpath)
(lambda (process state)
(if (= (process-exit-status process) 0)
(message "convert %s ✔" (file-name-nondirectory dst-fpath))
(message "convert %s ❌" (file-name-nondirectory dst-fpath))
(message (with-current-buffer (process-buffer process)
(buffer-string))))
(kill-buffer (process-buffer process)))))
(dired-map-over-marks (dired-get-filename) arg))))
Move all files and directories to the current directory, If everything goes well and the directory is empty, safely delete it. It might not work out though, when any of the entries has the same name, in that case throw a warning.
(defun +dired|ungroup-directory ()
"Move the marked files one folder upwards, never overwrite the files."
(interactive)
(->> (dired-get-marked-files)
(-map (lambda (x)
(let ((cmd (if (f-has-hidden-entry x)
"mv -n ./* ./.* ../"
"mv -n ./* ../")))
(shell-command (t! "cd \"<<x>>\"; <<cmd>>"))
(when (f-empty? x)
(dired-delete-file x))))))
(revert-buffer))
When I do gg
in dired, I mostly want to go to the first entry.
This function first goes to the first entry and then to the top of the buffer.
(defun +dired|goto-top ()
"Go to first directory, when already there go to first line."
(interactive)
(if (<= (line-number-at-pos) 3)
(goto-char (point-min))
(goto-line 3)
(dired-next-dirline 1)
(dired-prev-dirline 1)))
Dired Narrow allows narrowing a dired buffer to a subselection.
Widen can be restored with g
.
(use-package! dired-narrow
:after dired
:config
(bind-key "C-c C-n" #'dired-narrow))
(use-package! dired-subtree
:after dired
:config
(evil-define-key 'normal dired-mode-map
(kbd "<tab>") (cmd! () (dired-subtree-toggle)
;; Fix for dired-all-the-icons not showing up
(dired-revert))
(kbd "<backtab>") (cmd! () (dired-subtree-cycle)
(dired-revert))
(kbd "gh") 'dired-subtree-up))
(use-package! dired-filter
:after dired
:config
(setq dired-filter-saved-filters
(quote (("images"
(extension "jpg" "png" "gif"))
("media"
(extension "mp3" "mp4" "MP3" "MP4" "avi" "mpg" "flv" "ogg" "wmv" "mkv" "mov" "wma"))
("archives"
(extension "zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar"))
("documents"
(extension "doc" "docx" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub")))))
(dired-filter-define created-today
"Show only files that are newer than today."
(:description "created-today")
(let ((case-fold-search nil))
(time-less-p (time-subtract (current-time) (* 60 60 24))
(file-attribute-modification-time (file-attributes file-name))))))
(map! :after dired
:map dired-mode-map
:ng "<C-return>" '+dired|open-externally
:ng (kbd "<mouse-2>") '+dired|open-externally
:gn "o" '+dired|toggle-sort
:n "p" '+dired|paste-dwim
:n "gg" '+dired|goto-top)
(define-key dired-mode-map (kbd "<down-mouse-1>") 'dired-mouse-find-file)
;; Always truncate ElDoc messages to one line. This prevents the echo
;; area from resizing itself unexpectedly when point is on a variable
;; with a multiline docstring.
(setq eldoc-echo-area-use-multiline-p nil)
;; Show ElDoc messages in the echo area immediately, instead of after
;; 1/2 a second.
(setq eldoc-idle-delay 0)
;; Disable eldoc mode
(global-eldoc-mode -1)
Custom eshell aliases.
(setq +eshell-aliases
'(("q" "exit")
("f" "find-file $1")
("bd" "eshell-up $1")
("rg" "rg --color=always $*")
("ag" "ag --color=always $*")
("l" "ls -lh")
("ll" "ls -lah")
("gs" "git status")
("groot" "cd (projectile-project-root)")
("gc" "git commit")
("grha" "git reset --hard; git clean -f -d")
("clear" "clear-scrollback")))
(defun +eshell/cat (file)
"Like `cat' but output with Emacs syntax highlighting."
(with-temp-buffer
(insert-file-contents file)
(let ((buffer-file-name file))
(delay-mode-hooks
(set-auto-mode)
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
(with-no-warnings
(font-lock-fontify-buffer)))))
(buffer-string)))
(add-to-list '+eshell-aliases '("cat" "+eshell/cat $1"))
(after! evil-snipe
(setq evil-snipe-repeat-keys t))
(setq shr-width 100)
(use-package! jest
:after (js2-mode)
:hook (js2-mode . jest-minor-mode)
:config
(set-popup-rule! "^\\*jest\\*"
:side 'right
:size 0.5
:select nil :quit 'current :ttl nil))
(use-package! git-lens
:commands (git-lens))
(use-package! symex
:commands (symex-mode symex-mode-interface))
(use-package! indium
:commands (indium-connect indium-launch)
:init
(setq indium-chrome-use-temporary-profile t)
(setq indium-chrome--default-data-dir (f-join (getenv "XDG_CACHE_HOME") "inidum-chrome-profile"))
(setq indium-chrome-data-dir (f-join (getenv "XDG_CACHE_HOME") "inidum-chrome-profile"))
(map! :map (js2-mode-map rjsx-mode-map)
:localleader
(:prefix ("i" . "Indium")
:desc "Console" "c" #'indium-switch-to-repl-buffer
:desc "Launch" "l" #'indium-launch
:desc "Launch" "q" #'indium-quit
:desc "Add breakpoint" "r" #'indium-reload
(:prefix ("b" . "breakpoint")
:desc "Add" "b" #'indium-add-breakpoint
:desc "Conditional" "c" #'indium-add-conditional-breakpoint
:desc "Conditional" "e" #'indium-edit-breakpoint-condition
:desc "Conditional" "l" #'indium-list-breakpoints
:desc "Conditional" "0" #'indium-deactivate-breakpoints
:desc "Conditional" "1" #'indium-activate-breakpoints
:desc "Delete" "d" #'indium-remove-breakpoint
:desc "Delete all from buffer" "D" #'indium-remove-all-breakpoints-from-buffer
:desc "Edit Condition" "e" #'indium-toggle-breakpoint
:desc "Toggle" "t" #'indium-toggle-breakpoint)))
(map!
:map indium-inspector-mode-map
:n "-" #'indium-inspector-pop)
(map! :map indium-debugger-mode-map
:n "gr" #'indium-debugger-resume
:n "gi" #'indium-debugger-step-into
:n "go" #'indium-debugger-step-over
:n "ge" #'indium-debugger-evaluate
:n "gl" #'indium-debugger-locals)
:config
(set-popup-rule! "^\\*JS REPL*" :size 0.3)
(set-popup-rule! "^\\*JS Debugger Locals*" :size 0.3))
Serves the current buffer live over http.
(use-package! impatient-mode
:commands impatient-mode)
When narrowing to region or defun, make it in an indirect other window.
Source:
(use-package! narrow-indirect
:init
(global-set-key (kbd "C-x n n") 'ni-narrow-to-region-indirect-other-window)
(global-set-key (kbd "C-x n d") 'ni-narrow-to-defun-indirect-other-window))
Always show actions in hydra.
(setq ivy-read-action-function #'ivy-hydra-read-action)
Adds general bindings to ivy.
MOD + ↩
will insert the current item and continue with a new instance of the same ivy buffer.
CTRL + ↩
will abort completion and insert the inserted text.
(map!
:after ivy
:map ivy-minibuffer-map
"<s-return>" 'ivy-call
"<C-tab>" 'minibuffer-complete
"<C-return>" 'ivy-immediate-done
"M-m" 'ivy-mark)
Find file on enter press for ivy-occur-mode
(map! :map ivy-occur-mode-map
:gni "RET" #'ivy-occur-press-and-switch)
(defun +ivy/counsel-project-file-jump (x)
"Jump to file in project"
(interactive)
(counsel-file-jump nil (f-join (projectile-project-root) x)))
(defun +ivy|search-buffers (buffer-list)
"Grep workspace buffers."
(interactive)
(-->
(or buffer-list (+workspace-buffer-list))
(cl-remove-if-not #'buffer-file-name it)
(-map #'buffer-file-name it)
(-map #'shell-quote-argument it)
(s-join " " it)
(counsel-grep-files it)))
(defun +ivy/search-buffer-list (&optional buffer-list)
(--> (or buffer-list ivy--old-cands)
(-log it)
(-map #'shell-quote-argument it)
(--map (f-join (doom-project-root) it) it)
(s-join " " it)
(counsel-grep-files it)))
(defun counsel-grep-files (buffer-file-names)
"Grep for a string in the file visited by the current buffer."
(interactive)
(counsel-require-program counsel-grep-base-command
(setq counsel-grep-command
(format counsel-grep-base-command "%s" buffer-file-names))
(message "%s" counsel-grep-command)
(let ((init-point (point))
res)
(unwind-protect
(setq res (ivy-read "grep: " #'counsel-grep-function
:dynamic-collection t
:require-match t
:keymap counsel-grep-map
:history 'counsel-grep-history
:re-builder #'ivy--regex
:action #'counsel-grep-action
:caller 'counsel-grep))
(unless res
(goto-char init-point))))))
(after! ivy
(ivy-set-actions
'+git|ivy-changed-files
'(("/" (lambda (&optional arg)
(+ivy/search-buffer-list))
"Grep Forward"))))
(defun +ivy|counsel-mounted-drives ()
"Counsel of mounted drives from udiskie."
(interactive)
(let ((default-directory (concat "/run/media/" (user-login-name) "/")))
(counsel-find-file)))
The cursor will steal the focus from ivy-posframe, breaking ivy completely…
This option moves the mouse cursor to 0x0, which is really unacceptable, but works for now.
(setq posframe-mouse-banish t)
(after! ivy
(defadvice! respect-case-fold-search (args)
:filter-return #'counsel--ag-extra-switches
(concat args (pcase ivy-case-fold-search
(`auto " -S ")
(`t " -i ")
(`nil " -s ")))))
(defun +json/is-last-key? ()
"Is the next line the last json key."
(save-excursion
(forward-line)
(+my/buffer-line-has "}")))
Insert JSON key in a JSON document This functions is dependant on the yasnippet: key
(defun +json/insert-key (&optional above?)
"Adds a new JSON key pair."
(let ((last-line? (+my/buffer-line-has ",$")))
;; Insert comma
(if (and (not last-line?) (not above?))
(replace-regexp "$" "," nil (point-at-bol) (point-at-eol)))
(end-of-line)
(if above?
(evil-insert-newline-above)
(evil-insert-newline-below))
(indent-according-to-mode)
(yas/insert-by-name "key")))
(defun +json|insert-key-above ()
"Function docstring"
(interactive)
(+json/insert-key t))
(defun +json|insert-key-below ()
"Function docstring"
(interactive)
(+json/insert-key nil))
Uses json-fix to autofix JSON files.
npm i -g json-fix
(defun +json|autofix-buffer ()
"Autofix json buffer"
(interactive)
(let ((b (if mark-active (min (point) (mark)) (point-min)))
(e (if mark-active (max (point) (mark)) (point-max))))
(shell-command-on-region b e
(template "json-fix --no-sort --spaces <<tab-width>>") (current-buffer) t)))
(map!
:after json-mode
:map json-mode-map
:gni "<C-return>" #'+json|insert-key-below
:gni "<C-S-return>" #'+json|insert-key-above)
All are these are distracting and not helpful.
- Removes all popup UIs
- Remove signature message
- Remove lsp flycheck
(setq lsp-eldoc-render-all nil
lsp-eldoc-enable-hover nil
lsp-eldoc-enable-signature-help nil
lsp-eldoc-prefer-signature-help nil
lsp-inhibit-message t
lsp-eldoc-enable-hover nil
;; Disable Signature
lsp-signature-auto-activate nil
lsp-signature-render-documentation nil
lsp-signature-doc-lines 1
lsp-highlight-symbol-at-point nil
;; Disable make error highlighting
lsp-prefer-flymake nil)
;; (setq-hook! 'js2-mode lsp-eldoc-enable-hover nil)
(setq lsp-on-idle-hook nil)
LSP manually disables all checkers and chooses it’s own.
This way you can’t add checkers after your regular hooks like js2-hook
.
So I fix the checker manually for each mode after lsp was loaded.
(after! lsp-mode
(remove-hook 'lsp-mode-hook #'+lsp-init-flycheck-or-flymake-h))
(defun +js/fix-checker ()
"Fix LSP overwritten checkers."
(interactive)
(when (-contains? '(js2-mode rjsx-mode) major-mode)
(flycheck-select-checker 'javascript-eslint)))
(add-hook 'lsp-mode-hook #'+js/fix-checker)
(setq mu4e-bookmarks
`(("flag:unread AND NOT flag:trashed AND NOT maildir:/Spam" "Unread messages" ?u)
("date:today..now AND NOT maildir:/Spam" "Today's messages" ?T)))
(setq shr-use-colors nil)
(setq mu4e-maildir-shortcuts
'(("/mailbox/work/INBOX" . ?i)
("/mailbox/work/Sent Mail" . ?s)
("/mailbox/work/Trash" . ?t)
("/mailbox/work/All Mail" . ?a)))
(setq mu4e-headers-leave-behavior 'apply)
Creates a new git workspace from a branch.
Automatically adds .projectfile
and opens a new doom workspace.
(defun magit-worktree-branch-project-worktree (branch start-point &optional force)
"Create a new BRANCH and check it out in a new worktree at PATH in a new workspace."
(interactive
`(,@(butlast (magit-branch-read-args "Create and checkout branch"))
,current-prefix-arg))
(let* ((worktree-path (f-join (projectile-project-root) ".worktrees"))
(path (f-join (projectile-project-root) ".worktrees" branch)))
(when (not (f-exists-p worktree-path))
(mkdir worktree-path t))
(magit-run-git "worktree" "add" (if force "-B" "-b")
branch (expand-file-name path) start-point)
(f-touch (f-join path ".projectile"))
(+workspace-new branch)
(+workspace-switch branch)
(magit-diff-visit-directory path)
(projectile-add-known-project path)
path))
Show the original file when visiting a revision buffer. E.g.: When showing a diff from a commit, you may want to edit that file.
(defun magit-revision-show-original-file ()
"Show the orginal file from a revision buffer
If possible also go to the pointing line"
(interactive)
(when magit-buffer-file-name
(let ((file-name magit-buffer-file-name)
(line-number (line-number-at-pos)))
(if current-prefix-arg
(delete-other-windows))
(find-file file-name)
(goto-line line-number))))
Show a list of the changed files in the current branch. For now only works on branches that were directly forked from master.
(defun +git/new-files ()
"List of added files in the current branch."
(shell-command-to-list "git ls-files -om --exclude-standard"))
(defun +git/modfied-files (&optional branch)
"Get a list of modified files from the BRANCH to head."
(shell-command-to-list
(template "git --no-pager diff --no-renames --name-only --no-merges <<(magit-rev-parse \"HEAD\")>> <<branch>>;")))
(defun +git/changed-branch-files (branch)
"Get a list of new and modified files from BRANCH to head."
(->> (+git/modfied-files branch)
(-concat (+git/new-files))
(-uniq)
(-filter
(lambda (x)
(let ((default-directory (projectile-project-root)))
(f-exists? x))))))
(defun +git|ivy-changed-files (&optional branch)
(interactive)
(let ((enable-recursive-minibuffers t))
(ivy-read (template "Changed files for <<(or branch (magit-get-current-branch))>>:")
(+git/changed-branch-files (or "origin/master"))
:require-match t
:history 'file-name-history
:action counsel-projectile-find-file-action
:caller '+git|ivy-changed-files)))
(defun +git|undo ()
"Soft reset current git repo to HEAD~1."
(interactive)
(magit-reset-soft "HEAD~1"))
For work I need remote branches with a date prefix.
(defun +git|push-dated (&optional branch)
"Pushes the given the current BRANCH with a dated prefix
my-branch-name -> 19-01-my-branch-name
When no BRANCH is given, take the current one."
(interactive)
(let* ((branch (or branch (magit-get-current-branch)))
(date (format-time-string "%y-%m"))
(remote (template "origin/<<date>>-<<branch>>")))
(magit-git-push branch remote "--set-upstream")
remote))
When I’m on the log view, I want to quickly diff it against the currently checked out branch.
The transient shortcut for this is d R
define here.
(defun +magit|diff-range-from-current-branch ()
"Ranged diff from the checked out branch to the commit at point."
(interactive)
(magit-diff-range (template "<<(magit-commit-at-point)>>..<<(magit-get-current-branch)>>")))
(defun +magit|diff-range-from-pullreq ()
"Ranged diff from the pull request under point."
(interactive)
(-some->> (forge-current-topic)
(forge--pullreq-range)
(magit-diff-range)))
(defun +magit|delete-review-branches ()
"Delete all review branches that no longer have an upstream."
(interactive)
(->> (magit-list-branches)
(--filter (s-starts-with? "refs/heads/REVIEW" it))
(--map (magit-name-local-branch it))
(--reject (magit-get-upstream-branch it))
(--each (lambda (x) (magit-branch-delete x t)))))
(defun +magit|checkout-review-branch (&optional branch start-point)
"Create a branch with review prefix for easy cleanup afterwards."
(interactive)
(let* ((remotes (magit-list-remote-branch-names))
(atpoint (magit-branch-at-point))
(branch (magit-completing-read
"Checkout branch" remotes
nil nil nil 'magit-revision-history
(or (car (member atpoint remotes))
(and atpoint
(car (member (and (string-match "[^/]+/" atpoint)
(substring atpoint (match-end 0)))
remotes))))))
(review-branch-name (s-replace "origin/" "REVIEW-" branch)))
;; HACK Workaround where the buffer cant be read
(advice-remove 'magit-checkout #'+magit-revert-repo-buffers-deferred-a)
(magit-checkout branch)
(when (magit-anything-modified-p)
(user-error "Cannot checkout when there are uncommitted changes"))
(if (-contains? (magit-list-local-branch-names) review-branch-name)
(magit-branch-checkout review-branch-name)
(magit-branch-and-checkout (s-replace "origin/" "REVIEW-" branch) branch))))
Cleans up all merged and review branches
(defun +magit|cleanup-branches (&optional base-branch)
"Remove all merged and review branches."
(interactive)
(+magit|delete-review-branches)
(let* ((base-branch (or base-branch "master")))
(call-interactively #'+magit|delete-review-branches)
(deferred:$
(deferred:process-shell (template "git branch --merged | egrep -v \"(^\\*|<<base-branch>>)\" | xargs git branch -d"))
(deferred:nextc it
(magit-status-maybe-update-revision-buffer)))))
(defun +magit|branches-by-user (&optional ignore-review?)
"List all branches by user.
Universal argument to ignore review branches."
(interactive "P")
(let ((branches
(->>
(concat "git for-each-ref"
" --sort=-committerdate"
" --format='%(committerdate) %(authorname) %(refname)'"
" --sort=-committerdate"
;; refs/remotes/origin/ for remote branches
" refs/heads"
" | grep -e 'Florian Schroedl'")
(shell-command-to-string)
(s-split "\n")
(-butlast)
(-map (lambda (x) (->> (s-match ".*Florian Schroedl refs\\/heads\\/\\(.*\\)" x)
(-last-item))))
((lambda (xs)
(if ignore-review?
(--reject (s-starts-with? "REVIEW" it) xs)
xs))))))
(ivy-read "Checkout: " branches :action #'magit-checkout)))
(use-package! forge
:commands (forge-browse-commit))
Loads commit template from ./git/TEMPLATE
if the file exist.
(defun +git/template-file (branch)
(when-let* ((dir (+git/find-root-git-dir))
(entries (f-entries dir (lambda (x) (s-starts-with? "TEMPLATE" (f-base x)))))
(branch-template (template "TEMPLATE-<<branch>>")))
(-log entries)
(-log branch-template)
(or
(-find (lambda (x) (string= branch-template (f-base x))) entries)
(-find (lambda (x) (string= "TEMPLATE" (f-base x))) entries))))
(defun +git/load-git-template (&rest discard)
"When a TEMPLATE file in the git directory exists, insert it as a commit template."
(when (eq (point) (point-at-eol))
(-some->> (+git/template-file (magit-get-current-branch))
(f-read)
(s-trim)
(s-append " ")
(insert)))
(evil-insert-state))
And the hook
(add-hook! 'git-commit-setup-hook :append #'+git/load-git-template)
Find the root directory of a .git
repository
This also works for worktrees that are in a nested directory.
(defun +git/find-root-git-dir ()
"Find the root directory of a repository."
(-some->> (magit-toplevel)
(-f-join ".git")
(-id-when #'f-exists?)
((lambda (x)
(if (f-file? x)
(->> (magit-list-worktrees)
(car)
(car)
(-f-join ".git"))
x)))))
(advice-add #'magit-toggle-buffer-lock :after (lambda () (+my/bool-state-message 'magit-buffer-locked-p)))
Try two merge two branches in memory, to determine if there would be merge conflicts.
(defun +git|check-branch-for-merge-conflict (&optional source-branch target-branch)
"Try to merge SOURCE-BRANCH into TARGET-BRANCH, return list of conflicting files when there are any.
This will try to merge in memory, so no index files will be created.
SOURCE-BRANCH defaults to the current branch.
TARGET-BRANCH defaults to origin/master."
(interactive)
(let* ((source-branch (or source-branch (magit-get-current-branch)))
(target-branch (or target-branch "origin/master"))
(merge-base (shell-command-to-list (t! "git merge-base <<target-branch>> <<source-branch>>"))))
(shell-command-to-list (t! "git merge-tree <<(car merge-base)>> <<target-branch>> <<source-branch>>"))))
(defun +magit/diff-copy-file-changes ()
"Copies over contents of diff at point to the curent file system."
(interactive)
(save-excursion
(let ((buffer (call-interactively #'magit-diff-visit-file)))
(with-current-buffer buffer
(make-directory (f-dirname magit-buffer-file-name) t)
(f-write (substring-no-properties (buffer-string)) 'utf-8 magit-buffer-file-name)))
(kill-buffer)))
Restores functionality when editing magit buffers as text. For now only the toggle functionality is needed.
(defvar +magit-evil-edit-mode-map (make-sparse-keymap))
(define-minor-mode +magit-evil-edit-mode ""
:keymap +magit-evil-edit-mode-map)
(map! :map +magit-evil-edit-mode-map
:n [tab] #'magit-section-toggle)
(after! magit
:config
(setq
magit-save-repository-buffers 'dontask
magithub-clone-default-directory "~/Code/Repositories"
git-commit-summary-max-length 120))
(after! forge
:config
(setq forge-database-file (f-join doom-local-dir "forge-database.sqlite")))
My workflow for navigating diffs
Use z1
to fold all diffs to their file headers and press’s {
or }
to
- Refold all sections
- Go to the next section
- Unfold everything in the current section
Then use ]
to navigate the sections
(defun floscr:magit-jumpunfold-section (&optional forward)
"Fold all section. Go to next section when FORWARD. Show all children"
(interactive)
(magit-section-show-level-1-all)
(call-interactively (if forward #'magit-section-forward-sibling #'magit-section-backward-sibling))
(call-interactively #'magit-section-show-children))
(map!
(:map magit-diff-mode-map
:nv "}" (cmd! (floscr:magit-jumpunfold-section 't))
:nv "{" (cmd! (floscr:magit-jumpunfold-section))))
(map!
:after git-timemachine
:map git-timemachine-mode-map
:n "[[" #'git-timemachine-show-previous-revision
:n "]]" #'git-timemachine-show-previous-revision)
Disable accidentally quitting magit buffers with q
when the buffer is locked.
(defun +magit/disable-locked-quit (orig-fn &rest args)
(unless magit-buffer-locked-p
(apply orig-fn args)))
(advice-add #'magit-mode-bury-buffer :around #'+magit/disable-locked-quit)
You can add flags or commands to the magit interface transient here. To append something, just state the flag that you see in the transient popup as the 2nd argument.
(after! magit
(transient-append-suffix 'magit-log "-f" '("-0" "No merges" "--no-merges"))
(transient-append-suffix 'magit-push "p" '("d" "dated" +git|push-dated))
(transient-append-suffix 'magit-diff "d" '("R" "Diff range from current branch" +magit|diff-range-from-current-branch))
(transient-append-suffix 'magit-diff "d" '("P" "Pullrequest Range" +magit|diff-range-from-pullreq))
(transient-append-suffix 'magit-diff "d" '("f" "File" magit-diff-buffer-file))
(transient-append-suffix 'magit-commit
"-D" '("-D" "Override the author date" "--date=" transient-read-date))
(transient-append-suffix 'magit-branch "l" '("R" "Create review branch" +magit|checkout-review-branch))
(transient-append-suffix 'magit-branch "l" '("U" "My Branches" +magit|branches-by-user)))
Always keep markdown centered, without line numbers.
(use-package! markdown-mode
:init
(add-to-list 'auto-mode-alist '("\\.mdx\\'" . markdown-mode))
(setq markdown-fontify-code-blocks-natively t)
:config
(add-hook! markdown-mode
(hl-line-mode -1)
(visual-line-mode)
(visual-fill-column-mode)
(outline-minor-mode)
(setq visual-fill-column-width 90
display-line-numbers nil)
(setq line-spacing 2
fill-column 80))
(map! (:map markdown-mode-map
:n "<" #'markdown-promote
:n ">" #'markdown-demote)))
Adds EPUB reading mode wasamasa/nov.el: Major mode for reading EPUBs in Emacs
I want to keep the buffer centered, but let nov take care of breaking the text,
since this is much nicer then visual-line-mode
.
(defun my-nov-config ()
(setq line-spacing 5)
(face-remap-add-relative 'variable-pitch :family "Liberation Serif" :height 1.4)
(setq visual-fill-column-center-text t)
(setq visual-fill-column-width (+ nov-text-width 25))
(visual-fill-column-mode t))
(use-package! nov
:defer t
:init
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
(setq nov-text-width 75)
(setq nov-save-place-file (f-join doom-local-dir "nov-places"))
:config
(progn
(add-hook 'nov-mode-hook 'my-nov-config)))
The font can be installed via brew cask.
brew tap homebrew/cask-fonts
brew cask install font-liberation-sans
(defun npm-mode-npm-ci ()
"Run the 'npm install' command."
(interactive)
(npm-mode--exec-process "npm ci"))
;; Fix midnight colors for doom-one theme
(setq pdf-view-midnight-colors '("#BBC2CD" . "#282C34"))
(use-package! rainbow-mode
:commands (rainbow-mode))
(after! smerge-mode
:config
;; TODO This is broken after switching the theme but works for now
;; This fixes the smerge diff color is really bright an ugly
(set-face-attribute 'smerge-refined-added nil :foreground nil :background nil))
(use-package smerge-mode
:after hydra
:config
(defhydra unpackaged/smerge-hydra
(:color pink :hint nil :post (smerge-auto-leave))
"
^Move^ ^Keep^ ^Diff^ ^Other^
^^-----------^^-------------------^^---------------------^^-------
_n_ext _b_ase _<_: upper/base _C_ombine
_p_rev _u_pper _=_: upper/lower _r_esolve
^^ _l_ower _>_: base/lower _k_ill current
^^ _a_ll _R_efine
^^ _RET_: current _E_diff
"
("n" smerge-next)
("p" smerge-prev)
("b" smerge-keep-base)
("u" smerge-keep-upper)
("l" smerge-keep-lower)
("a" smerge-keep-all)
("RET" smerge-keep-current)
("\C-m" smerge-keep-current)
("<" smerge-diff-base-upper)
("=" smerge-diff-upper-lower)
(">" smerge-diff-base-lower)
("R" smerge-refine)
("E" smerge-ediff)
("C" smerge-combine-with-next)
("r" smerge-resolve)
("k" smerge-kill-current)
("ZZ" (lambda ()
(interactive)
(save-buffer)
(bury-buffer))
"Save and bury buffer" :color blue)
("q" nil "cancel" :color blue))
:hook (magit-diff-visit-file . (lambda ()
(when smerge-mode
(flycheck-mode -1)
(unpackaged/smerge-hydra/body)))))
Doom per default adds buffers to the current workspace on find-file
.
I want buffers added whenever I visit a buffer.
(after! persp-mode
(defun +workspace*add-special-buffer ()
(if-let* ((name (buffer-name))
(add-buffer? (or
;; Always add files to workspaces
(buffer-file-name)
;; Add src buffer
(s-matches? "\\*Org Src.*" name))))
(persp-add-buffer (current-buffer) (get-current-persp))))
(add-hook 'doom-switch-buffer-hook #'+workspace*add-special-buffer))
Enhancement of the default +workspace/switch-to
.
This allows quick deletion of workspaces from ivy with CTRL + BACKSPACE
.
(defvar counsel-workspace-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-<backspace>") #'+workspace/switch-to-delete-space)
map))
(defun +workspace//switch-to-delete-space (workspace)
(let* ((current-workspace-name (+workspace-current-name))
(new-workspace-name
(or (--first (string= current-workspace-name it) (+workspace-list-names)) "main")))
(+workspace/delete workspace)
(+workspace-switch new-workspace-name)
(+workspace/my-switch-to)))
(defun +workspace/switch-to-delete-space ()
(interactive)
(ivy-set-action #'+workspace//switch-to-delete-space)
(ivy-done))
(defun +workspace/my-switch-to ()
(interactive)
(ivy-read "Switch to workspace: "
(+workspace-list-names)
:keymap counsel-workspace-map
:action #'+workspace/switch-to))
(defun +workspace|close-others ()
"Close all other workspaces."
(interactive)
(--> (+workspace-list-names)
(--reject (string= (+workspace-current-name) it) it)
(-each it #'+workspace-delete))) ;
Most of the time you create workspaces from a project. But when the CWD has changed in that workspace, you would have to relocate to the projects cwd to find a file.
(defun +workspace/workspace-project-root (&optional arg)
"Gets the root dir for the current workspace"
(--find (s-match (concat (+workspace-current-name) "/$") it) projectile-known-projects))
(defun +workspace|find-workspace-project-file ()
"Projectile find file for the project named after the current workspace."
(interactive)
(cl-letf (((symbol-function 'projectile-project-root) #'+workspace/workspace-project-root))
(projectile-find-file)))
(defun +workspace|workspace-project-vc ()
"Projectile find file for the project named after the current workspace."
(interactive)
(let ((default-directory (+workspace/workspace-project-root)))
(magit-status)))
(defun +workspace/new-named ()
"Create a new named workspace."
(interactive)
(let ((name (read-string "New workspace name: ")))
(if name (+workspace/new name))))
(defun +workspace/remove-other-buffers (&optional keep-alive?)
"Kill or remove all other buffers from current workspace."
(interactive)
(--> (+workspace-buffer-list)
(--reject (eq (current-buffer) it) it)
(if keep-alive?
(persp-remove-buffer it)
(kill-buffer it))))
(defun +workspace|hide-other-buffers ()
"Hide all inactive buffers from the current workspace."
(interactive)
(+workspace/remove-other-buffers t))
(defun +workspace|kill-other-buffers ()
"Kill all interactive buffers from the current workspace."
(interactive)
(+workspace/remove-other-buffers))
(defun +workspace|hide-non-project-buffers ()
"Hide all file buffers that don't belong to the project workspace."
(interactive)
(let ((project-path (or (expand-file-name (+workspace/workspace-project-root))
(projectile-project-root))))
(-some--> (+workspace-buffer-list)
;; Dont remove non-remove buffers
(--filter (buffer-file-name it) it)
(--reject (s-contains? project-path (buffer-file-name it)) it)
(--each (persp-remove-buffer it) it))))
(map!
:map (wgrep-mode-map ivy-occur-grep-mode-map)
:n [return] #'compile-goto-error
:localleader
:desc "Remove line" "d" (cmd! (let ((inhibit-read-only t))
(+my/delete-current-line))))
(defun yas/insert-by-name (name)
(require 'noflet)
(noflet ((dummy-prompt
(prompt choices &optional display-fn)
(declare (ignore prompt))
(or (cl-find name choices :key display-fn :test #'string=)
(throw 'notfound nil))))
(let ((yas-prompt-functions '(dummy-prompt)))
(catch 'notfound
(yas-insert-snippet t)))))
Used with src snippet to auto fill the language from the previously used src language.
(defun +yas/org-src-lang ()
"Try to find the current language of the src/header at point.
Return nil otherwise."
(save-excursion
(pcase
(downcase
(buffer-substring-no-properties
(goto-char (line-beginning-position))
(or (ignore-errors (1- (search-forward " " (line-end-position))))
(1+ (point)))))
("#+property:"
(when (re-search-forward "header-args:")
(buffer-substring-no-properties
(point)
(or (and (forward-symbol 1) (point))
(1+ (point))))))
("#+begin_src"
(buffer-substring-no-properties
(point)
(or (and (forward-symbol 1) (point))
(1+ (point)))))
("#+header:"
(search-forward "#+begin_src")
(+yas/org-src-lang))
(_ nil))))
(defun +yas/org-last-src-lang ()
(save-excursion
(beginning-of-line)
(when (search-backward "#+begin_src" nil t)
(+yas/org-src-lang))))
(use-package! yasnippet
:init
(require 'doom-snippets nil t))
(use-package! ob-async
:after org-babel)
(use-package! proced-narrow
:after proced
:config
(map!
:map proced-mode-map
:n "/" #'proced-narrow))
Nixos paths can be very long, which is distracting in proced.
This shortens the nixos paths to {nix}
.
(defun +proced/remove-nixos-path-name (oldformat &rest xs)
(let ((xs (--map (->> it
(s-replace-regexp "/nix/store/[^/]+" "{nix}")
(s-replace-regexp (template "^/home/<<(user-login-name)>>") "~")
((lambda (x) (if (s-contains? "chromium" x) "{chromium}" x))))
xs)))
(apply oldformat xs)))
(advice-add #'proced-format-args :around #'+proced/remove-nixos-path-name)
(map! :map process-menu-mode-map
:n "gr" #'list-processes)
(map! :map proced-mode-map
:n "gr" #'proced)
Edit nix script regions in an indirect buffer just like org-edit-special
.
(defun +nix|edit-indirect ()
"Edit script in an indirect buffer."
(interactive)
(and-let* ((beg (save-excursion
(search-backward "''\n" nil t)
(forward-char 3)
(point)))
(end (save-excursion
(re-search-forward "''" nil t)
(previous-line 1)
(goto-char (point-at-eol))
(point))))
(+indirect-indent|edit beg end #'sh-mode)))
(map! :map nix-mode-map "C-c '" '+nix|edit-indirect)
(set-popup-rule! "^\\*edit-indirect" :ignore t)
(map! :g "C-±" #'+popup/raise)
(map! :nm "C-z" nil)
(map!
(:map override
:g "s-n" #'evil-buffer-new
:g "s-;" #'eval-expression
:g "s-a" #'mark-whole-buffer
:g "s-s" #'save-buffer
:g "s-v" #'yank
:g "s-x" #'execute-extended-command
:g "s-y" #'helm-show-kill-ring
;; Text scale
:g "s-=" #'doom/increase-font-size
:g "s--" #'doom/decrease-font-size
:g "s-0" #'doom/reset-font-size))
I almost always want global search and replace, doom changed this in 1a6f50864.
To undo this behavior just add g
flag
(setq evil-ex-substitute-global t)
Replace the current selection with a register.
Press gr
with a following motion character like w
.
(use-package! evil-replace-with-register
:config
(setq evil-replace-with-register-key (kbd "gr"))
(define-key evil-normal-state-map
evil-replace-with-register-key 'evil-replace-with-register)
(define-key evil-visual-state-map
evil-replace-with-register-key 'evil-replace-with-register))
q
for any type of quoteB
for curly bracesr
for square brackets
(after! evil
(require 'evil-textobj-anyblock)
(evil-define-text-object my-evil-textobj-anyblock-inner-quote
(count &optional beg end type)
"Select the closest outer quote."
(let ((evil-textobj-anyblock-blocks
'(("'" . "'")
("\"" . "\"")
("`" . "`")
("“" . "”"))))
(evil-textobj-anyblock--make-textobj beg end type count nil)))
(evil-define-text-object my-evil-textobj-anyblock-a-quote
(count &optional beg end type)
"Select the closest outer quote."
(let ((evil-textobj-anyblock-blocks
'(("'" . "'")
("\"" . "\"")
("`" . "`")
("“" . "”"))))
(evil-textobj-anyblock--make-textobj beg end type count t)))
(define-key evil-inner-text-objects-map "q" 'my-evil-textobj-anyblock-inner-quote)
(define-key evil-outer-text-objects-map "q" 'my-evil-textobj-anyblock-a-quote)
(define-key evil-inner-text-objects-map "r" 'evil-inner-bracket)
(define-key evil-inner-text-objects-map "B" 'evil-inner-curly))
Text objects for lines after or before the current line, that have the same or deeper indent.
(defun evil-indent-plus--line-down-indent-range (&optional point)
(require 'evil-indent-plus)
(let* ((range (evil-indent-plus--same-indent-range point))
(base (point))
(begin (point)))
(list begin (cl-second range) base)))
(evil-define-text-object evil-indent-plus-i-indent-line-down (&optional count beg end type)
"Text object describing the block with the same (or greater) indentation as the current line,
and the line above, skipping empty lines."
:type line
(require 'evil-indent-plus)
(evil-indent-plus--linify (evil-indent-plus--line-down-indent-range)))
(define-key evil-inner-text-objects-map "+" 'evil-indent-plus-i-indent-line-down)
(defun evil-indent-plus--line-up-indent-range (&optional point)
(require 'evil-indent-plus)
(let* ((range (evil-indent-plus--same-indent-range point))
(base (point))
(begin (point)))
(list begin (cl-first range) base)))
(evil-define-text-object evil-indent-plus-i-indent-line-up (&optional count beg end type)
"Text object describing the block with the same (or greater) indentation as the current line,
and the line above, skipping empty lines."
:type line
(require 'evil-indent-plus)
(evil-indent-plus--linify (evil-indent-plus--line-up-indent-range)))
(define-key evil-inner-text-objects-map "-" 'evil-indent-plus-i-indent-line-up)
(defun load-evil-camel-case-motion ()
(require 'evil-little-word)
(define-key evil-normal-state-map (kbd "M-w") 'evil-forward-little-word-begin)
(define-key evil-normal-state-map (kbd "M-b") 'evil-backward-little-word-begin)
(define-key evil-operator-state-map (kbd "M-w") 'evil-forward-little-word-begin)
(define-key evil-operator-state-map (kbd "M-b") 'evil-backward-little-word-begin)
(define-key evil-visual-state-map (kbd "M-w") 'evil-forward-little-word-begin)
(define-key evil-visual-state-map (kbd "M-b") 'evil-backward-little-word-begin)
(define-key evil-visual-state-map (kbd "i M-w") 'evil-inner-little-word))
(load-evil-camel-case-motion)
(evil-define-operator +evil/sort (beg end)
"Sort lines with motion"
(interactive "<r>")
(sort-lines nil beg end))
(map!
(:after evil
:m "gS" #'+evil/sort))
Copied code from strickinato/evil-briefcase since it’s not maintained anymore.
Convert case motion via Z
.
Example pressing ZciW
would convert the inner Word
into camelCase
(evil-define-operator +evil/case-upper (beg end type)
"Convert text to upper case."
(if (eq type 'block)
(evil-apply-on-block #'s-upcase beg end nil)
(let ((str (s-upcase (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-lower (beg end type)
"Convert text to lowercase."
(if (eq type 'block)
(evil-apply-on-block #'s-downcase beg end nil)
(let ((str (s-downcase (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-camel-upper (beg end type)
"Convert text to CamelCase with a Capital C"
(if (eq type 'block)
(evil-apply-on-block #'s-upper-camel-case beg end nil)
(let ((str (s-upper-camel-case (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-camel-lower (beg end type)
"Convert text to camelCase with a small C"
(if (eq type 'block)
(evil-apply-on-block #'s-lower-camel-case beg end nil)
(let ((str (s-lower-camel-case (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-snake-lower (beg end type)
"Convert text to snake_case, slithering"
(if (eq type 'block)
(evil-apply-on-block #'s-snake-case beg end nil)
(let ((str (s-snake-case (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-snake-upper (beg end type)
"Convert text to SNAKE_CASE, AKA SCREAMING_SNAKE_CASE"
(if (eq type 'block)
(evil-apply-on-block #'s-snake-case beg end nil)
(let ((str (s-upcase (s-snake-case (buffer-substring-no-properties beg end)))))
(delete-region beg end)
(insert str))))
(evil-define-operator +evil/case-kebab-upper (beg end type)
"Convert text to KEBAB-KASE, mmmm... THICK MEAT"
(if (eq type 'block)
(evil-apply-on-block #'s-dashed-words beg end nil)
(let ((str (s-dashed-words (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert (upcase str)))))
(evil-define-operator +evil/case-kebab-lower (beg end type)
"Convert text to kebab-kase, mmmm... hyphens"
(if (eq type 'block)
(evil-apply-on-block #'s-dashed-words beg end nil)
(let ((str (s-dashed-words (buffer-substring-no-properties beg end))))
(delete-region beg end)
(insert str))))
(map!
(:after evil
:m "Zu" #'+evil/case-upper
:m "Zl" #'+evil/case-lower
:m "ZC" #'+evil/case-camel-upper
:m "Zc" #'+evil/case-camel-lower
:m "ZS" #'+evil/case-snake-upper
:m "Zs" #'+evil/case-snake-lower
:m "ZK" #'+evil/case-kebab-upper
:m "Zk" #'+evil/case-kebab-lower))
Matches any block with BEGIN_
, e.g.: SRC blocks.
(defun +evil-org/src-block ()
"Matches src blocks using org-element-context."
(-when-let* ((ctx (org-element-context))
(type (car ctx))
(ctx (cond
((equal type 'src-block) ctx)
((equal type 'example-block) ctx)
((equal type 'quote-block) ctx)
;; Inside quote blocks org-element-context matches the paragraphs
;; So we have to take the parent block to get the quote-block
((equal type 'paragraph)
(-some->> ctx
(nth 1)
((lambda (x) (doom-plist-get x :parent)))
(--id-when (equal (car it) 'quote-block)))))))
ctx))
(evil-define-text-object +evil-org-inner-src-block (count &optional beg end type)
"Select an org src/quote/example block object."
(evil-org-select-inner-element (+evil-org/src-block)))
(evil-define-text-object +evil-org-an-src-block (count beg end type)
"An org object.
Matches urls and table cells."
(evil-org-select-an-element (+evil-org/src-block)))
(after! evil-org
(evil-define-key '(visual operator) evil-org-mode-map
"ib" #'+evil-org-inner-src-block
"ab" #'+evil-org-an-src-block))
Fix window navigation for various modes.
I don’t like pressing C-w
or SPC w
as leader to navigate,
so I have to work around it:
(map!
:en "C-h" #'evil-window-left
:en "C-j" #'evil-window-down
:en "C-k" #'evil-window-up
:en "C-l" #'evil-window-right)
(map!
:map (image-mode-map
magit-diff-mode-map
magit-revision-mode-map
magit-status-mode-map
eshell-mode-map
evil-org-mode-map)
:en "C-h" #'evil-window-left
:en "C-j" #'evil-window-down
:en "C-k" #'evil-window-up
:en "C-l" #'evil-window-right)
(add-hook! 'eshell-first-time-mode-hook
(map!
:map eshell-mode-map
:en "C-h" #'evil-window-left
:en "C-j" #'evil-window-down
:en "C-k" #'evil-window-up
:en "C-l" #'evil-window-right))
(map!
:map org-agenda-mode-map
"C-h" #'evil-window-left
"C-j" #'evil-window-down
"C-k" #'evil-window-up
"C-l" #'evil-window-right)
(define-key minibuffer-local-map "\C-p" 'previous-history-element)
(define-key minibuffer-local-map "\C-n" 'next-history-element)
(map! :n "gb" #'evil-switch-to-windows-last-buffer)
(defun +evil|select-pasted ()
(interactive)
(let ((start-marker (evil-get-marker ?\[))
(end-marker (evil-get-marker ?\])))
(evil-visual-select start-marker end-marker)))
(map! :n "gp" #'+evil|select-pasted)
(map! :m "-" #'dired-jump)
(map! :map visual-line-mode-map
:nv "j" #'evil-next-visual-line
:nv "k" #'evil-previous-visual-line)
Insert Mode bindings, mostly unicode insertion and workaround for german umlaut.
(map! :i "A-y" #'helm-show-kill-ring)
(map!
:i "M-`" (cmd! (insert "°"))
:i "M-." (cmd! (insert "…"))
:i "M-^" (cmd! (insert "°"))
:i "M-l" (cmd! (insert "λ"))
:i "M-w" (cmd! (insert "⚠"))
:i "M-i" (cmd! (insert "ℹ")))
Global [
& ]
combinator bindings
(map!
:n "]F" #'dumb-jump-go
:n "[F" #'dumb-jump-back)
(map!
:n "]e" #'flycheck-next-error
:n "[e" #'flycheck-previous-error)
Custom evil text objects mostly stolen from Spacemacs|define-text-object-regexp.
(defmacro +evil/define-text-object-regexp (key name start-regexp end-regexp)
"Define a text object.
START-REGEXP and END-REGEXP are the boundaries of the text object."
(let ((inner-name (make-symbol (concat "evil-inner-" name)))
(outer-name (make-symbol (concat "evil-outer-" name))))
`(progn
(evil-define-text-object ,inner-name (count &optional beg end type)
(evil-select-paren ,start-regexp ,end-regexp beg end type count nil))
(evil-define-text-object ,outer-name (count &optional beg end type)
(evil-select-paren ,start-regexp ,end-regexp beg end type count t))
(define-key evil-inner-text-objects-map ,key (quote ,inner-name))
(define-key evil-outer-text-objects-map ,key (quote ,outer-name)))))
(+evil/define-text-object-regexp "~" "tilde" "~" "~")
(+evil/define-text-object-regexp "=" "equal" "=" "=")
(+evil/define-text-object-regexp "|" "bar" "|" "|")
(+evil/define-text-object-regexp "*" "star" "*" "*")
(+evil/define-text-object-regexp "$" "dollar" "$" "$")
(+evil/define-text-object-regexp "%" "percent" "%" "%")
(+evil/define-text-object-regexp "/" "slash" "/" "/")
(+evil/define-text-object-regexp "_" "underscore" "_" "_")
(+evil/define-text-object-regexp "-" "hyphen" "-" "-")
Changes the text matching inside quotes with q
motion (e.g. ciq
)
Change inner bracket with r
(after! evil
(require 'evil-textobj-anyblock)
(evil-define-text-object my-evil-textobj-anyblock-inner-quote
(count &optional beg end type)
"Select the closest outer quote."
(let ((evil-textobj-anyblock-blocks
'(("'" . "'")
("\"" . "\"")
("`" . "'")
("“" . "”"))))
(evil-textobj-anyblock--make-textobj beg end type count nil)))
(evil-define-text-object my-evil-textobj-anyblock-a-quote
(count &optional beg end type)
"Select the closest outer quote."
(let ((evil-textobj-anyblock-blocks
'(("'" . "'")
("\"" . "\"")
("`" . "'")
("“" . "”"))))
(evil-textobj-anyblock--make-textobj beg end type count t)))
(define-key evil-inner-text-objects-map "q" 'my-evil-textobj-anyblock-inner-quote)
(define-key evil-outer-text-objects-map "q" 'my-evil-textobj-anyblock-a-quote)
(define-key evil-inner-text-objects-map "r" 'evil-inner-bracket))
Extend the default evil ex commands from +commands.el
(after! evil-ex
:config
(evil-ex-define-cmd "W" #'evil-write))
(map! :nv "C-M-d" #'evil-multiedit-match-all)
(map! :n [tab] (general-predicate-dispatch nil
(and (featurep! :editor fold)
(save-excursion (end-of-line) (invisible-p (point))))
#'+fold/toggle
(fboundp 'evil-jump-item)
#'evil-jump-item)
:v [tab] (general-predicate-dispatch nil
(and (bound-and-true-p yas-minor-mode)
(or (eq evil-visual-selection 'line)
(not (memq (char-after) (list ?\( ?\[ ?\{ ?\} ?\] ?\))))))
#'yas-insert-snippet
(fboundp 'evil-jump-item)
#'evil-jump-item))
Evil pastes at the current cursor, which I don’t expect it to do. Most of the time it requires me to move into insert mode, move one character, and then do my function.
(defun +evil/normal-mode-paste-fix (fn &optional insert-char)
"Move forward one character and then paste."
(interactive)
(if (and (evil-normal-state-p)
(+my/buffer-line-has "[^\s]"))
(progn
(forward-char 1)
(when (and insert-char (looking-at insert-char))
(insert insert-char))
(let ((line (substring-no-properties (thing-at-point 'line))))
(call-interactively fn)
(when (eq line (substring-no-properties (thing-at-point 'line)))
(delete-char 1))))
(call-interactively fn)))
(map!
:leader
"RET" #'+bookmarks|main
"'" #'+popup/toggle
"au" #'undo-tree-visualize
"-" #'quick-calc)
(map!
:leader
(:prefix-map ("b" . "buffer")
:desc "Rename Buffer" "r" #'rename-buffer))
(map!
:leader
(:prefix-map ("f" . "file")
:desc "Open Private Config" "P" (cmd! (find-file (f-join doom-private-dir "config.org")))
(:prefix-map ("g" . "goto")
:desc "Drive" "/" #'+ivy|counsel-mounted-drives
:desc "Desktop" "D" (cmd! (find-file "~/Desktop"))
:desc "Code" "c" (cmd! (find-file "~/Code"))
:desc "Last captured" "c" (cmd! (org-goto-marker-or-bmk org-capture-last-stored-marker))
:desc "Downloads" "d" (cmd! (find-file "~/Downloads"))
:desc "Elfeed" "e" (cmd! (find-file (car rmh-elfeed-org-files)))
:desc "Scans" "s" (cmd! (find-file (f-dirname (f-expand +docs:index-file))))
:desc "Music" "m" (cmd! (find-file "~/Media/Music"))
:desc "Project Root" "p" (cmd! (find-file (projectile-project-root)))
:desc "Last refiled" "r" (cmd! (org-refile-goto-last-stored))
:desc "Tmp" "t" (cmd! (find-file "/tmp"))
:desc "Home" "h" (cmd! (find-file "~"))
:desc "Home" "h" (cmd! (find-file "~")))))
(map!
:leader
(:prefix-map ("g" . "git")
:desc "Amend Commit (No Message)" "A" (cmd! (magit-commit-amend "--no-edit"))
:desc "Blame" "B" #'magit-blame
:desc "Changed Files" "F" #'+git|ivy-changed-files
:desc "New Branch" "N" #'magit-branch-spinoff
:desc "Show revision original File" "O" #'magit-revision-show-original-file
:desc "Map-editor Changed Files" "T" (cmd! (+git|ivy-changed-files "map-editor"))
:desc "Amend Commit" "a" #'magit-commit-amend
:desc "Checkout" "b" #'magit-checkout
:desc "Diff" "d" #'magit-diff
:desc "Push" "p" #'magit-push
:desc "Undo" "u" #'+git|undo
:desc "Worktree Popup" "w" #'magit-worktree
(:prefix ("l" . "list")
:desc "List gists" "g" #'+gist:list
:desc "List submodules" "n" #'magit-list-submodules
:desc "List issues" "p" #'forge-list-issues
:desc "List pull requests" "r" #'forge-list-pullreqs
:desc "List pull awaiting review requests" "R" #'forge-list-requested-reviews
:desc "List notifications" "s" #'forge-list-notifications)))
(map!
:leader
(:prefix-map ("i" . "insert")
:desc "New Snippet" "S" #'+snippets/new
:desc "Killring" "y" #'helm-show-kill-ring))
(map!
:leader
(:desc "open" :prefix "o"
:desc "Calc" :g "c" #'calc
:desc "Calc" :g "C" #'=calendar
:desc "Transmission" :g "D" #'transmission
:desc "Elfeed" :g "e" #'+eshell/toggle
:desc "Eshell" :g "E" #'+rss|open
:desc "Irc" :g "i" #'=irc
:desc "Mail" :g "m" #'=mu4e
:desc "Proced" :g "p" #'proced
:desc "Snippet" :g "s" #'+snippets/edit))
(map!
:leader
(:prefix-map ("p" . "project")
:desc "Workspace Project Files" "P" #'+workspace|find-workspace-project-file
:desc "Project VC" "v" #'+workspace|workspace-project-vc
:desc "Project Bookmarks" "RET" #'+bookmarks))
(map!
:leader
(:prefix-map ("s" . "search")
:desc "Project for symbol" "P" #'+default/search-project-for-symbol-at-point))
(map!
:leader
(:prefix-map ("t" . "toggle")
:desc "Theme Dark/Light" "t" #'+doom|toggle-theme))
(map!
:leader
:desc "Split Vertical" "|" #'evil-window-vsplit
:desc "Split Horizontal" "-" #'evil-window-split
(:prefix-map ("w" . "window")
:desc "Delete" "d" #'delete-window
:desc "Ace Delete Windo" "D" #'ace-delete-window
:desc "New" "n" #'evil-window-new
:desc "Undo" "u" #'winner-undo
:desc "Redo" "r" #'winner-redo
:desc "Enlargen" "o" #'doom/window-enlargen
:desc "Toggle Split" "T" #'+window|toggle-split-direction
:desc "Split Vertical" "|" #'evil-window-vsplit
:desc "Split Horizontal" "_" #'evil-window-split
:desc "Move Left" "H" #'+evil/window-move-left
:desc "Move Down" "U" #'+evil/window-move-down
:desc "Move Up" "K" #'+evil/window-move-up
:desc "Move Right" "L" #'+evil/window-move-right
:desc "Set Height" "C-_" #'evil-window-set-height
:desc "Set Height" "C-|" #'evil-window-set-width
:desc "Swap" "SPC" #'ace-swap-window
:desc "Toggle Locked" "#" #'+my|toggle-window-dedicated
:desc "Toggle Locked" "." #'+popup/raise))
(map!
:leader
(:prefix-map ("TAB" . "workspace")
:desc "Switch to" "." #'+workspace/my-switch-to
:desc "Close others" "o" #'+workspace|close-others
:desc "Create" "c" #'+workspace/new-named
:desc "Rename" "," #'+workspace/rename
:desc "Project Files" "p" #'+workspace|find-workspace-project-file
:desc "Project VC" "v" #'+workspace|workspace-project-vc
:desc "Clone" "C" (cmd!
(+workspace/new (format "Clone: %s" (+workspace-current-name)) t)
(message "Cloned current workspace %s" (+workspace-current-name)))
:desc "Switch to last workspace" "0" #'+workspace/other))
(defun +yank/dired-path ()
"Returns the current dired entry or the buffer directory."
(or (dired-file-name-at-point) dired-directory))
(defun +yank/buffer-filename ()
"Returns the current buffers file name if possible."
(cond ((doom-dired-buffer-p (current-buffer)) (+yank/dired-path))
(buffer-file-name buffer-file-name)
(org-src-source-file-name org-src-source-file-name)
(t nil)))
(defun +yank/buffer-path ()
"Returns the current buffers path."
(or (+yank/buffer-filename) default-directory))
(defun +yank|filename ()
"Yank the buffer file name."
(interactive)
(or
(-some->> (+yank/buffer-filename)
;; When pointing at a directory at dired, I still take the directory
((lambda (x) (if (f-file? x)
(file-name-directory x)
x)))
(+my/kill-and-message))
(user-error "Error: Not a file bufer!")))
(defun +yank|base ()
"Yank the buffer files base name."
(interactive)
(or
(-some->> (+yank/buffer-filename)
(file-name-base)
(+my/kill-and-message))
(user-error "Error: Not a file bufer!")))
(defun +yank|directory ()
"Yank the buffer files directory."
(interactive)
(or
(-some->> (+yank/buffer-path)
(file-name-directory)
(+my/kill-and-message))
(user-error "Error: Not a file bufer!")))
(defun +yank|path ()
"Yank the buffer files path."
(interactive)
(or
(-some->> (+yank/buffer-path)
(+my/kill-and-message))
(user-error "Error: Not a file bufer!")))
(defun +yank|relative-to-project ()
"Yank the buffer path relative to the projectile-project-root."
(interactive)
(or
(-some->> (+yank/buffer-path)
(f-full)
((lambda (x)
(or
(->> (s-replace (projectile-project-root) "" x)
(-id-when #'s-present?))
x)))
(+my/kill-and-message))
(user-error "Error: Not a file bufer!")))
(map!
:leader
(:prefix-map ("y" . "Yank")
:desc "filename" "f" #'+yank|filename
:desc "base" "b" #'+yank|base
:desc "directory" "d" #'+yank|directory
:desc "path" "p" #'+yank|path
:desc "path" "y" #'+yank|path
:desc "relative to project" "r" #'+yank|relative-to-project))
(defun floscr|+eshell|init-keymap ()
"Setup additional custom eshell keybindings to already existing doom bindings. This must be done in a hook because eshell-mode
redefines its keys every time `eshell-mode' is enabled."
(map! :map eshell-mode-map
:in "C-p" #'eshell-previous-input
:in "C-n" #'eshell-next-input
:localleader "l" #'eshell/clear))
(add-hook 'eshell-first-time-mode-hook #'floscr|+eshell|init-keymap)
(map! :map emacs-lisp-mode-map
;;
;; Rearrange Sexps
:gni "M-;" #'symex-mode-interface
:n "s-k" (cmd! (sp-transpose-sexp)
(evil-previous-line))
:n "s-j" (cmd! (sp-push-hybrid-sexp)
(evil-next-line))
;; Eval Buffer
:n "s-r" #'eval-buffer
;; Slurp and barf
:n "g]" #'sp-slurp-hybrid-sexp
:n "g[" #'sp-forward-barf-sexp
:localleader
:desc "Ly Mode" "l" #'+ly-mode
:desc "Jump to tangled source" "j" #'org-babel-tangle-jump-to-org
:desc "Symex Mode" "s" #'symex-mode-interface
:desc "Raise sexp" "<" #'raise-sexp
:desc "Barf Sexp" ">" #'barf-sexp)
(map! :map sh-mode-map
:localleader
:desc "Eval Region" "e" #'sh-execute-region
:desc "Eval Region" "E" #'executable-interpret)
(map! :map magit-mode-map
:localleader
:desc "Toggle Magit Buffer Lock" "#" #'magit-toggle-buffer-lock)
(map! :map reason-mode-map
:localleader
:desc "Eval Region" "r" #'refmt)
(map! :map (mu4e-view-mode-map mu4e-headers-mode-map)
:localleader
:g "x" (cmd!
(require 'org-mu4e)
(org-mu4e-store-and-capture)))
(map! :map (mu4e-view-mode-map mu4e-headers-mode-map)
:localleader
:g "x" (cmd!
(require 'org-mu4e)
(org-mu4e-store-and-capture)))
(map! :map 'image-mode-map
:desc "Zoom in" :gn "+" #'image-increase-size
:desc "Zoom in" :gn "-" #'image-decrease-size
:desc "Zoom in" :gn "0" (cmd! (image-transform-set-scale 1)))
Since the minibuffer has no evil mode, i’ve got these bindings to help out:
M-c
: Copy the minibuffer lineM-v
: Paste from clipboard to minibuffer (Same asC-r 0
) This also removes trailing newlines
(defun evil-get-register-string (REGISTER)
"Get evil-register pure text content
Registers can be selected with ?letter
E.g.: ?* -> Clipboard Contents"
(evil-vector-to-string (evil-get-register REGISTER)))
(defun paste-evil-register-clipboard-pruned ()
"Paste the current clipboard pruned from newlines"
(interactive)
(insert (s-trim (shell-command-to-string "pbpaste")))
(doom/forward-to-last-non-comment-or-eol))
(defun copy-minibuffer-line ()
"Copies the minibuffer content to the clipboard"
(interactive)
(save-excursion
(doom/forward-to-last-non-comment-or-eol)
(set-mark-command nil)
(doom/backward-to-bol-or-indent)
(kill-ring-save (mark) (point))))
(defun setup-minibuffer ()
"Set up keybindings for the minibuffer"
(local-set-key (kbd "s-v") 'paste-evil-register-clipboard-pruned)
(local-set-key (kbd "s-c") 'copy-minibuffer-line))
(add-hook 'minibuffer-setup-hook 'setup-minibuffer)
;; (define-key! :keymaps +default-minibuffer-maps
;; "C-w" 'sp-backward-delete-word)
Fixes (void-function ad-Advice-newline-and-indent)
error for now.
I honestly don’t know where this comes from.
(map!
:map evil-org-mode-map
:after org
:i [return] #'org-return-and-maybe-indent)
I’m using woman
for manuals, but it throws this error on first launch
Warning (defvaralias): Overwriting value of ‘woman-topic-history’ by aliasing to ‘Man-topic-history’
Solution from here: Doom Discord question
(add-to-list 'warning-suppress-types '(defvaralias))