Merge branch 'develop' into add_shellcheck

This commit is contained in:
chrunchyjesus 2019-04-23 19:44:02 +02:00
commit e5e05f9d51
No known key found for this signature in database
GPG key ID: 0C364160C9308A88
584 changed files with 32999 additions and 15507 deletions

174
modules/README.org Normal file
View file

@ -0,0 +1,174 @@
#+TITLE: Doom Modules
* Table of Contents :TOC:noexport:
- [[#feature][:feature]]
- [[#completion][:completion]]
- [[#ui][:ui]]
- [[#editor][:editor]]
- [[#emacs][:emacs]]
- [[#tools][:tools]]
- [[#lang][:lang]]
- [[#app][:app]]
- [[#collab][:collab]]
- [[#config][:config]]
* :feature
Broad modules that bring essential IDE functionality to Emacs.
+ debugger: A (nigh-)universal debugger in Emacs
+ [[file:feature/eval/README.org][eval]]: REPL & code evaluation support for a variety of languages
+ [[file:feature/evil/README.org][evil]] =+everywhere=: Vim in Emacs
+ [[file:feature/file-templates/README.org][file-templates]]: Auto-inserted templates in blank new files
+ [[file:feature/lookup/README.org][lookup]] =+docsets=: Universal jump-to & documentation lookup backend
+ [[file:feature/snippets/README.org][snippets]]: A templating system for Emacs for lazy typers (aka programmers)
+ [[file:feature/workspaces/README.org][workspaces]]: Isolated workspaces
* :completion
Swappable completion modules for quickly narrowing down lists of candidates.
+ [[file:completion/company/README.org][company]] =+auto +childframe=: The ultimate code completion backend
+ helm =+fuzzy +childframe=: *Another* search engine for love and life
+ ido: The /other/ *other* search engine for love and life
+ [[file:completion/ivy/README.org][ivy]] =+fuzzy +childframe=: /The/ search engine for love and life
* :ui
Aesthetic modules that affect the Emacs interface or user experience.
+ [[file:ui/deft/README.org][deft]]:
+ [[file:ui/doom/README.org][doom]]:
+ [[file:ui/doom-dashboard/README.org][doom-dashboard]]:
+ [[file:ui/doom-quit/README.org][doom-quit]]:
+ fill-column:
+ [[file:ui/hl-todo/README.org][hl-todo]]:
+ indent-guides:
+ [[file:ui/modeline/README.org][modeline]]:
+ [[file:ui/nav-flash/README.org][nav-flash]]:
+ [[file:ui/neotree/README.org][neotree]]:
+ [[file:ui/ophints/README.org][ophints]]:
+ [[file:ui/popup/README.org][popup]] =+all +defaults=: Makes temporary/disposable windows less intrusive
+ pretty-code:
+ [[file:ui/tabbar/README.org][tabbar]]:
+ treemacs:
+ [[file:ui/unicode/README.org][unicode]]:
+ vc-gutter:
+ vi-tilde-fringe:
+ [[file:ui/window-select/README.org][window-select]]:
* :editor
Modules that affect and augment your ability to write and edit text.
+ [[file:editor/fold/README.org][fold]]: universal code folding
+ [[file:editor/format/README.org][format]] =+onsave=:
+ [[file:editor/lispy/README.org][lispy]]:
+ multiple-cursors:
+ [[file:editor/parinfer/README.org][parinfer]]:
+ rotate-text:
* :emacs
Modules that reconfigure packages or features built into Emacs
+ dired =+ranger +icons=:
+ electric:
+ eshell:
+ imenu:
+ term:
+ vc:
* :tools
Small modules that give Emacs access to external tools & services.
+ ansible:
+ docker:
+ [[file:tools/editorconfig/README.org][editorconfig]]:
+ [[file:tools/ein/README.org][ein]]:
+ flycheck: Live error/warning highlights
+ flyspell: Spell checking
+ gist:
+ [[file:tools/lsp/README.org][lsp]]:
+ macos:
+ magit:
+ make:
+ password-store:
+ pdf:
+ prodigy:
+ rgb:
+ terraform:
+ tmux:
+ upload:
+ [[file:tools/wakatime/README.org][wakatime]]:
+ vterm:
* :lang
Modules that bring support for a language or group of languages to Emacs.
+ agda:
+ assembly:
+ [[file:lang/cc/README.org][cc]] =+lsp=:
+ clojure:
+ common-lisp:
+ [[file:lang/coq/README.org][coq]]:
+ crystal:
+ [[file:lang/csharp/README.org][csharp]]:
+ data:
+ erlang:
+ elixir:
+ elm:
+ emacs-lisp:
+ [[file:lang/ess/README.org][ess]]:
+ [[file:lang/go/README.org][go]] =+lsp=:
+ [[file:lang/haskell/README.org][haskell]] =+intero +dante=:
+ hy:
+ [[file:lang/idris/README.org][idris]]:
+ java =+meghanada=:
+ [[file:lang/javascript/README.org][javascript]] =+lsp=:
+ julia:
+ kotlin:
+ [[file:lang/latex/README.org][latex]]:
+ ledger:
+ lua:
+ markdown:
+ [[file:lang/nim/README.org][nim]]:
+ nix:
+ [[file:lang/ocaml/README.org][ocaml]] =+lsp=:
+ [[file:lang/org/README.org][org]] =+attach +babel +capture +export +present +ipython=:
+ [[file:lang/perl/README.org][perl]]:
+ [[file:lang/php/README.org][php]] =+lsp=:
+ plantuml:
+ purescript:
+ python =+lsp=:
+ qt:
+ racket:
+ [[file:lang/rest/README.org][rest]]:
+ ruby =+lsp=:
+ [[file:lang/rust/README.org][rust]] =+lsp=:
+ scala:
+ [[file:lang/sh/README.org][sh]] =+fish +lsp=:
+ [[file:lang/solidity/README.org][solidity]]:
+ swift:
+ terra:
+ web =+lsp=:
+ vala:
* :app
Large, opinionated modules that transform and take over Emacs, i.e.
Doom-specific porcelains.
+ calendar:
+ [[file:app/email/README.org][email]] =+gmail=:
+ [[file:app/irc/README.org][irc]]:
+ rss =+org=:
+ twitter:
+ [[file:app/write/README.org][write]] =+wordnut +langtool=:
* :collab
Modules that enable collaborative programming over the internet.
+ floobits:
+ impatient-mode:
* :config
Modules that configure Emacs one way or another, or focus on making it easier
for you to customize it yourself.
+ literate:
+ [[file:config/default/README.org][default]] =+bindings +smartparens=:

View file

@ -0,0 +1,61 @@
;;; app/calendar/autoload.el -*- lexical-binding: t; -*-
(defvar +calendar--wconf nil)
(defun +calendar--init ()
(if-let* ((win (cl-loop for win in (doom-visible-windows)
if (string-match-p "^\\*cfw:" (buffer-name (window-buffer win)))
return win)))
(select-window win)
(call-interactively +calendar-open-function)))
;;;###autoload
(defun =calendar ()
"Activate (or switch to) `calendar' in its workspace."
(interactive)
(if (featurep! :feature workspaces)
(progn
(+workspace-switch "Calendar" t)
(doom/switch-to-scratch-buffer)
(+calendar--init)
(+workspace/display))
(setq +calendar--wconf (current-window-configuration))
(delete-other-windows)
(switch-to-buffer (doom-fallback-buffer))
(+calendar--init)))
;;;###autoload
(defun +calendar/quit ()
"TODO"
(interactive)
(if (featurep! :feature workspaces)
(+workspace/delete "Calendar")
(doom-kill-matching-buffers "^\\*cfw:")
(set-window-configuration +calendar--wconf)
(setq +calendar--wconf nil)))
;;;###autoload
(defun +calendar/open-calendar ()
"TODO"
(interactive)
(cfw:open-calendar-buffer
;; :custom-map cfw:my-cal-map
:contents-sources
(list
(cfw:org-create-source (doom-color 'fg)) ; orgmode source
)))
;;;###autoload
(defun +calendar*cfw:render-button (title command &optional state)
"render-button
TITLE
COMMAND
STATE"
(let ((text (concat " " title " "))
(keymap (make-sparse-keymap)))
(cfw:rt text (if state 'cfw:face-toolbar-button-on
'cfw:face-toolbar-button-off))
(define-key keymap [mouse-1] command)
(cfw:tp text 'keymap keymap)
(cfw:tp text 'mouse-face 'highlight)
text))

View file

@ -0,0 +1,56 @@
;;; app/calendar/config.el -*- lexical-binding: t; -*-
(defvar +calendar-org-gcal-secret-file
(expand-file-name "private/org/secret.el" doom-modules-dir)
"TODO")
(defvar +calendar-open-function #'+calendar/open-calendar
"TODO")
;;
;; Packages
(def-package! calfw
:commands (cfw:open-calendar-buffer)
:config
;; better frame for calendar
(setq cfw:face-item-separator-color nil
cfw:render-line-breaker 'cfw:render-line-breaker-none
cfw:fchar-junction ?╋
cfw:fchar-vertical-line ?┃
cfw:fchar-horizontal-line ?━
cfw:fchar-left-junction ?┣
cfw:fchar-right-junction ?┫
cfw:fchar-top-junction ?┯
cfw:fchar-top-left-corner ?┏
cfw:fchar-top-right-corner ?┓)
(define-key cfw:calendar-mode-map "q" #'+calendar/quit)
(add-hook 'cfw:calendar-mode-hook #'doom|mark-buffer-as-real)
(add-hook 'cfw:calendar-mode-hook 'hide-mode-line-mode)
(advice-add #'cfw:render-button :override #'+calendar*cfw:render-button))
(def-package! calfw-org
:commands (cfw:open-org-calendar
cfw:org-create-source
cfw:open-org-calendar-withkevin
my-open-calendar))
(def-package! org-gcal
:commands (org-gcal-sync
org-gcal-fetch
org-gcal-post-at-point
org-gcal-delete-at-point)
:config
(load-file +calendar-org-gcal-secret-file)
;; hack to avoid the deferred.el error
(defun org-gcal--notify (title mes)
(message "org-gcal::%s - %s" title mes)))
;; (def-package! alert)

View file

@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; app/calendar/packages.el
(package! calfw)
(package! calfw-org)
(package! org-gcal)

View file

@ -0,0 +1,71 @@
#+TITLE: `=Calendar App`
* Setup sync between google calendar and org file
:PROPERTIES:
:ID: 5E190E8A-CA26-4679-B5F8-BF9CFD289271
:END:
- Checkout https://github.com/myuhe/org-gcal.el, put the following content in a file ~secret.el~ and set the variable `+calendar-org-gcal-secret-file` to the path of that file.
#+BEGIN_SRC emacs-lisp
(setq org-gcal-client-id "your-id-foo.apps.googleusercontent.com"
org-gcal-client-secret "your-secret"
org-gcal-file-alist '(("your-mail@gmail.com" . "~/schedule.org")
("another-mail@gmail.com" . "~/task.org")))
#+END_SRC
* Doom faces
:PROPERTIES:
:ID: 8223894E-EA68-4259-A2EA-AF7E3653C610
:END:
I'm using the following setting:
#+BEGIN_SRC emacs-lisp
;; calfw
(cfw:face-title :foreground blue :bold bold :height 2.0 :inherit 'variable-pitch)
(cfw:face-header :foreground (doom-blend blue bg 0.8) :bold bold)
(cfw:face-sunday :foreground (doom-blend red bg 0.8) :bold bold)
(cfw:face-saturday :foreground (doom-blend red bg 0.8) :bold bold)
(cfw:face-holiday :foreground nil :background bg-alt :bold bold)
(cfw:face-grid :foreground vertical-bar)
(cfw:face-periods :foreground yellow)
(cfw:face-toolbar :foreground nil :background nil)
(cfw:face-toolbar-button-off :foreground base6 :bold bold :inherit 'variable-pitch)
(cfw:face-toolbar-button-on :foreground blue :bold bold :inherit 'variable-pitch)
(cfw:face-default-content :foreground fg)
(cfw:face-day-title :foreground fg :bold bold)
(cfw:face-today-title :foreground bg :background blue :bold bold)
(cfw:face-default-day :bold bold)
(cfw:face-today :foreground nil :background nil :bold bold)
(cfw:face-annotation :foreground violet)
(cfw:face-disable :foreground grey)
(cfw:face-select :background region)
#+END_SRC
* Adjust calendar to be included
:PROPERTIES:
:ID: D734975C-4B49-4F66-A088-AB2707A77537
:END:
Checkout example from https://github.com/kiwanami/emacs-calfw
#+BEGIN_SRC emacs-lisp
(defun my-open-calendar ()
(interactive)
(cfw:open-calendar-buffer
:contents-sources
(list
(cfw:org-create-source "Green") ; orgmode source
(cfw:howm-create-source "Blue") ; howm source
(cfw:cal-create-source "Orange") ; diary source
(cfw:ical-create-source "Moon" "~/moon.ics" "Gray") ; ICS source1
(cfw:ical-create-source "gcal" "https://..../basic.ics" "IndianRed") ; google calendar ICS
)))
#+END_SRC
Specifically, if you want to adjust the org files to be included, use a ~let~ binding to set the ~org-agenda-files~ like below:
#+BEGIN_SRC emacs-lisp
;;;###autoload
(defun cfw:open-org-calendar-with-cal1 ()
(interactive)
(let ((org-agenda-files '("/path/to/org/" "/path/to/cal1.org")))
(call-interactively '+calendar/open-calendar)))
;;;###autoload
(defun cfw:open-org-calendar-with-cal2 ()
(interactive)
(let ((org-agenda-files '("/path/to/org/" "/path/to/cal2.org")))
(call-interactively '+calendar/open-calendar)))
#+END_SRC

View file

@ -0,0 +1,45 @@
;;; app/email/+gmail.el -*- lexical-binding: t; -*-
(after! mu4e
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
(setq mu4e-sent-messages-behavior 'delete
;; don't need to run cleanup after indexing for gmail
mu4e-index-cleanup nil
;; because gmail uses labels as folders we can use lazy check since
;; messages don't really "move"
mu4e-index-lazy-check t)
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+email|gmail-fix-flags'.
;;
;; Gmail will handle the rest.
(defun +email--mark-seen (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))
(delq (assq 'delete mu4e-marks) mu4e-marks)
(setf (alist-get 'trash mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+email--mark-seen)
;; Refile will be my "archive" function.
(alist-get 'refile mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+email--mark-seen))
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(defun +email|gmail-fix-flags (mark msg)
(pcase mark
(`trash (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
(`refile (mu4e-action-retag-message msg "-\\Inbox"))
(`flag (mu4e-action-retag-message msg "+\\Starred"))
(`unflag (mu4e-action-retag-message msg "-\\Starred"))))
(add-hook 'mu4e-mark-execute-pre-hook #'+email|gmail-fix-flags))

View file

@ -1,42 +1,75 @@
#+TITLE: :app email
#+TITLE: app/email
#+DATE: April 8, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[MacOS][MacOS]]
- [[Arch Linux][Arch Linux]]
- [[Features][Features]]
- [[Configuration][Configuration]]
- [[offlineimap][offlineimap]]
- [[mbsync][mbsync]]
* Description
This module makes Emacs an email client, using ~mu4e~.
#+begin_quote
I want to live in Emacs, but as we all know, living is incomplete without email. So I prayed to the text editor gods and they (I) answered. Emacs+evil's editing combined with org-mode for writing emails? /Yes please./
I want to live in Emacs, but as we all know, living is incomplete without email.
So I prayed to the text editor gods and they (I) answered. Emacs+evil's editing
combined with org-mode for writing emails? /Yes please./
It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
WARNING: my config is gmail/gsuite oriented, and since Google has its own opinions on the IMAP standard, it is unlikely to translate to other hosts.
It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email
via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
#+end_quote
* Table of Contents :TOC:
- [[#install][Install]]
- [[#macos][MacOS]]
- [[#arch-linux][Arch Linux]]
- [[#dependencies][Dependencies]]
** Module Flags
+ ~+gmail~ Enables gmail-specific configuration.
* Install
** Plugins
+ [[https://github.com/agpchil/mu4e-maildirs-extension][mu4e-maildirs-extension]]
* Prerequisites
This module requires:
+ ~offlineimap~ (to sync mail with)
+ Either ~mbsync~ (default) or ~offlineimap~ (to sync mail with)
+ ~mu~ (to index your downloaded messages)
** MacOS
#+BEGIN_SRC sh :tangle (if (doom-system-os 'macos) "yes")
#+BEGIN_SRC sh
brew install mu --with-emacs
# And one of the following
brew install isync # mbsync
brew install offlineimap
#+END_SRC
** Arch Linux
#+BEGIN_SRC sh :dir /sudo:: :tangle (if (doom-system-os 'arch) "yes")
sudo pacman --noconfirm --needed -S offlineimap mu
#+BEGIN_SRC sh
sudo pacman --noconfirm --needed -S mu
# And one of the following
sudo pacman -S isync # mbsync
sudo pacman -S offlineimap
#+END_SRC
* Dependencies
You need to do the following:
* TODO Features
1. Write a ~\~/.offlineimaprc~. Mine can be found [[https://github.com/hlissner/dotfiles/tree/master/shell/+mu][in my dotfiles repository]]. It is configured to download mail to ~\~/.mail~. I use [[https://www.passwordstore.org/][unix pass]] to securely store my login credentials.
* Configuration
** offlineimap
This module uses =mbsync= by default. To change this, change ~+email-backend~:
#+BEGIN_SRC emacs-lisp
(setq +email-backend 'offlineimap)
#+END_SRC
Then you must set up offlineimap and index your mail:
1. Write a ~\~/.offlineimaprc~. Mine can be found [[https://github.com/hlissner/dotfiles/tree/master/shell/mu][in my dotfiles repository]]. It
is configured to download mail to ~\~/.mail~. I use [[https://www.passwordstore.org/][unix pass]] to securely
store my login credentials.
2. Download your email: ~offlineimap -o~ (may take a while)
3. Index it with mu: ~mu index --maildir ~/.mail~
@ -44,14 +77,15 @@ Then configure Emacs to use your email address:
#+BEGIN_SRC emacs-lisp :tangle no
;; Each path is relative to `+email-mu4e-mail-path', which is ~/.mail by default
(set! :email "Lissner.net"
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
(mu4e-drafts-folder . "/Lissner.net/Drafts")
(mu4e-trash-folder . "/Lissner.net/Trash")
(mu4e-refile-folder . "/Lissner.net/All Mail")
(smtpmail-smtp-user . "henrik@lissner.net")
(user-mail-address . "henrik@lissner.net")
(mu4e-compose-signature . "---\nHenrik Lissner"))
t)
(set-email-account! "Lissner.net"
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
(mu4e-drafts-folder . "/Lissner.net/Drafts")
(mu4e-trash-folder . "/Lissner.net/Trash")
(mu4e-refile-folder . "/Lissner.net/All Mail")
(smtpmail-smtp-user . "henrik@lissner.net")
(user-mail-address . "henrik@lissner.net")
(mu4e-compose-signature . "---\nHenrik Lissner"))
t)
#+END_SRC
** TODO mbsync

View file

@ -1,10 +1,62 @@
;;; app/email/autoload/email.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-email-account! (label letvars &optional default-p)
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
+ `user-full-name' (this or the global `user-full-name' is required)
+ `user-mail-address' (required)
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
OPTIONAL:
+ `mu4e-sent-folder'
+ `mu4e-drafts-folder'
+ `mu4e-trash-folder'
+ `mu4e-refile-folder'
+ `mu4e-compose-signature'
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
default/fallback account."
(after! mu4e
(when-let* ((address (cdr (assq 'user-mail-address letvars))))
(add-to-list 'mu4e-user-mail-address-list address))
(setq mu4e-contexts
(cl-loop for context in mu4e-contexts
unless (string= (mu4e-context-name context) label)
collect context))
(let ((context (make-mu4e-context
:name label
:enter-func (lambda () (mu4e-message "Switched to %s" label))
:leave-func #'mu4e-clear-caches
:match-func
(lambda (msg)
(when msg
(string-prefix-p (format "/%s" label)
(mu4e-message-field msg :maildir))))
:vars letvars)))
(push context mu4e-contexts)
(when default-p
(setq-default mu4e-context-current context))
context)))
(defvar +email-workspace-name "*mu4e*"
"TODO")
(add-hook 'mu4e-main-mode-hook #'+email|init)
;;;###autoload
(defun =email ()
"Start email client."
(interactive)
(call-interactively #'mu4e))
(require 'mu4e)
(+workspace-switch +email-workspace-name t)
(mu4e~start 'mu4e~main-view)
;; (save-selected-window
;; (prolusion-mail-show))
)
;;;###autoload
(defun +email/compose ()
@ -13,3 +65,15 @@
;; TODO Interactively select email account
(call-interactively #'mu4e-compose-new))
;;
;; Hooks
(defun +email|init ()
(add-hook 'kill-buffer-hook #'+email|kill-mu4e nil t))
(defun +email|kill-mu4e ()
;; (prolusion-mail-hide)
(when (+workspace-exists-p +email-workspace-name)
(+workspace/delete +email-workspace-name)))

View file

@ -4,88 +4,52 @@
;; to give me the ability to read, search, write and send my email. It does so
;; with `mu4e', and requires `offlineimap' and `mu' to be installed.
(defvar +email-mu4e-mail-path "~/.mail"
"The directory path of mu's maildir.")
(defvar +email-backend 'mbsync
"Which backend to use. Can either be offlineimap, mbsync or nil (manual).")
;;
;; Config
;;
;; Packages
(def-setting! :email (label letvars &optional default-p)
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
(add-to-list 'auto-mode-alist '("\\.\\(?:offlineimap\\|mbsync\\)rc\\'" . conf-mode))
+ `user-full-name' (this or the global `user-full-name' is required)
+ `user-mail-address' (required)
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
OPTIONAL:
+ `mu4e-sent-folder'
+ `mu4e-drafts-folder'
+ `mu4e-trash-folder'
+ `mu4e-refile-folder'
+ `mu4e-compose-signature'
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
default/fallback account."
`(after! mu4e
(let ((account-vars ,letvars))
(when-let* ((address (cdr (assq 'user-mail-address account-vars))))
(cl-pushnew address mu4e-user-mail-address-list :test #'equal))
(let ((context (make-mu4e-context
:name ,label
:enter-func (lambda () (mu4e-message "Switched to %s" ,label))
:leave-func (lambda () (mu4e-clear-caches))
:match-func
(lambda (msg)
(when msg
(string-prefix-p (format "/%s" ,label)
(mu4e-message-field msg :maildir))))
:vars ,letvars)))
(push context mu4e-contexts)
,(when default-p
`(setq-default mu4e-context-current context))))))
;;
;; Plugins
;;
(def-package! mu4e
:commands (mu4e mu4e-compose-new)
:init
(provide 'html2text) ; disable obsolete package
(setq mu4e-maildir "~/.mail"
mu4e-attachment-dir "~/.mail/.attachments"
mu4e-user-mail-address-list nil)
:config
(setq mu4e-maildir +email-mu4e-mail-path
mu4e-attachment-dir "~/Downloads"
mu4e-user-mail-address-list nil
mu4e-update-interval nil
(pcase +email-backend
(`mbsync
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t))
(`offlineimap
(setq mu4e-get-mail-command "offlineimap -o -q")))
(setq mu4e-update-interval nil
mu4e-compose-format-flowed t ; visual-line-mode + auto-fill upon sending
mu4e-view-show-addresses t
mu4e-sent-messages-behavior 'sent
mu4e-hide-index-messages t
;; try to show images
mu4e-view-show-images t
mu4e-view-image-max-width 800
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
mu4e-sent-messages-behavior 'delete
;; allow for updating mail using 'U' in the main view:
;; for mbsync
;; mu4e-headers-skip-duplicates t
;; mu4e-change-filenames-when-moving nil
;; mu4e-get-mail-command "mbsync -a"
;; for offlineimap
mu4e-get-mail-command "offlineimap -o -q"
;; configuration for sending mail
message-send-mail-function #'smtpmail-send-it
smtpmail-stream-type 'starttls
message-kill-buffer-on-exit t ; close after sending
;; start with the first (default) context;
mu4e-context-policy 'pick-first
;; compose with the current context, or ask
mu4e-compose-context-policy 'ask-if-none
;; use helm/ivy
mu4e-completing-read-function (cond ((featurep! :completion ivy) #'ivy-completing-read)
((featurep! :completion helm) #'completing-read)
(t #'ido-completing-read))
;; close message after sending it
message-kill-buffer-on-exit t
mu4e-completing-read-function
(cond ((featurep! :completion ivy) #'ivy-completing-read)
((featurep! :completion helm) #'completing-read)
(t #'ido-completing-read))
;; no need to ask
mu4e-confirm-quit nil
;; remove 'lists' column
@ -94,14 +58,28 @@ default/fallback account."
(:human-date . 12)
(:flags . 4)
(:from . 25)
(:subject))
mu4e-bookmarks `(("\\\\Inbox" "Inbox" ?i)
("\\\\Draft" "Drafts" ?d)
("flag:unread AND \\\\Inbox" "Unread messages" ?u)
("flag:flagged" "Starred messages" ?s)
("date:today..now" "Today's messages" ?t)
("date:7d..now" "Last 7 days" ?w)
("mime:image/*" "Messages with images" ?p)))
(:subject)))
;; set mail user agent
(setq mail-user-agent 'mu4e-user-agent)
;; Use fancy icons
(setq mu4e-headers-has-child-prefix '("+" . "")
mu4e-headers-empty-parent-prefix '("-" . "")
mu4e-headers-first-child-prefix '("\\" . "")
mu4e-headers-duplicate-prefix '("=" . "")
mu4e-headers-default-prefix '("|" . "")
mu4e-headers-draft-mark '("D" . "")
mu4e-headers-flagged-mark '("F" . "")
mu4e-headers-new-mark '("N" . "")
mu4e-headers-passed-mark '("P" . "")
mu4e-headers-replied-mark '("R" . "")
mu4e-headers-seen-mark '("S" . "")
mu4e-headers-trashed-mark '("T" . "")
mu4e-headers-attach-mark '("a" . "")
mu4e-headers-encrypted-mark '("x" . "")
mu4e-headers-signed-mark '("s" . "")
mu4e-headers-unread-mark '("u" . ""))
;; Add a column to display what email account the email belongs to.
(add-to-list 'mu4e-header-info-custom
@ -114,152 +92,33 @@ default/fallback account."
(let ((maildir (mu4e-message-field msg :maildir)))
(format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+email|gmail-fix-flags'.
;;
;; Gmail will handle the rest.
(setq mu4e-marks (assq-delete-all 'delete mu4e-marks))
(setq mu4e-marks (assq-delete-all 'trash mu4e-marks))
(push '(trash :char ("d" . "")
:prompt "dtrash"
:dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
:action
(lambda (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N")))
mu4e-marks)
;; Refile will be my "archive" function.
(setq mu4e-marks (assq-delete-all 'refile mu4e-marks))
(push '(refile :char ("r" . "")
:prompt "refile"
:show-target (lambda (target) "archive")
:action
(lambda (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N")))
mu4e-marks)
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(defun +email|gmail-fix-flags (mark msg)
(cond ((eq mark 'trash) (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
((eq mark 'refile) (mu4e-action-retag-message msg "-\\Inbox"))
((eq mark 'flag) (mu4e-action-retag-message msg "+\\Starred"))
((eq mark 'unflag) (mu4e-action-retag-message msg "-\\Starred"))))
(add-hook 'mu4e-mark-execute-pre-hook #'+email|gmail-fix-flags)
;; Refresh the current view after marks are executed
(defun +email*refresh (&rest _) (mu4e-headers-rerun-search))
(advice-add #'mu4e-mark-execute-all :after #'+email*refresh)
(when (featurep! :feature spellcheck)
(when (featurep! :tools flyspell)
(add-hook 'mu4e-compose-mode-hook #'flyspell-mode))
;; Wrap text in messages
(add-hook! 'mu4e-view-mode-hook
(setq-local truncate-lines nil))
(setq-hook! 'mu4e-view-mode-hook truncate-lines nil)
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(after! evil
(cl-loop for str in '((mu4e-main-mode . normal)
(mu4e-view-mode . normal)
(mu4e-headers-mode . normal)
(mu4e-compose-mode . normal)
(mu4e~update-mail-mode . normal))
do (evil-set-initial-state (car str) (cdr str)))
(setq mu4e-view-mode-map (make-sparse-keymap)
;; mu4e-compose-mode-map (make-sparse-keymap)
mu4e-headers-mode-map (make-sparse-keymap)
mu4e-main-mode-map (make-sparse-keymap))
(map! (:map (mu4e-main-mode-map mu4e-view-mode-map)
:leader
:n "," #'mu4e-context-switch
:n "." #'mu4e-headers-search-bookmark
:n ">" #'mu4e-headers-search-bookmark-edit
:n "/" #'mu4e~headers-jump-to-maildir)
(:map (mu4e-headers-mode-map mu4e-view-mode-map)
:localleader
:n "f" #'mu4e-compose-forward
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-new
:n "e" #'mu4e-compose-edit)
(:map mu4e-main-mode-map
:n "q" #'mu4e-quit
:n "u" #'mu4e-update-index
:n "U" #'mu4e-update-mail-and-index
:n "J" #'mu4e~headers-jump-to-maildir
:n "c" #'+email/compose
:n "b" #'mu4e-headers-search-bookmark)
(:map mu4e-headers-mode-map
:n "q" #'mu4e~headers-quit-buffer
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-edit
:n "s" #'mu4e-headers-search-edit
:n "S" #'mu4e-headers-search-narrow
:n "RET" #'mu4e-headers-view-message
:n "u" #'mu4e-headers-mark-for-unmark
:n "U" #'mu4e-mark-unmark-all
:n "v" #'evil-visual-line
:nv "d" #'+email/mark
:nv "=" #'+email/mark
:nv "-" #'+email/mark
:nv "+" #'+email/mark
:nv "!" #'+email/mark
:nv "?" #'+email/mark
:nv "r" #'+email/mark
:nv "m" #'+email/mark
:n "x" #'mu4e-mark-execute-all
:n "]]" #'mu4e-headers-next-unread
:n "[[" #'mu4e-headers-prev-unread
(:localleader
:n "s" 'mu4e-headers-change-sorting
:n "t" 'mu4e-headers-toggle-threading
:n "r" 'mu4e-headers-toggle-include-related
:n "%" #'mu4e-headers-mark-pattern
:n "t" #'mu4e-headers-mark-subthread
:n "T" #'mu4e-headers-mark-thread))
(:map mu4e-view-mode-map
:n "q" #'mu4e~view-quit-buffer
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-edit
:n "o" #'ace-link-mu4e
:n "<M-Left>" #'mu4e-view-headers-prev
:n "<M-Right>" #'mu4e-view-headers-next
:n "[m" #'mu4e-view-headers-prev
:n "]m" #'mu4e-view-headers-next
:n "[u" #'mu4e-view-headers-prev-unread
:n "]u" #'mu4e-view-headers-next-unread
(:localleader
:n "%" #'mu4e-view-mark-pattern
:n "t" #'mu4e-view-mark-subthread
:n "T" #'mu4e-view-mark-thread
:n "d" #'mu4e-view-mark-for-trash
:n "r" #'mu4e-view-mark-for-refile
:n "m" #'mu4e-view-mark-for-move))
(:map mu4e~update-mail-mode-map
:n "q" #'mu4e-interrupt-update-mail))))
(set-evil-initial-state!
'(mu4e-main-mode
mu4e-view-mode
mu4e-headers-mode
mu4e-compose-mode
mu4e~update-mail-mode)
'normal))
(def-package! mu4e-maildirs-extension
:after mu4e
:config (mu4e-maildirs-extension-load))
:config
(mu4e-maildirs-extension)
(setq mu4e-maildirs-extension-title nil))
(def-package! org-mu4e
@ -273,3 +132,8 @@ default/fallback account."
(add-hook! 'message-send-hook
(setq-local org-mu4e-convert-to-html nil)))
;;
;; Sub-modules
(if (featurep! +gmail) (load! "+gmail"))

View file

@ -1,59 +1,118 @@
#+TITLE: :app irc
This module turns adds an IRC client to Emacs ([[https://github.com/jorgenschaefer/circe][~circe~)]] with native notifications ([[https://github.com/eqyiel/circe-notifications][circe-notifications]]).
#+TITLE: app/irc
#+DATE: June 11, 2017
#+SINCE: v2.0.3
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#dependencies][Dependencies]]
- [[#configure][Configure]]
- [[#pass-the-unix-password-manager][Pass: the unix password manager]]
- [[#emacs-auth-source-api][Emacs' auth-source API]]
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Dependencies][Dependencies]]
- [[Prerequisites][Prerequisites]]
- [[Features][Features]]
- [[An IRC Client in Emacs][An IRC Client in Emacs]]
- [[Configuration][Configuration]]
- [[Pass: the unix password manager][Pass: the unix password manager]]
- [[Emacs' auth-source API][Emacs' auth-source API]]
- [[Troubleshooting][Troubleshooting]]
* Description
This module turns adds an IRC client to Emacs with OS notifications.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/jorgenschaefer/circe][circe]]
+ [[https://github.com/eqyiel/circe-notifications][circe-notifications]]
* Dependencies
This module has no dependencies, besides =gnutls-cli= or =openssl= for secure connections.
This module requires =gnutls-cli= or =openssl= for secure connections.
* Configure
Use the ~:irc~ setting to configure IRC servers. Its second argument (a plist) takes the same arguments as ~circe-network-options~.
* Prerequisites
This module has no direct prerequisites.
* Features
** An IRC Client in Emacs
To connect to IRC you can invoke the ~=irc~ function using =M-x= or your own
custom keybinding.
| command | description |
|---------+-------------------------------------------|
| ~=irc~ | Connect to IRC and all configured servers |
When in a circe buffer these keybindings will be available.
| command | key | description |
|-----------------------------+-----------+----------------------------------------------|
| ~+irc/tracking-next-buffer~ | =SPC m a= | Switch to the next active buffer |
| ~circe-command-JOIN~ | =SPC m j= | Join a channel |
| ~+irc/send-message~ | =SPC m m= | Send a private message |
| ~circe-command-NAMES~ | =SPC m n= | List the names of the current channel |
| ~circe-command-PART~ | =SPC m p= | Part the current channel |
| ~+irc/quit~ | =SPC m Q= | Kill the current circe session and workgroup |
| ~circe-reconnect~ | =SPC m R= | Reconnect the current server |
* Configuration
Use ~set-irc-server!~ to configure IRC servers. Its second argument (a plist)
takes the same arguments as ~circe-network-options~.
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username "myusername"
:sasl-password "mypassword"
:channels ("#emacs")))
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username "myusername"
:sasl-password "mypassword"
:channels ("#emacs")))
#+END_SRC
*It is a obviously a bad idea to store auth-details in plaintext,* so here are some ways to avoid that:
*It is a obviously a bad idea to store auth-details in plaintext,* so here are
some ways to avoid that:
** Pass: the unix password manager
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the [[/modules/tools/password-store/README.org][:tools password-store]] module you get an elisp API through which to access your password store.
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the
[[../../../modules/tools/password-store/README.org][:tools password-store]] module you get an elisp API through which to access your
password store.
~:irc~'s plist can use functions instead of strings. ~+pass-get-user~ and ~+pass-get-secret~ can help here:
~set-irc-server!~ accepts a plist can use functions instead of strings.
~+pass-get-user~ and ~+pass-get-secret~ can help here:
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password ,(+pass-get-secret "irc/freenode.net")
:channels ("#emacs")))
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password ,(+pass-get-secret "irc/freenode.net")
:channels ("#emacs")))
#+END_SRC
But wait, there's more! This stores your password in a public variable which could be accessed or appear in backtraces. Not good! So we go a step further:
But wait, there's more! This stores your password in a public variable which
could be accessed or appear in backtraces. Not good! So we go a step further:
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
:channels ("#emacs")))
(set-irc-server! "chat.freenode.net"
`(:tls t
:port 6697
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
:channels ("#emacs")))
#+END_SRC
And you're good to go!
Note that =+pass-get-user= tries to find your username by looking for the fields
listed in =+pass-user-fields= (by default =login=, =user==, =username== and
=email=)=). An example configuration looks like
#+BEGIN_SRC txt :tangle no
mysecretpassword
username: myusername
#+END_SRC
** Emacs' auth-source API
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store (and retrieve) encrypted passwords with it.
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store
(and retrieve) encrypted passwords with it.
#+BEGIN_SRC emacs-lisp :tangle no
(setq auth-sources '("~/.authinfo.gpg"))
@ -71,10 +130,12 @@ And you're good to go!
(defun my-nickserv-password (server)
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
(set! :irc "chat.freenode.net"
'(:tls t
:nick "doom"
:sasl-password my-nickserver-password
:channels ("#emacs")))
(set-irc-server! "chat.freenode.net"
'(:tls t
:port 6697
:nick "doom"
:sasl-password my-nickserver-password
:channels ("#emacs")))
#+END_SRC
* TODO Troubleshooting

View file

@ -20,11 +20,17 @@
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(if (+workspace-exists-p +irc--workspace-name)
(+workspace-switch +irc--workspace-name)
(and (+irc-setup-wconf inhibit-workspace)
(cl-loop for network in circe-network-options
collect (circe (car network))))))
(cond ((and (featurep! :feature workspaces)
(+workspace-exists-p +irc--workspace-name))
(+workspace-switch +irc--workspace-name))
((not (+irc-setup-wconf inhibit-workspace))
(user-error "Couldn't start up a workspace for IRC")))
(if (doom-buffers-in-mode 'circe-mode (buffer-list) t)
(message "Circe buffers are already open")
(if circe-network-options
(cl-loop for network in circe-network-options
collect (circe (car network)))
(call-interactively #'circe))))
;;;###autoload
(defun +irc/connect (&optional inhibit-workspace)
@ -36,6 +42,12 @@ workspace for it."
(and (+irc-setup-wconf inhibit-workspace)
(call-interactively #'circe)))
;;;###autoload
(defun +irc/send-message (who what)
"Send WHO a message containing WHAT."
(interactive "sWho: \nsWhat: ")
(circe-command-MSG who what))
;;;###autoload
(defun +irc/quit ()
"Kill current circe session and workgroup."
@ -78,3 +90,10 @@ argument) is non-nil only show channels in current server."
(defun +irc--ivy-switch-to-buffer-action (buffer)
(when (stringp buffer)
(ivy--switch-buffer-action (string-trim-left buffer))))
;;;###autoload
(defun +irc/tracking-next-buffer ()
"Dissables switching to an unread buffer unless in the irc workspace."
(interactive)
(when (derived-mode-p 'circe-mode)
(tracking-next-buffer)))

View file

@ -0,0 +1,9 @@
;;; app/irc/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-irc-server! (server letvars)
"Registers an irc SERVER for circe.
See `circe-network-options' for details."
(after! circe
(push (cons server letvars) circe-network-options)))

View file

@ -1,7 +1,9 @@
;;; app/irc/config.el -*- lexical-binding: t; -*-
(defvar +irc-left-padding 13
"TODO")
"By how much spaces the left hand side of the line should be padded.
Below a value of 12 this may result in uneven alignment between the various
types of messages.")
(defvar +irc-truncate-nick-char ?…
"Character to displayed when nick > `+irc-left-padding' in length.")
@ -11,27 +13,29 @@
"If these commands are called pre prompt the buffer will scroll to `point-max'.")
(defvar +irc-disconnect-hook nil
"TODO")
"Runs each hook when circe noticies the connection has been disconnected.
Useful for scenarios where an instant reconnect will not be successful.")
(defvar +irc-bot-list '("fsbot" "rudybot")
"TODO")
"Nicks listed have `circe-fool-face' applied and will not be tracked.")
(defvar +irc-time-stamp-format "%H:%M"
"TODO")
"The format of time stamps.
See `format-time-string' for a full description of available
formatting directives. ")
(defvar +irc-notifications-watch-strings nil
"TODO")
"A list of strings which can trigger a notification. You don't need to put
your nick here.
See `circe-notifications-watch-strings'.")
(defvar +irc-defer-notifications nil
"How long to defer enabling notifications, in seconds (e.g. 5min = 300).
Useful for ZNC users who want to avoid the deluge of notifications during buffer
playback.")
(def-setting! :irc (server letvars)
"Registers an irc server for circe."
`(after! circe
(push (cons ,server ,letvars) circe-network-options)))
(defvar +irc--defer-timer nil)
(defsubst +irc--pad (left right)
@ -40,8 +44,7 @@ playback.")
;;
;; Plugins
;;
;; Packages
(def-package! circe
:commands (circe circe-server-buffers)
@ -56,6 +59,10 @@ playback.")
circe-format-self-say circe-format-say
circe-format-action (format "{nick:+%ss} * {body}" +irc-left-padding)
circe-format-self-action circe-format-action
circe-format-server-notice
(let ((left "-Server-")) (concat (make-string (- +irc-left-padding (length left)) ? )
(concat left " _ {body}")))
circe-format-notice (format "{nick:%ss} _ {body}" +irc-left-padding)
circe-format-server-topic
(+irc--pad "Topic" "{userhost}: {topic-diff}")
circe-format-server-join-in-channel
@ -70,6 +77,8 @@ playback.")
(+irc--pad "Quit" "{nick} ({userhost}) left {channel}: {reason}]")
circe-format-server-rejoin
(+irc--pad "Re-join" "{nick} ({userhost}), left {departuredelta} ago")
circe-format-server-netmerge
(+irc--pad "Netmerge" "{split}, split {ago} ago (Use /WL to see who's still missing)")
circe-format-server-nick-change
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
circe-format-server-nick-change-self
@ -83,8 +92,9 @@ playback.")
(add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode)
;; Let `+irc/quit' and `circe' handle buffer cleanup
(map! :map circe-mode-map [remap doom/kill-this-buffer] #'bury-buffer)
(defun +irc*circe-disconnect-hook (&rest _)
(run-hooks '+irc-disconnect-hook))
(advice-add 'circe--irc-conn-disconnected :after #'+irc*circe-disconnect-hook)
(defun +irc*circe-truncate-nicks ()
"Truncate long nicknames in chat output non-destructively."
@ -98,15 +108,48 @@ playback.")
+irc-truncate-nick-char)))))
(add-hook 'lui-pre-output-hook #'+irc*circe-truncate-nicks)
(defun +circe-buffer-p (buf)
"Return non-nil if BUF is a `circe-mode' buffer."
(with-current-buffer buf
(and (derived-mode-p 'circe-mode)
(eq (safe-persp-name (get-current-persp))
+irc--workspace-name))))
(add-hook 'doom-real-buffer-functions #'+circe-buffer-p)
(defun +irc|circe-message-option-bot (nick &rest ignored)
"Fontify known bots and mark them to not be tracked."
(when (member nick +irc-bot-list)
'((text-properties . (face circe-fool-face lui-do-not-track t)))))
(add-hook 'circe-message-option-functions #'+irc|circe-message-option-bot)
(after! solaire-mode
;; distinguish chat/channel buffers from server buffers.
(add-hook 'circe-chat-mode-hook #'solaire-mode)))
(defun +irc|add-circe-buffer-to-persp ()
(let ((persp (get-current-persp))
(buf (current-buffer)))
;; Add a new circe buffer to irc workspace when we're in another workspace
(unless (eq (safe-persp-name persp) +irc--workspace-name)
;; Add new circe buffers to the persp containing circe buffers
(persp-add-buffer buf (persp-get-by-name +irc--workspace-name))
;; Remove new buffer from accidental workspace
(persp-remove-buffer buf persp))))
(add-hook 'circe-mode-hook #'+irc|add-circe-buffer-to-persp)
;; Let `+irc/quit' and `circe' handle buffer cleanup
(define-key circe-mode-map [remap kill-buffer] #'bury-buffer)
;; Fail gracefully if not in a circe buffer
(global-set-key [remap tracking-next-buffer] #'+irc/tracking-next-buffer)
(map! :localleader
(:map circe-mode-map
"a" #'tracking-next-buffer
"j" #'circe-command-JOIN
"m" #'+irc/send-message
"p" #'circe-command-PART
"Q" #'+irc/quit
"R" #'circe-reconnect
(:when (featurep! :completion ivy)
"c" #'+irc/ivy-jump-to-channel))
(:map circe-channel-mode-map
"n" #'circe-command-NAMES)))
(def-package! circe-color-nicks
@ -144,10 +187,11 @@ playback.")
(def-package! lui
:commands lui-mode
:config
(map! :map lui-mode-map "C-u" #'lui-kill-to-beginning-of-line)
(when (featurep! :feature spellcheck)
(setq lui-flyspell-p t
lui-fill-type nil))
(define-key lui-mode-map "\C-u" #'lui-kill-to-beginning-of-line)
(setq lui-fill-type nil)
(when (featurep! :tools flyspell)
(setq lui-flyspell-p t))
(after! evil
(defun +irc|evil-insert ()
@ -182,6 +226,9 @@ Courtesy of esh-mode.el"
(add-hook! 'lui-mode-hook
(add-hook 'pre-command-hook #'+irc|preinput-scroll-to-bottom nil t))
;; enable a horizontal line marking the last read message
(add-hook! 'lui-mode-hook #'enable-lui-track-bar)
(defun +irc|init-lui-margins ()
(setq lui-time-stamp-position 'right-margin
lui-time-stamp-format +irc-time-stamp-format

View file

@ -0,0 +1,189 @@
;;; app/notmuch/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun =notmuch ()
"Activate (or switch to) `notmuch' in its workspace."
(interactive)
(unless (featurep! :feature workspaces)
(user-error ":feature workspaces is required, but disabled"))
(condition-case-unless-debug e
(progn
(+workspace-switch "*MAIL*" t)
(if-let* ((buf (cl-find-if (lambda (it) (string-match-p "^\\*notmuch" (buffer-name (window-buffer it))))
(doom-visible-windows))))
(select-window (get-buffer-window buf))
(notmuch-search "tag:inbox"))
(+workspace/display))
('error
(+notmuch/quit)
(signal (car e) (cdr e)))))
;;
;; Commands
;;;###autoload
(defun +notmuch/quit ()
(interactive)
;; (+popup/close (get-buffer-window "*notmuch-hello*"))
(doom-kill-matching-buffers "^\\*notmuch")
(+workspace/delete "*MAIL*"))
;;;###autoload
(defun +notmuch/update ()
(interactive)
(start-process-shell-command
"notmuch update" nil
(pcase +notmuch-sync-backend
(`gmi
(concat "cd " +notmuch-mail-folder " && gmi push && gmi pull && notmuch new && afew -a -t"))
(`mbsync
"mbsync -a && notmuch new && afew -a -t")
(`mbsync-xdg
"mbsync -c \"$XDG_CONFIG_HOME\"/isync/mbsyncrc -a && notmuch new && afew -a -t")
(`offlineimap
"offlineimap && notmuch new && afew -a -t"))))
;;;###autoload
(defun +notmuch/search-delete ()
(interactive)
(notmuch-search-add-tag (list "+trash" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/tree-delete ()
(interactive)
(notmuch-tree-add-tag (list "+trash" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/search-spam ()
(interactive)
(notmuch-search-add-tag (list "+spam" "-inbox" "-unread"))
(notmuch-search-next-thread))
;;;###autoload
(defun +notmuch/tree-spam ()
(interactive)
(notmuch-tree-add-tag (list "+spam" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/open-message-with-mail-app-notmuch-tree ()
(interactive)
(let* ((msg-path (car (plist-get (notmuch-tree-get-message-properties) :filename)))
(temp (make-temp-file "notmuch-message-" nil ".eml")))
(shell-command-to-string (format "cp '%s' '%s'" msg-path temp))
(start-process-shell-command "email" nil (format "xdg-open '%s'" temp))))
;;;###autoload
(defun +notmuch/open-message-with-mail-app-notmuch-show ()
(interactive)
(let* ((msg-path (car (plist-get (notmuch-show-get-message-properties) :filename)))
(temp (make-temp-file "notmuch-message-" nil ".eml")))
(shell-command-to-string (format "cp '%s' '%s'" msg-path temp))
(start-process-shell-command "email" nil (format "xdg-open '%s'" temp))))
;;
;; Advice
;;;###autoload
(defun +notmuch*dont-confirm-on-kill-process (orig-fn &rest args)
"Don't prompt for confirmation when killing notmuch sentinel."
(let (confirm-kill-processes)
(apply orig-fn args)))
;; (defun +notmuch*hello-insert-searches (title query-list &rest options)
;; (widget-insert (propertize title 'face 'org-agenda-structure))
;; (if (and notmuch-hello-first-run (plist-get options :initially-hidden))
;; (add-to-list 'notmuch-hello-hidden-sections title))
;; (let ((is-hidden (member title notmuch-hello-hidden-sections))
;; (widget-push-button-prefix "")
;; (widget-push-button-suffix "")
;; (start (point)))
;; (if is-hidden
;; (widget-create 'push-button
;; :notify `(lambda (widget &rest ignore)
;; (setq notmuch-hello-hidden-sections
;; (delete ,title notmuch-hello-hidden-sections))
;; (notmuch-hello-update))
;; (propertize " +" 'face 'org-agenda-structure))
;; (widget-create 'push-button
;; :notify `(lambda (widget &rest ignore)
;; (add-to-list 'notmuch-hello-hidden-sections
;; ,title)
;; (notmuch-hello-update))
;; " -"))
;; (widget-insert "\n")
;; (when (not is-hidden)
;; (let ((searches (apply 'notmuch-hello-query-counts query-list options)))
;; (when (or (not (plist-get options :hide-if-empty))
;; searches)
;; (widget-insert "\n")
;; (notmuch-hello-insert-buttons searches)
;; (indent-rigidly start (point) notmuch-hello-indent))))))
;; (defun +notmuch*hello-insert-saved-searches ()
;; "Insert the saved-searches section."
;; (let ((searches (notmuch-hello-query-counts
;; (if notmuch-saved-search-sort-function
;; (funcall notmuch-saved-search-sort-function
;; notmuch-saved-searches)
;; notmuch-saved-searches)
;; :show-empty-searches notmuch-show-empty-saved-searches)))
;; (when searches
;; (widget-insert (propertize "Notmuch" 'face 'org-agenda-date-today))
;; (widget-insert "\n\n")
;; (widget-insert (propertize "Saved searches" 'face 'org-agenda-structure))
;; (widget-insert "\n\n")
;; (let ((start (point)))
;; (notmuch-hello-insert-buttons searches)
;; (indent-rigidly start (point) notmuch-hello-indent)))))
;; (defun +notmuch*hello-insert-buttons (searches)
;; (let* ((widest (notmuch-hello-longest-label searches))
;; (tags-and-width (notmuch-hello-tags-per-line widest))
;; (tags-per-line (car tags-and-width))
;; (column-width (cdr tags-and-width))
;; (column-indent 0)
;; (count 0)
;; (reordered-list (notmuch-hello-reflect searches tags-per-line))
;; ;; Hack the display of the buttons used.
;; (widget-push-button-prefix "")
;; (widget-push-button-suffix ""))
;; ;; dme: It feels as though there should be a better way to
;; ;; implement this loop than using an incrementing counter.
;; (mapc (lambda (elem)
;; ;; (not elem) indicates an empty slot in the matrix.
;; (when elem
;; (if (> column-indent 0)
;; (widget-insert (make-string column-indent ? )))
;; (let* ((name (plist-get elem :name))
;; (query (plist-get elem :query))
;; (oldest-first (case (plist-get elem :sort-order)
;; (newest-first nil)
;; (oldest-first t)
;; (otherwise notmuch-search-oldest-first)))
;; (search-type (eq (plist-get elem :search-type) 'tree))
;; (msg-count (plist-get elem :count)))
;; (widget-insert (format "\n%5s "
;; (notmuch-hello-nice-number msg-count)))
;; (widget-create 'push-button
;; :notify #'notmuch-hello-widget-search
;; :notmuch-search-terms query
;; :notmuch-search-oldest-first oldest-first
;; :notmuch-search-type search-type
;; name)
;; (setq column-indent
;; (1+ (max 0 (- column-width (length name)))))))
;; (setq count (1+ count))
;; (when (eq (% count tags-per-line) 0)
;; (setq column-indent 0)
;; (widget-insert "\n")))
;; reordered-list)
;; ;; If the last line was not full (and hence did not include a
;; ;; carriage return), insert one now.
;; (unless (eq (% count tags-per-line) 0)
;; (widget-insert "\n"))))

View file

@ -0,0 +1,74 @@
;;; app/notmuch/config.el -*- lexical-binding: t; -*-
;; FIXME This module is a WIP!
(defvar +notmuch-sync-backend 'gmi
"Which backend to use. Can be either gmi, mbsync, offlineimap or nil (manual).")
(defvar +notmuch-mail-folder "~/.mail/account.gmail"
"Where your email folder is located (for use with gmailieer).")
(after! notmuch
(set-company-backend! 'notmuch-message-mode
'(notmuch-company (company-ispell :with company-yasnippet)))
(set-popup-rule! "^\\*notmuch-hello" :side 'left :size 30 :ttl 0)
(setq notmuch-fcc-dirs nil
notmuch-show-logo nil
notmuch-message-headers-visible nil
message-kill-buffer-on-exit t
message-send-mail-function 'message-send-mail-with-sendmail
notmuch-search-oldest-first nil
send-mail-function 'sendmail-send-it
;; sendmail-program "/usr/local/bin/msmtp"
notmuch-search-result-format
'(("date" . "%12s ")
("count" . "%-7s ")
("authors" . "%-30s ")
("subject" . "%-72s ")
("tags" . "(%s)"))
notmuch-tag-formats
'(("unread" (propertize tag 'face 'notmuch-tag-unread)))
notmuch-hello-sections
'(notmuch-hello-insert-saved-searches
notmuch-hello-insert-alltags)
notmuch-saved-searches
'((:name "inbox" :query "tag:inbox not tag:trash" :key "i")
(:name "flagged" :query "tag:flagged" :key "f")
(:name "sent" :query "tag:sent" :key "s")
(:name "drafts" :query "tag:draft" :key "d"))
notmuch-archive-tags '("-inbox" "-unread"))
;; (setq-hook! 'notmuch-show-mode-hook line-spacing 0)
(add-to-list 'doom-real-buffer-functions #'notmuch-interesting-buffer nil #'eq)
(advice-add #'notmuch-start-notmuch-sentinel :around #'+notmuch*dont-confirm-on-kill-process)
;; Visual enhancements
(defun +notmuch|center-window ()
(setq-local visual-fill-column-width 90)
(visual-fill-column-mode))
(add-hook 'notmuch-show-mode-hook #'+notmuch|center-window)
;; modeline doesn't have much use in these modes
(add-hook! (notmuch-show-mode notmuch-tree-mode notmuch-search-mode)
#'hide-mode-line-mode))
(def-package! org-mime
:after (org notmuch)
:config (setq org-mime-library 'mml))
(def-package! counsel-notmuch
:when (featurep! :completion ivy)
:commands counsel-notmuch
:after notmuch)
(def-package! helm-notmuch
:when (featurep! :completion helm)
:commands helm-notmuch
:after notmuch)

View file

@ -0,0 +1,9 @@
;; -*- no-byte-compile: t; -*-
;;; app/notmuch/packages.el
(package! notmuch)
(package! org-mime)
(when (featurep! :completion ivy)
(package! counsel-notmuch))
(when (featurep! :completion helm)
(package! helm-notmuch))

View file

@ -11,17 +11,17 @@
(defface +regex-match-0-face
`((t (:foreground "Black" :background ,(doom-color 'magenta) :bold t)))
"TODO"
:group 'doom)
:group 'faces)
(defface +regex-match-1-face
`((t (:foreground "Black" :background ,(doom-color 'blue) :bold t)))
"TODO"
:group 'doom)
:group 'faces)
(defface +regex-match-2-face
`((t (:foreground "Black" :background ,(doom-color 'green) :bold t)))
"TODO"
:group 'doom)
:group 'faces)
(defvar +regex-faces
'(+regex-match-0-face +regex-match-1-face +regex-match-2-face)
@ -33,8 +33,6 @@
(define-key map "\C-c\C-c" #'+regex-update-buffers)
(define-key map "\C-c\C-r" #'=regex/replace)
(define-key map "\C-c\C-k" #'+regex/quit)
(define-key map [remap doom-kill-buffer] #'+regex/quit)
(define-key map [remap doom/kill-this-buffer] #'+regex/quit)
(define-key map [remap kill-this-buffer] #'+regex/quit)
(define-key map [remap kill-buffer] #'+regex/quit)
map)
@ -66,8 +64,8 @@
(switch-to-buffer +regex--text-buffer)
(with-current-buffer +regex--text-buffer
(insert +regex-dummy-text)))
(doom-popup-buffer +regex--groups-buffer)
(doom-popup-buffer +regex--expr-buffer)
(pop-to-buffer +regex--groups-buffer)
(pop-to-buffer +regex--expr-buffer)
(with-current-buffer +regex--expr-buffer
(conf-mode)
(rainbow-delimiters-mode +1)

View file

@ -46,7 +46,7 @@ http://regexr.com/foo.html?q=bar
https://mediatemple.net"
"TODO")
(set! :popup
'("*doom-regex*" :size 4 :select t :noesc t)
'("*doom-regex-groups*" :align left :size 30 :noselect t :noesc t))
(set-popup-rules!
'(("^\\*doom-regex\\*$" :size 4 :quit nil)
("^\\*doom-regex-groups" :side 'left :size 28 :select nil :quit nil)))

View file

@ -1,47 +1,18 @@
;;; app/rss/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun =rss ()
"Activate (or switch to) `elfeed' in its workspace."
(interactive)
(call-interactively 'elfeed))
;;;###autoload
(defun +rss/quit ()
(interactive)
(doom-kill-matching-buffers "^\\*elfeed")
(dolist (file +rss-elfeed-files)
(when-let* ((buf (get-file-buffer (expand-file-name file +rss-org-dir))))
(kill-buffer buf))))
;;;###autoload
(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 85)
(set-buffer-modified-p nil)))
(defalias '=rss #'elfeed
"Activate (or switch to) `elfeed' in its workspace.")
;;;###autoload
(defun +rss/delete-pane ()
"Delete the *elfeed-entry* split pane."
(interactive)
(let* ((buff (get-buffer "*elfeed-entry*"))
(window (get-buffer-window buff)))
(kill-buffer buff)
(delete-window window)))
;;;###autoload
(defun +rss-popup-pane (buf)
"Display BUF in a popup."
(doom-popup-buffer buf
'(:align +rss-split-direction
:size 0.75
:select t
:autokill t
:autoclose t)))
(let* ((buf (get-buffer "*elfeed-entry*"))
(window (get-buffer-window buf)))
(delete-window window)
(when (buffer-live-p buf)
(kill-buffer buf))))
;;;###autoload
(defun +rss/open (entry)
@ -70,6 +41,48 @@
(forward-line -1)
(call-interactively '+rss/open)))
;;
;; Hooks
;;;###autoload
(defun +rss|elfeed-wrap ()
"Enhances an elfeed entry's readability by wrapping it to a width of
`fill-column'."
(let ((inhibit-read-only t)
(inhibit-modification-hooks t))
(setq-local truncate-lines nil)
(setq-local shr-use-fonts nil)
(setq-local shr-width 85)
(set-buffer-modified-p nil)))
;;;###autoload
(defun +rss|cleanup ()
"Clean up after an elfeed session. Kills all elfeed and elfeed-org files."
(interactive)
;; `delete-file-projectile-remove-from-cache' slows down `elfeed-db-compact'
;; tremendously, so we disable the projectile cache:
(let (projectile-enable-caching)
(elfeed-db-compact))
(let ((buf (previous-buffer)))
(when (or (null buf) (not (doom-real-buffer-p buf)))
(switch-to-buffer (doom-fallback-buffer))))
(let ((search-buffers (doom-buffers-in-mode 'elfeed-search-mode))
(show-buffers (doom-buffers-in-mode 'elfeed-show-mode))
kill-buffer-query-functions)
(dolist (file +rss-elfeed-files)
(when-let* ((buf (get-file-buffer (expand-file-name file org-directory))))
(kill-buffer buf)))
(dolist (b search-buffers)
(with-current-buffer b
(remove-hook 'kill-buffer-hook #'+rss|cleanup :local)
(kill-buffer b)))
(mapc #'kill-buffer show-buffers)))
;;
;; Functions
;;;###autoload
(defun +rss-dead-feeds (&optional years)
"Return a list of feeds that haven't posted anything in YEARS."
@ -84,3 +97,21 @@
(cl-loop for url in (elfeed-feed-list)
unless (gethash url living-feeds)
collect url)))
;;;###autoload
(defun +rss-put-sliced-image (spec alt &optional flags)
"TODO"
(cl-letf (((symbol-function #'insert-image)
(lambda (image &optional alt _area _slice)
(let ((height (cdr (image-size image t))))
(insert-sliced-image image alt nil (max 1 (/ height 20.0)) 1)))))
(shr-put-image spec alt flags)))
;;;###autoload
(defun +rss-render-image-tag-without-underline (dom &optional url)
"TODO"
(let ((start (point)))
(shr-tag-img dom url)
;; And remove underlines in case images are links, otherwise we get an
;; underline beneath every slice.
(put-text-property start (point) 'face '(:underline nil))))

View file

@ -4,16 +4,20 @@
;; by apps Reeder and Readkit. It can be invoked via `=rss'. Otherwise, if you
;; don't care for the UI you can invoke elfeed directly with `elfeed'.
(defvar +rss-elfeed-files (list "rss/elfeed.org")
"The files that configure `elfeed's rss feeds.")
(defvar +rss-elfeed-files (list "elfeed.org")
"Where to look for elfeed.org files, relative to `org-directory'. Can be
absolute paths.")
(defvar +rss-split-direction 'below
"What direction to pop up the entry buffer in elfeed.")
(defvar +rss-enable-sliced-images t
"Automatically slice images shown in elfeed-show-mode buffers, making them
easier to scroll through.")
;;
;; Packages
;;
(def-package! elfeed
:commands elfeed
@ -21,44 +25,50 @@
(setq elfeed-search-filter "@2-week-ago "
elfeed-db-directory (concat doom-local-dir "elfeed/db/")
elfeed-enclosure-default-dir (concat doom-local-dir "elfeed/enclosures/")
elfeed-show-entry-switch #'+rss-popup-pane
elfeed-show-entry-switch #'pop-to-buffer
elfeed-show-entry-delete #'+rss/delete-pane
shr-max-image-proportion 0.6)
shr-max-image-proportion 0.8)
(set-popup-rule! "^\\*elfeed-entry"
:size 0.75 :actions '(display-buffer-below-selected)
:select t :quit nil :ttl t)
(make-directory elfeed-db-directory t)
;; Ensure elfeed buffers are treated as real
(push (lambda (buf) (string-match-p "^\\*elfeed" (buffer-name buf)))
doom-real-buffer-functions)
(defun +rss-buffer-p (buf)
(string-match-p "^\\*elfeed" (buffer-name buf)))
(add-to-list 'doom-real-buffer-functions #'+rss-buffer-p nil #'eq)
;; Enhance readability of a post
(add-hook 'elfeed-show-mode-hook #'+rss|elfeed-wrap)
(add-hook! 'elfeed-search-mode-hook
(add-hook 'kill-buffer-hook #'+rss|cleanup nil t))
(map! (:map (elfeed-search-mode-map elfeed-show-mode-map)
[remap doom/kill-this-buffer] "q"
[remap kill-this-buffer] "q"
[remap kill-buffer] "q")
;; Large images are annoying to scroll through, because scrolling follows the
;; cursor, so we force shr to insert images in slices.
(when +rss-enable-sliced-images
(setq-hook! 'elfeed-show-mode-hook
shr-put-image-function #'+rss-put-sliced-image
shr-external-rendering-functions '((img . +rss-render-image-tag-without-underline))))
(:map elfeed-search-mode-map
:n "q" #'+rss/quit
:n "r" #'elfeed-update
:n "s" #'elfeed-search-live-filter
:n "RET" #'elfeed-search-show-entry)
(:map elfeed-show-mode-map
:n "q" #'elfeed-kill-buffer
:m "j" #'evil-next-visual-line
:m "k" #'evil-previous-visual-line
[remap doom/next-buffer] #'+rss/next
[remap doom/previous-buffer] #'+rss/previous
[remap next-buffer] #'+rss/next
[remap previous-buffer] #'+rss/previous)))
;; Keybindings
(after! elfeed-show
(define-key! elfeed-show-mode-map
[remap next-buffer] #'+rss/next
[remap previous-buffer] #'+rss/previous))
(when (featurep! :feature evil +everywhere)
(evil-define-key 'normal elfeed-search-mode-map
"q" #'elfeed-kill-buffer
"r" #'elfeed-search-update--force
(kbd "M-RET") #'elfeed-search-browse-url)))
(def-package! elfeed-org
:after (:all org elfeed)
:when (featurep! +org)
:after elfeed
:config
(setq rmh-elfeed-org-files
(let ((default-directory +org-dir))
(let ((default-directory org-directory))
(mapcar #'expand-file-name +rss-elfeed-files)))
(elfeed-org))

View file

@ -1,12 +1,39 @@
;;; app/twitter/autoload.el -*- lexical-binding: t; -*-
(defvar +twitter-workspace-name "*Twitter*"
"The name to use for the twitter workspace.")
;;;###autoload
(defun =twitter ()
(interactive)
(+workspace-switch "*Twitter*" t)
(delete-other-windows)
(condition-case _ex
(defun +twitter-display-buffer (buf)
"A replacement display-buffer command for `twittering-pop-to-buffer-function'
that works with the feature/popup module."
(let ((win (selected-window)))
(display-buffer buf)
;; This is required because the new window generated by `pop-to-buffer'
;; may hide the region following the current position.
(twittering-ensure-whole-of-status-is-visible win)))
;;;###autoload
(defun +twitter-buffer-p (buf)
"Return non-nil if BUF is a `twittering-mode' buffer."
(eq 'twittering-mode (buffer-local-value 'major-mode buf)))
;;
;; Commands
(defvar +twitter--old-wconf nil)
;;;###autoload
(defun =twitter (arg)
"Opens a workspace dedicated to `twittering-mode'."
(interactive "P")
(condition-case _
(progn
(if (and (not arg) (featurep! :feature workspaces))
(+workspace/new +twitter-workspace-name)
(setq +twitter--old-wconf (current-window-configuration))
(delete-other-windows)
(switch-to-buffer (doom-fallback-buffer)))
(call-interactively #'twit)
(unless (get-buffer (car twittering-initial-timeline-spec-string))
(error "Failed to open twitter"))
@ -14,28 +41,62 @@
(dolist (name (cdr twittering-initial-timeline-spec-string))
(split-window-horizontally)
(switch-to-buffer name))
(balance-windows))
('error
(+twitter/quit-all))))
(balance-windows)
(call-interactively #'+twitter/rerender-all))
('error (+twitter/quit-all))))
;;;###autoload
(defun +twitter/quit ()
"Close the current `twitter-mode' buffer."
(interactive)
(when (eq major-mode 'twittering-mode)
(twittering-kill-buffer)
(+workspace/close-window-or-workspace)))
(cond ((one-window-p) (+twitter/quit-all))
((featurep! :feature workspaces)
(+workspace/close-window-or-workspace))
((delete-window)))))
;;;###autoload
(defun +twitter/quit-all ()
"Close all open `twitter-mode' buffers and the associated workspace, if any."
(interactive)
(+workspace/delete "Twitter")
(dolist (buf (doom-buffers-in-mode 'twittering-mode))
(with-current-buffer buf
(twittering-kill-buffer))))
(when (featurep! :feature workspaces)
(+workspace/delete +twitter-workspace-name))
(when +twitter--old-wconf
(set-window-configuration +twitter--old-wconf)
(setq +twitter--old-wconf nil))
(dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t))
(twittering-kill-buffer buf)))
;;;###autoload
(defun +twitter/rerender-all ()
"Rerender all `twittering-mode' buffers."
(interactive)
(dolist (buf (doom-buffers-in-mode 'twittering-mode))
(dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t))
(with-current-buffer buf
(twittering-rerender-timeline-all buf))))
(twittering-rerender-timeline-all buf)
(setq-local line-spacing 0.2)
(goto-char (point-min)))))
;;;###autoload
(defun +twitter/ace-link ()
"Open a visible link, username or hashtag in a `twittering-mode' buffer."
(interactive)
(let ((pt (avy-with +twitter/ace-link
(avy--process
(+twitter--collect-links)
(avy--style-fn avy-style)))))
(when (number-or-marker-p pt)
(goto-char pt)
(let ((uri (get-text-property (point) 'uri)))
(if uri (browse-url uri))))))
(defun +twitter--collect-links ()
(let ((end (window-end))
points)
(save-excursion
(goto-char (window-start))
(while (and (< (point) end)
(ignore-errors (twittering-goto-next-thing) t))
(push (point) points))
(nreverse points))))

View file

@ -3,34 +3,84 @@
(def-package! twittering-mode
:commands twit
:config
(setq twittering-use-master-password t
twittering-icon-mode nil
(setq twittering-private-info-file (expand-file-name "twittering-mode.gpg" doom-etc-dir)
twittering-use-master-password t
twittering-request-confirmation-on-posting t
;; twittering-icon-mode t
;; twittering-use-icon-storage t
;; twittering-icon-storage-file (concat doom-cache-dir "twittering-mode-icons.gz")
;; twittering-convert-fix-size 12
twittering-timeline-header ""
twittering-timeline-footer ""
twittering-edit-skeleton 'inherit-any
twittering-status-format
"%RT{%FACE[bold]{RT }}%S (%FACE[bold]{@%s}), %@%r%R:\n%FOLD[ ]{%t %QT{\n+----\n%FOLD[|]{ %S (@%s), %@:\n%FOLD[ ]{%t}}\n+----}}\n "
twittering-status-format "%FACE[font-lock-function-name-face]{ @%s} %FACE[italic]{%@} %FACE[error]{%FIELD-IF-NONZERO[❤ %d]{favorite_count}} %FACE[warning]{%FIELD-IF-NONZERO[↺ %d]{retweet_count}}
%FOLD[ ]{%FILL{%t}%QT{
%FOLD[ ]{%FACE[font-lock-function-name-face]{@%s}\t%FACE[shadow]{%@}
%FOLD[ ]{%FILL{%t}}
}}}
%FACE[twitter-divider]{ }
"
;; twittering-timeline-spec-alias '()
twittering-initial-timeline-spec-string
'(":home" ":mentions" ":direct_messages"))
(set! :popup "*twittering-edit*" :size 12 :select t)
(set-popup-rule! "^\\*twittering-edit" :size 15 :ttl nil :quit nil :select t)
(add-hook! twittering-mode
(setq header-line-format (or (doom-modeline 'twitter) mode-line-format)
(defface twitter-divider
'((((background dark)) (:underline (:color "#141519")))
(((background light)) (:underline (:color "#d3d3d3"))))
"The vertical divider between tweets."
:group 'twittering-mode)
(add-hook 'doom-real-buffer-functions #'+twitter-buffer-p)
(when (featurep! :ui popup)
(setq twittering-pop-to-buffer-function #'+twitter-display-buffer))
(after! solaire-mode
(add-hook 'twittering-mode-hook #'solaire-mode))
;; Custom header-line for twitter buffers
(defun +twitter|switch-mode-and-header-line ()
(setq header-line-format mode-line-format
mode-line-format nil))
(add-hook 'twittering-mode-hook #'+twitter|switch-mode-and-header-line)
(map! :map twittering-mode-map
[remap twittering-kill-buffer] #'+twitter/quit
"Q" #'+twitter/quit-all
"o" #'ace-link-addr
"j" #'evil-next-visual-line
"k" #'evil-previous-visual-line
"J" #'twittering-goto-next-status
"K" #'twittering-goto-previous-status)
(cond ((featurep! :ui doom-modeline +new)
(setq-hook! 'twittering-mode-hook mode-line-format-right nil))
((featurep! :ui doom-modeline)
(def-modeline! 'twitter
'(bar matches " %b " selection-info)
'())
(add-hook! 'twittering-mode-hook (doom-set-modeline 'twitter))))
(def-modeline! twitter
(bar matches " %b " selection-info)
()))
;; `epa--decode-coding-string' isn't defined in later versions of Emacs 27
(unless (fboundp 'epa--decode-coding-string)
(defalias 'epa--decode-coding-string #'decode-coding-string))
(define-key! twittering-mode-map
"q" #'+twitter/quit
"Q" #'+twitter/quit-all
[remap twittering-kill-buffer] #'+twitter/quit
[remap delete-window] #'+twitter/quit
[remap +workspace/close-window-or-workspace] #'+twitter/quit)
(when (featurep! :feature evil +everywhere)
(define-key! twittering-mode-map
[remap evil-window-delete] #'+twitter/quit
"f" #'twittering-favorite
"F" #'twittering-unfavorite
"\C-f" #'twittering-follow
"\C-F" #'twittering-unfollow
"d" #'twittering-delete-status
"r" #'twittering-retweet
"R" #'twittering-toggle-or-retrieve-replied-statuses
"o" #'twittering-update-status-interactive
"O" #'+twitter/ace-link
"/" #'twittering-search
"J" #'twittering-goto-next-status
"K" #'twittering-goto-previous-status
"g" nil
"gg" #'twittering-goto-first-status
"G" #'twittering-goto-last-status
"gj" #'twittering-goto-next-status-of-user
"gk" #'twittering-goto-previous-status-of-user)))

View file

@ -0,0 +1,113 @@
#+TITLE: :app write
Adds word processing tools and the ~+write-mode~ minor mode, which converts
Emacs into a more comfortable writing environment.
* Table of Contents :TOC:
- [[Features][Features]]
- [[~M-x +write-mode~][~M-x +write-mode~]]
- [[Language Tool ~+langtool~][Language Tool ~+langtool~]]
- [[Wordnut ~+wordnut~][Wordnut ~+wordnut~]]
- [[Synosaurus][Synosaurus]]
- [[Prerequisites][Prerequisites]]
- [[Language Tool][Language Tool]]
- [[Wordnut][Wordnut]]
- [[Configuration][Configuration]]
- [[mixed-pitch-mode][mixed-pitch-mode]]
- [[Appendix][Appendix]]
- [[Minor modes][Minor modes]]
- [[Commands][Commands]]
* Features
This module provides two module flags:
- ~+langtool~ Enables language tool integration.
- ~+wordnut~ Enables wordnet integration.
** ~M-x +write-mode~
Write mode makes Emacs a more comfortable writing environment by:
- Centering the buffer (with ~visual-fill-column-mode~), ala distraction-free
mode from other text editors.
- Soft-wrapping long text lines with ~visual-line-mode~.
- Enabling ~mixed-pitch-mode~, allowing fixed-width and variable-pitch fonts to
co-exist in one buffer. For example, a monospace font for SRC blocks and Arial
for everything else.
- In org-mode:
- Turns on ~org-indent-mode~
- Turns on ~+org-pretty-mode~
** Language Tool ~+langtool~
[[https://www.languagetool.org/][Language Tool]] is a polyglot proofreader service that checks for grammar and
stylistic issues in your writing. This requires Java 1.8+.
#+begin_quote
This requires Java 1.8+
#+end_quote
*** Commands
- ~langtool-check~
- ~langtool-correct-buffer~
** Wordnut ~+wordnut~
Wordnut provides a searchable dictionary frontend for Emacs. This requires
~wordnet~, which should be available in your OS's package manager.
*** Commands
- ~wordnut-search~
- ~wordnut-lookup-curent-word~
** Synosaurus
Synosaurus provides a service for looking up synonyms. It requires an internet
connection.
*** Commands
- ~synosaurus-lookup~
- ~synosaurus-choose-and-replace~
* Prerequisites
** Language Tool
Either download and deploy it from https://languagetool.org/ or install it
through your OS package manager:
#+BEGIN_SRC sh
# MacOS/Homebrew users:
brew install languagetool
# Arch Linux users:
sudo pacman -S languagetool
#+END_SRC
This module tries to guess the location of languagetool-commandline.jar. If you
get a warning that Doom =couldn't find languagetool-commandline.jar=, you will
need to find langaugetool-commandline.jar and set ~langtool-language-tool-jar~
to its path.
** Wordnut
This requires =wordnet= to be installed, which should be available through your
OS package manager:
#+BEGIN_SRC sh
# MacOS/Homebrew users:
brew install wordnet
# Arch Linux users:
sudo pacaur -S wordnet # on the AUR
#+END_SRC
* Configuration
** mixed-pitch-mode
To configure which faces are displayed with fixed-pitch fonts in
~mixed-pitch-mode~, look into ~mixed-pitch-fixed-pitch-faces~.
* Appendix
** Minor modes
- ~+write-mode~
- ~mixed-pitch-mode~
** Commands
- ~langtool-check~
- ~langtool-correct-buffer~
- ~synosaurus-choose-and-replace~
- ~synosaurus-lookup~
- ~wordnut-lookup-curent-word~
- ~wordnut-search~

View file

@ -1,18 +1,43 @@
;;; app/write/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(define-minor-mode +write-mode
"TODO"
:init-value nil
:keymap nil
(let ((arg (if +write-mode +1 -1))
(iarg (if +write-mode -1 +1)))
(text-scale-set (if +write-mode 2 0))
(doom/toggle-line-numbers iarg)
(setq-local visual-fill-column-center-text +write-mode)
(visual-fill-column-mode arg)
(visual-line-mode arg)
(when (eq major-mode 'org-mode)
(+org-pretty-mode arg))
(setq line-spacing (if +write-mode 4))))
(defvar +write-mode-map (make-sparse-keymap)
"TODO")
;;;###autoload
(define-minor-mode +write-mode
"Turns Emacs into a more comfortable writing environment and word processor."
:init-value nil
:keymap +write-mode-map
(setq-local visual-fill-column-center-text t)
(when +write-text-scale
(text-scale-set (if +write-mode 2 0)))
(when +write-line-spacing
(setq-local line-spacing +write-line-spacing)))
;;;###autoload
(defun +write|init-org-mode ()
"Initializes `org-mode' specific settings for `+write-mode'."
(when (eq major-mode 'org-mode)
(+org-pretty-mode (if +write-mode +1 -1))))
;;;###autoload
(defun +write|init-line-numbers ()
(display-line-numbers-mode (if +write-mode +1 -1)))
;;;###autoload
(defun +write|init-mixed-pitch ()
(mixed-pitch-mode (if +write-mode +1 -1)))
;;;###autoload
(defun +write|init-visual-fill-column ()
(visual-fill-column-mode (if +write-mode +1 -1)))
;;;###autoload
(add-hook! '+write-mode-hook
#'(flyspell-mode
visual-line-mode
+write|init-mixed-pitch
+write|init-visual-fill-column
+write|init-line-numbers
+write|init-org-mode))

View file

@ -0,0 +1,56 @@
;;; app/write/config.el -*- lexical-binding: t; -*-
(defvar +write-text-scale nil
"What to scale the text up to in `+write-mode'. Uses `text-scale-set'.")
(defvar +write-line-spacing nil
"What to set `line-spacing' in `+write-mode'.")
;;
;; Packages
(def-package! langtool
:when (featurep! +langtool)
:commands (langtool-check
langtool-check-done
langtool-show-message-at-point
langtool-correct-buffer)
:init (setq langtool-default-language "en-US")
:config
(unless langtool-language-tool-jar
(setq langtool-language-tool-jar
(cond (IS-MAC
(locate-file "libexec/languagetool-commandline.jar"
(doom-files-in "/usr/local/Cellar/languagetool"
:type 'dirs
:depth 1)))
(IS-LINUX
"/usr/share/java/languagetool/languagetool-commandline.jar")))))
;; `synosaurus'
(setq synosaurus-choose-method 'default)
;; `mixed-pitch'
(after! mixed-pitch
(setq mixed-pitch-fixed-pitch-faces
(append mixed-pitch-fixed-pitch-faces
'(org-todo-keyword-todo
org-todo-keyword-habt
org-todo-keyword-done
org-todo-keyword-wait
org-todo-keyword-kill
org-todo-keyword-outd
org-todo
org-indent
line-number
line-number-current-line
org-special-keyword
org-date
org-property-value
org-special-keyword
org-property-value
org-ref-cite-face
org-tag
font-lock-comment-face))))

View file

@ -0,0 +1,7 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; app/write/doctor.el
(when (featurep! +langtool)
(require 'langtool)
(unless (file-exists-p langtool-language-tool-jar)
(warn! "Couldn't find languagetool-commandline.jar")))

View file

@ -0,0 +1,11 @@
;; -*- no-byte-compile: t; -*-
;;; app/write/packages.el
(package! synosaurus)
(package! mixed-pitch)
(when (featurep! +langtool)
(package! langtool))
(when (featurep! +wordnut)
(package! wordnut))

View file

@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; collab/foobits/packages.el
(package! floobits)

View file

@ -1,10 +1,9 @@
;;; tools/impatient-mode/autoload.el -*- lexical-binding: t; -*-
;;; collab/impatient-mode/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +impatient-mode/toggle ()
"TODO"
"Toggle `impatient-mode' in the current buffer."
(interactive)
(require 'simple-httpd)
(unless (process-status "httpd")
(httpd-start))
(impatient-mode)

View file

@ -1,5 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; tools/impatient-mode/packages.el
;;; collab/impatient-mode/packages.el
(package! htmlize)
(package! impatient-mode)

View file

@ -1,48 +1,150 @@
#+TITLE: :completion company
#+TITLE: completion/company
#+DATE: February 19, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
This module adds code-completion support, powered by [[https://github.com/company-mode/company-mode][company]].
* Table of Contents :TOC_3:noexport:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[Features][Features]]
- [[Code completion][Code completion]]
- [[Vim-esque omni-completion prefix (C-x)][Vim-esque omni-completion prefix (C-x)]]
- [[Configuration][Configuration]]
- [[Enable as-you-type code completion][Enable as-you-type code completion]]
- [[Enable company backend(s) in certain modes][Enable company backend(s) in certain modes]]
- [[Troubleshooting][Troubleshooting]]
- [[Code-completion doesn't pop up automatically.][Code-completion doesn't pop up automatically.]]
- [[X-mode doesn't have code completion support or requires extra setup.][X-mode doesn't have code completion support or requires extra setup.]]
- [[No backends (or the incorrect ones) have been registered for X-mode.][No backends (or the incorrect ones) have been registered for X-mode.]]
+ Uses ~company-quickhelp~ for documentation tooltips
+ Uses ~company-statistics~ to order results by usage frequency
* Description
This module provides code completion, powered by [[https://github.com/company-mode/company-mode][company-mode]]. It is required
for code completion in many of Doom's :lang modules.
[[/../screenshots/company.png]]
https://assets.doomemacs.org/completion/company/overlay.png
* Table of Contents :TOC:
- [[#install][Install]]
- [[#configure][Configure]]
- [[#auto-completion][Auto-completion]]
- [[#troubleshooting][Troubleshooting]]
** Module Flags
+ =+auto= Enables as-you-type completion.
+ =+childframe= Enables displaying completion candidates in a child frame,
rather than an overlay or tooltip (among with other UI enhancements). *This
requires GUI Emacs 26.1+.*
+ =+tng= Enables completion using only ~TAB~. Pressing ~TAB~ will select the
next completion suggestion, while ~S-TAB~ will select the previous one.
* Install
Some languages require additional setup, and some languages may have no
completion support at all.
** Plugins
+ [[https://github.com/company-mode/company-mode][company-mode]]
+ [[https://github.com/hlissner/emacs-company-dict][company-dict]]
+ [[https://github.com/raxod502/prescient.el][company-prescient]]
+ [[https://github.com/sebastiencs/company-box][company-box]]
Check the README.org in that language's module for details.
* Prerequisites
This module has no direct prerequisites.
* Configure
** Auto-completion
By default, I've disabled auto-completion. This is my preference. I prefer to
invoke company when I need it by calling ~company-complete~ manually (typically,
bound to =C-SPC= in insert mode). However, some may not share my preference.
However, some major modes may require additional setup for code completion to
work in them. Some major modes may have no completion support at all. Check that
major mode's module's documentation for details.
To enable auto-completion you must:
* Features
** Code completion
Ccompletion must be triggered manually with the =C-SPC= key. If you want
as-you-type code completion, the ~+auto~ module flag will enable it.
1. Load ~company~,
2. and change ~company-idle-delay~ to a non-nil float (the default is 0.5)
| Keybind | Description |
|---------+------------------------------------------|
| =C-SPC= | Invoke code completion manually |
| =C-n= | Go to next candidate |
| =C-p= | Go to previous candidate |
| =C-j= | (evil) Go to next candidate |
| =C-k= | (evil) Go to previous candidate |
| =C-h= | Display documentation (if available) |
| =C-u= | Move to previous page of candidates |
| =C-d= | Move to next page of candidates |
| =C-s= | Filter candidates |
| =C-S-s= | Search candidates with helm/ivy |
| =C-SPC= | Complete common |
| =TAB= | Complete common or select next candidate |
| =S-TAB= | Select previous candidate |
For example:
** Vim-esque omni-completion prefix (C-x)
In the spirit of Vim's omni-completion, the following insert mode keybinds are
available to evil users to access specific company backends:
| Keybind | Description |
|-----------+-----------------------------------|
| =C-x C-]= | Complete etags |
| =C-x C-f= | Complete file path |
| =C-x C-k= | Complete from dictionary/keyword |
| =C-x C-l= | Complete full line |
| =C-x C-o= | Invoke complete-at-point function |
| =C-x C-n= | Complete next symbol at point |
| =C-x C-p= | Complete previous symbol at point |
| =C-x C-s= | Complete snippet |
| =C-x s= | Complete spelling suggestions |
* Configuration
** Enable as-you-type code completion
The =+auto= module flag enables this. You may customize ~company-idle-delay~ to
control how quickly the popup should appear.
The ~+company/toggle-auto-completion~ command is also available to toggle this
interactively.
** Enable company backend(s) in certain modes
The ~set-company-backend!~ function exists for setting ~company-backends~
buffer-locally in MODES, which is either a major-mode symbol, a minor-mode
symbol, or a list of either. BACKENDS are prepended to ~company-backends~ for
those modes.
#+BEGIN_SRC emacs-lisp
(require 'company)
(setq company-idle-delay 0.2
company-minimum-prefix-length 3)
(after! js2-mode
(set-company-backend! 'js2-mode 'company-tide 'company-yasnippet))
(after! sh-script
(set-company-backend! 'sh-mode
'(company-shell :with company-yasnippet)))
(after! cc-mode
(set-company-backend! 'c-mode
'(:separate company-irony-c-headers company-irony)))
#+END_SRC
To unset the backends for a particular mode, pass ~nil~ to it:
#+BEGIN_SRC emacs-lisp
(after! sh-script
(set-company-backend! 'sh-mode nil))
#+END_SRC
* Troubleshooting
If completion isn't working for you, please consider the following before
posting a bug report:
If code completion isn't working for you, consider the following common causes
before you file a bug report:
+ If what you are expecting is popup-as-you-type completion (which is disabled
by default), see the "Configure > Auto-completion" section above, which will
instruct you on how to enable this.
+ Some languages don't have any auto-completion support at all.
** Code-completion doesn't pop up automatically.
This is by design. The expectation is that you invoke completion manually with
=C-SPC=. This was decided because code-completion backends can be slow, some
dreadfully so, and invoking them every time you move your cursor can add pauses
and delays while editing.
If, despite that, you still want this functionality, use the =+auto= flag to
enable it.
** X-mode doesn't have code completion support or requires extra setup.
There is no guarantee your language mode will have completion support.
Some, like ~lua-mode~, don't have completion support in Emacs at all. Others may
requires additional setup to get code completion working. For instance,
~go-mode~ requires ~guru~ to be installed on your system, and ~enh-ruby-mode~
requires that you have a Robe server running (~M-x robe-start~).
Check the relevant module's documentation for this kind of information.
** No backends (or the incorrect ones) have been registered for X-mode.
Doom expects every mode to have an explicit list of company-backends (and as
short a list as possible). This may mean you aren't getting all the completion
you want or any at all.
Check the value of ~company-backends~ (=SPC h v company-backends=) from that
mode to see what backends are available. Check the [[*Assigning company backend(s) to modes][Configuration section]] for
details on changing what backends are available for that mode.

View file

@ -1,14 +1,123 @@
;;; completion/company/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar +company-backend-alist
'((text-mode :derived (company-dabbrev company-yasnippet company-ispell))
(prog-mode :derived (:separate company-capf company-yasnippet))
(conf-mode :derived company-capf company-dabbrev-code company-yasnippet))
"An alist matching modes to company backends. The backends for any mode is
built from this.")
;;;###autodef
(defun set-company-backend! (modes &rest backends)
"Prepends BACKENDS (in order) to `company-backends' in MODES.
MODES should be one symbol or a list of them, representing major or minor modes.
This will overwrite backends for MODES on consecutive uses.
If the car of BACKENDS is nil, unset the backends for MODES.
Examples:
(set-company-backend! 'js2-mode
'company-tide 'company-yasnippet)
(set-company-backend! 'sh-mode
'(company-shell :with company-yasnippet))
(set-company-backend! '(c-mode c++-mode)
'(:separate company-irony-c-headers company-irony))
(set-company-backend! 'sh-mode nil) ; unsets backends for sh-mode
To have BACKENDS apply to any mode that is a parent of MODES, set MODES to
:derived, e.g.
(set-company-backend! :derived 'text-mode 'company-dabbrev 'company-yasnippet)"
(declare (indent defun))
(let ((type :exact))
(when (eq modes :derived)
(setq type :derived
modes (pop backends)))
(dolist (mode (doom-enlist modes))
(if (null (car backends))
(setq +company-backend-alist
(delq (assq mode +company-backend-alist)
+company-backend-alist))
(setf (alist-get mode +company-backend-alist)
(cons type backends))))))
;;
;;; Library
(defun +company--backends ()
(append (cl-loop for (mode . rest) in +company-backend-alist
for type = (car rest)
for backends = (cdr rest)
if (or (and (eq type :derived) (derived-mode-p mode)) ; parent modes
(and (eq type :exact)
(or (eq major-mode mode) ; major modes
(and (boundp mode)
(symbol-value mode))))) ; minor modes
append backends)
(default-value 'company-backends)))
;;
;;; Hooks
;;;###autoload
(defun +company|init-backends ()
"Set `company-backends' for the current buffer."
(unless (eq major-mode 'fundamental-mode)
(set (make-local-variable 'company-backends) (+company--backends)))
(add-hook 'after-change-major-mode-hook #'+company|init-backends nil 'local))
(put '+company|init-backends 'permanent-local-hook t)
;;
;;; Commands
;;;###autoload
(defun +company-has-completion-p ()
"Return non-nil if a completion candidate exists at point."
(and (company-manual-begin)
(= company-candidates-length 1)))
;;;###autoload
(defun +company/toggle-auto-completion ()
"Toggle as-you-type code completion."
(interactive)
(require 'company)
(setq company-idle-delay (unless company-idle-delay 0.2))
(message "Auto completion %s"
(if company-idle-delay "enabled" "disabled")))
;;;###autoload
(defun +company/complete ()
"Bring up the completion popup. If only one result, complete it."
(interactive)
(require 'company)
(when (ignore-errors
(/= (point)
(cdr (bounds-of-thing-at-point 'symbol))))
(save-excursion (insert " ")))
(when (and (company-manual-begin)
(= company-candidates-length 1))
(company-complete-common)))
;;;###autoload
(defun +company/dabbrev ()
"Invokes `company-dabbrev-code' in prog-mode buffers and `company-dabbrev'
everywhere else."
(interactive)
(call-interactively
(if (derived-mode-p 'prog-mode)
#'company-dabbrev-code
#'company-dabbrev)))
;;;###autoload
(defun +company/whole-lines (command &optional arg &rest ignored)
"`company-mode' completion backend that completes whole-lines, akin to vim's
@ -16,9 +125,9 @@ C-x C-l."
(interactive (list 'interactive))
(require 'company)
(pcase command
('interactive (company-begin-backend '+company/whole-lines))
('prefix (company-grab-line "^[\t\s]*\\(.+\\)" 1))
('candidates
(`interactive (company-begin-backend '+company/whole-lines))
(`prefix (company-grab-line "^[\t\s]*\\(.+\\)" 1))
(`candidates
(all-completions
arg
(split-string
@ -35,12 +144,13 @@ C-x C-l."
(require 'company-dict)
(require 'company-keywords)
(let ((company-backends '((company-keywords company-dict))))
(call-interactively 'company-complete)))
(call-interactively #'company-complete)))
;;;###autoload
(defun +company/dabbrev-code-previous ()
"TODO"
(interactive)
(require 'company-dabbrev)
(let ((company-selection-wrap-around t))
(call-interactively #'company-dabbrev-code)
(call-interactively #'+company/dabbrev)
(company-select-previous-or-abort)))

View file

@ -1,86 +1,124 @@
;;; completion/company/config.el -*- lexical-binding: t; -*-
(def-setting! :company-backend (modes &rest backends)
"Prepends BACKENDS to `company-backends' in major MODES.
MODES should be one major-mode symbol or a list of them."
`(progn
,@(cl-loop for mode in (doom-enlist (doom-unquote modes))
for def-name = (intern (format "doom--init-company-%s" mode))
collect
`(defun ,def-name ()
(when (and (eq major-mode ',mode)
,(not (eq backends '(nil))))
(require 'company)
(make-variable-buffer-local 'company-backends)
(dolist (backend (list ,@(reverse backends)))
(cl-pushnew backend company-backends :test #'equal))))
collect `(add-hook! ,mode #',def-name))))
;;
;; Packages
;;
(def-package! company
:commands (company-mode global-company-mode company-complete
company-complete-common company-manual-begin company-grab-line)
:config
:commands (company-complete-common company-manual-begin company-grab-line)
:init
(setq company-idle-delay nil
company-tooltip-limit 10
company-tooltip-limit 14
company-dabbrev-downcase nil
company-dabbrev-ignore-case nil
company-dabbrev-code-other-buffers t
company-tooltip-align-annotations t
company-require-match 'never
company-global-modes '(not eshell-mode comint-mode erc-mode message-mode help-mode gud-mode)
company-frontends '(company-pseudo-tooltip-frontend company-echo-metadata-frontend)
company-backends '(company-capf company-dabbrev company-ispell)
company-transformers '(company-sort-by-occurrence))
(after! yasnippet
(nconc company-backends '(company-yasnippet)))
company-global-modes
'(not erc-mode message-mode help-mode gud-mode eshell-mode)
company-backends '(company-capf)
company-frontends
'(company-pseudo-tooltip-frontend
company-echo-metadata-frontend))
:config
(add-hook 'company-mode-hook #'+company|init-backends)
(when (featurep! :feature evil)
(add-hook 'company-mode-hook #'evil-normalize-keymaps))
(global-company-mode +1))
(def-package! company-statistics
:after company
:config
(setq company-statistics-file (concat doom-cache-dir "company-stats-cache.el"))
(quiet! (company-statistics-mode +1)))
(def-package! company
:when (featurep! +auto)
:defer 2
:after-call post-self-insert-hook
:config (setq company-idle-delay 0.1))
;; Looks ugly on OSX without emacs-mac build
(def-package! company-quickhelp
:after company
(def-package! company-tng
:when (featurep! +tng)
:defer 2
:after-call post-self-insert-hook
:config
(setq company-quickhelp-delay nil)
(company-quickhelp-mode +1))
(add-to-list 'company-frontends 'company-tng-frontend)
(define-key! company-active-map
"RET" nil
[return] nil
"TAB" #'company-select-next
[tab] #'company-select-next
[backtab] #'company-select-previous))
;;
;; Packages
(def-package! company-prescient
:hook (company-mode . company-prescient-mode)
:config
(setq prescient-save-file (concat doom-cache-dir "prescient-save.el"))
(prescient-persist-mode +1))
(def-package! company-box
:when (and EMACS26+ (featurep! +childframe))
:hook (company-mode . company-box-mode)
:config
(setq company-box-show-single-candidate t
company-box-backends-colors nil
company-box-max-candidates 50
company-box-icons-alist 'company-box-icons-all-the-icons
company-box-icons-functions
'(+company-box-icons--yasnippet company-box-icons--lsp +company-box-icons--elisp company-box-icons--acphp)
company-box-icons-all-the-icons
`((Unknown . ,(all-the-icons-material "find_in_page" :height 0.8 :face 'all-the-icons-purple))
(Text . ,(all-the-icons-material "text_fields" :height 0.8 :face 'all-the-icons-green))
(Method . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red))
(Function . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red))
(Constructor . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red))
(Field . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red))
(Variable . ,(all-the-icons-material "adjust" :height 0.8 :face 'all-the-icons-blue))
(Class . ,(all-the-icons-material "class" :height 0.8 :face 'all-the-icons-red))
(Interface . ,(all-the-icons-material "settings_input_component" :height 0.8 :face 'all-the-icons-red))
(Module . ,(all-the-icons-material "view_module" :height 0.8 :face 'all-the-icons-red))
(Property . ,(all-the-icons-material "settings" :height 0.8 :face 'all-the-icons-red))
(Unit . ,(all-the-icons-material "straighten" :height 0.8 :face 'all-the-icons-red))
(Value . ,(all-the-icons-material "filter_1" :height 0.8 :face 'all-the-icons-red))
(Enum . ,(all-the-icons-material "plus_one" :height 0.8 :face 'all-the-icons-red))
(Keyword . ,(all-the-icons-material "filter_center_focus" :height 0.8 :face 'all-the-icons-red))
(Snippet . ,(all-the-icons-material "short_text" :height 0.8 :face 'all-the-icons-red))
(Color . ,(all-the-icons-material "color_lens" :height 0.8 :face 'all-the-icons-red))
(File . ,(all-the-icons-material "insert_drive_file" :height 0.8 :face 'all-the-icons-red))
(Reference . ,(all-the-icons-material "collections_bookmark" :height 0.8 :face 'all-the-icons-red))
(Folder . ,(all-the-icons-material "folder" :height 0.8 :face 'all-the-icons-red))
(EnumMember . ,(all-the-icons-material "people" :height 0.8 :face 'all-the-icons-red))
(Constant . ,(all-the-icons-material "pause_circle_filled" :height 0.8 :face 'all-the-icons-red))
(Struct . ,(all-the-icons-material "streetview" :height 0.8 :face 'all-the-icons-red))
(Event . ,(all-the-icons-material "event" :height 0.8 :face 'all-the-icons-red))
(Operator . ,(all-the-icons-material "control_point" :height 0.8 :face 'all-the-icons-red))
(TypeParameter . ,(all-the-icons-material "class" :height 0.8 :face 'all-the-icons-red))
;; (Template . ,(company-box-icons-image "Template.png"))))
(Yasnippet . ,(all-the-icons-material "short_text" :height 0.8 :face 'all-the-icons-green))
(ElispFunction . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red))
(ElispVariable . ,(all-the-icons-material "check_circle" :height 0.8 :face 'all-the-icons-blue))
(ElispFeature . ,(all-the-icons-material "stars" :height 0.8 :face 'all-the-icons-orange))
(ElispFace . ,(all-the-icons-material "format_paint" :height 0.8 :face 'all-the-icons-pink))))
(defun +company-box-icons--yasnippet (candidate)
(when (get-text-property 0 'yas-annotation candidate)
'Yasnippet))
(defun +company-box-icons--elisp (candidate)
(when (derived-mode-p 'emacs-lisp-mode)
(let ((sym (intern candidate)))
(cond ((fboundp sym) 'ElispFunction)
((boundp sym) 'ElispVariable)
((featurep sym) 'ElispFeature)
((facep sym) 'ElispFace))))))
(def-package! company-dict
:commands company-dict
:defer t
:config
(setq company-dict-dir (expand-file-name "dicts" doom-private-dir))
(defun +company|enable-project-dicts (mode &rest _)
"Enable per-project dictionaries."
(if (symbol-value mode)
(cl-pushnew mode company-dict-minor-mode-list :test #'eq)
(add-to-list 'company-dict-minor-mode-list mode nil #'eq)
(setq company-dict-minor-mode-list (delq mode company-dict-minor-mode-list))))
(add-hook 'doom-project-hook #'+company|enable-project-dicts))
;;
;; Autoloads
;;
(autoload 'company-capf "company-capf")
(autoload 'company-yasnippet "company-yasnippet")
(autoload 'company-dabbrev "company-dabbrev")
(autoload 'company-dabbrev-code "company-dabbrev-code")
(autoload 'company-etags "company-etags")
(autoload 'company-elisp "company-elisp")
(autoload 'company-files "company-files")
(autoload 'company-gtags "company-gtags")
(autoload 'company-ispell "company-ispell")

View file

@ -3,5 +3,6 @@
(package! company)
(package! company-dict)
(package! company-quickhelp)
(package! company-statistics)
(package! company-prescient)
(when (and EMACS26+ (featurep! +childframe))
(package! company-box))

View file

@ -1,24 +0,0 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; completion/company/test/company.el
(require! :completion company)
(require 'company)
;;
(def-test! set-company-backend
:minor-mode company-mode
(let ((company-backends '(default)))
(set! :company-backend 'emacs-lisp-mode '(backend-1))
(set! :company-backend 'lisp-interaction-mode 'backend-1 'backend-2)
(set! :company-backend 'text-mode 'backend-1)
(with-temp-buffer
(emacs-lisp-mode)
(should (equal company-backends '((backend-1) default))))
(with-temp-buffer
(lisp-interaction-mode)
(should (equal company-backends '(backend-1 backend-2 default))))
(with-temp-buffer
(text-mode)
(should (equal company-backends '(backend-1 default))))
;; global backends shouldn't be affected
(should (equal company-backends '(default)))))

View file

@ -0,0 +1,75 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; completion/company/test/test-company.el
(describe "completion/company"
(before-all
(load! "../autoload"))
(describe ":company-backend"
:var (a +company-backend-alist backends)
(before-each
(setq-default company-backends '(t))
(setq +company-backend-alist nil
a (get-buffer-create "x"))
(fset 'backends
(lambda (mode)
(let ((major-mode mode))
(+company--backends))))
(set-buffer a)
(spy-on 'require))
(after-each
(kill-buffer a))
;;
(it "sets backends for a major mode"
(set-company-backend! 'text-mode 'a)
(expect (backends 'text-mode) :to-equal '(a t)))
(it "sets backends for a derived-mode"
(set-company-backend! :derived 'prog-mode 'a)
(expect (backends 'prog-mode) :to-equal '(a t))
(expect (backends 'emacs-lisp-mode) :to-equal '(a t)))
(it "sets multiple backends for exact major modes"
(set-company-backend! '(text-mode emacs-lisp-mode) 'a 'b)
(expect (backends 'text-mode) :to-equal (backends 'emacs-lisp-mode)))
(it "sets cumulative backends"
(set-company-backend! :derived 'prog-mode '(a b c))
(set-company-backend! 'emacs-lisp-mode 'd 'e)
(expect (backends 'emacs-lisp-mode) :to-equal '(d e (a b c) t)))
(it "sets cumulative backends with a minor mode"
(set-company-backend! :derived 'prog-mode '(a b c))
(set-company-backend! 'emacs-lisp-mode 'd 'e)
(set-company-backend! 'some-minor-mode 'x 'y)
(setq-local some-minor-mode t)
(expect (backends 'emacs-lisp-mode) :to-equal '(x y d e (a b c) t)))
(it "overwrites past backends"
(set-company-backend! 'text-mode 'old 'backends)
(set-company-backend! 'text-mode 'new 'backends)
(expect (backends 'text-mode) :to-equal '(new backends t)))
(it "unsets past backends"
(set-company-backend! 'text-mode 'old)
(set-company-backend! 'text-mode nil)
(expect (backends 'text-mode) :to-equal (default-value 'company-backends)))
(it "unsets past parent backends"
(set-company-backend! :derived 'prog-mode 'old)
(set-company-backend! 'emacs-lisp-mode 'child)
(set-company-backend! :derived 'prog-mode nil)
(expect (backends 'emacs-lisp-mode) :to-equal '(child t)))
(it "overwrites past cumulative backends"
(set-company-backend! :derived 'prog-mode 'base)
(set-company-backend! 'emacs-lisp-mode 'old)
(set-company-backend! 'emacs-lisp-mode 'new)
(expect (backends 'emacs-lisp-mode) :to-equal '(new base t)))
(it "overwrites past parent backends"
(set-company-backend! :derived 'prog-mode 'base)
(set-company-backend! 'emacs-lisp-mode 'child)
(set-company-backend! :derived 'prog-mode 'new)
(expect (backends 'emacs-lisp-mode) :to-equal '(child new t)))))

View file

@ -1,60 +1,83 @@
;;; completion/helm/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
;;;###autoload (autoload '+helm:swoop "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:swoop (&optional search bang)
"Invoke `swoop' with SEARCH. If BANG, do multiline search."
(interactive "<a><!>")
(helm-swoop :$query search :$multiline bang))
;;
;; Project searching
(defun +helm--file-search (beg end query &optional directory options)
(require 'helm-ag)
(helm-ag--init-state)
(let ((helm-ag--default-directory (or directory (doom-project-root)))
(query (or query
(if (evil-visual-state-p)
(and beg end
(> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))
+helm--file-last-query)
+helm--file-last-query))
(helm-ag-command-option (concat helm-ag-command-option " " (string-join options " "))))
(setq helm-ag--last-query query)
(helm-attrset 'search-this-file nil helm-ag-source)
(helm-attrset 'name (helm-ag--helm-header helm-ag--default-directory) helm-ag-source)
(helm :sources '(helm-ag-source)
:input query
:buffer "*helm-ag*"
:keymap helm-ag-map
:history 'helm-ag--helm-history)))
;;;###autoload (autoload '+helm:pt "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:pt (all-files-p query)
"Ex interface for `+helm/pt'"
(interactive "<!><a>")
(+helm/pt all-files-p query))
;;;###autoload (autoload '+helm:grep "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:grep (all-files-p query)
"Ex interface for `+helm/grep'"
(interactive "<!><a>")
(+helm/grep all-files-p query))
(defvar +helm--file-last-search nil)
;;;###autoload (autoload '+helm:ag "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:ag (beg end query &optional bang)
"TODO"
(interactive "<r><a><!>")
(+helm--file-search beg end query nil
(if bang (list "-a" "--hidden"))))
;;;###autoload (autoload '+helm:ag-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:ag-cwd (beg end query &optional bang)
"TODO"
(interactive "<r><a><!>")
(+helm--file-search beg end query default-directory
(list "-n" (if bang "-a"))))
(evil-define-command +helm:ag (all-files-p query)
"Ex interface for `+helm/ag'"
(interactive "<!><a>")
(+helm/ag all-files-p query))
;;;###autoload (autoload '+helm:rg "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:rg (beg end query &optional bang)
"TODO"
(interactive "<r><a><!>")
(let ((helm-ag-base-command "rg --no-heading"))
(+helm--file-search beg end query nil
(if bang (list "-uu")))))
(evil-define-command +helm:rg (all-files-p query)
"Ex interface for `+helm/rg'"
(interactive "<!><a>")
(+helm/rg all-files-p query))
;;;###autoload (autoload '+helm:rg-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:rg-cwd (beg end query &optional bang)
;;;###autoload (autoload '+helm:pt-from-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:pt-from-cwd (query &optional recurse-p)
"Ex interface for `+helm/pt-from-cwd'."
(interactive "<a><!>")
(+helm/pt-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+helm:grep-from-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:grep-from-cwd (query &optional recurse-p)
"Ex interface for `+helm/grep-from-cwd'."
(interactive "<a><!>")
(+helm/grep-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+helm:ag-from-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:ag-from-cwd (query &optional recurse-p)
"Ex interface for `+helm/ag-from-cwd'."
(interactive "<a><!>")
(+helm/ag-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+helm:rg-from-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:rg-from-cwd (query &optional recurse-p)
"Ex interface for `+helm/rg-from-cwd'."
(interactive "<a><!>")
(+helm/rg-from-cwd (not recurse-p) query))
;;;###autoload
(defun +helm--set-prompt-display (pos)
"TODO"
(interactive "<r><a><!>")
(let ((helm-ag-base-command "rg --no-heading --maxdepth 1"))
(+helm--file-search beg end query default-directory
(if bang (list "-uu")))))
(let (beg state region-active m)
(with-selected-window (minibuffer-window)
(setq beg (save-excursion (vertical-motion 0 (helm-window)) (point))
state evil-state
region-active (region-active-p)
m (mark t)))
(when region-active
(setq m (- m beg))
;; Increment pos to handle the space before prompt (i.e `pref').
(put-text-property (1+ (min m pos)) (+ 2 (max m pos))
'face
(list :background (face-background 'region))
header-line-format))
(put-text-property
;; Increment pos to handle the space before prompt (i.e `pref').
(+ 1 pos) (+ 2 pos)
'face
(if (eq state 'insert)
'underline
;; Don't just use 'cursor, this can hide the current character.
(list :inverse-video t
:foreground (face-background 'cursor)
:background (face-background 'default)))
header-line-format)))

View file

@ -0,0 +1,249 @@
;;; completion/helm/autoload/helm.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +helm/tasks (&optional _arg)
(interactive "P")
;; TODO Implement `+helm/tasks'
(error "Not implemented yet"))
;;;###autoload
(defun +helm/projectile-find-file ()
"Call `helm-find-files' if called from HOME, otherwise
`helm-projectile-find-file'."
(interactive)
(call-interactively
(if (or (file-equal-p default-directory "~")
(if-let* ((proot (doom-project-root)))
(file-equal-p proot "~")
t))
#'helm-find-files
#'helm-projectile-find-file)))
;;;###autoload
(defun +helm/workspace-buffer-list ()
"A version of `helm-buffers-list' with its buffer list restricted to the
current workspace."
(interactive)
(unless (featurep! :feature workspaces)
(user-error "This command requires the :feature workspaces module"))
(with-no-warnings
(with-persp-buffer-list nil (helm-buffers-list))))
;;;###autoload
(defun +helm/workspace-mini ()
"A version of `helm-mini' with its buffer list restricted to the current
workspace."
(interactive)
(unless (featurep! :feature workspaces)
(user-error "This command requires the :feature workspaces module"))
(with-no-warnings
(with-persp-buffer-list nil (helm-mini))))
;;
;; Project search
(defun +helm-ag-search-args (all-files-p recursive-p)
(list (concat "ag " (if IS-WINDOWS "--vimgrep" "--nocolor --nogroup"))
"-S"
(if all-files-p "-z -a")
(unless recursive-p "--depth 1")))
(defun +helm-rg-search-args (all-files-p recursive-p)
(list "rg --no-heading --line-number --color never"
"-S"
(when all-files-p "-z -uu")
(unless recursive-p "--maxdepth 1")))
(defun +helm-pt-search-args (all-files-p recursive-p)
(list "pt --nocolor --nogroup -e"
"-S"
(if all-files-p "-z -a")
(unless recursive-p "--depth 1")))
;;
(defun +helm--grep-source ()
(require 'helm-projectile)
(helm-build-async-source (capitalize (helm-grep-command t))
:header-name (lambda (_name) "Helm Projectile Grep (C-c ? Help)")
:candidates-process #'helm-grep-collect-candidates
:filter-one-by-one #'helm-grep-filter-one-by-one
:candidate-number-limit 9999
:nohighlight t
:keymap helm-grep-map
:history 'helm-grep-history
:action (apply #'helm-make-actions helm-projectile-grep-or-ack-actions)
:persistent-action 'helm-grep-persistent-action
:persistent-help "Jump to line (`C-u' Record in mark ring)"
:requires-pattern 2))
(defun +helm--grep-search (directory query prompt &optional all-files-p recursive-p)
(let* ((default-directory directory)
(helm-ff-default-directory directory)
(helm-grep-in-recurse recursive-p)
(helm-grep-ignored-files
(unless all-files-p
(cl-union (projectile-ignored-files-rel) grep-find-ignored-files)))
(helm-grep-ignored-directories
(unless all-files-p
(cl-union (mapcar 'directory-file-name (projectile-ignored-directories-rel))
grep-find-ignored-directories)))
(helm-grep-default-command
(if (and nil (eq (projectile-project-vcs) 'git))
(format "git --no-pager grep --no-color -n%%c -e %%p %s -- %%f"
(if recursive-p "" "--max-depth 1 "))
(format "grep -si -a%s %%e -n%%cH -e %%p %%f %s"
(if recursive-p " -R" "")
(if recursive-p "." "./*"))))
(helm-grep-default-recurse-command helm-grep-default-command))
(setq helm-source-grep (+helm--grep-source))
(helm :sources 'helm-source-grep
:input query
:prompt prompt
:buffer "*helm grep*"
:default-directory directory
:keymap helm-grep-map
:history 'helm-grep-history
:truncate-lines helm-grep-truncate-lines)))
;;;###autoload
(cl-defun +helm-file-search (engine &key query in all-files (recursive t))
"Conduct a file search using ENGINE, which can be any of: rg, ag, pt, and
grep. If omitted, ENGINE will default to the first one it detects, in that
order.
:query STRING
Determines the initial input to search for.
:in PATH
Sets what directory to base the search out of. Defaults to the current
project's root.
:recursive BOOL
Whether or not to search files recursively from the base directory."
(declare (indent defun))
(require 'helm-ag)
(helm-ag--init-state)
(let* ((project-root (or (doom-project-root) default-directory))
(directory (or in project-root))
(default-directory directory)
(helm-ag--default-directory directory)
(helm-ag--default-target (list directory))
(engine (or engine
(cl-find-if #'executable-find +helm-project-search-engines
:key #'symbol-name)
(and (or (executable-find "grep")
(executable-find "git"))
'grep)
(user-error "No search engine specified (is ag, rg, pt or git installed?)")))
(query (or query
(when (use-region-p)
(let ((beg (or (bound-and-true-p evil-visual-beginning) (region-beginning)))
(end (or (bound-and-true-p evil-visual-end) (region-end))))
(when (> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))))
""))
(prompt (format "[%s %s] "
(symbol-name engine)
(cond ((file-equal-p directory project-root)
(projectile-project-name))
((file-equal-p directory default-directory)
"./")
((file-relative-name directory project-root)))))
(command
(pcase engine
(`ag (+helm-ag-search-args all-files recursive))
(`rg (+helm-rg-search-args all-files recursive))
(`pt (+helm-pt-search-args all-files recursive))
('grep (+helm--grep-search directory query prompt all-files recursive)
(cl-return t))))
(helm-ag-base-command (string-join command " ")))
;; TODO Define our own sources instead
(helm-attrset 'name (format "[%s %s] Searching %s"
engine
(string-join (delq nil (cdr command)) " ")
(abbreviate-file-name directory))
helm-source-do-ag)
(helm-attrset '+helm-command command helm-source-do-ag)
(cl-letf (((symbol-function 'helm-do-ag--helm)
(lambda () (helm :sources '(helm-source-do-ag)
:prompt prompt
:buffer "*helm-ag*"
:keymap helm-do-ag-map
:input query
:history 'helm-ag--helm-history))))
(helm-do-ag directory))))
(defun +helm--get-command (format)
(cl-loop for tool in (cl-remove-duplicates +helm-project-search-engines :from-end t)
if (executable-find (symbol-name tool))
return (intern (format format tool))))
;;;###autoload
(defun +helm/project-search (&optional arg initial-query directory)
"Performs a project search from the project root.
Uses the first available search backend from `+helm-project-search-engines'. If
ARG (universal argument), include all files, even hidden or compressed ones, in
the search."
(interactive "P")
(funcall (or (+helm--get-command "+helm/%s")
#'+helm/grep)
arg
initial-query
directory))
;;;###autoload
(defun +helm/project-search-from-cwd (&optional arg initial-query)
"Performs a project search recursively from the current directory.
Uses the first available search backend from `+helm-project-search-engines'. If
ARG (universal argument), include all files, even hidden or compressed ones."
(interactive "P")
(funcall (or (+helm--get-command "+helm/%s-from-cwd")
#'+helm/grep-from-cwd)
arg
initial-query))
;;;###autoload (autoload '+helm/rg "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/rg-from-cwd "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/ag "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/ag-from-cwd "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/pt "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/pt-from-cwd "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/grep "completion/helm/autoload/helm")
;;;###autoload (autoload '+helm/grep-from-cwd "completion/helm/autoload/helm")
(dolist (engine `(,@(cl-remove-duplicates +helm-project-search-engines :from-end t) grep))
(defalias (intern (format "+helm/%s" engine))
(lambda (arg &optional query directory)
(interactive "P")
(+helm-file-search engine
:query query
:in directory
:all-files (and (not (null arg))
(listp arg))))
(format "Perform a project file search using %s.
QUERY is a regexp. If omitted, the current selection is used. If no selection is
active, the last known search is used.
ARG is the universal argument. If a number is passed through it, e.g. C-u 3, then
If ALL-FILES-P, search compressed and hidden files as well."
engine))
(defalias (intern (format "+helm/%s-from-cwd" engine))
(lambda (arg &optional query)
(interactive "P")
(+helm-file-search engine
:query query
:in default-directory
:all-files (and (not (null arg))
(listp arg))))
(format "Perform a project file search from the current directory using %s.
QUERY is a regexp. If omitted, the current selection is used. If no selection is
active, the last known search is used.
If ALL-FILES-P, search compressed and hidden files as well."
engine)))

View file

@ -0,0 +1,66 @@
;;; completion/helm/autoload/posframe.el -*- lexical-binding: t; -*-
(add-hook 'helm-cleanup-hook #'+helm|posframe-cleanup)
;;;###autoload
(defun +helm-poshandler-frame-center-near-bottom (info)
"Display the child frame in the center of the frame, slightly closer to the
bottom, which is easier on the eyes on big displays."
(let ((parent-frame (plist-get info :parent-frame))
(pos (posframe-poshandler-frame-center info)))
(cons (car pos)
(truncate (/ (frame-pixel-height parent-frame)
2)))))
(defvar +helm--posframe-buffer nil)
;;;###autoload
(defun +helm-posframe-display (buffer &optional _resume)
"TODO"
(setq helm--buffer-in-new-frame-p t)
(let ((solaire-p (bound-and-true-p solaire-mode))
(params (copy-sequence +helm-posframe-parameters)))
(let-alist params
(require 'posframe)
(posframe-show
(setq +helm--posframe-buffer buffer)
:position (point)
:poshandler +helm-posframe-handler
:width
(max (cl-typecase .width
(integer .width)
(float (truncate (* (frame-width) .width)))
(function (funcall .width))
(t 0))
.min-width)
:height
(max (cl-typecase .height
(integer .height)
(float (truncate (* (frame-height) .height)))
(function (funcall .height))
(t 0))
.min-height)
:override-parameters
(dolist (p '(width height min-width min-height) params)
(setq params (delq (assq p params) params)))))
;;
(unless (or (null +helm-posframe-text-scale)
(= +helm-posframe-text-scale 0))
(with-current-buffer buffer
(when (and (featurep 'solaire-mode)
(not solaire-p))
(solaire-mode +1))
(text-scale-set +helm-posframe-text-scale)))))
;;;###autoload
(defun +helm|posframe-cleanup ()
"TODO"
;; Ensure focus is properly returned to the underlying window, by forcing a
;; chance in buffer/window focus. This gives the modeline a chance to refresh.
(switch-to-buffer +helm--posframe-buffer t)
;;
(posframe-delete +helm--posframe-buffer))
;;;###autoload
(defun +helm*fix-get-font-height (orig-fn position)
(ignore-errors (funcall orig-fn position)))

View file

@ -1,124 +1,183 @@
;;; completion/helm/config.el -*- lexical-binding: t; -*-
;; Warning: since I don't use helm, this may be out of date.
(defvar +helm-project-search-engines '(rg ag pt)
"What search tools for `+helm/project-search' (and `+helm-file-search' when no
ENGINE is specified) to try, and in what order.
(defvar +helm-global-prompt " "
"The helm text prompt prefix string is globally replaced with this string.")
To disable a particular tool, remove it from this list. To prioritize a tool
over others, move it to the front of the list. Later duplicates in this list are
silently ignored.
This falls back to git-grep (then grep) if none of these available.")
;; Posframe (requires +childframe)
(defvar +helm-posframe-handler
#'+helm-poshandler-frame-center-near-bottom
"The function that determines the location of the childframe. It should return
a cons cell representing the X and Y coordinates. See
`posframe-poshandler-frame-center' as a reference.")
(defvar +helm-posframe-text-scale 1
"The text-scale to use in the helm childframe. Set to nil for no scaling. Can
be negative.")
(defvar +helm-posframe-parameters
'((internal-border-width . 8)
(width . 0.5)
(height . 0.35)
(min-width . 80)
(min-height . 16))
"TODO")
;;
;; Packages
;;
(def-package! helm-mode
:defer t
:after-call pre-command-hook
:init
(map! [remap apropos] #'helm-apropos
[remap find-library] #'helm-locate-library
[remap bookmark-jump] #'helm-bookmarks
[remap execute-extended-command] #'helm-M-x
[remap find-file] #'helm-find-files
[remap imenu-anywhere] #'helm-imenu-anywhere
[remap imenu] #'helm-semantic-or-imenu
[remap noop-show-kill-ring] #'helm-show-kill-ring
[remap persp-switch-to-buffer] #'+helm/workspace-mini
[remap switch-to-buffer] #'helm-buffers-list
[remap projectile-find-file] #'+helm/projectile-find-file
[remap projectile-recentf] #'helm-projectile-recentf
[remap projectile-switch-project] #'helm-projectile-switch-project
[remap projectile-switch-to-buffer] #'helm-projectile-switch-to-buffer
[remap recentf-open-files] #'helm-recentf
[remap yank-pop] #'helm-show-kill-ring)
:config
(helm-mode +1)
;; helm is too heavy for `find-file-at-point'
(add-to-list 'helm-completing-read-handlers-alist (cons #'find-file-at-point nil)))
(def-package! helm
:init
(setq helm-quick-update t
;; Speedier without fuzzy matching
helm-mode-fuzzy-match nil
helm-buffers-fuzzy-matching nil
helm-apropos-fuzzy-match nil
helm-M-x-fuzzy-match nil
helm-recentf-fuzzy-match nil
helm-projectile-fuzzy-match nil
;; Display extraineous helm UI elements
:after helm-mode
:preface
(setq helm-candidate-number-limit 50
;; Remove extraineous helm UI elements
helm-display-header-line nil
helm-mode-line-string nil
helm-ff-auto-update-initial-value nil
helm-find-files-doc-header nil
;; Don't override evil-ex's completion
helm-mode-handle-completion-in-region nil
helm-candidate-number-limit 50
;; Don't wrap item cycling
helm-move-to-line-cycle-in-source t)
;; Default helm window sizes
helm-display-buffer-default-width nil
helm-display-buffer-default-height 0.25
;; When calling `helm-semantic-or-imenu', don't immediately jump to
;; symbol at point
helm-imenu-execute-action-at-once-if-one nil
;; disable special behavior for left/right, M-left/right keys.
helm-ff-lynx-style-map nil)
(when (featurep! :feature evil +everywhere)
(setq helm-default-prompt-display-function #'+helm--set-prompt-display))
:init
(when (and EMACS26+ (featurep! +childframe))
(setq helm-display-function #'+helm-posframe-display)
;; Fix "Specified window is not displaying the current buffer" error
(advice-add #'posframe--get-font-height :around #'+helm*fix-get-font-height))
(let ((fuzzy (featurep! +fuzzy)))
(setq helm-M-x-fuzzy-match fuzzy
helm-ag-fuzzy-match fuzzy
helm-apropos-fuzzy-match fuzzy
helm-apropos-fuzzy-match fuzzy
helm-bookmark-show-location fuzzy
helm-buffers-fuzzy-matching fuzzy
helm-completion-in-region-fuzzy-match fuzzy
helm-completion-in-region-fuzzy-match fuzzy
helm-ff-fuzzy-matching fuzzy
helm-file-cache-fuzzy-match fuzzy
helm-flx-for-helm-locate fuzzy
helm-imenu-fuzzy-match fuzzy
helm-lisp-fuzzy-completion fuzzy
helm-locate-fuzzy-match fuzzy
helm-mode-fuzzy-match fuzzy
helm-projectile-fuzzy-match fuzzy
helm-recentf-fuzzy-match fuzzy
helm-semantic-fuzzy-match fuzzy))
:config
(load "helm-autoloads" nil t)
(add-hook 'doom-init-hook #'helm-mode)
(set-popup-rule! "^\\*helm" :vslot -100 :size 0.22 :ttl nil)
(defvar helm-projectile-find-file-map (make-sparse-keymap))
(require 'helm-projectile)
(set-keymap-parent helm-projectile-find-file-map helm-map)
;; Hide the modeline
(defun +helm|hide-mode-line (&rest _)
(with-current-buffer (helm-buffer-get)
(unless helm-mode-line-string
(hide-mode-line-mode +1))))
(add-hook 'helm-after-initialize-hook #'+helm|hide-mode-line)
(advice-add #'helm-display-mode-line :override #'+helm|hide-mode-line)
(advice-add #'helm-ag-show-status-default-mode-line :override #'ignore)
;; helm is too heavy for find-file-at-point
(after! helm-mode
(add-to-list 'helm-completing-read-handlers-alist '(find-file-at-point . nil)))
(set! :popup "\\` ?\\*[hH]elm.*?\\*\\'" :size 14 :regexp t)
(setq projectile-completion-system 'helm)
;;; Helm hacks
(defun +helm*replace-prompt (plist)
"Globally replace helm prompts with `+helm-global-prompt'."
(if (keywordp (car plist))
(plist-put plist :prompt +helm-global-prompt)
(setf (nth 2 plist) +helm-global-prompt)
plist))
(advice-add #'helm :filter-args #'+helm*replace-prompt)
(defun +helm*hide-header (&rest _)
"Hide header-line & mode-line in helm windows."
(setq mode-line-format nil))
(advice-add #'helm-display-mode-line :override #'+helm*hide-header)
(map! :map global-map
[remap apropos] #'helm-apropos
[remap find-file] #'helm-find-files
[remap recentf-open-files] #'helm-recentf
[remap projectile-switch-to-buffer] #'helm-projectile-switch-to-buffer
[remap projectile-recentf] #'helm-projectile-recentf
[remap projectile-find-file] #'helm-projectile-find-file
[remap imenu] #'helm-semantic-or-imenu
[remap bookmark-jump] #'helm-bookmarks
[remap noop-show-kill-ring] #'helm-show-kill-ring
[remap projectile-switch-project] #'helm-projectile-switch-project
[remap projectile-find-file] #'helm-projectile-find-file
[remap imenu-anywhere] #'helm-imenu-anywhere
[remap execute-extended-command] #'helm-M-x))
;; TODO Find a better way
(defun +helm*use-helpful (orig-fn arg)
(cl-letf (((symbol-function #'describe-function)
(symbol-function #'helpful-callable))
((symbol-function #'describe-variable)
(symbol-function #'helpful-variable)))
(funcall orig-fn arg)))
(advice-add #'helm-describe-variable :around #'+helm*use-helpful)
(advice-add #'helm-describe-function :around #'+helm*use-helpful))
(def-package! helm-locate
:defer t
:init (defvar helm-generic-files-map (make-sparse-keymap))
:config (set-keymap-parent helm-generic-files-map helm-map))
(def-package! helm-flx
:when (featurep! +fuzzy)
:hook (helm-mode . helm-flx-mode)
:config (helm-flx-mode +1))
(def-package! helm-bookmark
:commands helm-bookmark
:config (setq-default helm-bookmark-show-location t))
;; `helm-ag'
(after! helm-ag
(map! :map helm-ag-edit-map :n "RET" #'compile-goto-error)
(define-key helm-ag-edit-map [remap quit-window] #'helm-ag--edit-abort)
(set-popup-rule! "^\\*helm-ag-edit" :size 0.35 :ttl 0 :quit nil)
;; Recenter after jumping to match
(advice-add #'helm-ag--find-file-action :after-while #'doom*recenter))
(def-package! helm-files
:defer t
:config
;; `helm-bookmark'
(setq helm-bookmark-show-location t)
;; `helm-files'
(after! helm-files
(setq helm-boring-file-regexp-list
(append (list "\\.projects$" "\\.DS_Store$")
helm-boring-file-regexp-list)))
(def-package! helm-ag
:defer t
;; `helm-locate'
(defvar helm-generic-files-map (make-sparse-keymap))
(after! helm-locate (set-keymap-parent helm-generic-files-map helm-map))
;; `helm-projectile'
(def-package! helm-projectile
:commands (helm-projectile-find-file
helm-projectile-recentf
helm-projectile-switch-project
helm-projectile-switch-to-buffer)
:init
(setq projectile-completion-system 'helm)
(defvar helm-projectile-find-file-map (make-sparse-keymap))
:config
(map! :map helm-ag-edit-map
[remap doom/kill-this-buffer] #'helm-ag--edit-abort
[remap quit-window] #'helm-ag--edit-abort))
(set-keymap-parent helm-projectile-find-file-map helm-map))
(def-package! helm-css-scss ; https://github.com/ShingoFukuyama/helm-css-scss
:commands (helm-css-scss
helm-css-scss-multi
helm-css-scss-insert-close-comment)
:config
(setq helm-css-scss-split-direction #'split-window-vertically
helm-css-scss-split-with-multiple-windows t))
(def-package! helm-swoop ; https://github.com/ShingoFukuyama/helm-swoop
:commands (helm-swoop helm-multi-swoop helm-multi-swoop-all)
:config
(setq helm-swoop-use-line-number-face t
helm-swoop-candidate-number-limit 200
helm-swoop-speed-or-color t
helm-swoop-pre-input-function (lambda () "")))
(def-package! helm-describe-modes :commands helm-describe-modes)
;; `swiper-helm'
(after! swiper-helm
(setq swiper-helm-display-function
(lambda (buf &optional _resume) (pop-to-buffer buf)))
(global-set-key [remap swiper] #'swiper-helm)
(add-to-list 'swiper-font-lock-exclude #'+doom-dashboard-mode nil #'eq))

View file

@ -5,7 +5,10 @@
(package! helm-ag)
(package! helm-c-yasnippet)
(package! helm-company)
(package! helm-css-scss)
(package! helm-describe-modes :recipe (:fetcher github :repo "emacs-helm/helm-describe-modes"))
(package! helm-projectile)
(package! helm-swoop)
(package! swiper-helm)
(when (featurep! +fuzzy)
(package! helm-flx))
(when (and EMACS26+ (featurep! +childframe))
(package! posframe))

View file

@ -1,7 +1,6 @@
;;; completion/ido/config.el -*- lexical-binding: t; -*-
(def-package! ido
:config
(defun +ido|init ()
(setq ido-ignore-buffers
'("\\` " "^\\*ESS\\*" "^\\*Messages\\*" "^\\*Help\\*" "^\\*Buffer"
"^\\*.*Completions\\*$" "^\\*Ediff" "^\\*tramp" "^\\*cvs-"
@ -16,29 +15,18 @@
ido-enable-last-directory-history t
ido-save-directory-list-file (concat doom-cache-dir "ido.last"))
(push "\\`.DS_Store$" ido-ignore-files)
(push "Icon\\?$" ido-ignore-files)
(unless (member "\\`.DS_Store$" ido-ignore-files)
(push "\\`.DS_Store$" ido-ignore-files)
(push "Icon\\?$" ido-ignore-files))
(ido-mode 1)
(ido-everywhere 1)
(require 'ido-ubiquitous)
(ido-ubiquitous-mode 1)
(defun +ido|init ()
(require 'ido-vertical-mode)
(ido-vertical-mode 1)
(require 'flx-ido)
(flx-ido-mode +1)
(require 'crm-custom)
(crm-custom-mode +1)
(map! :map (ido-common-completion-map ido-completion-map ido-file-completion-map)
"C-n" #'ido-next-match
"C-p" #'ido-prev-match
"C-w" #'ido-delete-backward-word-updir))
(add-hook 'ido-setup-hook #'+ido|init)
(define-key! (ido-common-completion-map ido-completion-map ido-file-completion-map)
"\C-n" #'ido-next-match
"\C-p" #'ido-prev-match
"\C-w" #'ido-delete-backward-word-updir
;; Go to $HOME with ~
"~" (λ! (if (looking-back "/" (point-min))
(insert "~/")
(call-interactively #'self-insert-command))))
(defun +ido*sort-mtime ()
"Sort ido filelist by mtime instead of alphabetically."
@ -55,10 +43,16 @@
(advice-add #'ido-sort-mtime :override #'+ido*sort-mtime)
(add-hook! (ido-make-file-list ido-make-dir-list) #'+ido*sort-mtime)
(defun +ido|setup-home-keybind ()
"Go to $HOME with ~"
(define-key ido-file-completion-map (kbd "~")
(λ! (if (looking-back "/" (point-min))
(insert "~/")
(call-interactively #'self-insert-command)))))
(add-hook 'ido-setup-hook #'+ido|setup-home-keybind))
;;
(ido-mode 1)
(ido-everywhere 1)
(ido-ubiquitous-mode 1)
(ido-vertical-mode 1)
(flx-ido-mode +1)
(crm-custom-mode +1)
;;
(remove-hook 'ido-setup-hook #'+ido|init))
;;
(add-hook 'ido-setup-hook #'+ido|init)

View file

@ -2,6 +2,6 @@
;;; completion/ido/packages.el
(package! flx-ido)
(package! ido-ubiquitous)
(package! ido-completing-read+)
(package! ido-vertical-mode)
(package! crm-custom)

View file

@ -1,128 +1,57 @@
#+TITLE: :completion ivy
#+TITLE: completion/ivy
#+DATE: February 13, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
This module adds Ivy, a completion backend.
* Table of Contents :TOC_3:noexport:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Hacks][Hacks]]
- [[Prerequisites][Prerequisites]]
- [[Install][Install]]
- [[MacOS][MacOS]]
- [[Arch Linux][Arch Linux]]
- [[Features][Features]]
- [[Jump-to-file project navigation][Jump-to-file project navigation]]
- [[Project search & replace][Project search & replace]]
- [[In-buffer searching][In-buffer searching]]
- [[Task lookup][Task lookup]]
- [[Ivy integration for various completing commands][Ivy integration for various completing commands]]
- [[General][General]]
- [[Jump to files, buffers or projects)][Jump to files, buffers or projects)]]
- [[Search][Search]]
- [[Configuration][Configuration]]
- [[Enable fuzzy/non-fuzzy search for specific commands][Enable fuzzy/non-fuzzy search for specific commands]]
- [[Change the position of the ivy childframe][Change the position of the ivy childframe]]
- [[Troubleshooting][Troubleshooting]]
* Description
This module provides Ivy integration for a variety of Emacs commands, as well as
a unified interface for project search and replace, powered by ag, rg, pt,
git-grep & grep (whichever is available).
#+begin_quote
I prefer ivy over ido for its flexibility. I prefer ivy over helm because it's
lighter.
lighter, simpler and faster in many cases.
#+end_quote
+ Project-wide search & replace powered by ~rg~ or ~ag~
+ Project jump-to navigation ala Command-T, Sublime Text's Jump-to-anywhere or
Vim's CtrlP plugin.
+ Ivy integration for ~M-x~, ~imenu~, ~recentf~ and others.
+ A powerful, interactive in-buffer search using ~swiper~.
+ Ivy-powered TODO/FIXME navigation
** Module Flags
+ =+fuzzy= Enables the fuzzy method for ivy searches.
+ =+childframe= Causes Ivy to display in a floating child frame, above Emacs.
*This requires GUI Emacs 26.1+*
* Table of Contents :TOC:
- [[#install][Install]]
- [[#macos][MacOS]]
- [[#arch-linux][Arch Linux]]
- [[#usage][Usage]]
- [[#project-search--replace][Project search & replace]]
- [[#jump-to-file-project-navigation][Jump-to-file project navigation]]
- [[#in-buffer-searching][In-buffer searching]]
- [[#task-lookup][Task lookup]]
- [[#appendix][Appendix]]
- [[#commands][Commands]]
- [[#hacks][Hacks]]
* Install
This module optionally depends on [[https://github.com/BurntSushi/ripgrep][ripgrep]] and [[https://github.com/ggreer/the_silver_searcher][the_silver_searcher]].
~rg~ is faster, but its results aren't deterministic, neither does it support
multiline search or full PCRE (at the time of writing), that's where ~ag~ is
useful.
** MacOS
#+BEGIN_SRC sh :tangle (if (doom-system-os 'macos) "yes")
brew install ripgrep the_silver_searcher
#+END_SRC
** Arch Linux
#+BEGIN_SRC sh :dir /sudo:: :tangle (if (doom-system-os 'arch) "yes")
sudo pacman --needed --noconfirm -S ripgrep the_silver_searcher
#+END_SRC
* Usage
Here is some insight into how I use this module.
** Project search & replace
There are four Ex interfaces for the silver searcher and ripgrep. They are:
+ ~:ag[!]~
+ ~:agcwd[!]~
+ ~:rg[!]~
+ ~:rgcwd[!]~
The optional BANG tells ag/rg to include ignored files in the search. And the
\*cwd variant of each command will only search in the current directory
(non-recursively).
[[/../screenshots/modules/completion/ivy/ivy-search.gif]]
Now, how do we do text replacements? With the ivy popup open you can press
=S+Tab= to create an wgrep buffer out of the results.
[[/../screenshots/modules/completion/ivy/ivy-search-replace.gif]]
Make your modifications and press =C-c C-c= to commit them, or =C-c C-k= to
abort.
** Jump-to-file project navigation
Inspired by Sublime Text's jump-to-anywhere, Vim's CtrlP/Unite plugins, and
Textmate's Command-T, a marriage of ~projectile~ and ~ivy~ makes this available
in Emacs.
Invoke it with =SPC f /=, =SPC SPC= or ~M-x counsel-projectile-find-file~.
[[/../screenshots/modules/completion/ivy/ivy-projectile.gif]]
** In-buffer searching
I use ~evil-search~ (invoked by pressing =/= in normal mode) when jumping
small/moderate (or predictable) distances. However, there are occasions where I
need more feedback, so I turn to ~swiper~ (available directly with =M-x swiper
RET=, or via ~:sw[iper]~).
[[/../screenshots/modules/completion/ivy/ivy-swiper.gif]]
** Task lookup
I sprinkle my projects with TODO's & FIXME's. You can navigate to and peruse
them via ~M-x +ivy/tasks~ or ~:todo[!]~ (ex command).
[[/../screenshots/modules/completion/ivy/ivy-todo.gif]]
* Appendix
** Commands
Here is a list of my commonly used commands, their default keybinds (defined in
[[../../private/default/+bindings.el][private/default/+bindings.el]]), and their corresponding ex command (defined in
[[../../private/default/+evil-commands.el][private/default/+evil-commands.el]]).
| command | key / ex command | description |
|-------------------------------------+------------------------+------------------------------------------------------------------|
| ~counsel-M-x~ | =M-x= | Smarter, smex-powered M-x |
| ~counsel-bookmark~ | =SPC RET= | Find bookmark |
| ~counsel-find-file~ | =SPC f .= or =SPC .= | Browse from current directory |
| ~counsel-projectile-find-file~ | =SPC f /= or =SPC SPC= | Find file in project |
| ~counsel-projectile-switch-project~ | =SPC p p= | Open another project |
| ~counsel-recentf~ | =SPC f r= | Find recently opened file |
| ~ivy-switch-buffer~ | =SPC b b= | Jump to buffer in current workspace |
| ~+ivy/switch-workspace-buffer~ | =SPC b B= | Jump to buffer across workspaces |
| ~+ivy:ag~ | ~:ag[!] [QUERY]~ | Search project (BANG = ignore gitignore) |
| ~+ivy:ag-cwd~ | ~:agcwd[!] [QUERY]~ | Search this directory (BANG = don't recurse into subdirectories) |
| ~+ivy:rg~ | ~:rg[!] [QUERY]~ | Search project (if BANG, ignore gitignore) |
| ~+ivy:rg-cwd~ | ~:rgcwd[!] [QUERY]~ | Search this directory (BANG = don't recurse into subdirectories) |
| ~+ivy:swiper~ | ~:sw[iper] [QUERY]~ | Search current buffer |
| ~+ivy:todo~ | ~:todo[!]~ | List all TODO/FIXMEs in project (or current file if BANG) |
While in a search (e.g. invoked from ~+ivy:ag~ or ~+ivy:rg~), these new
keybindings are available to you:
| key | description |
|-------------+--------------------------------------------------------------------------------|
| =<backtab>= | Perform search/replace on the search results (open occur buffer in wgrep mode) |
| =C-SPC= | Preview the current candidate |
| =M-RET= | Open the selected candidate in other-window |
** Plugins
+ [[https://github.com/abo-abo/swiper][ivy]]
+ [[https://github.com/abo-abo/swiper][counsel]]
+ [[https://github.com/ericdanan/counsel-projectile][counsel-projectile]]
+ [[https://github.com/abo-abo/swiper][swiper]]
+ [[https://github.com/abo-abo/swiper][ivy-hydra]]
+ [[https://github.com/yevgnen/ivy-rich][ivy-rich]]
+ [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]]
+ [[https://github.com/DarwinAwardWinner/amx][amx]]
+ [[https://github.com/lewang/flx][flx]]* (=+fuzzy=)
+ [[https://github.com/tumashu/ivy-posframe][ivy-posframe]]* (=+childframe=)
** Hacks
+ Functions with ivy/counsel equivalents have been globally remapped (like
@ -131,4 +60,161 @@ keybindings are available to you:
+ ~counsel-[arp]g~'s 3-character limit was reduced to 1 (mainly for the ex
command)
* Prerequisites
This module optionally depends on one of:
+ [[https://github.com/BurntSushi/ripgrep][ripgrep]] (rg)
+ [[https://github.com/ggreer/the_silver_searcher][the_silver_searcher]] (ag)
+ [[https://github.com/monochromegane/the_platinum_searcher][the_platinum_searcher]] (pt)
Ripgrep is recommended, but the order of its results aren't deterministic and it
doesn't support full PCRE (at the time of writing). The_silver_searcher is a
good alternative if either of these bother you.
If none of these are installed, file search commands will use git-grep (falling
back to grep, otherwise).
** Install
*** MacOS
#+BEGIN_SRC sh
brew install ripgrep the_silver_searcher
#+END_SRC
*** Arch Linux
#+BEGIN_SRC sh :dir /sudo::
sudo pacman --needed --noconfirm -S ripgrep the_silver_searcher
#+END_SRC
* Features
Ivy and its ilk are large plugins. Covering everything about them is outside of
this documentation's scope, so only Doom-specific Ivy features are listed here:
** Jump-to-file project navigation
Inspired by Sublime Text's jump-to-anywhere, CtrlP/Unite in Vim, and Textmate's
Command-T, this module provides similar functionality by bringing ~projectile~
and ~ivy~ together.
https://assets.doomemacs.org/completion/ivy/projectile.png
| Keybind | Description |
|----------------------+-------------------------------------|
| =SPC f /=, =SPC SPC= | Jump to file in project |
| =SPC f .=, =SPC .= | Jump to file from current directory |
** Project search & replace
This module provides interactive text search and replace using the first search
program available on your system (rg, ag, pt, git-grep or grep).
| Keybind | Description |
|----------------------+-------------------------------------|
| =SPC / b=, =M-f= | Search the current buffer |
| =SPC / p= | Search project |
| =SPC / d= | Search this directory |
| =SPC p t= | List all TODO/FIXMEs in project |
https://assets.doomemacs.org/completion/ivy/search.png
The ~+ivy-project-search-engines~ variable is consulted to determine which
underlying program to check for (and in what order). It's default value is ~'(rg
ag pt)~. If none of these are available, it will resort to =git-grep= (falling
back to =grep= after that).
To use a specific program, the following engine-specific commands are available
(but not bound to any key by default) for searching from the project root or the
current directory (recursively), respectively:
+ ~+ivy/ag~ / ~+ivy/ag-from-cwd~
+ ~+ivy/rg~ / ~+ivy/rg-from-cwd~
+ ~+ivy/pt~ / ~+ivy/pt-from-cwd~
+ ~+ivy/grep~ / ~+ivy/grep-from-cwd~
The universal argument (=SPC u= for evil users; =C-u= otherwise) changes the
behavior of these commands, instructing the underlying search engine to include
ignored files.
This module also provides Ex Commands for evil users:
| Ex command | Description |
|-----------------------+------------------------------------------------|
| ~:ag[!] [QUERY]~ | Search project w/ ag[fn:1] |
| ~:rg[!] [QUERY]~ | Search project w/ rg[fn:1] |
| ~:pt[!] [QUERY]~ | Search project w/ pt[fn:1] |
| ~:grep[!] [QUERY]~ | Search project w/ git-grep/grep[fn:1] |
| ~:agcwd[!] [QUERY]~ | Search this directory w/ the_silver_searcher |
| ~:rgcwd[!] [QUERY]~ | Search this directory w/ ripgrep |
| ~:ptcwd[!] [QUERY]~ | Search this directory w/ the_platinum_searcher |
| ~:grepcwd[!] [QUERY]~ | Search this directory w/ git-grep/grep |
The optional BANG functions is equivalent to the universal argument for the
previous commands.
-----
While in a search (e.g. invoked from ~+ivy:ag~ or ~:rg~), these extra
keybindings are available to you:
| Keybind | Description |
|---------+------------------------------------------------|
| =S-TAB= | Open a writable buffer of your search results |
| =C-SPC= | Preview the current candidate |
| =M-RET= | Open the selected candidate in other-window |
Changes to the resulting wgrep buffer (opened by =S-TAB=) can be committed with
=C-c C-c= and aborted with =C-c C-k=.
https://assets.doomemacs.org/completion/ivy/search-replace.png
** In-buffer searching
The =swiper= package provides an interactive buffer search powered by ivy. It
can be invoked with:
+ =SPC / b=
+ =M-f=
+ ~:sw[iper] [QUERY]~
https://assets.doomemacs.org/completion/ivy/swiper.png
A wgrep buffer can be opened from swiper with =S-TAB=.
** Task lookup
Some projects have TODO's and FIXME's littered across them. The ~+ivy/tasks~
command allows you to search and jump to them. It can be invoked with:
+ =SPC p t= (C-u = restrict search to current file)
+ ~:todo[!]~ (BANG = restrict search to current file)
https://assets.doomemacs.org/completion/ivy/todo.png
** Ivy integration for various completing commands
*** General
| Keybind | Description |
|----------------+---------------------------|
| =M-x=, =SPC := | Smarter, smex-powered M-x |
| =SPC '= | Resume last ivy session |
*** Jump to files, buffers or projects)
| Keybind | Description |
|---------------------------------+---------------------------------------|
| =SPC RET= | Find bookmark |
| =SPC f .=, =SPC .= | Browse from current directory |
| =SPC f /=, =SPC p /=, =SPC SPC= | Find file in project |
| =SPC f r= | Find recently opened file |
| =SPC p p= | Open another project |
| =SPC b b=, =SPC ,= | Switch to buffer in current workspace |
| =SPC b B=, =SPC <= | Switch to buffer |
*** Search
| Keybind | Description |
|------------------+------------------------------------------|
| =SPC / i= | Search for symbol in current buffer |
| =SPC / I= | Search for symbol in all similar buffers |
| =SPC / b=, =M-f= | Search the current buffer |
| =SPC / p= | Search project |
| =SPC / d= | Search this directory |
| =SPC p t= | List all TODO/FIXMEs in project |
* Configuration
** TODO Enable fuzzy/non-fuzzy search for specific commands
** TODO Change the position of the ivy childframe
* TODO Troubleshooting

View file

@ -14,94 +14,55 @@
(+ivy/tasks bang))
;; --- file searching ---------------------
;;
;; Project searching
(defvar +ivy--file-last-search nil)
(defvar +ivy--file-search-recursion-p t)
(defvar +ivy--file-search-all-files-p nil)
;;;###autoload (autoload '+ivy:pt "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:pt (all-files-p query)
"Ex interface for `+ivy/pt'"
(interactive "<!><a>")
(+ivy/pt all-files-p query))
(defun +ivy--file-search (engine beg end query &optional directory)
(let* ((project-root (doom-project-root))
(directory (or directory project-root))
(recursion-p +ivy--file-search-recursion-p)
(all-files-p +ivy--file-search-all-files-p)
(engine (or engine
(and (executable-find "rg") 'rg)
(and (executable-find "ag") 'ag)))
(query
(or query
(if (evil-visual-state-p)
(and beg end
(> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))
+ivy--file-last-search)
+ivy--file-last-search))
(prompt
(format "%s%%s %s"
(symbol-name engine)
(cond ((equal directory default-directory)
"./")
((equal directory project-root)
(projectile-project-name))
(t
(file-relative-name directory project-root))))))
(setq +ivy--file-last-search query)
(pcase engine
('ag
(let ((args (concat
(if all-files-p " -a")
(unless recursion-p " -n"))))
(counsel-ag query directory args (format prompt args))))
('rg
;; smart-case instead of case-insensitive flag
(let ((counsel-rg-base-command
(replace-regexp-in-string " -i " " -S " counsel-rg-base-command))
(args (concat
(if all-files-p " -uu")
(unless recursion-p " --maxdepth 1"))))
(counsel-rg query directory args (format prompt args))))
('pt) ;; TODO pt search engine (necessary?)
(_ (error "No search engine specified")))))
;;;###autoload (autoload '+ivy:grep "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:grep (all-files-p query)
"Ex interface for `+ivy/grep'"
(interactive "<!><a>")
(+ivy/grep all-files-p query))
;;;###autoload (autoload '+ivy:ag "completion/ivy/autoload/evil" nil t)
(evil-define-operator +ivy:ag (beg end query &optional all-files-p directory)
"Perform a project file search using the silver search. QUERY is a pcre
regexp. If omitted, the current selection is used. If no selection is active,
the last known search is used.
If ALL-FILES-P, don't respect .gitignore files and search everything."
(interactive "<r><a><!>")
(let ((+ivy--file-search-all-files-p all-files-p))
(+ivy--file-search 'ag beg end query directory)))
(evil-define-command +ivy:ag (all-files-p query)
"Ex interface for `+ivy/ag'"
(interactive "<!><a>")
(+ivy/ag all-files-p query))
;;;###autoload (autoload '+ivy:rg "completion/ivy/autoload/evil" nil t)
(evil-define-operator +ivy:rg (beg end query &optional all-files-p directory)
"Perform a project file search using ripgrep. QUERY is a regexp. If omitted,
the current selection is used. If no selection is active, the last known search
is used.
If ALL-FILES-P, don't respect .gitignore files and search everything.
NOTE: ripgrep doesn't support multiline searches (yet)."
(interactive "<r><a><!>")
(let ((+ivy--file-search-all-files-p all-files-p))
(+ivy--file-search 'rg beg end query directory)))
(evil-define-command +ivy:rg (all-files-p query)
"Ex interface for `+ivy/rg'"
(interactive "<!><a>")
(+ivy/rg all-files-p query))
;;;###autoload (autoload '+ivy:ag-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-operator +ivy:ag-cwd (beg end query &optional bang)
"The same as :ag, but searches the current directory. If BANG, don't recurse
into sub-directories."
(interactive "<r><a><!>")
(let ((+ivy--file-search-recursion-p (not bang)))
(+ivy:ag beg end query t default-directory)))
;;;###autoload (autoload '+ivy:pt-from-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:pt-from-cwd (query &optional recurse-p)
"Ex interface for `+ivy/pt-from-cwd'."
(interactive "<a><!>")
(+ivy/pt-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+ivy:rg-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-operator +ivy:rg-cwd (beg end query &optional bang)
"The same as :rg, but only searches the current directory. If BANG, don't
recurse into sub-directories.
;;;###autoload (autoload '+ivy:grep-from-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:grep-from-cwd (query &optional recurse-p)
"Ex interface for `+ivy/grep-from-cwd'."
(interactive "<a><!>")
(+ivy/grep-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+ivy:ag-from-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:ag-from-cwd (query &optional recurse-p)
"Ex interface for `+ivy/ag-from-cwd'."
(interactive "<a><!>")
(+ivy/ag-from-cwd (not recurse-p) query))
;;;###autoload (autoload '+ivy:rg-from-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:rg-from-cwd (query &optional recurse-p)
"Ex interface for `+ivy/rg-from-cwd'."
(interactive "<a><!>")
(+ivy/rg-from-cwd (not recurse-p) query))
NOTE: ripgrep doesn't support multiline searches (yet)."
(interactive "<r><a><!>")
(let ((+ivy--file-search-recursion-p (not bang)))
(+ivy:rg beg end query t default-directory)))

View file

@ -0,0 +1,31 @@
;;; completion/ivy/autoload/hydras.el -*- lexical-binding: t; -*-
;;;###autoload
(after! ivy-hydra
(defhydra+ hydra-ivy (:hint nil :color pink)
"
Move ^^^^^^^^^^ | Call ^^^^ | Cancel^^ | Options^^ | Action _w_/_s_/_a_: %s(ivy-action-name)
----------^^^^^^^^^^-+--------------^^^^-+-------^^-+--------^^-+---------------------------------
_g_ ^ ^ _k_ ^ ^ _u_ | _f_orward _o_ccur | _i_nsert | _c_alling: %-7s(if ivy-calling \"on\" \"off\") _C_ase-fold: %-10`ivy-case-fold-search
^^ _h_ ^+^ _l_ ^^ | _RET_ done ^^ | _q_uit | _m_atcher: %-7s(ivy--matcher-desc) _t_runcate: %-11`truncate-lines
_G_ ^ ^ _j_ ^ ^ _d_ | _TAB_ alt-done ^^ | ^ ^ | _<_/_>_: shrink/grow
"
;; arrows
("l" ivy-alt-done)
("h" ivy-backward-delete-char)
("g" ivy-beginning-of-buffer)
("G" ivy-end-of-buffer)
("d" ivy-scroll-up-command)
("u" ivy-scroll-down-command)
("e" ivy-scroll-down-command)
;; actions
("q" keyboard-escape-quit :exit t)
("<escape>" keyboard-escape-quit :exit t)
("TAB" ivy-alt-done :exit nil)
("RET" ivy-done :exit t)
("C-SPC" ivy-call-and-recenter :exit nil)
("f" ivy-call)
("c" ivy-toggle-calling)
("m" ivy-toggle-fuzzy)
("t" (setq truncate-lines (not truncate-lines)))
("o" ivy-occur :exit t)))

View file

@ -1,36 +1,91 @@
;;; completion/ivy/autoload/ivy.el -*- lexical-binding: t; -*-
(defsubst +ivy--icon-for-mode (mode)
"Apply `all-the-icons-for-mode' on MODE but either return an icon or nil."
(let ((icon (all-the-icons-icon-for-mode mode)))
(unless (symbolp icon) icon)))
(defun +ivy--is-workspace-buffer-p (buffer)
(let ((buffer (car buffer)))
(when (stringp buffer)
(setq buffer (get-buffer buffer)))
(+workspace-contains-buffer-p buffer)))
(defun +ivy--is-workspace-other-buffer-p (buffer)
(let ((buffer (car buffer)))
(when (stringp buffer)
(setq buffer (get-buffer buffer)))
(and (not (eq buffer (current-buffer)))
(+workspace-contains-buffer-p buffer))))
;;;###autoload
(defun +ivy-buffer-transformer (str)
(let* ((buf (get-buffer str))
(path (buffer-file-name buf))
(mode (buffer-local-value 'major-mode buf))
(faces
(with-current-buffer buf
(cond ((string-match-p "^ ?\\*" (buffer-name buf))
'font-lock-comment-face)
((buffer-modified-p buf)
'doom-modeline-buffer-modified)
(buffer-read-only
'error)))))
(propertize
(format "%-40s %s%-20s %s"
str
(if +ivy-buffer-icons
(concat (propertize " " 'display
(or (+ivy--icon-for-mode mode)
(+ivy--icon-for-mode (get mode 'derived-mode-parent))))
"\t")
"")
mode
(or (and path (abbreviate-file-name (file-name-directory (file-truename path))))
""))
'face faces)))
(defun +ivy-rich-buffer-name (candidate)
"Display the buffer name.
Buffers that are considered unreal (see `doom-real-buffer-p') are dimmed with
`+ivy-buffer-unreal-face'."
(let ((b (get-buffer candidate)))
(cond ((ignore-errors
(file-remote-p
(buffer-local-value 'default-directory b)))
(ivy-append-face candidate 'ivy-remote))
((doom-unreal-buffer-p b)
(ivy-append-face candidate +ivy-buffer-unreal-face))
((not (buffer-file-name b))
(ivy-append-face candidate 'ivy-subdir))
((buffer-modified-p b)
(ivy-append-face candidate 'ivy-modified-buffer))
(candidate))))
;;;###autoload
(defun +ivy-rich-buffer-icon (candidate)
"Display the icon for CANDIDATE buffer.
Otherwise show the fundamental-mode icon."
(with-current-buffer candidate
(let ((icon (all-the-icons-icon-for-mode major-mode)))
(if (symbolp icon)
(all-the-icons-icon-for-mode 'fundamental-mode)
icon))))
;;
;; Library
(defun +ivy--switch-buffer-preview ()
(let (ivy-use-virtual-buffers ivy--virtual-buffers)
(counsel--switch-buffer-update-fn)))
(defalias '+ivy--switch-buffer-preview-all #'counsel--switch-buffer-update-fn)
(defalias '+ivy--switch-buffer-unwind #'counsel--switch-buffer-unwind)
(defun +ivy--switch-buffer (workspace other)
(let ((current (not other))
prompt action filter update unwind)
(cond ((and workspace current)
(setq prompt "Switch to workspace buffer: "
action #'ivy--switch-buffer-action
filter #'+ivy--is-workspace-other-buffer-p))
(workspace
(setq prompt "Switch to workspace buffer in other window: "
action #'ivy--switch-buffer-other-window-action
filter #'+ivy--is-workspace-buffer-p))
(current
(setq prompt "Switch to buffer: "
action #'ivy--switch-buffer-action))
((setq prompt "Switch to buffer in other window: "
action #'ivy--switch-buffer-other-window-action)))
(when +ivy-buffer-preview
(cond ((not (and ivy-use-virtual-buffers
(eq +ivy-buffer-preview 'everything)))
(setq update #'+ivy--switch-buffer-preview
unwind #'+ivy--switch-buffer-unwind))
((setq update #'+ivy--switch-buffer-preview-all
unwind #'+ivy--switch-buffer-unwind))))
(ivy-read prompt 'internal-complete-buffer
:action action
:predicate filter
:update-fn update
:unwind unwind
:preselect (buffer-name (other-buffer (current-buffer)))
:matcher #'ivy--switch-buffer-matcher
:keymap ivy-switch-buffer-map
:caller #'+ivy--switch-buffer)))
;;;###autoload
(defun +ivy/switch-workspace-buffer (&optional arg)
@ -38,14 +93,25 @@
If ARG (universal argument), open selection in other-window."
(interactive "P")
(ivy-read "Switch to workspace buffer: "
(mapcar #'buffer-name (delq (current-buffer) (doom-buffer-list)))
:action (if arg
#'ivy--switch-buffer-other-window-action
#'ivy--switch-buffer-action)
:matcher #'ivy--switch-buffer-matcher
:keymap ivy-switch-buffer-map
:caller #'+ivy/switch-workspace-buffer))
(+ivy--switch-buffer t arg))
;;;###autoload
(defun +ivy/switch-workspace-buffer-other-window ()
"Switch another window to a buffer within the current workspace."
(interactive)
(+ivy--switch-buffer t t))
;;;###autoload
(defun +ivy/switch-buffer ()
"Switch to another buffer."
(interactive)
(+ivy--switch-buffer nil nil))
;;;###autoload
(defun +ivy/switch-buffer-other-window ()
"Switch to another buffer in another window."
(interactive)
(+ivy--switch-buffer nil t))
(defun +ivy--tasks-candidates (tasks)
"Generate a list of task tags (specified by `+ivy-task-tags') for
@ -93,9 +159,9 @@ If ARG (universal argument), open selection in other-window."
"\\):?\\s-*\\(.+\\)")
x)
(error
(message! (red "Error matching task in file: (%s) %s"
(error-message-string ex)
(car (split-string x ":"))))
(print! (red "Error matching task in file: (%s) %s")
(error-message-string ex)
(car (split-string x ":")))
nil))
collect `((type . ,(match-string 3 x))
(desc . ,(match-string 4 x))
@ -124,44 +190,13 @@ search current file. See `+ivy-task-tags' to customize what this searches for."
(if arg
(concat "in: " (file-relative-name buffer-file-name))
"project"))
(+ivy--tasks-candidates
(+ivy--tasks (if arg buffer-file-name (doom-project-root))))
(let ((tasks (+ivy--tasks (if arg buffer-file-name (doom-project-root)))))
(unless tasks
(user-error "No tasks in your project! Good job!"))
(+ivy--tasks-candidates tasks))
:action #'+ivy--tasks-open-action
:caller '+ivy/tasks))
;;;###autoload
(defun +ivy*counsel-ag-function (string base-cmd extra-ag-args)
"Advice to 1) get rid of the character limit from `counsel-ag-function' and 2)
disable ivy's over-zealous parentheses quoting behavior (if i want literal
parentheses, I'll escape them myself).
NOTE This may need to be updated frequently, to meet changes upstream (in
counsel-rg)."
(when (null extra-ag-args)
(setq extra-ag-args ""))
(if (< (length string) 1) ;; #1
(counsel-more-chars 1)
(let ((default-directory counsel--git-dir)
(regex (counsel-unquote-regex-parens
(setq ivy--old-re
(ivy--regex string)))))
(let* ((args-end (string-match " -- " extra-ag-args))
(file (if args-end
(substring-no-properties extra-ag-args (+ args-end 3))
""))
(extra-ag-args (if args-end
(substring-no-properties extra-ag-args 0 args-end)
extra-ag-args))
(ag-cmd (format base-cmd
(concat extra-ag-args
" -- "
(shell-quote-argument regex)
file))))
(if (file-remote-p default-directory)
(split-string (shell-command-to-string ag-cmd) "\n" t)
(counsel--async-command ag-cmd)
nil)))))
;;;###autoload
(defun +ivy/wgrep-occur ()
"Invoke the search+replace wgrep buffer on the current ag/rg search results."
@ -200,7 +235,7 @@ counsel-rg)."
(with-ivy-window
(let ((file-name (match-string-no-properties 1 x))
(line-number (match-string-no-properties 2 x)))
(find-file-other-window (expand-file-name file-name counsel--git-dir))
(find-file-other-window (expand-file-name file-name (ivy-state-directory ivy-last)))
(goto-char (point-min))
(forward-line (1- (string-to-number line-number)))
(re-search-forward (ivy--regex ivy-text t) (line-end-position) t)
@ -208,10 +243,169 @@ counsel-rg)."
(selected-window))))))
;;;###autoload
(defun +ivy-quit-and-resume ()
"Close the current popup window and resume ivy."
(interactive)
(when (doom-popup-p)
(doom/popup-close))
(ivy-resume))
(defun +ivy-confirm-delete-file (x)
(dired-delete-file x 'confirm-each-subdirectory))
;;
;; File searching
;;;###autoload
(defun +ivy/projectile-find-file ()
"A more sensible `counsel-projectile-find-file', which will revert to
`counsel-find-file' if invoked from $HOME, `counsel-file-jump' if invoked from a
non-project, `projectile-find-file' if in a big project (more than
`ivy-sort-max-size' files), or `counsel-projectile-find-file' otherwise.
The point of this is to avoid Emacs locking up indexing massive file trees."
(interactive)
(call-interactively
(cond ((or (file-equal-p default-directory "~")
(when-let* ((proot (doom-project-root)))
(file-equal-p proot "~")))
#'counsel-find-file)
((doom-project-p)
(let ((files (projectile-current-project-files)))
(if (<= (length files) ivy-sort-max-size)
#'counsel-projectile-find-file
#'projectile-find-file)))
(#'counsel-file-jump))))
;;;###autoload
(cl-defun +ivy-file-search (engine &key query in all-files (recursive t))
"Conduct a file search using ENGINE, which can be any of: rg, ag, pt, and
grep. If omitted, ENGINE will default to the first one it detects, in that
order.
:query STRING
Determines the initial input to search for.
:in PATH
Sets what directory to base the search out of. Defaults to the current
project's root.
:recursive BOOL
Whether or not to search files recursively from the base directory."
(declare (indent defun))
(let* ((project-root (or (doom-project-root) default-directory))
(directory (or in project-root))
(default-directory directory)
(engine (or engine
(cl-loop for tool in +ivy-project-search-engines
if (executable-find (symbol-name tool))
return tool)
(and (or (executable-find "grep")
(executable-find "git"))
'grep)
(error "No search engine specified (is ag, rg, pt or git installed?)")))
(query
(or (if query (rxt-quote-pcre query))
(when (use-region-p)
(let ((beg (or (bound-and-true-p evil-visual-beginning) (region-beginning)))
(end (or (bound-and-true-p evil-visual-end) (region-end))))
(when (> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))))))
(prompt
(format "%s%%s %s"
(symbol-name engine)
(cond ((equal directory default-directory)
"./")
((equal directory project-root)
(projectile-project-name))
((file-relative-name directory project-root))))))
(require 'counsel)
(let ((ivy-more-chars-alist
(if query '((t . 1)) ivy-more-chars-alist)))
(pcase engine
('grep
(let ((args (if recursive " -R"))
(counsel-projectile-grep-initial-input query))
(if all-files
(cl-letf (((symbol-function #'projectile-ignored-directories-rel)
(symbol-function #'ignore))
((symbol-function #'projectile-ignored-files-rel)
(symbol-function #'ignore)))
(counsel-projectile-grep args))
(counsel-projectile-grep args))))
('ag
(let ((args (concat (if all-files " -a")
(unless recursive " --depth 1"))))
(counsel-ag query directory args (format prompt args))))
('rg
(let ((args (concat (if all-files " -uu")
(unless recursive " --maxdepth 1"))))
(counsel-rg query directory args (format prompt args))))
('pt
(let ((counsel-pt-base-command
(concat counsel-pt-base-command
(if all-files " -U")
(unless recursive " --depth=1")))
(default-directory directory))
(counsel-pt query)))
(_ (error "No search engine specified"))))))
(defun +ivy--get-command (format)
(cl-loop for tool in (cl-remove-duplicates +ivy-project-search-engines :from-end t)
if (executable-find (symbol-name tool))
return (intern (format format tool))))
;;;###autoload
(defun +ivy/project-search (&optional arg initial-query directory)
"Performs a project search from the project root.
Uses the first available search backend from `+ivy-project-search-engines'. If
ARG (universal argument), include all files, even hidden or compressed ones, in
the search."
(interactive "P")
(funcall (or (+ivy--get-command "+ivy/%s")
#'+ivy/grep)
arg
initial-query
directory))
;;;###autoload
(defun +ivy/project-search-from-cwd (&optional arg initial-query)
"Performs a project search recursively from the current directory.
Uses the first available search backend from `+ivy-project-search-engines'. If
ARG (universal argument), include all files, even hidden or compressed ones."
(interactive "P")
(funcall (or (+ivy--get-command "+ivy/%s-from-cwd")
#'+ivy/grep-from-cwd)
arg
initial-query))
;;;###autoload (autoload '+ivy/rg "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/rg-from-cwd "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/ag "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/ag-from-cwd "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/pt "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/pt-from-cwd "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/grep "completion/ivy/autoload/ivy")
;;;###autoload (autoload '+ivy/grep-from-cwd "completion/ivy/autoload/ivy")
(dolist (engine `(,@(cl-remove-duplicates +ivy-project-search-engines :from-end t) grep))
(defalias (intern (format "+ivy/%s" engine))
(lambda (all-files-p &optional query directory)
(interactive "P")
(+ivy-file-search engine :query query :in directory :all-files all-files-p))
(format "Perform a project file search using %s.
QUERY is a regexp. If omitted, the current selection is used. If no selection is
active, the last known search is used.
If ALL-FILES-P, search compressed and hidden files as well."
engine))
(defalias (intern (format "+ivy/%s-from-cwd" engine))
(lambda (all-files-p &optional query)
(interactive "P")
(+ivy-file-search engine :query query :in default-directory :all-files all-files-p))
(format "Perform a project file search from the current directory using %s.
QUERY is a regexp. If omitted, the current selection is used. If no selection is
active, the last known search is used.
If ALL-FILES-P, search compressed and hidden files as well."
engine)))

View file

@ -0,0 +1,16 @@
;;; completion/ivy/autoload/posframe.el -*- lexical-binding: t; -*-
;;;###if (featurep! +childframe)
;;;###autoload
(defun +ivy-display-at-frame-center-near-bottom (str)
"TODO"
(ivy-posframe--display str #'+ivy-poshandler-frame-center-near-bottom))
;;;###autoload
(defun +ivy-poshandler-frame-center-near-bottom (info)
"TODO"
(let ((parent-frame (plist-get info :parent-frame))
(pos (posframe-poshandler-frame-center info)))
(cons (car pos)
(truncate (/ (frame-pixel-height parent-frame) 2)))))

View file

@ -3,12 +3,32 @@
(defvar +ivy-buffer-icons nil
"If non-nil, show buffer mode icons in `ivy-switch-buffer' and the like.")
(defvar +ivy-buffer-preview nil
"If non-nil, preview buffers while switching, à la `counsel-switch-buffer'.
When nil, don't preview anything.
When non-nil, preview non-virtual buffers.
When 'everything, also preview virtual buffers")
(defvar +ivy-task-tags
'(("TODO" . warning)
("FIXME" . error))
"An alist of tags for `+ivy/tasks' to include in its search, whose CDR is the
face to render it with.")
(defvar +ivy-project-search-engines '(rg ag pt)
"What search tools for `+ivy/project-search' (and `+ivy-file-search' when no
ENGINE is specified) to try, and in what order.
To disable a particular tool, remove it from this list. To prioritize a tool
over others, move it to the front of the list. Later duplicates in this list are
silently ignored.
If you want to already use git-grep or grep, set this to nil.")
(defvar +ivy-buffer-unreal-face 'font-lock-comment-face
"The face for unreal buffers in `ivy-switch-to-buffer'.")
(defmacro +ivy-do-action! (action)
"Returns an interactive lambda that sets the current ivy action and
immediately runs it on the current candidate (ending the ivy session)."
@ -20,133 +40,274 @@ immediately runs it on the current candidate (ending the ivy session)."
;;
;; Packages
;;
;;; Packages
(def-package! ivy
:init
(add-hook 'doom-post-init-hook #'ivy-mode)
:defer 1
:after-call pre-command-hook
:config
(setq ivy-height 12
ivy-do-completion-in-region nil
(setq ivy-height 15
ivy-wrap t
ivy-fixed-height-minibuffer t
projectile-completion-system 'ivy
smex-completion-method 'ivy
;; Don't use ^ as initial input
ivy-initial-inputs-alist nil
;; highlight til EOL
ivy-format-function #'ivy-format-function-line
;; disable magic slash on non-match
ivy-magic-slash-non-match-action nil)
ivy-magic-slash-non-match-action nil
;; don't show recent files in switch-buffer
ivy-use-virtual-buffers nil
;; ...but if that ever changes, show their full path
ivy-virtual-abbreviate 'full
;; don't quit minibuffer on delete-error
ivy-on-del-error-function nil
;; enable ability to select prompt (alternative to `ivy-immediate-done')
ivy-use-selectable-prompt t)
(after! magit (setq magit-completing-read-function #'ivy-completing-read))
(after! yasnippet (push #'+ivy-yas-prompt yas-prompt-functions))
(after! yasnippet
(add-to-list 'yas-prompt-functions #'+ivy-yas-prompt nil #'eq))
(map! [remap apropos] #'counsel-apropos
[remap describe-face] #'counsel-describe-face
[remap find-file] #'counsel-find-file
[remap switch-to-buffer] #'ivy-switch-buffer
[remap persp-switch-to-buffer] #'+ivy/switch-workspace-buffer
[remap recentf-open-files] #'counsel-recentf
[remap imenu] #'counsel-imenu
[remap bookmark-jump] #'counsel-bookmark
[remap projectile-find-file] #'counsel-projectile-find-file
[remap imenu-anywhere] #'ivy-imenu-anywhere
[remap execute-extended-command] #'counsel-M-x
[remap describe-face] #'counsel-describe-face)
(define-key! ivy-mode-map
[remap switch-to-buffer] #'+ivy/switch-buffer
[remap switch-to-buffer-other-window] #'+ivy/switch-buffer-other-window
[remap persp-switch-to-buffer] #'+ivy/switch-workspace-buffer
[remap imenu-anywhere] #'ivy-imenu-anywhere)
;; Show more buffer information in switch-buffer commands
(ivy-set-display-transformer #'ivy-switch-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'ivy-switch-buffer-other-window #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'+ivy/switch-workspace-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'counsel-recentf #'abbreviate-file-name)
(ivy-mode +1)
(nconc ivy-sort-functions-alist
'((persp-kill-buffer . nil)
(persp-remove-buffer . nil)
(persp-add-buffer . nil)
(persp-switch . nil)
(persp-window-switch . nil)
(persp-frame-switch . nil)
(+workspace/switch-to . nil)
(+workspace/delete . nil))))
(def-package! ivy-hydra
:commands (ivy-dispatching-done-hydra ivy--matcher-desc ivy-hydra/body)
:init
(define-key! ivy-minibuffer-map
"C-o" #'ivy-dispatching-done-hydra
"M-o" #'hydra-ivy/body)
:config
;; ivy-hydra rebinds this, so we have to do so again
(define-key ivy-minibuffer-map (kbd "M-o") #'hydra-ivy/body)))
(def-package! swiper :commands (swiper swiper-all))
(def-package! ivy-rich
:hook (ivy-mode . ivy-rich-mode)
:config
(when +ivy-buffer-icons
(cl-pushnew '(+ivy-rich-buffer-icon (:width 2 :align right))
(cadr (plist-get ivy-rich-display-transformers-list
'ivy-switch-buffer)))
(after! counsel-projectile
(setq ivy-rich-display-transformers-list
(plist-put ivy-rich-display-transformers-list
'counsel-projectile-switch-project
'(:columns
(((lambda (_) (all-the-icons-octicon "file-directory"))
(:width 2 :align right))
(ivy-rich-candidate)))))
(setq ivy-rich-display-transformers-list
(plist-put ivy-rich-display-transformers-list
'counsel-projectile-find-file
'(:columns
((all-the-icons-icon-for-file (:width 2 :align right))
(ivy-rich-candidate)))))))
;; Remove built-in coloring of buffer list; we do our own
(setq ivy-switch-buffer-faces-alist nil)
(ivy-set-display-transformer 'internal-complete-buffer nil)
;; Highlight buffers differently based on whether they're in the same project
;; as the current project or not.
(let* ((plist (plist-get ivy-rich-display-transformers-list 'ivy-switch-buffer))
(switch-buffer-alist (assq 'ivy-rich-candidate (plist-get plist :columns))))
(when switch-buffer-alist
(setcar switch-buffer-alist '+ivy-rich-buffer-name)))
;; Allow these transformers to apply to more switch-buffer commands
(let ((ivy-switch-buffer-transformer (plist-get ivy-rich-display-transformers-list 'ivy-switch-buffer)))
(dolist (cmd '(+ivy--switch-buffer counsel-projectile-switch-to-buffer))
(setq ivy-rich-display-transformers-list
(plist-put ivy-rich-display-transformers-list
cmd ivy-switch-buffer-transformer)))))
(def-package! counsel
:requires ivy
:commands counsel-describe-face
:init
(map! [remap apropos] #'counsel-apropos
[remap bookmark-jump] #'counsel-bookmark
[remap describe-face] #'counsel-faces
[remap describe-function] #'counsel-describe-function
[remap describe-variable] #'counsel-describe-variable
[remap describe-bindings] #'counsel-descbinds
[remap set-variable] #'counsel-set-variable
[remap execute-extended-command] #'counsel-M-x
[remap find-file] #'counsel-find-file
[remap find-library] #'counsel-find-library
[remap info-lookup-symbol] #'counsel-info-lookup-symbol
[remap imenu] #'counsel-imenu
[remap recentf-open-files] #'counsel-recentf
[remap org-capture] #'counsel-org-capture
[remap swiper] #'counsel-grep-or-swiper
[remap evil-ex-registers] #'counsel-evil-registers
[remap yank-pop] #'counsel-yank-pop)
:config
(require 'counsel-projectile)
(setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)")
(set-popup-rule! "^\\*ivy-occur" :size 0.35 :ttl 0 :quit nil)
;; Configure `counsel-rg', `counsel-ag' & `counsel-pt'
(set! :popup 'ivy-occur-grep-mode :size (+ 2 ivy-height) :regexp t :autokill t)
(dolist (cmd '(counsel-ag counsel-rg counsel-pt))
(ivy-add-actions
cmd
'(("O" +ivy-git-grep-other-window-action "open in other window"))))
(setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)"
counsel-describe-function-function #'helpful-callable
counsel-describe-variable-function #'helpful-variable
;; Add smart-casing (-S) to default command arguments:
counsel-rg-base-command "rg -S --no-heading --line-number --color never %s ."
counsel-ag-base-command "ag -S --nocolor --nogroup %s"
counsel-pt-base-command "pt -S --nocolor --nogroup -e %s")
;; 1. Remove character limit from `counsel-ag-function'
;; 2. This may need to be updated frequently, to meet changes upstream
;; 3. counsel-ag, counsel-rg and counsel-pt all use this function
(advice-add #'counsel-ag-function :override #'+ivy*counsel-ag-function))
(add-to-list 'swiper-font-lock-exclude #'+doom-dashboard-mode nil #'eq)
;; Factories
(defun +ivy-action-reloading (cmd)
(lambda (x)
(funcall cmd x)
(ivy--reset-state ivy-last)))
(defun +ivy-action-given-file (cmd prompt)
(lambda (source)
(let* ((enable-recursive-minibuffers t)
(target (read-file-name (format "%s %s to:" prompt source))))
(funcall cmd source target 1))))
;; Configure `counsel-find-file'
(ivy-add-actions
'counsel-find-file
`(("b" counsel-find-file-cd-bookmark-action "cd bookmark")
("s" counsel-find-file-as-root "open as root")
("m" counsel-find-file-mkdir-action "mkdir")
("c" ,(+ivy-action-given-file #'copy-file "Copy file") "copy file")
("d" ,(+ivy-action-reloading #'+ivy-confirm-delete-file) "delete")
("r" (lambda (path) (rename-file path (read-string "New name: "))) "rename")
("R" ,(+ivy-action-reloading (+ivy-action-given-file #'rename-file "Move")) "move")
("f" find-file-other-window "other window")
("F" find-file-other-frame "other frame")
("p" (lambda (path) (with-ivy-window (insert (file-relative-name path default-directory)))) "insert relative path")
("P" (lambda (path) (with-ivy-window (insert path))) "insert absolute path")
("l" (lambda (path) "Insert org-link with relative path"
(with-ivy-window (insert (format "[[./%s]]" (file-relative-name path default-directory))))) "insert org-link (rel. path)")
("L" (lambda (path) "Insert org-link with absolute path"
(with-ivy-window (insert (format "[[%s]]" path)))) "insert org-link (abs. path)")))
(ivy-add-actions
'counsel-ag ; also applies to `counsel-rg' & `counsel-pt'
'(("O" +ivy-git-grep-other-window-action "open in other window"))))
(def-package! counsel-projectile
:commands (counsel-projectile-find-file counsel-projectile-find-dir counsel-projectile-switch-to-buffer
counsel-projectile-grep counsel-projectile-ag counsel-projectile-switch-project)
:init
(map! [remap projectile-find-file] #'+ivy/projectile-find-file
[remap projectile-find-dir] #'counsel-projectile-find-dir
[remap projectile-switch-to-buffer] #'counsel-projectile-switch-to-buffer
[remap projectile-grep] #'counsel-projectile-grep
[remap projectile-ag] #'counsel-projectile-ag
[remap projectile-switch-project] #'counsel-projectile-switch-project)
:config
;; no highlighting visited files; slows down the filtering
(ivy-set-display-transformer #'counsel-projectile-find-file nil))
(def-package! wgrep
:commands wgrep-change-to-wgrep-mode
:config (setq wgrep-auto-save-buffer t))
(def-package! ivy-posframe
:when (and EMACS26+ (featurep! +childframe))
:hook (ivy-mode . ivy-posframe-enable)
:preface
;; This function searches the entire `obarray' just to populate
;; `ivy-display-functions-props'. There are 15k entries in mine! This is
;; wasteful, so...
(advice-add #'ivy-posframe-setup :override #'ignore)
:config
(setq ivy-fixed-height-minibuffer nil
ivy-posframe-parameters
`((min-width . 90)
(min-height . ,ivy-height)
(internal-border-width . 10)))
;; ... let's do it manually instead
(unless (assq 'ivy-posframe-display-at-frame-bottom-left ivy-display-functions-props)
(dolist (fn (list 'ivy-posframe-display-at-frame-bottom-left
'ivy-posframe-display-at-frame-center
'ivy-posframe-display-at-point
'ivy-posframe-display-at-frame-bottom-window-center
'ivy-posframe-display
'ivy-posframe-display-at-window-bottom-left
'ivy-posframe-display-at-window-center
'+ivy-display-at-frame-center-near-bottom))
(push (cons fn '(:cleanup ivy-posframe-cleanup)) ivy-display-functions-props)))
;; default to posframe display function
(setf (alist-get t ivy-display-functions-alist) #'+ivy-display-at-frame-center-near-bottom)
;; Fix #1017: stop session persistence from restoring a broken posframe
(defun +workspace|delete-all-posframes (&rest _) (posframe-delete-all))
(add-hook 'persp-after-load-state-functions #'+workspace|delete-all-posframes)
;; posframe doesn't work well with async sources
(dolist (fn '(swiper counsel-ag counsel-grep counsel-git-grep))
(setf (alist-get fn ivy-display-functions-alist) #'ivy-display-function-fallback)))
(def-package! flx
:when (featurep! +fuzzy)
:defer t ; is loaded by ivy
:init
(setq ivy-re-builders-alist
'((counsel-ag . ivy--regex-plus)
(counsel-rg . ivy--regex-plus)
(counsel-grep . ivy--regex-plus)
(swiper . ivy--regex-plus)
(swiper-isearch . ivy--regex-plus)
(t . ivy--regex-fuzzy))
ivy-initial-inputs-alist nil))
;; Used by `counsel-M-x'
(def-package! smex
:commands (smex smex-major-mode-commands)
:config
(setq smex-save-file (concat doom-cache-dir "/smex-items"))
(smex-initialize))
(setq amx-save-file (concat doom-cache-dir "amx-items"))
(def-package! ivy-hydra
:commands (+ivy@coo/body ivy-dispatching-done-hydra)
:init
(map! :map ivy-minibuffer-map
"C-o" #'+ivy@coo/body
"M-o" #'ivy-dispatching-done-hydra)
:config
(def-hydra! +ivy@coo (:hint nil :color pink)
"
Move ^^^^^^^^^^ | Call ^^^^ | Cancel^^ | Options^^ | Action _w_/_s_/_a_: %s(ivy-action-name)
----------^^^^^^^^^^-+--------------^^^^-+-------^^-+--------^^-+---------------------------------
_g_ ^ ^ _k_ ^ ^ _u_ | _f_orward _o_ccur | _i_nsert | _c_alling: %-7s(if ivy-calling \"on\" \"off\") _C_ase-fold: %-10`ivy-case-fold-search
^^ _h_ ^+^ _l_ ^^ | _RET_ done ^^ | _q_uit | _m_atcher: %-7s(ivy--matcher-desc) _t_runcate: %-11`truncate-lines
_G_ ^ ^ _j_ ^ ^ _d_ | _TAB_ alt-done ^^ | ^ ^ | _<_/_>_: shrink/grow
"
;; arrows
("j" ivy-next-line)
("k" ivy-previous-line)
("l" ivy-alt-done)
("h" ivy-backward-delete-char)
("g" ivy-beginning-of-buffer)
("G" ivy-end-of-buffer)
("d" ivy-scroll-up-command)
("u" ivy-scroll-down-command)
("e" ivy-scroll-down-command)
;; actions
("q" keyboard-escape-quit :exit t)
("C-g" keyboard-escape-quit :exit t)
("<escape>" keyboard-escape-quit :exit t)
("C-o" nil)
("i" nil)
("TAB" ivy-alt-done :exit nil)
("C-j" ivy-alt-done :exit nil)
("RET" ivy-done :exit t)
("C-m" ivy-done :exit t)
("C-SPC" ivy-call-and-recenter :exit nil)
("f" ivy-call)
("c" ivy-toggle-calling)
("m" ivy-toggle-fuzzy)
(">" ivy-minibuffer-grow)
("<" ivy-minibuffer-shrink)
("w" ivy-prev-action)
("s" ivy-next-action)
("a" ivy-read-action)
("t" (setq truncate-lines (not truncate-lines)))
("C" ivy-toggle-case-fold)
("o" ivy-occur :exit t)))
;;
;; Evil key fixes
(map! :when (featurep! :feature evil +everywhere)
:after ivy
:map (ivy-occur-mode-map ivy-occur-grep-mode-map)
:m "j" #'ivy-occur-next-line
:m "k" #'ivy-occur-previous-line
:m "h" #'evil-backward-char
:m "l" #'evil-forward-char
:m "g" nil
:m "gg" #'evil-goto-first-line
:map ivy-occur-mode-map
:n [mouse-1] #'ivy-occur-click
:n [return] #'ivy-occur-press-and-switch
:n "gf" #'ivy-occur-press
:n "ga" #'ivy-occur-read-action
:n "go" #'ivy-occur-dispatch
:n "gc" #'ivy-occur-toggle-calling
:n "gr" #'ivy-occur-revert-buffer
:n "q" #'quit-window
:map ivy-occur-grep-mode-map
:v "j" #'evil-next-line
:v "k" #'evil-previous-line
:n "D" #'ivy-occur-delete-candidate
:n "C-d" #'evil-scroll-down
:n "d" #'ivy-occur-delete-candidate
:n "C-x C-q" #'ivy-wgrep-change-to-wgrep-mode
:n "i" #'ivy-wgrep-change-to-wgrep-mode
:n "gd" #'ivy-occur-delete-candidate
:n [mouse-1] #'ivy-occur-click
:n [return] #'ivy-occur-press-and-switch
:n "gf" #'ivy-occur-press
:n "gr" #'ivy-occur-revert-buffer
:n "ga" #'ivy-occur-read-action
:n "go" #'ivy-occur-dispatch
:n "gc" #'ivy-occur-toggle-calling
:n "q" #'quit-window)

View file

@ -0,0 +1,5 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; completion/ivy/doctor.el
(when (and (not EMACS26+) (featurep! +childframe))
(error! "The +childframe feature requires Emacs 26+"))

View file

@ -1,9 +1,17 @@
;; -*- no-byte-compile: t; -*-
;;; completion/ivy/packages.el
(package! amx)
(package! ivy)
(package! counsel)
(package! counsel-projectile)
(package! smex)
(package! swiper)
(package! ivy-hydra)
(package! ivy-rich)
(package! wgrep)
(when (featurep! +fuzzy)
(package! flx))
(when (and EMACS26+ (featurep! +childframe))
(package! ivy-posframe))

View file

@ -0,0 +1,353 @@
;;; config/default/+emacs-bindings.el -*- lexical-binding: t; -*-
;; Sensible deafult key bindings for non-evil users
(setq doom-leader-alt-key "C-c"
doom-localleader-alt-key "C-c l")
;; persp-mode and projectile in different prefixes
(setq persp-keymap-prefix (kbd "C-c w"))
(after! projectile
(define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map))
(after! which-key
(which-key-add-key-based-replacements "C-c !" "checking")
(which-key-add-key-based-replacements "C-c l" "<localleader>"))
;;
;;; Global keybinds
(map! "C-'" #'imenu
;; Text scaling
"<C-mouse-4>" #'text-scale-increase
"<C-mouse-5>" #'text-scale-decrease
"<C-down-mouse-2>" (λ! (text-scale-set 0))
"M-+" (λ! (text-scale-set 0))
"M-=" #'text-scale-increase
"M--" #'text-scale-decrease
;; Editor related bindings
[remap newline] #'newline-and-indent
"C-j" #'+default/newline
(:when (featurep! :completion ivy)
"C-S-s" #'swiper
"C-S-r" #'ivy-resume)
(:when (featurep! :completion helm)
"C-S-s" #'swiper-helm
"C-S-r" #'helm-resume)
;; Buffer related bindings
"C-x b" #'persp-switch-to-buffer
(:when (featurep! :completion ivy)
"C-x 4 b" #'+ivy/switch-workspace-buffer-other-window)
"C-x C-b" #'ibuffer-list-buffers
"C-x B" #'switch-to-buffer
"C-x 4 B" #'switch-to-buffer-other-window
"C-x k" #'doom/kill-this-buffer-in-all-windows
;; Popup bindigns
"C-x p" #'+popup/other
"C-`" #'+popup/toggle
"C-~" #'+popup/raise)
;;
;;; Leader keys
(map! :leader
:desc "Find file in project" "C-f" #'projectile-find-file
:desc "Evaluate line/region" "e" #'+eval/line-or-region
:desc "Open scratch buffer" "x" #'doom/open-scratch-buffer
:desc "Open project scratch buffer" "X" #'doom/open-project-scratch-buffer
(:when (featurep! :emacs term)
:desc "Terminal" "`" #'+term/open
:desc "Terminal in popup" "~" #'+term/open-popup-in-project)
(:when (featurep! :tools vterm)
:desc "Terminal" "`" #'+vterm/open
:desc "Terminal in popup" "~" #'+vterm/open-popup-in-project)
(:when (featurep! :emacs eshell)
:desc "Eshell" "`" #'+eshell/open
:desc "Eshell in popup" "~" #'+eshell/open-popup)
;; Add labels to prefixes defined elsewhere
:desc "project" "p" nil
(:prefix ("f" . "file")
:desc "Find other file" "a" #'projectile-find-other-file
:desc "Browse private config" "c" #'doom/open-private-config
:desc "Find file in private config" "C" #'doom/find-file-in-private-config
:desc "Open project editorconfig" "." #'editorconfig-find-current-editorconfig
:desc "Find directory" "d" #'dired
:desc "Find file in emacs.d" "e" #'+default/find-in-emacsd
:desc "Browse emacs.d" "E" #'+default/browse-emacsd
:desc "Find file from here" "f" (if (fboundp 'counsel-file-jump) #'counsel-file-jump #'find-file)
:desc "Find file in other project" "F" #'doom/browse-in-other-project
:desc "Find file in project" "p" #'projectile-find-file
:desc "Find file in other project" "P" #'doom/find-file-in-other-project
:desc "Recent files" "r" #'recentf-open-files
:desc "Recent project files" "R" #'projectile-recentf
:desc "Sudo this file" "s" #'doom/sudo-this-file
:desc "Sudo find file" "S" #'doom/sudo-find-file
:desc "Delete this file" "X" #'doom/delete-this-file
:desc "Yank filename" "y" #'+default/yank-buffer-filename)
"o" nil ; we need to unbind it first as Org claims this
(:prefix ("o". "org")
(:prefix ("a" . "org agenda")
:desc "Agenda" "a" #'org-agenda
:desc "Todo list" "t" #'org-todo-list
:desc "Tags view" "m" #'org-tags-view
:desc "View search" "v" #'org-search-view)
:desc "Switch org buffers" "b" #'org-switchb
:desc "Capture" "c" #'org-capture
:desc "Goto capture" "C" (λ! (require 'org-capture) (call-interactively #'org-capture-goto-target))
:desc "Link store" "l" #'org-store-link
:desc "Sync org caldav" "s" #'org-caldav-sync)
(:prefix ("q" . "quit/restart")
:desc "Quit Emacs" "q" #'kill-emacs
:desc "Save and quit Emacs" "Q" #'save-buffers-kill-terminal
(:when (featurep! :feature workspaces)
:desc "Quit Emacs & forget session" "X" #'+workspace/kill-session-and-quit)
:desc "Restart & restore Emacs" "r" #'doom/restart-and-restore
:desc "Restart Emacs" "R" #'doom/restart)
(:prefix ("&" . "snippets")
:desc "New snippet" "n" #'yas-new-snippet
:desc "Insert snippet" "i" #'yas-insert-snippet
:desc "Find global snippet" "/" #'yas-visit-snippet-file
:desc "Reload snippets" "r" #'yas-reload-all
:desc "Create Temp Template" "c" #'aya-create
:desc "Use Temp Template" "e" #'aya-expand)
(:prefix ("v" . "versioning")
:desc "Git revert file" "R" #'vc-revert
(:when (featurep! :ui vc-gutter)
:desc "Git revert hunk" "r" #'git-gutter:revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "n" #'git-gutter:next-hunk
:desc "Jump to previous hunk" "p" #'git-gutter:previous-hunk)
(:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-dispatch
:desc "Forge dispatch" "'" #'forge-dispatch
:desc "Magit status" "g" #'magit-status
:desc "Magit file delete" "x" #'magit-file-delete
:desc "Magit blame" "B" #'magit-blame-addition
:desc "Magit clone" "C" #'+magit/clone
:desc "Magit fetch" "F" #'magit-fetch
:desc "Magit buffer log" "L" #'magit-log
:desc "Git stage file" "S" #'magit-stage-file
:desc "Git unstage file" "U" #'magit-unstage-file
(:prefix ("f" . "find")
:desc "Find file" "f" #'magit-find-file
:desc "Find gitconfig file" "g" #'magit-find-git-config-file
:desc "Find commit" "c" #'magit-show-commit
:desc "Find issue" "i" #'forge-visit-issue
:desc "Find pull request" "p" #'forge-visit-pullreq)
(:prefix ("o" . "open in browser")
:desc "Browse region or line" "." #'+vc/git-browse-region-or-line
:desc "Browse remote" "r" #'forge-browse-remote
:desc "Browse commit" "c" #'forge-browse-commit
:desc "Browse an issue" "i" #'forge-browse-issue
:desc "Browse a pull request" "p" #'forge-browse-pullreq
:desc "Browse issues" "I" #'forge-browse-issues
:desc "Browse pull requests" "P" #'forge-browse-pullreqs)
(:prefix ("l" . "list")
(:when (featurep! :tools gist)
:desc "List gists" "g" #'+gist:list)
:desc "List repositories" "r" #'magit-list-repositories
:desc "List submodules" "s" #'magit-list-submodules
:desc "List issues" "i" #'forge-list-issues
:desc "List pull requests" "p" #'forge-list-pullreqs
:desc "List notifications" "n" #'forge-list-notifications)
(:prefix ("c" . "create")
:desc "Initialize repo" "r" #'magit-init
:desc "Clone repo" "R" #'+magit/clone
:desc "Commit" "c" #'magit-commit-create
:desc "Issue" "i" #'forge-create-issue
:desc "Pull request" "p" #'forge-create-pullreq)))
(:prefix ("w" . "workspaces/windows")
:desc "Autosave session" "a" #'doom/quicksave-session
:desc "Display workspaces" "d" #'+workspace/display
:desc "Rename workspace" "r" #'+workspace/rename
:desc "Create workspace" "c" #'+workspace/new
:desc "Delete workspace" "k" #'+workspace/delete
:desc "Save session" "s" #'doom/save-session
:desc "Save workspace" "S" #'+workspace/save
:desc "Load session" "l" #'doom/load-session
:desc "Load last autosaved session" "L" #'doom/quickload-session
:desc "Kill other buffers" "o" #'doom/kill-other-buffers
:desc "Undo window config" "u" #'winner-undo
:desc "Redo window config" "U" #'winner-redo
:desc "Switch to left workspace" "p" #'+workspace/switch-left
:desc "Switch to right workspace" "n" #'+workspace/switch-right
:desc "Switch to" "w" #'+workspace/switch-to
:desc "Switch to workspace 1" "1" (λ! (+workspace/switch-to 0))
:desc "Switch to workspace 2" "2" (λ! (+workspace/switch-to 1))
:desc "Switch to workspace 3" "3" (λ! (+workspace/switch-to 2))
:desc "Switch to workspace 4" "4" (λ! (+workspace/switch-to 3))
:desc "Switch to workspace 5" "5" (λ! (+workspace/switch-to 4))
:desc "Switch to workspace 6" "6" (λ! (+workspace/switch-to 5))
:desc "Switch to workspace 7" "7" (λ! (+workspace/switch-to 6))
:desc "Switch to workspace 8" "8" (λ! (+workspace/switch-to 7))
:desc "Switch to workspace 9" "9" (λ! (+workspace/switch-to 8))
:desc "Switch to last workspace" "0" #'+workspace/switch-to-last)
(:when (featurep! :editor multiple-cursors)
(:prefix ("m" . "multiple cursors")
:desc "Edit lines" "l" #'mc/edit-lines
:desc "Mark next" "n" #'mc/mark-next-like-this
:desc "Unmark next" "N" #'mc/unmark-next-like-this
:desc "Mark previous" "p" #'mc/mark-previous-like-this
:desc "Unmark previous" "P" #'mc/unmark-previous-like-this
:desc "Mark all" "t" #'mc/mark-all-like-this
:desc "Mark all DWIM" "m" #'mc/mark-all-like-this-dwim
:desc "Edit line endings" "e" #'mc/edit-ends-of-lines
:desc "Edit line starts" "a" #'mc/edit-beginnings-of-lines
:desc "Mark tag" "s" #'mc/mark-sgml-tag-pair
:desc "Mark in defun" "d" #'mc/mark-all-like-this-in-defun
:desc "Add cursor w/mouse" "<mouse-1>" #'mc/add-cursor-on-click))
;; APPs
(:when (featurep! :app email)
(:prefix ("M" . "email")
:desc "Open email app" "M" #'=email
:desc "Compose email" "c" #'+email/compose))
(:when (featurep! :app irc)
(:prefix ("I" . "irc")
:desc "Open irc app" "I" #'=irc
:desc "Next unread buffer" "a" #'tracking-next-buffer
:desc "Quit irc" "q" #'+irc/quit
:desc "Reconnect all" "r" #'circe-reconnect-all
:desc "Send message" "s" #'+irc/send-message
(:when (featurep! :completion ivy)
:desc "Jump to channel" "j" #'irc/ivy-jump-to-channel)))
(:when (featurep! :app twitter)
(:prefix ("T" . "twitter")
:desc "Open twitter app" "T" #'=twitter
:desc "Quit twitter" "q" #'+twitter/quit
:desc "Rerender twits" "r" #'+twitter/rerender-all
:desc "Ace link" "l" #'+twitter/ace-link)))
;;
;;; Plugins
(map! "C-=" #'er/expand-region
"C--" #'er/contract-region
(:when (featurep! :ui neotree)
"<f9>" #'+neotree/open
"<F-f9>" #'+neotree/find-this-file)
(:when (featurep! :ui treemacs)
"<f9>" #'+treemacs/open
"<F-f9>" #'+treemacs/find-file)
;; smartparens
(:after smartparens
:map smartparens-mode-map
"C-M-a" #'sp-beginning-of-sexp
"C-M-e" #'sp-end-of-sexp
"C-M-f" #'sp-forward-sexp
"C-M-b" #'sp-backward-sexp
"C-M-d" #'sp-splice-sexp
"C-M-k" #'sp-kill-sexp
"C-M-t" #'sp-transpose-sexp
"C-<right>" #'sp-forward-slurp-sexp
"M-<right>" #'sp-forward-barf-sexp
"C-<left>" #'sp-backward-slurp-sexp
"M-<left>" #'sp-backward-barf-sexp)
;; company mode
"C-;" #'+company/complete
;; Counsel
(:when (featurep! :completion ivy)
(:after counsel
:map counsel-ag-map
"C-c C-e" #'+ivy/wgrep-occur ; search/replace on results
[backtab] #'+ivy/wgrep-occur ; search/replace on results
"C-SPC" #'ivy-call-and-recenter ; preview
"M-RET" (+ivy-do-action! #'+ivy-git-grep-other-window-action))
"C-M-y" #'counsel-yank-pop)
;; repl toggle
"C-c C-z" #'+eval/open-repl-other-window
;; company mode
(:after company
:map company-active-map
"C-o" #'company-search-kill-others
"C-n" #'company-select-next
"C-p" #'company-select-previous
"C-h" #'company-quickhelp-manual-begin
"C-S-h" #'company-show-doc-buffer
"C-s" #'company-search-candidates
"M-s" #'company-filter-candidates
"<C-tab>" #'company-complete-common-or-cycle
[tab] #'company-complete-common-or-cycle
[backtab] #'company-select-previous
"C-RET" #'counsel-company
:map company-search-map
"C-n" #'company-search-repeat-forward
"C-p" #'company-search-repeat-backward
"C-s" (λ! (company-search-abort) (company-filter-candidates)))
;; neotree bindings
(:after neotree
:map neotree-mode-map
"q" #'neotree-hide
"RET" #'neotree-enter
"SPC" #'neotree-quick-look
"v" #'neotree-enter-vertical-split
"s" #'neotree-enter-horizontal-split
"c" #'neotree-create-node
"D" #'neotree-delete-node
"g" #'neotree-refresh
"r" #'neotree-rename-node
"R" #'neotree-refresh
"h" #'+neotree/collapse-or-up
"l" #'+neotree/expand-or-open
"n" #'neotree-next-line
"p" #'neotree-previous-line
"N" #'neotree-select-next-sibling-node
"P" #'neotree-select-previous-sibling-node)
;; help and info
(:after help-mode
:map help-mode-map
"o" #'ace-link-help
">" #'help-go-forward
"<" #'help-go-back
"n" #'forward-button
"p" #'backward-button)
(:after helpful
:map helpful-mode-map
"o" #'ace-link-help)
(:after apropos
:map apropos-mode-map
"o" #'ace-link-help
"n" #'forward-button
"p" #'backward-button)
(:after info
:map Info-mode-map
"o" #'ace-link-info)
;; yasnippet
(:after yasnippet
;; keymap while editing an inserted snippet
:map yas-keymap
"C-e" #'+snippets/goto-end-of-field
"C-a" #'+snippets/goto-start-of-field
"<S-tab>" #'yas-prev-field
"<M-backspace>" #'+snippets/delete-to-start-of-field
[backspace] #'+snippets/delete-backward-char
[delete] #'+snippets/delete-forward-char-or-field)
;; flycheck
(:after flycheck
:map flycheck-error-list-mode-map
"C-n" #'flycheck-error-list-next-error
"C-p" #'flycheck-error-list-previous-error
"RET" #'flycheck-error-list-goto-error)
;; ivy
(:after ivy
:map ivy-minibuffer-map
"TAB" #'ivy-alt-done
"C-g" #'keyboard-escape-quit)
;; ein notebokks
(:after ein:notebook-multilang
:map ein:notebook-multilang-mode-map
"C-c h" #'+ein/hydra/body))

View file

@ -0,0 +1,32 @@
;;; config/default/+emacs.el -*- lexical-binding: t; -*-
(require 'projectile) ; we need its keybinds immediately
;;
;;; Reasonable defaults
(setq shift-select-mode t)
(delete-selection-mode +1)
(def-package! expand-region
:commands (er/contract-region er/mark-symbol er/mark-word)
:config
(defun doom*quit-expand-region ()
"Properly abort an expand-region region."
(when (memq last-command '(er/expand-region er/contract-region))
(er/contract-region 0)))
(advice-add #'evil-escape :before #'doom*quit-expand-region)
(advice-add #'doom/escape :before #'doom*quit-expand-region))
(def-package! winum
:after-call (doom-switch-window-hook)
:config (winum-mode +1))
;;
;;; Keybinds
(when (featurep! +bindings)
(load! "+emacs-bindings"))

View file

@ -0,0 +1,871 @@
;;; config/default/+bindings.el -*- lexical-binding: t; -*-
;; This file defines a Spacemacs-esque keybinding scheme
;; Don't let evil-collection interfere with certain keys
(setq evil-collection-key-blacklist
(list "C-j" "C-k" "gd" "gf" "K" "[" "]" "gz"
doom-leader-key doom-localleader-key
doom-leader-alt-key doom-localleader-alt-key))
;;
;;; Global keybindings
(map! (:map override
;; A little sandbox to run code in
"M-;" #'eval-expression
"A-;" #'eval-expression)
;; Smart tab
:i [tab] (general-predicate-dispatch nil ; fall back to nearest keymap
(and (featurep! :feature snippets)
(bound-and-true-p yas-minor-mode)
(yas-maybe-expand-abbrev-key-filter 'yas-expand))
'yas-expand
(and (featurep! :completion company +tng)
(+company-has-completion-p))
'+company/complete)
:n [tab] (general-predicate-dispatch nil
(and (featurep! :editor fold)
(save-excursion (end-of-line) (invisible-p (point))))
'+fold/toggle
(fboundp 'evilmi-jump-items)
'evilmi-jump-items)
:v [tab] (general-predicate-dispatch nil
(and (bound-and-true-p yas-minor-mode)
(or (eq evil-visual-selection 'line)
(and (fboundp 'evilmi-jump-items)
(save-excursion
(/= (point)
(progn (evilmi-jump-items nil)
(point)))))))
'yas-insert-snippet
(fboundp 'evilmi-jump-items)
'evilmi-jump-items)
;; Smarter newlines
:i [remap newline] #'newline-and-indent ; auto-indent on newline
:i "C-j" #'+default/newline ; default behavior
(:after vc-annotate
:map vc-annotate-mode-map
[remap quit-window] #'kill-this-buffer)
(:map (help-mode-map helpful-mode-map)
:n "o" 'ace-link-help)
;; misc
:n "C-S-f" #'toggle-frame-fullscreen
;; Global evil keybinds
:m "]a" #'evil-forward-arg
:m "[a" #'evil-backward-arg
:m "]o" #'outline-next-visible-heading
:m "[o" #'outline-previous-visible-heading
:n "]b" #'next-buffer
:n "[b" #'previous-buffer
:n "zx" #'kill-this-buffer
:n "ZX" #'bury-buffer
:n "gp" #'+evil/reselect-paste
:n "g=" #'widen
:v "g=" #'+evil:narrow-buffer
:nv "z=" #'flyspell-correct-word-generic
:nv "g@" #'+evil:apply-macro
:nv "gc" #'evil-commentary
:nv "gx" #'evil-exchange
:nv "C-a" #'evil-numbers/inc-at-pt
:nv "C-S-a" #'evil-numbers/dec-at-pt
:v "gp" #'+evil/paste-preserve-register
:v "@" #'+evil:apply-macro
;; repeat in visual mode (FIXME buggy)
:v "." #'+evil:apply-macro
;; don't leave visual mode after shifting
:v "<" #'+evil/visual-dedent ; vnoremap < <gv
:v ">" #'+evil/visual-indent ; vnoremap > >gv
;; window management (prefix "C-w")
(:map evil-window-map
;; Navigation
"C-h" #'evil-window-left
"C-j" #'evil-window-down
"C-k" #'evil-window-up
"C-l" #'evil-window-right
"C-w" #'other-window
;; Swapping windows
"H" #'+evil/window-move-left
"J" #'+evil/window-move-down
"K" #'+evil/window-move-up
"L" #'+evil/window-move-right
"C-S-w" #'ace-swap-window
;; Window undo/redo
(:prefix "m"
"m" #'doom/window-maximize-buffer
"v" #'doom/window-maximize-vertically
"s" #'doom/window-maximize-horizontally)
"u" #'winner-undo
"C-u" #'winner-undo
"C-r" #'winner-redo
"o" #'doom/window-enlargen
;; Delete window
"c" #'+workspace/close-window-or-workspace
"C-C" #'ace-delete-window)
;; Plugins
;; evil-easymotion
:m "gs" #'+evil/easymotion ; lazy-load `evil-easymotion'
(:after evil-easymotion
:map evilem-map
"a" (evilem-create #'evil-forward-arg)
"A" (evilem-create #'evil-backward-arg)
"s" (evilem-create #'evil-snipe-repeat
:name 'evil-easymotion-snipe-forward
:pre-hook (save-excursion (call-interactively #'evil-snipe-s))
:bind ((evil-snipe-scope 'buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight)))
"S" (evilem-create #'evil-snipe-repeat
:name 'evil-easymotion-snipe-backward
:pre-hook (save-excursion (call-interactively #'evil-snipe-S))
:bind ((evil-snipe-scope 'buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight)))
"SPC" #'avy-goto-char-timer
"/" (evilem-create #'evil-ex-search-next
:pre-hook (save-excursion (call-interactively #'evil-ex-search-forward))
:bind ((evil-search-wrap)))
"?" (evilem-create #'evil-ex-search-previous
:pre-hook (save-excursion (call-interactively #'evil-ex-search-backward))
:bind ((evil-search-wrap))))
;; text object plugins
:textobj "x" #'evil-inner-xml-attr #'evil-outer-xml-attr
:textobj "a" #'evil-inner-arg #'evil-outer-arg
:textobj "B" #'evil-textobj-anyblock-inner-block #'evil-textobj-anyblock-a-block
:textobj "i" #'evil-indent-plus-i-indent #'evil-indent-plus-a-indent
:textobj "k" #'evil-indent-plus-i-indent-up #'evil-indent-plus-a-indent-up
:textobj "j" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down
;; evil-snipe
(:after evil-snipe
:map evil-snipe-parent-transient-map
"C-;" (λ! (require 'evil-easymotion)
(call-interactively
(evilem-create #'evil-snipe-repeat
:bind ((evil-snipe-scope 'whole-buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight))))))
;; evil-surround
:v "S" #'evil-surround-region
:o "s" #'evil-surround-edit
:o "S" #'evil-Surround-edit)
;;
;;; Module keybinds
;;; :feature
(map! (:when (featurep! :feature debugger)
:after realgud
:map realgud:shortkey-mode-map
:n "j" #'evil-next-line
:n "k" #'evil-previous-line
:n "h" #'evil-backward-char
:n "l" #'evil-forward-char
:n "c" #'realgud:cmd-continue
:m "n" #'realgud:cmd-next
:m "b" #'realgud:cmd-break
:m "B" #'realgud:cmd-clear)
(:when (featurep! :feature eval)
:g "M-r" #'+eval/buffer
:nv "gr" #'+eval:region
:n "gR" #'+eval/buffer
:v "gR" #'+eval:replace-region)
(:when (featurep! :feature lookup)
:nv "K" #'+lookup/documentation
:nv "gd" #'+lookup/definition
:nv "gD" #'+lookup/references
:nv "gf" #'+lookup/file)
(:when (featurep! :feature snippets)
;; auto-yasnippet
:i [C-tab] #'aya-expand
:nv [C-tab] #'aya-create
;; yasnippet
(:after yasnippet
(:map yas-keymap
"C-e" #'+snippets/goto-end-of-field
"C-a" #'+snippets/goto-start-of-field
[M-right] #'+snippets/goto-end-of-field
[M-left] #'+snippets/goto-start-of-field
[M-backspace] #'+snippets/delete-to-start-of-field
[backspace] #'+snippets/delete-backward-char
[delete] #'+snippets/delete-forward-char-or-field)))
(:when (featurep! :tools flyspell)
;; Keybinds that have no Emacs+evil analogues (i.e. don't exist):
;; zq - mark word at point as good word
;; zw - mark word at point as bad
;; zu{q,w} - undo last marking
;; Keybinds that evil define:
;; z= - correct flyspell word at point
;; ]s - jump to previous spelling error
;; [s - jump to next spelling error
(:map flyspell-mouse-map
"RET" #'flyspell-correct-word-generic
[return] #'flyspell-correct-word-generic
[mouse-1] #'flyspell-correct-word-generic))
(:when (featurep! :tools flycheck)
:m "]e" #'next-error
:m "[e" #'previous-error
(:after flycheck
:map flycheck-error-list-mode-map
:n "C-n" #'flycheck-error-list-next-error
:n "C-p" #'flycheck-error-list-previous-error
:n "j" #'flycheck-error-list-next-error
:n "k" #'flycheck-error-list-previous-error
:n "RET" #'flycheck-error-list-goto-error
:n [return] #'flycheck-error-list-goto-error))
(:when (featurep! :feature workspaces)
:n "gt" #'+workspace/switch-right
:n "gT" #'+workspace/switch-left
:n "]w" #'+workspace/switch-right
:n "[w" #'+workspace/switch-left
:g "M-1" (λ! (+workspace/switch-to 0))
:g "M-2" (λ! (+workspace/switch-to 1))
:g "M-3" (λ! (+workspace/switch-to 2))
:g "M-4" (λ! (+workspace/switch-to 3))
:g "M-5" (λ! (+workspace/switch-to 4))
:g "M-6" (λ! (+workspace/switch-to 5))
:g "M-7" (λ! (+workspace/switch-to 6))
:g "M-8" (λ! (+workspace/switch-to 7))
:g "M-9" (λ! (+workspace/switch-to 8))
:g "M-0" #'+workspace/switch-to-last
:g "M-t" #'+workspace/new
:g "M-T" #'+workspace/display))
;;; :completion
(map! (:when (featurep! :completion company)
:i "C-@" #'+company/complete
:i "C-SPC" #'+company/complete
(:prefix "C-x"
:i "C-l" #'+company/whole-lines
:i "C-k" #'+company/dict-or-keywords
:i "C-f" #'company-files
:i "C-]" #'company-etags
:i "s" #'company-ispell
:i "C-s" #'company-yasnippet
:i "C-o" #'company-capf
:i "C-n" #'+company/dabbrev
:i "C-p" #'+company/dabbrev-code-previous)
(:after company
(:map company-active-map
"C-w" nil ; don't interfere with `evil-delete-backward-word'
"C-n" #'company-select-next
"C-p" #'company-select-previous
"C-j" #'company-select-next
"C-k" #'company-select-previous
"C-h" #'company-show-doc-buffer
"C-u" #'company-previous-page
"C-d" #'company-next-page
"C-s" #'company-filter-candidates
"C-S-s" (cond ((featurep! :completion helm) #'helm-company)
((featurep! :completion ivy) #'counsel-company))
"C-SPC" #'company-complete-common
"TAB" #'company-complete-common-or-cycle
[tab] #'company-complete-common-or-cycle
[backtab] #'company-select-previous)
(:map company-search-map ; applies to `company-filter-map' too
"C-n" #'company-select-next-or-abort
"C-p" #'company-select-previous-or-abort
"C-j" #'company-select-next-or-abort
"C-k" #'company-select-previous-or-abort
"C-s" (λ! (company-search-abort) (company-filter-candidates))
"ESC" #'company-search-abort)
;; TAB auto-completion in term buffers
(:map comint-mode-map
"TAB" #'company-complete
[tab] #'company-complete)))
(:when (featurep! :completion ivy)
(:map (help-mode-map helpful-mode-map)
:n "Q" #'ivy-resume)
(:after ivy
:map ivy-minibuffer-map
"C-SPC" #'ivy-call-and-recenter ; preview file
"C-l" #'ivy-alt-done
"C-v" #'yank)
(:after counsel
:map counsel-ag-map
"C-SPC" #'ivy-call-and-recenter ; preview
"C-l" #'ivy-done
"C-c C-e" #'+ivy/wgrep-occur ; search/replace on results
[backtab] #'+ivy/wgrep-occur ; search/replace on results
[C-return] (+ivy-do-action! #'+ivy-git-grep-other-window-action))
(:after swiper
:map swiper-map
[backtab] #'+ivy/wgrep-occur))
(:when (featurep! :completion helm)
(:after helm
(:map helm-map
[left] #'left-char
[right] #'right-char
"C-S-n" #'helm-next-source
"C-S-p" #'helm-previous-source
"C-j" #'helm-next-line
"C-k" #'helm-previous-line
"C-S-j" #'helm-next-source
"C-S-k" #'helm-previous-source
"C-f" #'helm-next-page
"C-S-f" #'helm-previous-page
"C-u" #'helm-delete-minibuffer-contents
"C-w" #'backward-kill-word
"C-r" #'evil-paste-from-register ; Evil registers in helm! Glorious!
"C-s" #'helm-minibuffer-history
"C-b" #'backward-word
;; Swap TAB and C-z
"TAB" #'helm-execute-persistent-action
[tab] #'helm-execute-persistent-action
"C-z" #'helm-select-action)
(:after swiper-helm
:map swiper-helm-keymap [backtab] #'helm-ag-edit)
(:after helm-ag
:map helm-ag-map
"C--" #'+helm-do-ag-decrease-context
"C-=" #'+helm-do-ag-increase-context
[backtab] #'helm-ag-edit
[left] nil
[right] nil)
(:after helm-files
:map (helm-find-files-map helm-read-file-map)
[C-return] #'helm-ff-run-switch-other-window
"C-w" #'helm-find-files-up-one-level)
(:after helm-locate
:map helm-generic-files-map
[C-return] #'helm-ff-run-switch-other-window)
(:after helm-buffers
:map helm-buffer-map
[C-return] #'helm-buffer-switch-other-window)
(:after helm-occur
:map helm-occur-map
[C-return] #'helm-occur-run-goto-line-ow)
(:after helm-grep
:map helm-grep-map
[C-return] #'helm-grep-run-other-window-action))))
;;; :ui
(map! (:when (featurep! :ui hl-todo)
:m "]t" #'hl-todo-next
:m "[t" #'hl-todo-previous)
(:when (featurep! :ui neotree)
:after neotree
:map neotree-mode-map
:n "g" nil
:n "TAB" #'neotree-quick-look
:n "RET" #'neotree-enter
:n [tab] #'neotree-quick-look
:n [return] #'neotree-enter
:n "DEL" #'evil-window-prev
:n "c" #'neotree-create-node
:n "r" #'neotree-rename-node
:n "d" #'neotree-delete-node
:n "j" #'neotree-next-line
:n "k" #'neotree-previous-line
:n "n" #'neotree-next-line
:n "p" #'neotree-previous-line
:n "h" #'+neotree/collapse-or-up
:n "l" #'+neotree/expand-or-open
:n "J" #'neotree-select-next-sibling-node
:n "K" #'neotree-select-previous-sibling-node
:n "H" #'neotree-select-up-node
:n "L" #'neotree-select-down-node
:n "G" #'evil-goto-line
:n "gg" #'evil-goto-first-line
:n "v" #'neotree-enter-vertical-split
:n "s" #'neotree-enter-horizontal-split
:n "q" #'neotree-hide
:n "R" #'neotree-refresh)
(:when (featurep! :ui popup)
:n "C-`" #'+popup/toggle
:n "C-~" #'+popup/raise
:g "C-x p" #'+popup/other)
(:when (featurep! :ui vc-gutter)
:m "]d" #'git-gutter:next-hunk
:m "[d" #'git-gutter:previous-hunk))
;;; :editor
(map! (:when (featurep! :editor fold)
:nv "C-SPC" #'+fold/toggle)
(:when (featurep! :editor format)
:n "gQ" #'+format:region)
(:when (featurep! :editor multiple-cursors)
;; evil-mc
(:prefix "gz"
:nv "d" #'evil-mc-make-and-goto-next-match
:nv "D" #'evil-mc-make-and-goto-prev-match
:nv "j" #'evil-mc-make-cursor-move-next-line
:nv "k" #'evil-mc-make-cursor-move-prev-line
:nv "m" #'evil-mc-make-all-cursors
:nv "n" #'evil-mc-make-and-goto-next-cursor
:nv "N" #'evil-mc-make-and-goto-last-cursor
:nv "p" #'evil-mc-make-and-goto-prev-cursor
:nv "P" #'evil-mc-make-and-goto-first-cursor
:nv "q" #'evil-mc-undo-all-cursors
:nv "t" #'+multiple-cursors/evil-mc-toggle-cursors
:nv "u" #'evil-mc-undo-last-added-cursor
:nv "z" #'+multiple-cursors/evil-mc-make-cursor-here)
(:after evil-mc
:map evil-mc-key-map
:nv "C-n" #'evil-mc-make-and-goto-next-cursor
:nv "C-N" #'evil-mc-make-and-goto-last-cursor
:nv "C-p" #'evil-mc-make-and-goto-prev-cursor
:nv "C-P" #'evil-mc-make-and-goto-first-cursor)
;; evil-multiedit
:v "R" #'evil-multiedit-match-all
:n "M-d" #'evil-multiedit-match-symbol-and-next
:n "M-D" #'evil-multiedit-match-symbol-and-prev
:v "M-d" #'evil-multiedit-match-and-next
:v "M-D" #'evil-multiedit-match-and-prev
:nv "C-M-d" #'evil-multiedit-restore
(:after evil-multiedit
(:map evil-multiedit-state-map
"M-d" #'evil-multiedit-match-and-next
"M-D" #'evil-multiedit-match-and-prev
"RET" #'evil-multiedit-toggle-or-restrict-region
[return] #'evil-multiedit-toggle-or-restrict-region)
(:map (evil-multiedit-state-map evil-multiedit-insert-state-map)
"C-n" #'evil-multiedit-next
"C-p" #'evil-multiedit-prev)))
(:when (featurep! :editor rotate-text)
:n "!" #'rotate-text))
;;; :emacs
(map! (:when (featurep! :emacs vc)
:after git-timemachine
:map git-timemachine-mode-map
:n "C-p" #'git-timemachine-show-previous-revision
:n "C-n" #'git-timemachine-show-next-revision
:n "[[" #'git-timemachine-show-previous-revision
:n "]]" #'git-timemachine-show-next-revision
:n "q" #'git-timemachine-quit
:n "gb" #'git-timemachine-blame))
;;; :tools
(map! (:when (featurep! :tools magit)
(:after evil-magit
;; fix conflicts with private bindings
:map (magit-status-mode-map magit-revision-mode-map)
"C-j" nil
"C-k" nil)
(:map transient-map
"q" #'transient-quit-one))
(:when (featurep! :tools gist)
:after gist
:map gist-list-menu-mode-map
:n "go" #'gist-browse-current-url
:n "gr" #'gist-list-reload
:n "c" #'gist-add-buffer
:n "d" #'gist-kill-current
:n "e" #'gist-edit-current-description
:n "f" #'gist-fork
:n "q" #'kill-this-buffer
:n "s" #'gist-star
:n "S" #'gist-unstar
:n "y" #'gist-print-current-url))
;;; :lang
(map! (:when (featurep! :lang markdown)
:after markdown-mode
:map markdown-mode-map
;; fix conflicts with private bindings
[backspace] nil))
;;
;;; <leader>
(map! :leader
:desc "Eval expression" ";" #'eval-expression
:desc "M-x" ":" #'execute-extended-command
:desc "Pop up scratch buffer" "x" #'doom/open-scratch-buffer
:desc "Org Capture" "X" #'org-capture
;; C-u is used by evil
:desc "Universal argument" "u" #'universal-argument
:desc "window" "w" evil-window-map
:desc "help" "h" help-map
:desc "Toggle last popup" "~" #'+popup/toggle
:desc "Find file" "." #'find-file
:desc "Switch buffer" "," #'switch-to-buffer
(:when (featurep! :feature workspaces)
:desc "Switch workspace buffer" "," #'persp-switch-to-buffer
:desc "Switch buffer" "<" #'switch-to-buffer)
:desc "Resume last search" "'"
(cond ((featurep! :completion ivy) #'ivy-resume)
((featurep! :completion helm) #'helm-resume))
:desc "Search for symbol in project" "*" #'+default/search-project-for-symbol-at-point
:desc "Find file in project" "SPC" #'projectile-find-file
:desc "Blink cursor line" "DEL" #'+nav-flash/blink-cursor
:desc "Jump to bookmark" "RET" #'bookmark-jump
;; Prefixed key groups
(:prefix ("/" . "search")
:desc "Search buffer" "b" #'swiper
:desc "Search current directory" "d" #'+default/search-from-cwd
:desc "Jump to symbol" "i" #'imenu
:desc "Jump to symbol across buffers" "I" #'imenu-anywhere
:desc "Jump to link" "l" #'ace-link
:desc "Look up online" "o" #'+lookup/online-select
:desc "Search project" "p" #'+default/search-project)
(:when (featurep! :feature workspaces)
(:prefix ("TAB" . "workspace")
:desc "Display tab bar" "TAB" #'+workspace/display
:desc "Switch workspace" "." #'+workspace/switch-to
:desc "New workspace" "n" #'+workspace/new
:desc "Load workspace from file" "l" #'+workspace/load
:desc "Save workspace to file" "s" #'+workspace/save
:desc "Delete session" "x" #'+workspace/kill-session
:desc "Delete this workspace" "d" #'+workspace/delete
:desc "Rename workspace" "r" #'+workspace/rename
:desc "Restore last session" "R" #'+workspace/restore-last-session
:desc "Next workspace" "]" #'+workspace/switch-right
:desc "Previous workspace" "[" #'+workspace/switch-left
:desc "Switch to 1st workspace" "1" (λ! (+workspace/switch-to 0))
:desc "Switch to 2nd workspace" "2" (λ! (+workspace/switch-to 1))
:desc "Switch to 3rd workspace" "3" (λ! (+workspace/switch-to 2))
:desc "Switch to 4th workspace" "4" (λ! (+workspace/switch-to 3))
:desc "Switch to 5th workspace" "5" (λ! (+workspace/switch-to 4))
:desc "Switch to 6th workspace" "6" (λ! (+workspace/switch-to 5))
:desc "Switch to 7th workspace" "7" (λ! (+workspace/switch-to 6))
:desc "Switch to 8th workspace" "8" (λ! (+workspace/switch-to 7))
:desc "Switch to 9th workspace" "9" (λ! (+workspace/switch-to 8))
:desc "Switch to last workspace" "0" #'+workspace/switch-to-last))
(:prefix ("b" . "buffer")
:desc "Toggle narrowing" "-" #'doom/clone-and-narrow-buffer
:desc "Previous buffer" "[" #'previous-buffer
:desc "Next buffer" "]" #'next-buffer
(:when (featurep! :feature workspaces)
:desc "Switch workspace buffer" "b" #'persp-switch-to-buffer
:desc "Switch buffer" "B" #'switch-to-buffer)
(:unless (featurep! :feature workspaces)
:desc "Switch buffer" "b" #'switch-to-buffer)
:desc "Kill buffer" "k" #'kill-this-buffer
:desc "Next buffer" "n" #'next-buffer
:desc "New empty buffer" "N" #'evil-buffer-new
:desc "Kill other buffers" "o" #'doom/kill-other-buffers
:desc "Previous buffer" "p" #'previous-buffer
:desc "Save buffer" "s" #'save-buffer
:desc "Sudo edit this file" "S" #'doom/sudo-this-file
:desc "Pop scratch buffer" "x" #'doom/open-scratch-buffer
:desc "Bury buffer" "z" #'bury-buffer)
(:prefix ("c" . "code")
:desc "Compile" "c" #'compile
:desc "Jump to definition" "d" #'+lookup/definition
:desc "Jump to references" "D" #'+lookup/references
:desc "Evaluate buffer/region" "e" #'+eval/buffer-or-region
:desc "Evaluate & replace region" "E" #'+eval:replace-region
:desc "Format buffer/region" "f" #'+format/region-or-buffer
:desc "Open REPL" "r" #'+eval/open-repl-other-window
:desc "Delete trailing whitespace" "w" #'delete-trailing-whitespace
:desc "Delete trailing newlines" "W" #'doom/delete-trailing-newlines
:desc "List errors" "x" #'flycheck-list-errors)
(:prefix ("f" . "file")
:desc "Find file" "." #'find-file
:desc "Find file from here" "/"
(if (featurep! :completion ivy)
#'counsel-file-jump
(λ! (doom-project-find-file default-directory)))
:desc "Open project editorconfig" "c" #'editorconfig-find-current-editorconfig
:desc "Find directory" "d" #'dired
:desc "Find file in emacs.d" "e" #'+default/find-in-emacsd
:desc "Browse emacs.d" "E" #'+default/browse-emacsd
:desc "Find file from here" "f" #'find-file
:desc "Find file in private config" "p" #'doom/find-file-in-private-config
:desc "Browse private config" "P" #'doom/open-private-config
:desc "Recent files" "r" #'recentf-open-files
:desc "Recent project files" "R" #'projectile-recentf
:desc "Save file" "s" #'save-buffer
:desc "Sudo find file" "S" #'doom/sudo-find-file
:desc "Delete this file" "X" #'doom/delete-this-file
:desc "Yank filename" "y" #'+default/yank-buffer-filename)
(:prefix ("g" . "git")
:desc "Git revert file" "R" #'vc-revert
(:when (featurep! :ui vc-gutter)
:desc "Git revert hunk" "r" #'git-gutter:revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "]" #'git-gutter:next-hunk
:desc "Jump to previous hunk" "[" #'git-gutter:previous-hunk)
(:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-dispatch
:desc "Forge dispatch" "'" #'forge-dispatch
:desc "Magit status" "g" #'magit-status
:desc "Magit file delete" "x" #'magit-file-delete
:desc "Magit blame" "B" #'magit-blame-addition
:desc "Magit clone" "C" #'+magit/clone
:desc "Magit fetch" "F" #'magit-fetch
:desc "Magit buffer log" "L" #'magit-log
:desc "Git stage file" "S" #'magit-stage-file
:desc "Git unstage file" "U" #'magit-unstage-file
(:prefix ("f" . "find")
:desc "Find file" "f" #'magit-find-file
:desc "Find gitconfig file" "g" #'magit-find-git-config-file
:desc "Find commit" "c" #'magit-show-commit
:desc "Find issue" "i" #'forge-visit-issue
:desc "Find pull request" "p" #'forge-visit-pullreq)
(:prefix ("o" . "open in browser")
:desc "Browse region or line" "." #'+vc/git-browse-region-or-line
:desc "Browse remote" "r" #'forge-browse-remote
:desc "Browse commit" "c" #'forge-browse-commit
:desc "Browse an issue" "i" #'forge-browse-issue
:desc "Browse a pull request" "p" #'forge-browse-pullreq
:desc "Browse issues" "I" #'forge-browse-issues
:desc "Browse pull requests" "P" #'forge-browse-pullreqs)
(:prefix ("l" . "list")
(:when (featurep! :tools gist)
:desc "List gists" "g" #'+gist:list)
:desc "List repositories" "r" #'magit-list-repositories
:desc "List submodules" "s" #'magit-list-submodules
:desc "List issues" "i" #'forge-list-issues
:desc "List pull requests" "p" #'forge-list-pullreqs
:desc "List notifications" "n" #'forge-list-notifications)
(:prefix ("c" . "create")
:desc "Initialize repo" "r" #'magit-init
:desc "Clone repo" "R" #'+magit/clone
:desc "Commit" "c" #'magit-commit-create
:desc "Issue" "i" #'forge-create-issue
:desc "Pull request" "p" #'forge-create-pullreq)))
(:prefix ("i" . "insert")
:desc "Insert from clipboard" "y" #'+default/yank-pop
:desc "Insert from evil register" "r" #'evil-ex-registers
:desc "Insert snippet" "s" #'yas-insert-snippet)
(:prefix ("n" . "notes")
:desc "Open deft" "d" #'deft
:desc "Find file in notes" "n" #'+default/find-in-notes
:desc "Browse notes" "N" #'+default/browse-notes
:desc "Pop scratch buffer" "s" #'doom/open-scratch-buffer
:desc "Org capture" "x" #'org-capture
:desc "Org store link" "l" #'org-store-link)
(:prefix ("o" . "open")
:desc "Org agenda" "A" #'org-agenda
(:prefix ("a" . "org agenda")
:desc "Agenda" "a" #'org-agenda
:desc "Todo list" "t" #'org-todo-list
:desc "Tags search" "m" #'org-tags-view
:desc "View search" "v" #'org-search-view)
:desc "Default browser" "b" #'browse-url-of-file
:desc "Debugger" "d" #'+debug/open
:desc "REPL" "r" #'+eval/open-repl-other-window
:desc "REPL (same window)" "R" #'+eval/open-repl-same-window
:desc "Dired" "-" #'dired-jump
(:when (featurep! :ui neotree)
:desc "Project sidebar" "p" #'+neotree/open
:desc "Find file in project sidebar" "P" #'+neotree/find-this-file)
(:when (featurep! :ui treemacs)
:desc "Project sidebar" "p" #'+treemacs/toggle
:desc "Find file in project sidebar" "P" #'+treemacs/find-file)
(:when (featurep! :emacs imenu)
:desc "Imenu sidebar" "i" #'imenu-list-smart-toggle)
(:when (featurep! :emacs term)
:desc "Terminal" "t" #'+term/open
:desc "Terminal in popup" "T" #'+term/open-popup-in-project)
(:when (featurep! :tools vterm)
:desc "Terminal" "t" #'+vterm/open
:desc "Terminal in popup" "T" #'+vterm/open-popup-in-project)
(:when (featurep! :emacs eshell)
:desc "Eshell" "e" #'+eshell/open
:desc "Eshell in popup" "E" #'+eshell/open-popup)
(:when (featurep! :collab floobits)
(:prefix ("f" . "floobits")
"c" #'floobits-clear-highlights
"f" #'floobits-follow-user
"j" #'floobits-join-workspace
"l" #'floobits-leave-workspace
"R" #'floobits-share-dir-private
"s" #'floobits-summon
"t" #'floobits-follow-mode-toggle
"U" #'floobits-share-dir-public))
(:when (featurep! :tools macos)
:desc "Reveal in Finder" "o" #'+macos/reveal-in-finder
:desc "Reveal project in Finder" "O" #'+macos/reveal-project-in-finder
:desc "Send to Transmit" "u" #'+macos/send-to-transmit
:desc "Send project to Transmit" "U" #'+macos/send-project-to-transmit
:desc "Send to Launchbar" "l" #'+macos/send-to-launchbar
:desc "Send project to Launchbar" "L" #'+macos/send-project-to-launchbar)
(:when (featurep! :tools docker)
:desc "Docker" "D" #'docker))
(:prefix ("p" . "project")
:desc "Browse project" "." #'+default/browse-project
:desc "Find file in other project" ">" #'doom/find-file-in-other-project
:desc "Find file in project" "/" #'projectile-find-file
:desc "Browse other project" "?" #'doom/browse-in-other-project
:desc "Run cmd in project root" "!" #'projectile-run-shell-command-in-root
:desc "Add new project" "a" #'projectile-add-known-project
:desc "Switch to project buffer" "b" #'projectile-switch-to-buffer
:desc "Compile in project" "c" #'projectile-compile-project
:desc "Remove known project" "d" #'projectile-remove-known-project
:desc "Kill project buffers" "k" #'projectile-kill-buffers
:desc "Invalidate project cache" "i" #'projectile-invalidate-cache
:desc "Find other file" "o" #'projectile-find-other-file
:desc "Switch project" "p" #'projectile-switch-project
:desc "Find recent project files" "r" #'projectile-recentf
:desc "Scratch buffer" "s" #'doom/open-project-scratch-buffer
:desc "List project tasks" "t" #'+default/project-tasks
(:prefix ("x" . "terminal")
:desc "Open eshell in project" "e" #'projectile-run-eshell
:desc "Open ielm in project" "i" #'projectile-run-ielm
:desc "Open term in project" "t" #'projectile-run-term
:desc "Open shell in project" "s" #'projectile-run-shell))
(:prefix ("q" . "session")
:desc "Quit Emacs" "q" #'save-buffers-kill-terminal
:desc "Quit Emacs without saving" "Q" #'evil-quit-all-with-error-code
:desc "Quick save current session" "s" #'doom/quicksave-session
:desc "Restore last session" "l" #'doom/quickload-session
:desc "Save session to file" "S" #'doom/save-session
:desc "Restore session from file" "L" #'doom/load-session
:desc "Restart & restore Emacs" "r" #'doom/restart-and-restore
:desc "Restart Emacs" "R" #'doom/restart)
(:when (featurep! :tools upload)
(:prefix ("r" . "remote")
:desc "Upload local" "u" #'ssh-deploy-upload-handler
:desc "Upload local (force)" "U" #'ssh-deploy-upload-handler-forced
:desc "Download remote" "d" #'ssh-deploy-download-handler
:desc "Diff local & remote" "D" #'ssh-deploy-diff-handler
:desc "Browse remote files" "." #'ssh-deploy-browse-remote-handler
:desc "Detect remote changes" ">" #'ssh-deploy-remote-changes-handler))
(:when (featurep! :feature snippets)
(:prefix ("s" . "snippets")
:desc "New snippet" "n" #'yas-new-snippet
:desc "Insert snippet" "i" #'yas-insert-snippet
:desc "Jump to mode snippet" "/" #'yas-visit-snippet-file
:desc "Jump to snippet" "s" #'+snippets/find-file
:desc "Browse snippets" "S" #'+snippets/browse
:desc "Reload snippets" "r" #'yas-reload-all
:desc "Create temporary snippet" "c" #'aya-create
:desc "Use temporary snippet" "e" #'aya-expand))
(:prefix ("t" . "toggle")
:desc "Flyspell" "s" #'flyspell-mode
:desc "Flycheck" "f" #'flycheck-mode
:desc "Line numbers" "l" #'doom/toggle-line-numbers
:desc "Frame fullscreen" "F" #'toggle-frame-fullscreen
:desc "Indent guides" "i" #'highlight-indent-guides-mode
:desc "Impatient mode" "h" #'+impatient-mode/toggle
:desc "Big mode" "b" #'doom-big-font-mode
:desc "Evil goggles" "g" #'evil-goggles-mode
:desc "org-tree-slide mode" "p" #'+org-present/start))
;;
;;; Universal motion repeating keys
(defvar +default-repeat-keys (cons ";" ",")
"The keys to use for repeating motions.
This is a cons cell whose CAR is the key for repeating a motion forward, and
whose CDR is for repeating backward. They should both be kbd-able strings.")
(when +default-repeat-keys
(defmacro do-repeat! (command next-func prev-func)
"Makes ; and , the universal repeat-keys in evil-mode.
To change these keys see `+default-repeat-keys'."
(let ((fn-sym (intern (format "+default*repeat-%s" (doom-unquote command)))))
`(progn
(defun ,fn-sym (&rest _)
(define-key! :states 'motion
(car +default-repeat-keys) #',next-func
(cdr +default-repeat-keys) #',prev-func))
(advice-add #',command :before #',fn-sym))))
;; n/N
(do-repeat! evil-ex-search-next evil-ex-search-next evil-ex-search-previous)
(do-repeat! evil-ex-search-previous evil-ex-search-next evil-ex-search-previous)
(do-repeat! evil-ex-search-forward evil-ex-search-next evil-ex-search-previous)
(do-repeat! evil-ex-search-backward evil-ex-search-next evil-ex-search-previous)
;; f/F/t/T/s/S
(setq evil-snipe-repeat-keys nil
evil-snipe-override-evil-repeat-keys nil) ; causes problems with remapped ;
(do-repeat! evil-snipe-f evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-F evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-t evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-T evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-s evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-S evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-x evil-snipe-repeat evil-snipe-repeat-reverse)
(do-repeat! evil-snipe-X evil-snipe-repeat evil-snipe-repeat-reverse)
;; */#
(do-repeat! evil-visualstar/begin-search-forward
evil-ex-search-next evil-ex-search-previous)
(do-repeat! evil-visualstar/begin-search-backward
evil-ex-search-previous evil-ex-search-next))
;;
;;; Universal evil integration
(when (featurep! :feature evil +everywhere)
;; Have C-u behave similarly to `doom/backward-to-bol-or-indent'.
;; NOTE SPC u replaces C-u as the universal argument.
(map! :gi "C-u" #'doom/backward-kill-to-bol-and-indent
:gi "C-w" #'backward-kill-word
;; Vimmish ex motion keys
:gi "C-b" #'backward-word
:gi "C-f" #'forward-word)
(after! view
(define-key view-mode-map [escape] #'View-quit-all))
(after! man
(evil-define-key* 'normal Man-mode-map "q" #'kill-this-buffer))
;; Minibuffer
(define-key! evil-ex-completion-map
"C-a" #'move-beginning-of-line
"C-b" #'backward-word
"C-s" (if (featurep! :completion ivy)
#'counsel-minibuffer-history
#'helm-minibuffer-history))
(define-key! :keymaps +default-minibuffer-maps
[escape] #'abort-recursive-edit
"C-v" #'yank
"C-z" (λ! (ignore-errors (call-interactively #'undo)))
"C-a" #'move-beginning-of-line
"C-b" #'backward-word
"C-r" #'evil-paste-from-register
;; Scrolling lines
"C-j" #'next-line
"C-k" #'previous-line
"C-S-j" #'scroll-up-command
"C-S-k" #'scroll-down-command))

View file

@ -0,0 +1,21 @@
;;; config/default/+evil.el -*- lexical-binding: t; -*-
(defun +default|disable-delete-selection-mode ()
(delete-selection-mode -1))
(add-hook 'evil-insert-state-entry-hook #'delete-selection-mode)
(add-hook 'evil-insert-state-exit-hook #'+default|disable-delete-selection-mode)
;;
;;; Keybindings
;; This section is dedicated to "fixing" certain keys so that they behave
;; sensibly (and consistently with similar contexts).
;; Make SPC u SPC u [...] possible (#747)
(map! :map universal-argument-map
:prefix doom-leader-key "u" #'universal-argument-more
:prefix doom-leader-alt-key "u" #'universal-argument-more)
(when (featurep! +bindings)
(load! "+evil-bindings"))

View file

@ -0,0 +1,60 @@
#+TITLE: :config default
This module provides a set of reasonable defaults, including:
+ A Spacemacs-esque keybinding scheme
+ Extra Ex commands for evil-mode users
+ A yasnippet snippets library tailored to Doom emacs
+ A configuration for (almost) universally repeating searches with =;= and =,=
#+begin_quote
The defaults module is intended as a "reasonable-defaults" module, but also as a
reference for your own private modules. You'll find [[https://github.com/hlissner/doom-emacs-private][my private module in a
separate repo]].
Refer to the [[https://github.com/hlissner/doom-emacs/wiki/Customization][Customization page]] on the wiki for details on starting your own
private module.
#+end_quote
* Table of Contents :TOC:
- [[#install][Install]]
- [[#configuration][Configuration]]
- [[#using-another-snippets-library][Using another snippets library]]
- [[#im-not-an-evil-user][I'm not an evil user...]]
- [[#appendix][Appendix]]
- [[#commands][Commands]]
- [[#hacks][Hacks]]
* Install
This module has no external dependencies.
* Configuration
** Using another snippets library
Don't want to use provided one? Then add this to your private module,
#+BEGIN_SRC emacs-lisp
;; in config/$USER/packages.el
(package! emacs-snippets :ignore t)
;; in config/$USER/config.el
(def-package-hook! emacs-snippets :disabled t)
(after! yasnippet
(push "~/path/to/my/private/snippets" yas-snippet-dirs))
#+END_SRC
** I'm not an evil user...
That's fine. All evil configuration is ignored if =:feature evil= is disabled.
* Appendix
** Commands
+ ~+default/browse-project~
+ ~+default/browse-templates~
+ ~+default/find-in-templates~
+ ~+default/browse-emacsd~
+ ~+default/find-in-emacsd~
+ ~+default/browse-notes~
+ ~+default/find-in-notes~
+ ~+default/find-in-snippets~
** Hacks
+ ~epa-pinentry-mode~ is set to ~'loopback~, forcing gpg-agent to use the Emacs
minibuffer when prompting for your passphrase. *Only works with GPG 2.1+!*

View file

@ -0,0 +1,271 @@
;; config/default/autoload/default.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +default/yank-buffer-filename ()
"Copy the current buffer's path to the kill ring."
(interactive)
(if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory))))
(message (kill-new (abbreviate-file-name filename)))
(error "Couldn't find filename in current buffer")))
;;;###autoload
(defun +default/browse-project ()
(interactive) (doom-project-browse (doom-project-root)))
;; NOTE No need for find-in-project, use `projectile-find-file'
;;;###autoload
(defun +default/browse-templates ()
(interactive) (doom-project-browse +file-templates-dir))
;;;###autoload
(defun +default/find-in-templates ()
(interactive) (doom-project-find-file +file-templates-dir))
;;;###autoload
(defun +default/browse-emacsd ()
(interactive) (doom-project-browse doom-emacs-dir))
;;;###autoload
(defun +default/find-in-emacsd ()
(interactive) (doom-project-find-file doom-emacs-dir))
;;;###autoload
(defun +default/browse-notes ()
(interactive) (doom-project-browse org-directory))
;;;###autoload
(defun +default/find-in-notes ()
(interactive) (doom-project-find-file org-directory))
;;;###autoload
(defun +default/compile (arg)
"Runs `compile' from the root of the current project.
If a compilation window is already open, recompile that instead.
If ARG (universal argument), runs `compile' from the current directory."
(interactive "P")
(if (and (bound-and-true-p compilation-in-progress)
(buffer-live-p compilation-last-buffer))
(recompile)
(call-interactively
(if arg
#'projectile-compile-project
#'compile))))
;;;###autoload
(defun +default/man-or-woman ()
"Invoke `man' if man is installed, otherwise use `woman'."
(interactive)
(call-interactively
(if (executable-find "man")
#'man
#'woman)))
;;;###autoload
(defalias '+default/newline #'newline)
;;;###autoload
(defun +default/new-buffer ()
"TODO"
(interactive)
(if (featurep! 'evil)
(call-interactively #'evil-buffer-new)
(let ((buffer (generate-new-buffer "*new*")))
(set-window-buffer nil buffer)
(with-current-buffer buffer
(funcall (default-value 'major-mode))))))
;;;###autoload
(defun +default/project-tasks ()
"Invokes `+ivy/tasks' or `+helm/tasks', depending on which is available."
(interactive)
(cond ((featurep! :completion ivy) (+ivy/tasks))
((featurep! :completion helm) (+helm/tasks))))
;;;###autoload
(defun +default/newline-above ()
"Insert an indented new line before the current one."
(interactive)
(if (featurep 'evil)
(call-interactively 'evil-open-above)
(beginning-of-line)
(save-excursion (newline))
(indent-according-to-mode)))
;;;###autoload
(defun +default/newline-below ()
"Insert an indented new line after the current one."
(interactive)
(if (featurep 'evil)
(call-interactively 'evil-open-below)
(end-of-line)
(newline-and-indent)))
;;;###autoload
(defun +default/yank-pop ()
"Interactively select what text to insert from the kill ring."
(interactive)
(call-interactively
(cond ((fboundp 'counsel-yank-pop) #'counsel-yank-pop)
((fboundp 'helm-show-kill-ring) #'helm-show-kill-ring)
((error "No kill-ring search backend available. Enable ivy or helm!")))))
;;;###autoload
(defun +default*newline-indent-and-continue-comments (_orig-fn)
"Inserts a newline and possibly indents it. Also continues comments if
executed from a commented line; handling special cases for certain languages
with weak native support."
(interactive)
(cond ((sp-point-in-string) (newline))
((and (sp-point-in-comment)
comment-line-break-function)
(funcall comment-line-break-function))
(t
(newline nil t)
(indent-according-to-mode))))
(defun doom--backward-delete-whitespace-to-column ()
"Delete back to the previous column of whitespace, or as much whitespace as
possible, or just one char if that's not possible."
(interactive)
(let* ((context (ignore-errors (sp-get-thing)))
(op (plist-get context :op))
(cl (plist-get context :cl))
open-len close-len)
(cond ;; When in strings (sp acts weird with quotes; this is the fix)
;; Also, skip closing delimiters
((and op cl
(string= op cl)
(and (string= (char-to-string (or (char-before) 0)) op)
(setq open-len (length op)))
(and (string= (char-to-string (or (char-after) 0)) cl)
(setq close-len (length cl))))
(delete-char (- open-len))
(delete-char close-len))
;; Delete up to the nearest tab column IF only whitespace between
;; point and bol.
((and (not indent-tabs-mode)
(not (bolp))
(not (sp-point-in-string))
(save-excursion (>= (- (skip-chars-backward " \t")) tab-width)))
(let ((movement (% (current-column) tab-width)))
(when (= movement 0)
(setq movement tab-width))
(delete-char (- movement)))
(unless (memq (char-before) (list ?\n ?\ ))
(insert " ")))
;; Otherwise do a regular delete
((delete-char -1)))))
;;;###autoload
(defun +default*delete-backward-char (n &optional killflag)
"Same as `delete-backward-char', but preforms these additional checks:
+ If point is surrounded by (balanced) whitespace and a brace delimiter ({} []
()), delete a space on either side of the cursor.
+ If point is at BOL and surrounded by braces on adjacent lines, collapse
newlines:
{
|
} => {|}
+ Otherwise, resort to `doom--backward-delete-whitespace-to-column'.
+ Resorts to `delete-char' if n > 1"
(interactive "p\nP")
(or (integerp n)
(signal 'wrong-type-argument (list 'integerp n)))
(cond ((and (use-region-p)
delete-active-region
(= n 1))
;; If a region is active, kill or delete it.
(if (eq delete-active-region 'kill)
(kill-region (region-beginning) (region-end) 'region)
(funcall region-extract-function 'delete-only)))
;; In Overwrite mode, maybe untabify while deleting
((null (or (null overwrite-mode)
(<= n 0)
(memq (char-before) '(?\t ?\n))
(eobp)
(eq (char-after) ?\n)))
(let ((ocol (current-column)))
(delete-char (- n) killflag)
(save-excursion
(insert-char ?\s (- ocol (current-column)) nil))))
;;
((and (= n 1) (bound-and-true-p smartparens-mode))
(cond ((and (memq (char-before) (list ?\ ?\t))
(save-excursion
(and (/= (skip-chars-backward " \t" (line-beginning-position)) 0)
(bolp))))
(doom--backward-delete-whitespace-to-column))
((let* ((pair (ignore-errors (sp-get-thing)))
(op (plist-get pair :op))
(cl (plist-get pair :cl))
(beg (plist-get pair :beg))
(end (plist-get pair :end)))
(cond ((and end beg (= end (+ beg (length op) (length cl))))
(sp-backward-delete-char 1))
((doom-surrounded-p pair 'inline 'balanced)
(delete-char -1 killflag)
(delete-char 1)
(when (= (point) (+ (length cl) beg))
(sp-backward-delete-char 1)
(sp-insert-pair op)))
((and (bolp) (doom-surrounded-p pair nil 'balanced))
(delete-region beg end)
(sp-insert-pair op)
t)
((run-hook-with-args-until-success 'doom-delete-backward-functions))
((doom--backward-delete-whitespace-to-column)))))))
;; Otherwise, do simple deletion.
((delete-char (- n) killflag))))
;;;###autoload
(defun +default/search-from-cwd (&optional arg)
"Conduct a text search in files under the current folder.
If prefix ARG is set, prompt for a directory to search from."
(interactive "P")
(let ((default-directory
(if arg
(read-directory-name "Switch to project: " default-directory)
default-directory)))
(call-interactively
(cond ((featurep! :completion ivy) #'+ivy/project-search-from-cwd)
((featurep! :completion helm) #'+helm/project-search-from-cwd)
(#'projectile-grep)))))
;;;###autoload
(defun +default/search-project (&optional arg)
"Conduct a text search in the current project root.
If prefix ARG is set, prompt for a known project to search from."
(interactive "P")
(let ((default-directory
(if arg
(if-let* ((projects (projectile-relevant-known-projects)))
(completing-read "Switch to project: " projects
nil t nil nil (doom-project-root))
(user-error "There are no known projects"))
default-directory)))
(call-interactively
(cond ((featurep! :completion ivy) #'+ivy/project-search)
((featurep! :completion helm) #'+helm/project-search)
(#'rgrep)))))
;;;###autoload
(defun +default/search-project-for-symbol-at-point (&optional arg symbol)
"Conduct a text search in the current project for symbol at point.
If prefix ARG is set, prompt for a known project to search from."
(interactive
(list current-prefix-arg
(thing-at-point 'symbol t)))
(let ((default-directory
(if arg
(if-let* ((projects (projectile-relevant-known-projects)))
(completing-read "Switch to project: " projects
nil t nil nil (doom-project-root))
(user-error "There are no known projects"))
default-directory)))
(cond ((featurep! :completion ivy)
(+ivy/project-search nil (rxt-quote-pcre symbol)))
((featurep! :completion helm)
(+helm/project-search nil (rxt-quote-pcre symbol)))
((rgrep (regexp-quote symbol))))))

View file

@ -0,0 +1,265 @@
;;; config/default/config.el -*- lexical-binding: t; -*-
(defvar +default-minibuffer-maps
`(minibuffer-local-map
minibuffer-local-ns-map
minibuffer-local-completion-map
minibuffer-local-must-match-map
minibuffer-local-isearch-map
read-expression-map
,@(if (featurep! :completion ivy) '(ivy-minibuffer-map)))
"A list of all the keymaps used for the minibuffer.")
;;
;;; Reasonable defaults
(after! epa
(setq epa-file-encrypt-to
(or epa-file-encrypt-to
;; Collect all public key IDs with your username
(unless (string-empty-p user-full-name)
(cl-loop for key in (ignore-errors (epg-list-keys (epg-make-context) user-full-name))
collect (epg-sub-key-id (car (epg-key-sub-key-list key)))))
user-mail-address)
;; With GPG 2.1, this forces gpg-agent to use the Emacs minibuffer to
;; prompt for the key passphrase.
epa-pinentry-mode 'loopback))
;;
;;; Smartparens config
(when (featurep! +smartparens)
;; You can disable :unless predicates with (sp-pair "'" nil :unless nil)
;; And disable :post-handlers with (sp-pair "{" nil :post-handlers nil)
;; or specific :post-handlers with:
;; (sp-pair "{" nil :post-handlers '(:rem ("| " "SPC")))
(after! smartparens
;; Autopair quotes more conservatively; if I'm next to a word/before another
;; quote, I likely don't want to open a new pair.
(let ((unless-list '(sp-point-before-word-p
sp-point-after-word-p
sp-point-before-same-p)))
(sp-pair "'" nil :unless unless-list)
(sp-pair "\"" nil :unless unless-list))
;; Expand {|} => { | }
;; Expand {|} => {
;; |
;; }
(dolist (brace '("(" "{" "["))
(sp-pair brace nil
:post-handlers '(("||\n[i]" "RET") ("| " "SPC"))
;; I likely don't want a new pair if adjacent to a word or opening brace
:unless '(sp-point-before-word-p sp-point-before-same-p)))
;; Major-mode specific fixes
(sp-local-pair '(ruby-mode enh-ruby-mode) "{" "}"
:pre-handlers '(:rem sp-ruby-pre-handler)
:post-handlers '(:rem sp-ruby-post-handler))
;; Don't do square-bracket space-expansion where it doesn't make sense to
(sp-local-pair '(emacs-lisp-mode org-mode markdown-mode gfm-mode)
"[" nil :post-handlers '(:rem ("| " "SPC")))
;; Reasonable default pairs for HTML-style comments
(sp-local-pair (append sp--html-modes '(markdown-mode gfm-mode))
"<!--" "-->"
:unless '(sp-point-before-word-p sp-point-before-same-p)
:actions '(insert) :post-handlers '(("| " "SPC")))
;; Disable electric keys in C modes because it interferes with smartparens
;; and custom bindings. We'll do it ourselves (mostly).
(after! cc-mode
(c-toggle-electric-state -1)
(c-toggle-auto-newline -1)
(setq c-electric-flag nil)
(dolist (key '("#" "{" "}" "/" "*" ";" "," ":" "(" ")" "\177"))
(define-key c-mode-base-map key nil)))
;; Expand C-style doc comment blocks. Must be done manually because some of
;; these languages use specialized (and deferred) parsers, whose state we
;; can't access while smartparens is doing its thing.
(defun +default-expand-doc-comment-block (&rest _ignored)
(let ((indent (current-indentation)))
(newline-and-indent)
(save-excursion
(newline)
(insert (make-string indent 32) " */")
(delete-char 2))))
(sp-local-pair
'(js2-mode typescript-mode rjsx-mode rust-mode c-mode c++-mode objc-mode
csharp-mode java-mode php-mode css-mode scss-mode less-css-mode
stylus-mode)
"/*" "*/"
:actions '(insert)
:post-handlers '(("| " "SPC") ("|\n*/[i][d-2]" "RET") (+default-expand-doc-comment-block "*")))
;; Highjacks backspace to:
;; a) balance spaces inside brackets/parentheses ( | ) -> (|)
;; b) delete space-indented `tab-width' steps at a time
;; c) close empty multiline brace blocks in one step:
;; {
;; |
;; }
;; becomes {|}
;; d) refresh smartparens' :post-handlers, so SPC and RET expansions work
;; even after a backspace.
;; e) properly delete smartparen pairs when they are encountered, without
;; the need for strict mode.
;; f) do none of this when inside a string
(advice-add #'delete-backward-char :override #'+default*delete-backward-char)
;; Makes `newline-and-indent' continue comments (and more reliably)
(advice-add #'newline-and-indent :around #'+default*newline-indent-and-continue-comments)))
;;
;;; Keybinding fixes
;; This section is dedicated to "fixing" certain keys so that they behave
;; sensibly (and consistently with similar contexts).
;; Consistently use q to quit windows
(after! tabulated-list
(define-key tabulated-list-mode-map "q" #'quit-window))
;; OS specific fixes
(when IS-MAC
;; Fix MacOS shift+tab
(define-key input-decode-map [S-iso-lefttab] [backtab])
;; Fix conventional OS keys in Emacs
(map! "s-`" #'other-frame ; fix frame-switching
;; fix OS window/frame navigation/manipulation keys
"s-w" #'delete-window
"s-W" #'delete-frame
"s-n" #'+default/new-buffer
"s-N" #'make-frame
"s-q" (if (daemonp) #'delete-frame #'save-buffers-kill-terminal)
"C-s-f" #'toggle-frame-fullscreen
;; Restore somewhat common navigation
"s-l" #'goto-line
;; Restore OS undo, save, copy, & paste keys (without cua-mode, because
;; it imposes some other functionality and overhead we don't need)
"s-f" #'swiper
"s-z" #'undo
"s-Z" #'redo
"s-c" (if (featurep 'evil) #'evil-yank #'copy-region-as-kill)
"s-v" #'yank
"s-s" #'save-buffer
;; Buffer-local font scaling
"s-+" (λ! (text-scale-set 0))
"s-=" #'text-scale-increase
"s--" #'text-scale-decrease
;; Conventional text-editing keys & motions
"s-a" #'mark-whole-buffer
:g "s-/" (λ! (save-excursion (comment-line 1)))
:n "s-/" #'evil-commentary-line
:v "s-/" #'evil-commentary
:gni [s-return] #'+default/newline-below
:gni [S-s-return] #'+default/newline-above
:gi [s-backspace] #'doom/backward-kill-to-bol-and-indent
:gi [s-left] #'doom/backward-to-bol-or-indent
:gi [s-right] #'doom/forward-to-last-non-comment-or-eol
:gi [M-backspace] #'backward-kill-word
:gi [M-left] #'backward-word
:gi [M-right] #'forward-word))
;;
;;; Keybind schemes
;; Custom help keys -- these aren't under `+bindings' because they ought to be
;; universal.
(define-key! help-map
;; new keybinds
"'" #'describe-char
"A" #'doom/describe-autodefs
"B" #'doom/open-bug-report
"D" #'doom/open-manual
"E" #'doom/open-vanilla-sandbox
"M" #'doom/describe-active-minor-mode
"O" #'+lookup/online
"T" #'doom/toggle-profiler
"V" #'set-variable
"W" #'+default/man-or-woman
"C-k" #'describe-key-briefly
"C-l" #'describe-language-environment
"C-m" #'info-emacs-manual
"C-v" #'doom/version
;; Unbind `help-for-help'. Conflicts with which-key's help command for the
;; <leader> h prefix. It's already on ? and F1 anyway.
"C-h" nil
;; replacement keybinds
;; replaces `info-emacs-manual' b/c it's on C-m now
"r" nil
"rr" #'doom/reload
"rt" #'doom/reload-theme
"rp" #'doom/reload-packages
"rf" #'doom/reload-font
"re" #'doom/reload-env
;; replaces `apropos-command'
"a" #'apropos
;; replaces `describe-copying' b/c not useful
"C-c" #'describe-coding-system
;; replaces `apropos-documentation' b/c `apropos' covers this
"d" #'doom/describe-module
;; replaces `Info-got-emacs-command-node' b/c redundant w/ `Info-goto-node'
"F" #'describe-face
;; replaces `view-hello-file' b/c annoying
"h" #'doom/describe-symbol
;; replaces `describe-language-environment' b/c remapped to C-l
"L" #'global-command-log-mode
;; replaces `view-emacs-news' b/c it's on C-n too
"n" #'doom/open-news
;; replaces `finder-by-keyword'
;; "p" #'doom/describe-package
;; replaces `describe-package' b/c redundant w/ `doom/describe-package'
"P" #'find-library)
(after! which-key
(which-key-add-key-based-replacements doom-leader-key "<leader>")
(which-key-add-key-based-replacements doom-localleader-key "<localleader>")
(which-key-add-key-based-replacements "C-h r" "reload")
(when (featurep 'evil)
(which-key-add-key-based-replacements (concat doom-leader-key " r") "reload")
(which-key-add-key-based-replacements (concat doom-leader-alt-key " r") "reload")))
(when (featurep! +bindings)
;; Make M-x harder to miss
(define-key! 'override
"M-x" #'execute-extended-command
"A-x" #'execute-extended-command)
;; A Doom convention where C-s on popups and interactive searches will invoke
;; ivy/helm for their superior filtering.
(define-key! :keymaps +default-minibuffer-maps
"C-s" (if (featurep! :completion ivy)
#'counsel-minibuffer-history
#'helm-minibuffer-history))
;; Smarter C-a/C-e for both Emacs and Evil. C-a will jump to indentation.
;; Pressing it again will send you to the true bol. Same goes for C-e, except
;; it will ignore comments+trailing whitespace before jumping to eol.
(map! :gi "C-a" #'doom/backward-to-bol-or-indent
:gi "C-e" #'doom/forward-to-last-non-comment-or-eol
;; Standardize the behavior of M-RET/M-S-RET as a "add new item
;; below/above" key.
:gni [M-return] #'+default/newline-below
:gni [M-S-return] #'+default/newline-above
:gni [C-return] #'+default/newline-below
:gni [C-S-return] #'+default/newline-above))
;;
;;; Bootstrap configs
(if (featurep 'evil)
(load! "+evil")
(load! "+emacs"))

View file

@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; config/default/packages.el
(unless (featurep! :feature evil)
(package! winum)
(package! expand-region))

View file

@ -0,0 +1,14 @@
;;; config/literate/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defalias '+literate/reload #'doom/reload)
;;;###autoload
(defun +literate|recompile-maybe ()
"Recompile config.org if we're editing an org file in our DOOMDIR.
We assume any org file in `doom-private-dir' is connected to your literate
config, and should trigger a recompile if changed."
(when (and (eq major-mode 'org-mode)
(file-in-directory-p buffer-file-name doom-private-dir))
(+literate-tangle 'force)))

View file

@ -0,0 +1,49 @@
;;; config/literate/init.el -*- lexical-binding: t; -*-
(defvar +literate-config-file
(expand-file-name "config.org" doom-private-dir)
"The file path of your literate config file.")
(defvar +literate-config-cache-file
(expand-file-name "literate-last-compile" doom-cache-dir)
"The file path that `+literate-config-file' will be tangled to, then
byte-compiled from.")
;;
(defun +literate-tangle (&optional force-p)
"Tangles `+literate-config-file' if it has changed."
(let ((default-directory doom-private-dir)
(org +literate-config-file))
(when (or force-p (file-newer-than-file-p org +literate-config-cache-file))
(message "Compiling your literate config...")
(let* ((org (file-truename +literate-config-file))
(dest (concat (file-name-sans-extension org) ".el")))
(or (and (if (fboundp 'org-babel-tangle-file)
(org-babel-tangle-file org dest "emacs-lisp")
;; We tangle in a separate, blank process because loading it
;; here would load all of :lang org (very expensive!).
(zerop (call-process
"emacs" nil nil nil
"-q" "--batch" "-l" "ob-tangle" "--eval"
(format "(org-babel-tangle-file %S %S \"emacs-lisp\")"
org dest))))
;; Write the cache file to serve as our mtime cache
(with-temp-file +literate-config-cache-file
(message "Done!")))
(warn "There was a problem tangling your literate config!"))))))
;; Let 'er rip!
(when noninteractive
(require 'ob-tangle nil t))
(+literate-tangle (or doom-reloading-p noninteractive))
;; No need to load the resulting file. Doom will do this for us after all
;; modules have finished loading.
;; Recompile our literate config if we modify it
(after! org
(add-hook 'after-save-hook #'+literate|recompile-maybe))

View file

@ -0,0 +1,32 @@
#+TITLE: editor/fold
#+DATE: February 17, 2019
#+SINCE: v2.1
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[Features][Features]]
- [[Configuration][Configuration]]
- [[Troubleshooting][Troubleshooting]]
* Description
This module marries hideshow, vimish-fold and outline-minor-mode to bring you
marker, indent and syntax-based code folding for as many languages as possible.
** Module Flags
This module provides no flags.
** Plugins
+ evil-vimish-fold*
* Prerequisites
This module has no prerequisites.
* TODO Features
* TODO Configuration
* TODO Troubleshooting

View file

@ -0,0 +1,150 @@
;;; editor/fold/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
(require 'hideshow)
;; `hideshow' is a decent code folding implementation, but it won't let you
;; create custom folds. `vimish-fold' offers custom folds, but essentially
;; ignores any other type of folding (indent or custom markers, which
;; hs-minor-mode and `outline-mode' give you).
;;
;; So this is my effort to combine them.
(defun +fold--vimish-fold-p ()
(and (featurep 'vimish-fold)
(cl-some #'vimish-fold--vimish-overlay-p
(overlays-at (point)))))
(defun +fold--outline-fold-p ()
(and (or (bound-and-true-p outline-minor-mode)
(derived-mode-p 'outline-mode))
(outline-on-heading-p)))
(defun +fold--hideshow-fold-p ()
(hs-minor-mode +1)
(save-excursion
(ignore-errors
(or (hs-looking-at-block-start-p)
(hs-find-block-beginning)))))
;;
;; Code folding
(defmacro +fold-from-eol (&rest body)
"Perform action after moving to the end of the line."
`(save-excursion
(end-of-line)
,@body))
;;;###autoload
(defun +fold/toggle ()
"Toggle the fold at point.
Targets `vimmish-fold', `hideshow' and `outline' folds."
(interactive)
(save-excursion
(cond ((+fold--vimish-fold-p) (vimish-fold-toggle))
((+fold--outline-fold-p)
(cl-letf (((symbol-function #'outline-hide-subtree)
(symbol-function #'outline-hide-entry)))
(outline-toggle-children)))
((+fold--hideshow-fold-p) (+fold-from-eol (hs-toggle-hiding))))))
;;;###autoload
(defun +fold/open ()
"Open the folded region at point.
Targets `vimmish-fold', `hideshow' and `outline' folds."
(interactive)
(save-excursion
(cond ((+fold--vimish-fold-p) (vimish-fold-unfold))
((+fold--outline-fold-p)
(outline-show-children)
(outline-show-entry))
((+fold--hideshow-fold-p) (+fold-from-eol (hs-show-block))))))
;;;###autoload
(defun +fold/close ()
"Close the folded region at point.
Targets `vimmish-fold', `hideshow' and `outline' folds."
(interactive)
(save-excursion
(cond ((+fold--vimish-fold-p) (vimish-fold-refold))
((+fold--hideshow-fold-p) (+fold-from-eol (hs-hide-block)))
((+fold--outline-fold-p) (outline-hide-subtree)))))
;;;###autoload
(defun +fold/open-all (&optional level)
"Open folds at LEVEL (or all folds if LEVEL is nil)."
(interactive
(list (if current-prefix-arg (prefix-numeric-value current-prefix-arg))))
(when (featurep 'vimish-fold)
(vimish-fold-unfold-all))
(save-excursion
(if (integerp level)
(progn
(outline-hide-sublevels (max 1 (1- level)))
(hs-life-goes-on
(hs-hide-level-recursive (1- level) (point-min) (point-max))))
(hs-show-all)
(when (fboundp 'outline-show-all)
(outline-show-all)))))
;;;###autoload
(defun +fold/close-all (&optional level)
"Close folds at LEVEL (or all folds if LEVEL is nil)."
(interactive
(list (if current-prefix-arg (prefix-numeric-value current-prefix-arg))))
(save-excursion
(when (featurep 'vimish-fold)
(vimish-fold-refold-all))
(hs-life-goes-on
(if (integerp level)
(hs-hide-level-recursive (1- level) (point-min) (point-max))
(hs-hide-all)))))
(defun +fold--invisible-points (count)
(let (points)
(save-excursion
(catch 'abort
(if (< count 0) (beginning-of-line))
(while (re-search-forward hs-block-start-regexp nil t
(if (> count 0) 1 -1))
(unless (invisible-p (point))
(end-of-line)
(when (hs-already-hidden-p)
(push (point) points)
(when (>= (length points) count)
(throw 'abort nil))))
(forward-line (if (> count 0) 1 -1)))))
points))
;;;###autoload
(defun +fold/next (count)
"Jump to the next vimish fold, outline heading or folded region."
(interactive "p")
(cl-loop with orig-pt = (point)
for fn
in (list (lambda ()
(when hs-block-start-regexp
(car (+fold--invisible-points count))))
(lambda ()
(if (> count 0)
(evil-vimish-fold/next-fold count)
(evil-vimish-fold/previous-fold (- count)))
(if (/= (point) orig-pt) (point))))
if (save-excursion (funcall fn))
collect it into points
finally do
(if-let* ((pt (car (sort points (if (> count 0) #'< #'>)))))
(goto-char pt)
(message "No more folds %s point" (if (> count 0) "after" "before"))
(goto-char orig-pt))))
;;;###autoload
(defun +fold/previous (count)
"Jump to the previous vimish fold, outline heading or folded region."
(interactive "p")
(+fold/next (- count)))

View file

@ -0,0 +1,87 @@
;;; editor/fold/autoload/fold.el -*- lexical-binding: t; -*-
(defface +fold-hideshow-folded-face
`((t (:inherit font-lock-comment-face :weight light)))
"Face to hightlight `hideshow' overlays."
:group 'doom-themes)
;;;###autoload
(defun +fold-hideshow*ensure-mode (&rest _)
"Ensure hs-minor-mode is enabled."
(unless (bound-and-true-p hs-minor-mode)
(hs-minor-mode +1)))
;;;###autoload
(defun +fold-hideshow-haml-forward-sexp (arg)
(haml-forward-sexp arg)
(move-beginning-of-line 1))
;;;###autoload
(defun +fold-hideshow-forward-block-by-indent (_arg)
(let ((start (current-indentation)))
(forward-line)
(unless (= start (current-indentation))
(let ((range (+fold-hideshow-indent-range)))
(goto-char (cadr range))
(end-of-line)))))
;;;###autoload
(defun +fold-hideshow-set-up-overlay (ov)
(when (eq 'code (overlay-get ov 'hs))
(when (featurep 'vimish-fold)
(overlay-put
ov 'before-string
(propertize "" 'display
(list vimish-fold-indication-mode
'empty-line
'vimish-fold-fringe))))
(overlay-put
ov 'display (propertize " [...] " 'face '+fold-hideshow-folded-face))))
;;
;; Indentation detection
(defun +fold--hideshow-empty-line-p ()
(string= "" (string-trim (thing-at-point 'line))))
(defun +fold--hideshow-geq-or-empty-p ()
(or (+fold--hideshow-empty-line-p) (>= (current-indentation) base)))
(defun +fold--hideshow-g-or-empty-p ()
(or (+fold--hideshow-empty-line-p) (> (current-indentation) base)))
(defun +fold--hideshow-seek (start direction before skip predicate)
"Seeks forward (if direction is 1) or backward (if direction is -1) from start, until predicate
fails. If before is nil, it will return the first line where predicate fails, otherwise it returns
the last line where predicate holds."
(save-excursion
(goto-char start)
(goto-char (point-at-bol))
(let ((bnd (if (> 0 direction)
(point-min)
(point-max)))
(pt (point)))
(when skip (forward-line direction))
(cl-loop while (and (/= (point) bnd) (funcall predicate))
do (progn
(when before (setq pt (point-at-bol)))
(forward-line direction)
(unless before (setq pt (point-at-bol)))))
pt)))
(defun +fold-hideshow-indent-range (&optional point)
"Return the point at the begin and end of the text block with the same (or
greater) indentation. If `point' is supplied and non-nil it will return the
begin and end of the block surrounding point."
(save-excursion
(when point
(goto-char point))
(let ((base (current-indentation))
(begin (point))
(end (point)))
(setq begin (+fold--hideshow-seek begin -1 t nil #'+fold--hideshow-geq-or-empty-p)
begin (+fold--hideshow-seek begin 1 nil nil #'+fold--hideshow-g-or-empty-p)
end (+fold--hideshow-seek end 1 t nil #'+fold--hideshow-geq-or-empty-p)
end (+fold--hideshow-seek end -1 nil nil #'+fold--hideshow-empty-line-p))
(list begin end base))))

View file

@ -0,0 +1,73 @@
;;; editor/fold/config.el -*- lexical-binding: t; -*-
(when (featurep! :feature evil)
;; Add vimish-fold, outline-mode & hideshow support to folding commands
(define-key! 'global
[remap evil-toggle-fold] #'+fold/toggle
[remap evil-close-fold] #'+fold/close
[remap evil-open-fold] #'+fold/open
[remap evil-open-fold-rec] #'+fold/open
[remap evil-close-folds] #'+fold/close-all
[remap evil-open-folds] #'+fold/open-all)
(evil-define-key* 'motion 'global
"zj" #'+fold/next
"zk" #'+fold/previous))
;;
;; Packages
(def-package! hideshow ; built-in
:defer t
:init
;; Ensure `hs-minor-mode' is active when triggering these commands
(advice-add #'hs-toggle-hiding :before #'+fold-hideshow*ensure-mode)
(advice-add #'hs-hide-block :before #'+fold-hideshow*ensure-mode)
(advice-add #'hs-hide-level :before #'+fold-hideshow*ensure-mode)
(advice-add #'hs-show-all :before #'+fold-hideshow*ensure-mode)
(advice-add #'hs-hide-all :before #'+fold-hideshow*ensure-mode)
:config
(setq hs-hide-comments-when-hiding-all nil
;; Nicer code-folding overlays (with fringe indicators)
hs-set-up-overlay #'+fold-hideshow-set-up-overlay)
;; extra folding support for more languages
(unless (assq 't hs-special-modes-alist)
(setq hs-special-modes-alist
(append
'((vimrc-mode "{{{" "}}}" "\"")
(yaml-mode "\\s-*\\_<\\(?:[^:]+\\)\\_>"
""
"#"
+fold-hideshow-forward-block-by-indent nil)
(haml-mode "[#.%]" "\n" "/" +fold-hideshow-haml-forward-sexp nil)
(ruby-mode "class\\|d\\(?:ef\\|o\\)\\|module\\|[[{]"
"end\\|[]}]"
"#\\|=begin"
ruby-forward-sexp)
(enh-ruby-mode "class\\|d\\(?:ef\\|o\\)\\|module\\|[[{]"
"end\\|[]}]"
"#\\|=begin"
enh-ruby-forward-sexp nil)
(matlab-mode "if\\|switch\\|case\\|otherwise\\|while\\|for\\|try\\|catch"
"end"
nil (lambda (_arg) (matlab-forward-sexp))))
hs-special-modes-alist
'((t))))))
(def-package! evil-vimish-fold
:when (featurep! :feature evil)
:commands (evil-vimish-fold/next-fold evil-vimish-fold/previous-fold
evil-vimish-fold/delete evil-vimish-fold/delete-all
evil-vimish-fold/create evil-vimish-fold/create-line)
:init
(setq vimish-fold-dir (concat doom-cache-dir "vimish-fold/")
vimish-fold-indication-mode 'right-fringe)
(evil-define-key* 'motion 'global
"zf" #'evil-vimish-fold/create
"zF" #'evil-vimish-fold/create-line
"zd" #'vimish-fold-delete
"zE" #'vimish-fold-delete-all)
:config
(vimish-fold-global-mode +1))

View file

@ -1,5 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; ui/evil-goggles/packages.el
;;; editor/fold/packages.el
(when (featurep! :feature evil)
(package! evil-goggles))
(package! evil-vimish-fold))

View file

@ -0,0 +1,8 @@
;;; editor/format/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
;;;###autoload (autoload '+format:region "editor/format/autoload/evil" nil t)
(evil-define-operator +format:region (beg end)
"Evil ex interface to `+format/region'."
(interactive "<r>")
(+format/region beg end))

View file

@ -0,0 +1,229 @@
;;; editor/format/autoload.el -*- lexical-binding: t; -*-
(defvar +format-region-p nil
"Is non-nil if currently reformatting a selected region, rather than the whole
buffer.")
;;;###autoload
(autoload 'format-all-probe "format-all")
(defun +format--delete-whole-line (&optional arg)
"Delete the current line without putting it in the `kill-ring'.
Derived from function `kill-whole-line'. ARG is defined as for that
function.
Stolen shamelessly from go-mode"
(setq arg (or arg 1))
(if (and (> arg 0)
(eobp)
(save-excursion (forward-visible-line 0) (eobp)))
(signal 'end-of-buffer nil))
(if (and (< arg 0)
(bobp)
(save-excursion (end-of-visible-line) (bobp)))
(signal 'beginning-of-buffer nil))
(cond ((zerop arg)
(delete-region (progn (forward-visible-line 0) (point))
(progn (end-of-visible-line) (point))))
((< arg 0)
(delete-region (progn (end-of-visible-line) (point))
(progn (forward-visible-line (1+ arg))
(unless (bobp)
(backward-char))
(point))))
((delete-region (progn (forward-visible-line 0) (point))
(progn (forward-visible-line arg) (point))))))
;;;###autoload
(defun +format--apply-rcs-patch (patch-buffer)
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer.
Stolen shamelessly from go-mode"
(let ((target-buffer (current-buffer))
;; Relative offset between buffer line numbers and line numbers
;; in patch.
;;
;; Line numbers in the patch are based on the source file, so
;; we have to keep an offset when making changes to the
;; buffer.
;;
;; Appending lines decrements the offset (possibly making it
;; negative), deleting lines increments it. This order
;; simplifies the forward-line invocations.
(line-offset 0)
(column (current-column)))
(save-excursion
(with-current-buffer patch-buffer
(goto-char (point-min))
(while (not (eobp))
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
(error "Invalid rcs patch or internal error in +format--apply-rcs-patch"))
(forward-line)
(let ((action (match-string 1))
(from (string-to-number (match-string 2)))
(len (string-to-number (match-string 3))))
(cond
((equal action "a")
(let ((start (point)))
(forward-line len)
(let ((text (buffer-substring start (point))))
(with-current-buffer target-buffer
(cl-decf line-offset len)
(goto-char (point-min))
(forward-line (- from len line-offset))
(insert text)))))
((equal action "d")
(with-current-buffer target-buffer
(goto-char (point-min))
(forward-line (1- (- from line-offset)))
(cl-incf line-offset len)
(+format--delete-whole-line len)))
((error "Invalid rcs patch or internal error in +format--apply-rcs-patch")))))))
(move-to-column column)))
(defun +format--current-indentation ()
(save-excursion
(goto-char (point-min))
(skip-chars-forward " \t\n")
(current-indentation)))
;;
;; Public library
(defun +format-completing-read ()
(require 'format-all)
(let* ((fmtlist (mapcar #'symbol-name (hash-table-keys format-all-format-table)))
(fmt (completing-read "Formatter: " fmtlist)))
(if fmt (cons (intern fmt) t))))
;;;###autoload
(defun +format*probe (orig-fn)
"Use `+format-with' instead, if it is set."
(if +format-with
(list +format-with t)
(funcall orig-fn)))
;;;###autoload
(defun +format-buffer (formatter mode-result &optional preserve-indent-p)
"Format the source code in the current buffer.
Returns any of the following values:
'unknown No formatter is defined for this major-mode
'error Couldn't format buffer due to formatter errors
'noop Buffer is already formatted
Otherwise, returns a list: (list OUTPUT ERRORS FIRST-DIFF), where OUTPUT is the
formatted text, ERRORS are any errors in string format, and FIRST-DIFF is the
position of the first change in the buffer.
See `+format/buffer' for the interactive version of this function, and
`+format|buffer' to use as a `before-save-hook' hook."
(if (not formatter)
'no-formatter
(let ((f-function (gethash formatter format-all-format-table))
(executable (format-all-formatter-executable formatter))
(indent 0))
(pcase-let
((`(,output ,errput ,first-diff)
;; Since `format-all' functions (and various formatting functions,
;; like `gofmt') widen the buffer, in order to only format a region of
;; text, we must make a copy of the buffer to apply formatting to.
(let ((output (buffer-substring-no-properties (point-min) (point-max))))
(with-temp-buffer
(insert output)
;; Since we're piping a region of text to the formatter, remove
;; any leading indentation to make it look like a file.
(when preserve-indent-p
(setq indent (+format--current-indentation))
(when (> indent 0)
(indent-rigidly (point-min) (point-max) (- indent))))
(funcall f-function executable mode-result)))))
(unwind-protect
(cond ((null output) 'error)
((eq output t) 'noop)
((let ((tmpfile (make-temp-file "doom-format"))
(patchbuf (get-buffer-create " *doom format patch*"))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8))
(unwind-protect
(progn
(with-current-buffer patchbuf
(erase-buffer))
(with-temp-file tmpfile
(erase-buffer)
(insert output)
(when (> indent 0)
;; restore indentation without affecting new
;; indentation
(indent-rigidly (point-min) (point-max)
(max 0 (- indent (+format--current-indentation))))))
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
'noop
(+format--apply-rcs-patch patchbuf)
(list output errput first-diff)))
(kill-buffer patchbuf)
(delete-file tmpfile)))))
(unless (= 0 (length errput))
(message "Formatter error output:\n%s" errput)))))))
;;
;; Commands
;;;###autoload
(defun +format/buffer (&optional arg)
"Format the source code in the current buffer."
(interactive "P")
(let ((+format-with (or (if arg (+format-completing-read)) +format-with)))
(pcase-let ((`(,formatter ,mode-result) (format-all-probe)))
(pcase
(+format-buffer
formatter mode-result
(or +format-preserve-indentation +format-region-p))
(`no-formatter
(when (called-interactively-p 'any)
(message "No formatter specified for %s" major-mode))
nil)
(`error (message "Failed to format buffer due to errors") nil)
(`noop (message "Buffer was already formatted") nil)
(_ (message "Formatted (%s)" formatter) t)))))
;;;###autoload
(defun +format/region (beg end &optional arg)
"Runs the active formatter on the lines within BEG and END.
WARNING: this may not work everywhere. It will throw errors if the region
contains a syntax error in isolation. It is mostly useful for formatting
snippets or single lines."
(interactive "rP")
(save-restriction
(narrow-to-region beg end)
(let ((+format-region-p t))
(+format/buffer arg))))
;;;###autoload
(defun +format/region-or-buffer ()
"Runs the active formatter on the selected region (or whole buffer, if nothing
is selected)."
(interactive)
(call-interactively
(if (use-region-p)
#'+format/region
#'+format/buffer)))
;;
;; Hooks
;;;###autoload
(defun +format|enable-on-save ()
"Enables formatting on save."
(add-hook 'before-save-hook #'+format|buffer nil t))
;;;###autoload
(defalias '+format|buffer #'+format/buffer
"Format the source code in the current buffer with minimal feedback.
Meant for `before-save-hook'.")

View file

@ -0,0 +1,203 @@
;;; editor/format/autoload/settings.el -*- lexical-binding: t; -*-
;; This must be redefined here because `format-all' only makes it available at
;; compile time.
(defconst +format-system-type
(cl-case system-type
(windows-nt 'windows)
(cygwin 'windows)
(darwin 'macos)
(gnu/linux 'linux)
(berkeley-unix
(save-match-data
(let ((case-fold-search t))
(cond ((string-match "freebsd" system-configuration) 'freebsd)
((string-match "openbsd" system-configuration) 'openbsd)
((string-match "netbsd" system-configuration) 'netbsd))))))
"Current operating system according to the format-all package.")
(defun +format--resolve-system (choices)
"Get first choice matching `format-all-system-type' from CHOICES."
(cl-loop for choice in choices
if (atom choice) return choice
else if (eql +format-system-type (car choice))
return (cadr choice)))
(defun +format--make-command (formatter &rest _)
`(format-all-buffer-thunk
(lambda (input)
(with-silent-modifications
(setq buffer-file-name ,(buffer-file-name (buffer-base-buffer))
default-directory ,default-directory)
(delay-mode-hooks (funcall ',major-mode))
(insert input)
(condition-case e
(progn
(doom-log "formatter (commandp) %s" #',formatter)
(call-interactively #',formatter)
(list nil ""))
(error (list t (error-message-string e))))))))
(defun +format--make-function (formatter &rest _)
`(progn
(doom-log "formatter (functionp) %s" #',formatter)
(format-all-buffer-thunk #',formatter)))
(defun +format--make-shell-command (command ok-statuses error-regexp)
(+format--make-shell-command-list (split-string command " " t)
ok-statuses error-regexp))
(defun +format--make-shell-command-list (command-list ok-statuses error-regexp)
`(let (args)
(dolist (arg ',command-list)
(cond ((stringp arg)
(push arg args))
((listp arg)
(catch 'skip
(let (subargs this)
(while (setq this (pop arg))
(cond ((not (stringp (car arg)))
(let ((val (eval (pop arg) t)))
(unless val (throw 'skip nil))
(push (format this val) subargs)))
((stringp this)
(push this subargs))))
(setq args (append subargs args)))))))
(doom-log "formatter (arglist) %s" args)
(if ,(and (or ok-statuses error-regexp) t)
(apply #'format-all-buffer-hard
',ok-statuses ,error-regexp
(reverse args))
(apply #'format-all-buffer-easy (reverse args)))))
(cl-defun +format--set (name &key function modes unset)
(declare (indent defun))
(when (and unset (not (gethash name format-all-format-table)))
(error "'%s' formatter does not exist to be unset" name))
(puthash name function format-all-format-table)
(dolist (mode (doom-enlist modes))
(cl-destructuring-bind (m &optional probe)
(doom-enlist mode)
(if unset
(puthash m (assq-delete-all name (gethash key format-all-mode-table))
format-all-mode-table)
(format-all-pushhash
m (cons name (if probe `(lambda () ,probe)))
format-all-mode-table)))))
;;;###autodef
(cl-defun set-formatter!
(name formatter &key modes filter ok-statuses error-regexp)
"Define (or modify) a formatter named NAME.
Supported keywords: :modes :install :filter :ok-statuses :error-regexp
NAME is a symbol that identifies this formatter.
FORMATTER can be a symbol referring to another formatter, a function, string or
nested list.
If a function, it should be a formatter function that
`format-all-buffer-thunk' will accept.
If a string, it is assumed to be a shell command that the buffer's text will
be piped to (through stdin).
If a list, it should represent a shell command as a list of arguments. Each
element is either a string or list (STRING ARG) where STRING is a format
string and ARG is both a predicate and argument for STRING. If ARG is nil,
STRING will be omitted from the vector.
MODES is a major mode, a list thereof, or a list of two-element sublists with
the structure: (MAJOR-MODE FORM). FORM is evaluated when the buffer is formatted
and its return value serves two purposes:
1. It is a predicate for this formatter. Assuming the MAJOR-MODE matches the
current mode, if FORM evaluates to nil, the formatter is skipped.
2. It's return value is made available to FORMATTER if it is a function or
list of shell arguments via the `mode-result' variable.
FILTER is a function that takes three arguments: the formatted output, any error
output and the position of the first change. This function must return these
three after making whatever changes you like to them. This might be useful if
the output contains ANSI color codes that need to be stripped out (as is the
case with elm-format).
OK-STATUSES and ERROR-REGEXP are ignored if FORMATTER is not a shell command.
OK-STATUSES is a list of integer exit codes that should be treated as success
codes. However, if ERROR-REGEXP is given, and the program's stderr contains that
regexp, then the formatting is considered failed even if the exit status is in
OK-STATUSES.
Basic examples:
(set-formatter! 'asmfmt \"asmfmt\" :modes '(asm-mode nasm-mode))
(set-formatter! 'black \"black -q -\")
(set-formatter! 'html-tidy \"tidy -q -indent\" :modes '(html-mode web-mode))
Advanced examples:
(set-formatter!
'clang-format
'(\"clang-format\"
(\"-assume-filename=%S\" (or buffer-file-name mode-result \"\")))
:modes
'((c-mode \".c\")
(c++-mode \".cpp\")
(java-mode \".java\")
(objc-mode \".m\")
(protobuf-mode \".proto\")))
(set-formatter! 'html-tidy
'(\"tidy\" \"-q\" \"-indent\"
(\"-xml\" (memq major-mode '(nxml-mode xml-mode))))
:modes
'(html-mode
(web-mode (and (equal \"none\" web-mode-engine)
(car (member web-mode-content-type '(\"xml\" \"html\"))))))
:ok-statuses '(0 1)
:executable \"tidy\")
(set-formatter! 'html-tidy ; overwrite predefined html-tidy formatter
'(\"tidy\" \"-q\" \"-indent\"
\"--tidy-mark\" \"no\"
\"--drop-empty-elements\" \"no\"
\"--show-body-only\" \"auto\"
(\"--indent-spaces\" \"%d\" tab-width)
(\"--indent-with-tabs\" \"%s\" (if indent-tabs-mode \"yes\" \"no\"))
(\"-xml\" (memq major-mode '(nxml-mode xml-mode))))
:ok-statuses '(0 1)))
(set-formatter! 'elm-format
\"elm-format --yes --stdin\"
:filter
(lambda (output errput first-diff)
(list output
(format-all-remove-ansi-color errput)
first-diff)))"
(declare (indent defun))
(cl-check-type name symbol)
(after! format-all
(if (null formatter)
(+format--set name
:unset t
:modes modes)
(let ((fn (funcall (cond ((stringp formatter)
#'+format--make-shell-command)
((listp formatter)
#'+format--make-shell-command-list)
((and (commandp formatter)
(not (stringp formatter)))
#'+format--make-command)
((functionp formatter)
#'+format--make-function))
formatter
ok-statuses
error-regexp)))
(cl-check-type filter (or function null))
(+format--set name
:function
`(lambda (executable mode-result)
,(if filter `(apply #',filter ,fn) fn))
:modes modes)
name))))

View file

@ -0,0 +1,58 @@
;;; editor/format/config.el -*- lexical-binding: t; -*-
(defvar +format-on-save-enabled-modes
'(not emacs-lisp-mode ; elisp's mechanisms are good enough
sql-mode) ; sqlformat is currently broken
"A list of major modes in which to reformat the buffer upon saving.
If this list begins with `not', then it negates the list.
If it is `t', it is enabled in all modes.
If nil, it is disabled in all modes, the same as if the +onsave flag wasn't
used at all.
Irrelevant if you do not have the +onsave flag enabled for this module.")
(defvar +format-preserve-indentation t
"If non-nil, the leading indentation is preserved when formatting the whole
buffer. This is particularly useful for partials.
Indentation is always preserved when formatting regions. ")
(defvar-local +format-with nil
"Set this to explicitly use a certain formatter for the current buffer.")
;;
;; Bootstrap
(defun +format|enable-on-save-maybe ()
"Enable formatting on save in certain major modes.
This is controlled by `+format-on-save-enabled-modes'."
(unless (or (eq major-mode 'fundamental-mode)
(cond ((booleanp +format-on-save-enabled-modes)
(null +format-on-save-enabled-modes))
((eq (car +format-on-save-enabled-modes) 'not)
(memq major-mode (cdr +format-on-save-enabled-modes)))
((not (memq major-mode +format-on-save-enabled-modes))))
(not (require 'format-all nil t)))
(add-hook 'before-save-hook #'+format|buffer nil t)))
(when (featurep! +onsave)
(add-hook 'after-change-major-mode-hook #'+format|enable-on-save-maybe))
;;
;; Hacks
;; Allow a specific formatter to be used by setting `+format-with', either
;; buffer-locally or let-bound.
(advice-add #'format-all-probe :around #'+format*probe)
;; Doom uses a modded `format-all-buffer', which
;; 1. Doesn't move the cursorafter reformatting,
;; 2. Can reformat regions, rather than the entire buffer (while preserving
;; leading indentation),
;; 3. Applies changes via RCS patch, line by line, as not to protect buffer
;; markers and avoid any jarring cursor+window scrolling.
(advice-add #'format-all-buffer :override #'+format/buffer)

View file

@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; editor/format/packages.el
(package! format-all)

View file

@ -0,0 +1,103 @@
;; -*- no-byte-compile: t; -*-
;;; editor/format/test/test-format.el
(load! "../autoload/settings")
(load! "../autoload/format")
(require! :editor format)
(require 'format-all)
;;
(describe "editor/format"
:var (format-all-format-table
format-all-mode-table)
(before-each
(setq format-all-format-table (make-hash-table)
format-all-mode-table (make-hash-table)))
(describe "set-formatter!"
(before-each
(set-formatter! 'test (lambda () (interactive))))
(it "defines a formatter"
(set-formatter! 'new (lambda () (interactive)))
(expect (gethash 'new format-all-mode-table) :to-equal nil)
(expect (functionp (gethash 'new format-all-format-table))))
(it "defines a formatter with modes"
(set-formatter! 'new (lambda () (interactive))
:modes '(a-mode (b-mode "x")))
(expect (gethash 'a-mode format-all-mode-table)
:to-equal '((new)))
(expect (gethash 'b-mode format-all-mode-table)
:to-equal '((new . (lambda () "x")))))
(it "replaces a pre-existing formatter"
(let ((old-fn (gethash 'test format-all-format-table)))
(set-formatter! 'test "echo")
(expect (gethash 'test format-all-format-table) :not :to-equal old-fn)))
(it "unsets a pre-existing formatter"
(set-formatter! 'test nil)
(expect (gethash 'test format-all-format-table) :to-be nil))
(it "errors when unsetting non-existent formatter"
(expect (set-formatter! 'doesnt-exist nil) :to-throw)))
;; TODO
(xdescribe "hooks"
(describe "format|enable-on-save-maybe")
(describe "format|enable-on-save"))
;; TODO
(xdescribe "formatting"
(before-each
(set-formatter! 'command
(lambda ()
(interactive)
(let ((first-line (car (split-string (buffer-string) "\n"))))
(erase-buffer)
(insert first-line)))
:modes '(text-mode))
(set-formatter! 'faulty-command
(lambda ()
(interactive)
(error "This is a test"))
:modes '(text-mode))
(set-formatter! 'function
(lambda (input)
(insert (car (split-string input "\n")))
(list nil nil))
:modes '(text-mode))
(set-formatter! 'shellcmd "head -n 1"
:modes '(text-mode))
(set-formatter! 'cmdlist '("head" "-n" "1")
:modes '(text-mode)))
(describe "with an interactive command"
(it "formats a buffer" )
(it "formats a region" )
(it "no-ops if no change" )
(it "doesn't modify the buffer in case of errors" )
(it "preserves indentation" ))
(describe "with a function"
(it "formats a buffer" )
(it "formats a region" )
(it "no-ops if no change" )
(it "doesn't modify the buffer in case of errors" )
(it "preserves indentation" ))
(describe "with a shell command")
(describe "with a shell command list"
(it "formats a buffer" )
(it "formats a region" )
(it "no-ops if no change" )
(it "doesn't modify the buffer in case of errors" )
(it "preserves indentation" )
(it "interpolates non-strings into format strings" )
(it "conditionally appends sublisted options" ))))

View file

@ -0,0 +1,36 @@
#+TITLE: :editor lispy
This modules adds [[https://github.com/noctuid/lispyville][lispy]] key functionality in Lisp languages.
This includes:
- Common Lisp
- Emacs Lisp
- Scheme
- Racket
- [[http://docs.hylang.org/en/stable/][Hy]]
- [[http://lfe.io/][LFE]]
- Clojure
If evil is enabled, lispyville would also be activated for every mode where
lispy is active
The default key themes that are set are as follows:
#+BEGIN_SRC emacs-lisp
(lispyville-set-key-theme
'((operators normal)
c-w
(prettify insert)
(atom-movement normal visual)
slurp/barf-lispy
(wrap normal insert)
additional
additional-insert
(additional-wrap normal insert)
(escape insert)))
#+END_SRC
See noctuid's [[https://github.com/noctuid/lispyville/blob/master/README.org][README]] for more info on specific keybindings (starting [[https://github.com/noctuid/lispyville#operators-key-theme][here]]) of
each key theme. Think of ~lispyville-set-key-theme~ as adding
~parinfer-extensions~ via ~(setq parinfer-extensions '(blah blah blah))~.

View file

@ -0,0 +1,29 @@
;;; editor/lispy/config.el -*- lexical-binding: t; -*-
(def-package! lispy
:hook ((common-lisp-mode . lispy-mode)
(emacs-lisp-mode . lispy-mode)
(scheme-mode . lispy-mode)
(racket-mode . lispy-mode)
(hy-mode . lispy-mode)
(lfe-mode . lispy-mode)
(clojure-mode . lispy-mode))
:config
(setq lispy-close-quotes-at-end-p t)
(add-hook 'lispy-mode-hook #'turn-off-smartparens-mode))
(def-package! lispyville
:when (featurep! :feature evil)
:hook (lispy-mode . lispyville-mode)
:config
(lispyville-set-key-theme
'((operators normal)
c-w
(prettify insert)
(atom-movement normal visual)
slurp/barf-lispy
(wrap normal insert)
additional
additional-insert
(additional-wrap normal insert)
(escape insert))))

View file

@ -0,0 +1,7 @@
;; -*- no-byte-compile: t; -*-
;;; editor/lispyville/packages.el
(package! lispy)
(when (featurep! :feature evil)
(package! lispyville))

View file

@ -1,7 +1,8 @@
;;; feature/evil/autoload/evil-mc.el -*- lexical-binding: t; -*-
;;; editor/multiple-cursors/autoload/evil-mc.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
;;;###autoload
(defun +evil/mc-toggle-cursors ()
(defun +multiple-cursors/evil-mc-toggle-cursors ()
"Toggle frozen state of evil-mc cursors."
(interactive)
(setq evil-mc-frozen (not (and (evil-mc-has-cursors-p)
@ -10,8 +11,8 @@
(message "evil-mc paused")
(message "evil-mc resumed")))
;;;###autoload (autoload '+evil/mc-make-cursor-here "feature/evil/autoload/evil-mc" nil t)
(evil-define-command +evil/mc-make-cursor-here ()
;;;###autoload (autoload '+multiple-cursors/evil-mc-make-cursor-here "editor/multiple-cursors/autoload/evil-mc" nil t)
(evil-define-command +multiple-cursors/evil-mc-make-cursor-here ()
"Create a cursor at point. If in visual block or line mode, then create
cursors on each line of the selection, on the column of the cursor. Otherwise
pauses cursors."
@ -43,23 +44,31 @@ pauses cursors."
;; I assume I don't want the cursors to move yet
(evil-mc-make-cursor-here))))
;;;###autoload (autoload '+evil:mc "feature/evil/autoload/evil-mc" nil t)
(evil-define-command +evil:mc (beg end type pattern &optional bang)
;;;###autoload (autoload '+multiple-cursors:evil-mc "editor/multiple-cursors/autoload/evil-mc" nil t)
(evil-define-command +multiple-cursors:evil-mc (beg end type pattern &optional bang)
"Create mc cursors at each match of PATTERN within BEG and END, and leave the
cursor at the final match. If BANG, then treat PATTERN as literal."
:move-point nil
:evil-mc t
(interactive "<R><//g><!>")
(unless (and (stringp pattern)
(not (string-empty-p pattern)))
(user-error "A regexp pattern is required"))
(require 'evil-mc)
(setq evil-mc-pattern (cons (evil-mc-make-pattern (if bang (regexp-quote pattern) pattern) nil)
(list beg end type)))
(setq evil-mc-pattern
(cons (evil-ex-make-search-pattern
(if bang (regexp-quote pattern) pattern))
(list beg end type)))
(save-excursion
(evil-with-restriction beg end
(evil-mc-make-cursors-for-all)
(evil-mc-print-cursors-info "Created")))
(evil-mc-make-cursors-for-all)))
(evil-exit-visual-state)
(evil-mc-goto-cursor
(if (= (evil-visual-direction) 1)
(evil-mc-find-last-cursor)
(evil-mc-find-first-cursor))
nil))
nil)
(evil-mc-undo-cursor-at-pos (point))
(if (evil-mc-has-cursors-p)
(evil-mc-print-cursors-info "Created")
(evil-mc-message "No cursors were created")))

View file

@ -0,0 +1,115 @@
;;; editor/multiple-cursors/config.el -*- lexical-binding: t; -*-
(def-package! evil-mc
:when (featurep! :feature evil)
:commands (evil-mc-make-cursor-here evil-mc-make-all-cursors
evil-mc-undo-all-cursors evil-mc-pause-cursors
evil-mc-resume-cursors evil-mc-make-and-goto-first-cursor
evil-mc-make-and-goto-last-cursor
evil-mc-make-cursor-move-next-line
evil-mc-make-cursor-move-prev-line evil-mc-make-cursor-at-pos
evil-mc-has-cursors-p evil-mc-make-and-goto-next-cursor
evil-mc-skip-and-goto-next-cursor evil-mc-make-and-goto-prev-cursor
evil-mc-skip-and-goto-prev-cursor evil-mc-make-and-goto-next-match
evil-mc-skip-and-goto-next-match evil-mc-skip-and-goto-next-match
evil-mc-make-and-goto-prev-match evil-mc-skip-and-goto-prev-match)
:init
(defvar evil-mc-key-map (make-sparse-keymap))
:config
(global-evil-mc-mode +1)
(setq evil-mc-enable-bar-cursor (not (or IS-MAC IS-WINDOWS)))
(after! smartparens
;; Make evil-mc cooperate with smartparens better
(let ((vars (cdr (assq :default evil-mc-cursor-variables))))
(unless (memq (car sp--mc/cursor-specific-vars) vars)
(setcdr (assq :default evil-mc-cursor-variables)
(append vars sp--mc/cursor-specific-vars)))))
;; Add custom commands to whitelisted commands
(dolist (fn '(doom/backward-to-bol-or-indent doom/forward-to-last-non-comment-or-eol
doom/backward-kill-to-bol-and-indent delete-char))
(add-to-list 'evil-mc-custom-known-commands `(,fn (:default . evil-mc-execute-default-call-with-count))))
;; Have evil-mc work with explicit `evil-escape' (typically bound to C-g)
(add-to-list 'evil-mc-custom-known-commands '(evil-escape (:default . evil-mc-execute-default-evil-normal-state)))
;; Activate evil-mc cursors upon switching to insert mode
(add-hook 'evil-insert-state-entry-hook #'evil-mc-resume-cursors)
;; disable evil-escape in evil-mc; causes unwanted text on invocation
(add-to-list 'evil-mc-incompatible-minor-modes 'evil-escape-mode nil #'eq)
(defun +multiple-cursors|escape-multiple-cursors ()
"Clear evil-mc cursors and restore state."
(when (evil-mc-has-cursors-p)
(evil-mc-undo-all-cursors)
(evil-mc-resume-cursors)
t))
(add-hook 'doom-escape-hook #'+multiple-cursors|escape-multiple-cursors)
;; Forward declare these so that ex completion and evil-mc support is
;; recognized before the autoloaded functions are loaded.
(evil-add-command-properties '+evil:align :evil-mc t)
(evil-set-command-properties '+multiple-cursors:evil-mc
:move-point nil
:ex-arg 'global-match
:ex-bang t
:evil-mc t))
(after! multiple-cursors-core
(setq mc/list-file (concat doom-etc-dir "mc-lists.el"))
;; TODO multiple-cursors config for Emacs users?
;; mc doesn't play well with evil, this attempts to assuage some of its
;; problems so that any plugins that depend on multiple-cursors (which I have
;; no control over) can still use it in relative safety.
(when (featurep! :feature evil)
(evil-define-key* '(normal emacs) mc/keymap [escape] #'mc/keyboard-quit)
(defvar +mc--compat-evil-prev-state nil)
(defvar +mc--compat-mark-was-active nil)
(defun +multiple-cursors|compat-switch-to-emacs-state ()
(when (and (bound-and-true-p evil-mode)
(not (memq evil-state '(insert emacs))))
(setq +mc--compat-evil-prev-state evil-state)
(when (region-active-p)
(setq +mc--compat-mark-was-active t))
(let ((mark-before (mark))
(point-before (point)))
(evil-emacs-state 1)
(when (or +mc--compat-mark-was-active (region-active-p))
(goto-char point-before)
(set-mark mark-before)))))
(add-hook 'multiple-cursors-mode-enabled-hook #'+multiple-cursors|compat-switch-to-emacs-state)
(defun +multiple-cursors|compat-back-to-previous-state ()
(when +mc--compat-evil-prev-state
(unwind-protect
(case +mc--compat-evil-prev-state
((normal visual) (evil-force-normal-state))
(t (message "Don't know how to handle previous state: %S"
+mc--compat-evil-prev-state)))
(setq +mc--compat-evil-prev-state nil)
(setq +mc--compat-mark-was-active nil))))
(add-hook 'multiple-cursors-mode-disabled-hook #'+multiple-cursors|compat-back-to-previous-state)
;; When running edit-lines, point will return (position + 1) as a
;; result of how evil deals with regions
(defun +multiple-cursors*adjust-mark-for-evil (&rest _)
(when (and (bound-and-true-p evil-mode)
(not (memq evil-state '(insert emacs))))
(if (> (point) (mark))
(goto-char (1- (point)))
(push-mark (1- (mark))))))
(advice-add #'mc/edit-lines :before #'+multiple-cursors*adjust-mark-for-evil)
(defun +multiple-cursors|evil-compat-rect-switch-state ()
(if rectangular-region-mode
(+multiple-cursors|compat-switch-to-emacs-state)
(setq +mc--compat-evil-prev-state nil)))
(add-hook 'rectangular-region-mode-hook '+multiple-cursors|evil-compat-rect-switch-state)
(defvar mc--default-cmds-to-run-once nil)))

View file

@ -0,0 +1,9 @@
;; -*- no-byte-compile: t; -*-
;;; editor/multiple-cursors/packages.el
(cond ((featurep! :feature evil)
(package! evil-multiedit)
(package! evil-mc))
((package! multiple-cursors)))

View file

@ -0,0 +1,3 @@
#+TITLE: :editor parinfer
You can find out more about parinfer at https://shaunlebron.github.io/parinfer/

View file

@ -0,0 +1,19 @@
;;; editor/parinfer/config.el -*- lexical-binding: t; -*-
(def-package! parinfer
:hook ((emacs-lisp-mode clojure-mode scheme-mode lisp-mode) . parinfer-mode)
:init
(setq parinfer-extensions
'(defaults
pretty-parens
smart-tab
smart-yank))
(when (featurep! :feature evil +everywhere)
(push 'evil parinfer-extensions))
:config
(map! :map parinfer-mode-map
"\"" nil ; smartparens handles this
:i "<tab>" #'parinfer-smart-tab:dwim-right-or-complete
:i "<backtab>" #'parinfer-smart-tab:dwim-left
:localleader
:desc "Toggle parinfer-mode" "m" #'parinfer-toggle-mode))

View file

@ -0,0 +1,14 @@
;; -*- no-byte-compile: t; -*-
;;; editor/parinfer/packages.el
(when (featurep! :feature evil)
;; Parinfer uses `evil-define-key' without loading evil, so if evil is
;; installed *after* parinfer, parinfer will throw up void-function errors.
;; because evil-define-key (a macro) wasn't expanded at compile-time. So we
;; make sure evil is installed before parinfer...
(package! evil)
;; ...and that it can see `evil-define-key' if evil was installed in a
;; separate session:
(autoload 'evil-define-key "evil-core" nil nil 'macro))
(package! parinfer)

View file

@ -0,0 +1,19 @@
;;; editor/rotate-text/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(after! rotate-text
(add-to-list 'rotate-text-words '("true" "false")))
;;;###autodef
(cl-defun set-rotate-patterns! (modes &key symbols words patterns)
"Declare :symbols, :words or :patterns (all lists of strings) that
`rotate-text' will cycle through."
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(let ((fn-name (intern (format "+rotate-text|init-%s" mode))))
(fset fn-name
(lambda ()
(setq-local rotate-text-local-symbols symbols)
(setq-local rotate-text-local-words words)
(setq-local rotate-text-local-patterns patterns)))
(add-hook (intern (format "%s-hook" mode)) fn-name))))

View file

@ -1,4 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/rotate-text/packages.el
;;; editor/rotate-text/packages.el
(package! rotate-text :recipe (:fetcher github :repo "debug-ito/rotate-text.el"))

View file

@ -0,0 +1,262 @@
;;; tools/dired/config.el -*- lexical-binding: t; -*-
(def-package! dired
:commands dired-jump
:init
(setq ;; Always copy/delete recursively
dired-recursive-copies 'always
dired-recursive-deletes 'top
;; Auto refresh dired, but be quiet about it
global-auto-revert-non-file-buffers t
auto-revert-verbose nil
dired-hide-details-hide-symlink-targets nil
;; files
image-dired-dir (concat doom-cache-dir "image-dired/")
image-dired-db-file (concat image-dired-dir "db.el")
image-dired-gallery-dir (concat image-dired-dir "gallery/")
image-dired-temp-image-file (concat image-dired-dir "temp-image")
image-dired-temp-rotate-image-file (concat image-dired-dir "temp-rotate-image"))
:config
(let ((args (list "-aBhl" "--group-directories-first")))
(when IS-BSD
;; Use GNU ls as `gls' from `coreutils' if available. Add `(setq
;; dired-use-ls-dired nil)' to your config to suppress the Dired warning
;; when not using GNU ls.
(if-let* ((gls (executable-find "gls")))
(setq insert-directory-program gls)
(setq args (delete "--group-directories-first" args))
(message "Cannot find `gls` (GNU ls). Install coreutils via your system package manager")))
(setq dired-listing-switches (string-join args " ")))
(defun +dired|sort-directories-first ()
"List directories first in dired buffers."
(save-excursion
(let (buffer-read-only)
(forward-line 2) ;; beyond dir. header
(sort-regexp-fields t "^.*$" "[ ]*." (point) (point-max))))
(and (featurep 'xemacs)
(fboundp 'dired-insert-set-properties)
(dired-insert-set-properties (point-min) (point-max)))
(set-buffer-modified-p nil))
(add-hook 'dired-after-readin-hook #'+dired|sort-directories-first)
;; Automatically create missing directories when creating new files
(defun +dired|create-non-existent-directory ()
(let ((parent-directory (file-name-directory buffer-file-name)))
(when (and (not (file-exists-p parent-directory))
(y-or-n-p (format "Directory `%s' does not exist! Create it?" parent-directory)))
(make-directory parent-directory t))))
(add-to-list 'find-file-not-found-functions '+dired|create-non-existent-directory nil #'eq)
;; Kill buffer when quitting dired buffers
(define-key dired-mode-map [remap quit-window] (λ! (quit-window t))))
(def-package! dired-k
:unless (featurep! +ranger)
:hook (dired-initial-position . dired-k)
:hook (dired-after-readin . dired-k-no-revert)
:config
(defun +dired*interrupt-process (orig-fn &rest args)
"Fixes dired-k killing git processes too abruptly, leaving behind disruptive
.git/index.lock files."
(cl-letf (((symbol-function #'kill-process)
(symbol-function #'interrupt-process)))
(apply orig-fn args)))
(advice-add #'dired-k--start-git-status :around #'+dired*interrupt-process)
(defun +dired*dired-k-highlight (orig-fn &rest args)
"Butt out if the requested directory is remote (i.e. through tramp)."
(unless (file-remote-p default-directory)
(apply orig-fn args)))
(advice-add #'dired-k--highlight :around #'+dired*dired-k-highlight))
(def-package! ranger
:when (featurep! +ranger)
:after dired
:init
;; set up image-dired to allow picture resize
(setq image-dired-dir (concat doom-cache-dir "image-dir"))
:config
(unless (file-directory-p image-dired-dir)
(make-directory image-dired-dir))
(set-popup-rule! "^\\*ranger" :ignore t)
(setq ranger-override-dired t
ranger-cleanup-on-disable t
ranger-omit-regexp "^\.DS_Store$"
ranger-excluded-extensions '("mkv" "iso" "mp4")
ranger-deer-show-details nil
ranger-max-preview-size 10
ranger-show-literal nil
dired-omit-verbose nil))
(def-package! all-the-icons-dired
:when (featurep! +icons)
:hook (dired-mode . all-the-icons-dired-mode))
(def-package! dired-x
:hook (dired-mode . dired-omit-mode)
:config
(setq dired-omit-verbose nil))
;;
;; Evil integration
(map! :when (featurep! :feature evil +everywhere)
:after dired
:map dired-mode-map
:n "q" #'quit-window
:m "j" #'dired-next-line
:m "k" #'dired-previous-line
:n [mouse-2] #'dired-mouse-find-file-other-window
:n [follow-link] #'mouse-face
;; Commands to mark or flag certain categories of files
:n "#" #'dired-flag-auto-save-files
:n "." #'dired-clean-directory
:n "~" #'dired-flag-backup-files
;; Upper case keys (except !) for operating on the marked files
:n "A" #'dired-do-find-regexp
:n "C" #'dired-do-copy
:n "B" #'dired-do-byte-compile
:n "D" #'dired-do-delete
:n "gG" #'dired-do-chgrp ;; FIXME: This can probably live on a better binding.
:n "H" #'dired-do-hardlink
:n "L" #'dired-do-load
:n "M" #'dired-do-chmod
:n "O" #'dired-do-chown
:n "P" #'dired-do-print
:n "Q" #'dired-do-find-regexp-and-replace
:n "R" #'dired-do-rename
:n "S" #'dired-do-symlink
:n "T" #'dired-do-touch
:n "X" #'dired-do-shell-command
:n "Z" #'dired-do-compress
:n "c" #'dired-do-compress-to
:n "!" #'dired-do-shell-command
:n "&" #'dired-do-async-shell-command
;; Comparison commands
:n "=" #'dired-diff
;; Tree Dired commands
:n "M-C-?" #'dired-unmark-all-files
:n "M-C-d" #'dired-tree-down
:n "M-C-u" #'dired-tree-up
:n "M-C-n" #'dired-next-subdir
:n "M-C-p" #'dired-prev-subdir
;; move to marked files
:n "M-{" #'dired-prev-marked-file
:n "M-}" #'dired-next-marked-file
;; Make all regexp commands share a `%' prefix:
;; We used to get to the submap via a symbol dired-regexp-prefix, but that
;; seems to serve little purpose, and copy-keymap does a better job
;; without it.
:n "%" nil
:n "%u" #'dired-upcase
:n "%l" #'dired-downcase
:n "%d" #'dired-flag-files-regexp
:n "%g" #'dired-mark-files-containing-regexp
:n "%m" #'dired-mark-files-regexp
:n "%r" #'dired-do-rename-regexp
:n "%C" #'dired-do-copy-regexp
:n "%H" #'dired-do-hardlink-regexp
:n "%R" #'dired-do-rename-regexp
:n "%S" #'dired-do-symlink-regexp
:n "%&" #'dired-flag-garbage-files
;; mark
:n "*" nil
:n "**" #'dired-mark-executables
:n "*/" #'dired-mark-directories
:n "*@" #'dired-mark-symlinks
:n "*%" #'dired-mark-files-regexp
:n "*(" #'dired-mark-sexp
:n "*." #'dired-mark-extension
:n "*O" #'dired-mark-omitted
:n "*c" #'dired-change-marks
:n "*s" #'dired-mark-subdir-files
:n "*m" #'dired-mark
:n "*u" #'dired-unmark
:n "*?" #'dired-unmark-all-files
:n "*!" #'dired-unmark-all-marks
:n "U" #'dired-unmark-all-marks
:n "* <delete>" #'dired-unmark-backward
:n "* C-n" #'dired-next-marked-file
:n "* C-p" #'dired-prev-marked-file
:n "*t" #'dired-toggle-marks
;; Lower keys for commands not operating on all the marked files
:n "a" #'dired-find-alternate-file
:n "d" #'dired-flag-file-deletion
:n "gf" #'dired-find-file
:n "C-m" #'dired-find-file
:n "gr" #'revert-buffer
:n "i" #'dired-toggle-read-only
:n "I" #'dired-maybe-insert-subdir
:n "J" #'dired-goto-file
:n "K" #'dired-do-kill-lines
:n "r" #'dired-do-redisplay
:n "m" #'dired-mark
:n "t" #'dired-toggle-marks
:n "u" #'dired-unmark ; also "*u"
:n "W" #'browse-url-of-dired-file
:n "x" #'dired-do-flagged-delete
:n "gy" #'dired-show-file-type ;; FIXME: This could probably go on a better key.
:n "Y" #'dired-copy-filename-as-kill
:n "+" #'dired-create-directory
;; open
:n "<return>" #'dired-find-file
:n "S-<return>" #'dired-find-file-other-window
:n "M-<return>" #'dired-display-file
:n "gO" #'dired-find-file-other-window
:n "go" #'dired-view-file
;; sort
:n "o" #'dired-sort-toggle-or-edit
;; moving
:m "gj" #'dired-next-dirline
:m "gk" #'dired-prev-dirline
:n "[" #'dired-prev-dirline
:n "]" #'dired-next-dirline
:n "<" #'dired-prev-dirline
:n ">" #'dired-next-dirline
:n "^" #'dired-up-directory
:n [?\S-\ ] #'dired-previous-line
:n [remap next-line] #'dired-next-line
:n [remap previous-line] #'dired-previous-line
;; hiding
:n "g$" #'dired-hide-subdir ;; FIXME: This can probably live on a better binding.
:n "M-$" #'dired-hide-all
:n "(" #'dired-hide-details-mode
;; isearch
:n "M-s a C-s" #'dired-do-isearch
:n "M-s a M-C-s" #'dired-do-isearch-regexp
:n "M-s f C-s" #'dired-isearch-filenames
:n "M-s f M-C-s" #'dired-isearch-filenames-regexp
;; misc
:n [remap read-only-mode] #'dired-toggle-read-only
;; `toggle-read-only' is an obsolete alias for `read-only-mode'
:n [remap toggle-read-only] #'dired-toggle-read-only
:n "g?" #'dired-summary
:n "<delete>" #'dired-unmark-backward
:n [remap undo] #'dired-undo
:n [remap advertised-undo] #'dired-undo
;; thumbnail manipulation (image-dired)
:n "C-t d" #'image-dired-display-thumbs
:n "C-t t" #'image-dired-tag-files
:n "C-t r" #'image-dired-delete-tag
:n "C-t j" #'image-dired-jump-thumbnail-buffer
:n "C-t i" #'image-dired-dired-display-image
:n "C-t x" #'image-dired-dired-display-external
:n "C-t a" #'image-dired-display-thumbs-append
:n "C-t ." #'image-dired-display-thumb
:n "C-t c" #'image-dired-dired-comment-files
:n "C-t f" #'image-dired-mark-tagged-files
:n "C-t C-t" #'image-dired-dired-toggle-marked-thumbs
:n "C-t e" #'image-dired-dired-edit-comment-and-tags
;; encryption and decryption (epa-dired)
:n ";d" #'epa-dired-do-decrypt
:n ";v" #'epa-dired-do-verify
:n ";s" #'epa-dired-do-sign
:n ";e" #'epa-dired-do-encrypt)

View file

@ -0,0 +1,8 @@
;; -*- no-byte-compile: t; -*-
;;; emacs/dired/packages.el
(package! dired-k)
(when (featurep! +ranger)
(package! ranger))
(when (featurep! +icons)
(package! all-the-icons-dired))

View file

@ -0,0 +1,26 @@
;;; emacs/electric/autoload.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-electric! (modes &rest plist)
"Declare that WORDS (list of strings) or CHARS (lists of chars) should trigger
electric indentation.
Enables `electric-indent-local-mode' in MODES.
\(fn MODES &key WORDS CHARS)"
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(let ((hook (intern (format "%s-hook" mode)))
(fn (intern (format "+electric|init-%s" mode))))
(cond ((null (car-safe plist))
(remove-hook hook fn)
(unintern fn nil))
((fset fn
(lambda ()
(when (eq major-mode mode)
(setq-local electric-indent-inhibit nil)
(cl-destructuring-bind (&key chars words) plist
(electric-indent-local-mode +1)
(if chars (setq electric-indent-chars chars))
(if words (setq +electric-indent-words words))))))
(add-hook hook fn))))))

View file

@ -0,0 +1,19 @@
;;; emacs/electric/config.el -*- lexical-binding: t; -*-
;; Smarter, keyword-based electric-indent
(defvar-local +electric-indent-words '()
"The list of electric words. Typing these will trigger reindentation of the
current line.")
;;
(after! electric
(setq-default electric-indent-chars '(?\n ?\^?))
(defun +electric-indent|char (_c)
(when (and (eolp) +electric-indent-words)
(save-excursion
(backward-word)
(looking-at-p (concat "\\<" (regexp-opt +electric-indent-words))))))
(add-to-list 'electric-indent-functions #'+electric-indent|char nil #'eq))

View file

@ -0,0 +1,18 @@
;;; emacs/eshell/autoload/commands.el -*- lexical-binding: t; -*-
;;;###autoload
(defun eshell/cd-to-project ()
"Change to the project root of the current directory."
(eshell/cd (doom-project-root (eshell/pwd))))
;;;###autoload
(defun eshell/quit-and-close (&rest _)
"Quit the current eshell buffer and close the window it's in."
(setq-local +eshell-kill-window-on-exit t)
(throw 'eshell-terminal t))
;;;###autoload
(defun eshell/mkdir-and-cd (dir)
"Create a directory then cd into it."
(make-directory dir t)
(eshell/cd dir))

View file

@ -0,0 +1,297 @@
;;; emacs/eshell/autoload/eshell.el -*- lexical-binding: t; -*-
(defvar eshell-buffer-name "*doom eshell*")
(defvar +eshell-buffers (make-ring 25)
"List of open eshell buffers.")
(defvar +eshell--last-buffer nil)
;;
;; Helpers
(defun +eshell--add-buffer (buf)
(ring-remove+insert+extend +eshell-buffers buf 'grow))
(defun +eshell--remove-buffer (buf)
(when-let* ((idx (ring-member +eshell-buffers buf)))
(ring-remove +eshell-buffers idx)
t))
(defun +eshell--bury-buffer (&optional dedicated-p)
(unless (switch-to-prev-buffer nil 'bury)
(switch-to-buffer (doom-fallback-buffer)))
(when (eq major-mode 'eshell-mode)
(switch-to-buffer (doom-fallback-buffer)))
(when +eshell-enable-new-shell-on-split
(when-let* ((win (get-buffer-window (+eshell/open t))))
(set-window-dedicated-p win dedicated-p))))
(defun +eshell--setup-window (window &optional flag)
(when (window-live-p window)
(set-window-parameter window 'no-other-window flag)
(set-window-parameter window 'visible flag)))
(defun +eshell--unused-buffer (&optional new-p)
(or (unless new-p
(cl-loop for buf in (+eshell-buffers)
if (and (buffer-live-p buf)
(not (get-buffer-window buf t)))
return buf))
(generate-new-buffer eshell-buffer-name)))
;;;###autoload
(defun +eshell-last-buffer (&optional noerror)
"Return the last opened eshell buffer."
(let ((buffer (cl-find-if #'buffer-live-p (+eshell-buffers))))
(cond (buffer)
(noerror nil)
((user-error "No live eshell buffers remaining")))))
;;;###autoload
(defun +eshell-buffers ()
"TODO"
(ring-elements +eshell-buffers))
;;;###autoload
(defun +eshell-run-command (command &optional buffer)
"TODO"
(let ((buffer
(or buffer
(if (eq major-mode 'eshell-mode)
(current-buffer)
(cl-find-if #'buffer-live-p (+eshell-buffers))))))
(unless buffer
(user-error "No living eshell buffers available"))
(unless (buffer-live-p buffer)
(user-error "Cannot operate on a dead buffer"))
(with-current-buffer buffer
(goto-char eshell-last-output-end)
(goto-char (line-end-position))
(insert command)
(eshell-send-input nil t))))
;;
;; Commands
;;;###autoload
(defun +eshell/open (arg &optional command)
"Open eshell in the current buffer."
(interactive "P")
(when (eq major-mode 'eshell-mode)
(user-error "Already in an eshell buffer"))
(let* ((default-directory (or (if arg default-directory (doom-project-root))
default-directory))
(buf (+eshell--unused-buffer)))
(with-current-buffer (switch-to-buffer buf)
(if (eq major-mode 'eshell-mode)
(run-hooks 'eshell-mode-hook)
(eshell-mode))
(if command (+eshell-run-command command buf)))
buf))
;;;###autoload
(defun +eshell/open-popup (arg &optional command)
"Open eshell in a popup window."
(interactive "P")
(let* ((default-directory (or (if arg default-directory (doom-project-root))
default-directory))
(buf (+eshell--unused-buffer)))
(with-current-buffer (pop-to-buffer buf)
(if (eq major-mode 'eshell-mode)
(run-hooks 'eshell-mode-hook)
(eshell-mode))
(if command (+eshell-run-command command buf)))
buf))
;;;###autoload
(defun +eshell/open-fullscreen (arg &optional command)
"Open eshell in a separate workspace. Requires the (:feature workspaces)
module to be loaded."
(interactive "P")
(let ((default-directory (or (if arg default-directory (doom-project-root))
default-directory))
(buf (+eshell--unused-buffer 'new)))
(set-frame-parameter nil 'saved-wconf (current-window-configuration))
(delete-other-windows)
(with-current-buffer (switch-to-buffer buf)
(eshell-mode)
(if command (+eshell-run-command command buf)))
buf))
;;
;; Keybinds
;;;###autoload
(defun +eshell/search-history ()
"Search the eshell command history with helm, ivy or `eshell-list-history'."
(interactive)
(cond ((featurep! :completion ivy)
(require 'em-hist)
(let* ((ivy-completion-beg (eshell-bol))
(ivy-completion-end (point-at-eol))
(input (buffer-substring-no-properties
ivy-completion-beg
ivy-completion-end)))
;; Better than `counsel-esh-history' because that doesn't
;; pre-populate the initial input or selection.
(ivy-read "Command: "
(delete-dups
(when (> (ring-size eshell-history-ring) 0)
(ring-elements eshell-history-ring)))
:initial-input input
:action #'ivy-completion-in-region-action)))
((featurep! :completion helm)
(helm-eshell-history))
((eshell-list-history))))
;;;###autoload
(defun +eshell/pcomplete ()
"Use pcomplete with completion-in-region backend instead of popup window at
bottom. This ties pcomplete into ivy or helm, if they are enabled."
(interactive)
(require 'pcomplete)
(ignore-errors (pcomplete-std-complete)))
;;;###autoload
(defun +eshell/quit-or-delete-char (arg)
"Delete a character (ahead of the cursor) or quit eshell if there's nothing to
delete."
(interactive "p")
(if (and (eolp) (looking-back eshell-prompt-regexp nil))
(eshell-life-is-too-much)
(delete-char arg)))
;;;###autoload
(defun +eshell/split-below ()
"Create a new eshell window below the current one."
(interactive)
(let ((ignore-window-parameters t)
(dedicated-p (window-dedicated-p))
(+eshell-enable-new-shell-on-split
(or +eshell-enable-new-shell-on-split (frame-parameter nil 'saved-wconf))))
(select-window (split-window-vertically))
(+eshell--bury-buffer dedicated-p)))
;;;###autoload
(defun +eshell/split-right ()
"Create a new eshell window to the right of the current one."
(interactive)
(let* ((ignore-window-parameters t)
(dedicated-p (window-dedicated-p))
(+eshell-enable-new-shell-on-split
(or +eshell-enable-new-shell-on-split (frame-parameter nil 'saved-wconf))))
(select-window (split-window-horizontally))
(+eshell--bury-buffer dedicated-p)))
;;;###autoload
(defun +eshell/switch-to-next ()
"Switch to the next eshell buffer."
(interactive)
(when (ring-empty-p +eshell-buffers)
(user-error "No eshell buffers are available"))
(switch-to-buffer (ring-next +eshell-buffers (current-buffer))))
;;;###autoload
(defun +eshell/switch-to-previous ()
"Switch to the previous eshell buffer."
(interactive)
(when (ring-empty-p +eshell-buffers)
(user-error "No eshell buffers are available"))
(switch-to-buffer (ring-previous +eshell-buffers (current-buffer))))
;;;###autoload
(defun +eshell/switch-to-last ()
"Switch to the last eshell buffer that was open (and is still alive)."
(interactive)
(unless (buffer-live-p +eshell--last-buffer)
(setq +eshell--last-buffer nil)
(user-error "No last eshell buffer to jump to"))
(switch-to-buffer +eshell--last-buffer))
;;;###autoload
(defun +eshell/switch-to (buffer)
"Interactively switch to another eshell buffer."
(interactive
(let ((buffers (doom-buffers-in-mode
'eshell-mode (delq (current-buffer) (+eshell-buffers)))))
(if (not buffers)
(user-error "No eshell buffers are available")
(list
(completing-read "Eshell buffers"
(mapcar #'buffer-name buffers)
#'get-buffer
'require-match
nil nil
(when (eq major-mode 'eshell-mode)
(buffer-name (current-buffer))))))))
(if-let* ((window (get-buffer-window buffer)))
(select-window window)
(switch-to-buffer buffer)))
;;;###autoload
(defun +eshell/kill-and-close ()
"Kill the current eshell buffer and close its window."
(interactive)
(unless (eq major-mode 'eshell-mode)
(user-error "Not in an eshell buffer"))
(let ((+eshell-kill-window-on-exit t))
(kill-this-buffer)))
;;
;; Hooks
;;;###autoload
(defun +eshell|init ()
"Initialize and track this eshell buffer in `+eshell-buffers'."
(let ((current-buffer (current-buffer)))
(dolist (buf (+eshell-buffers))
(unless (buffer-live-p buf)
(+eshell--remove-buffer buf)))
(+eshell--setup-window (get-buffer-window current-buffer))
(+eshell--add-buffer current-buffer)
(setq +eshell--last-buffer current-buffer)))
;;;###autoload
(defun +eshell|cleanup ()
"Close window (or workspace) on quit."
(let ((buf (current-buffer)))
(when (+eshell--remove-buffer buf)
(when-let* ((win (get-buffer-window buf)))
(+eshell--setup-window win nil)
(cond ((and (one-window-p t)
(window-configuration-p (frame-parameter nil 'saved-wconf)))
(set-window-configuration (frame-parameter nil 'saved-wconf))
(set-frame-parameter win 'saved-wconf nil))
((one-window-p)
(let ((prev (save-window-excursion (previous-buffer))))
(unless (and prev (doom-real-buffer-p prev))
(switch-to-buffer (doom-fallback-buffer)))))
((or (window-dedicated-p win)
+eshell-kill-window-on-exit)
(let ((ignore-window-parameters t)
(popup-p (window-dedicated-p win)))
(delete-window win)
(when popup-p
(cl-loop for win in (window-list)
for buf = (window-buffer win)
for mode = (buffer-local-value 'major-mode buf)
if (eq mode 'eshell-mode)
return (select-window win))))))))))
;;;###autoload
(defun +eshell|switch-workspace (type)
(when (eq type 'frame)
(setq +eshell-buffers
(or (persp-parameter 'eshell-buffers)
(make-ring 25)))))
;;;###autoload
(defun +eshell|save-workspace (_workspace target)
(when (framep target)
(set-persp-parameter 'eshell-buffers +eshell-buffers)))

View file

@ -0,0 +1,76 @@
;;; emacs/eshell/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
;;;###autoload
(defun +eshell|init-evil ()
"Replace `evil-collection-eshell-next-prompt-on-insert' with
`+eshell|goto-prompt-on-insert', which ensures the point is on the prompt when
changing to insert mode."
(dolist (hook '(evil-replace-state-entry-hook evil-insert-state-entry-hook))
(remove-hook hook 'evil-collection-eshell-next-prompt-on-insert t)
(add-hook hook '+eshell|goto-prompt-on-insert nil t)))
;;;###autoload (autoload '+eshell:run "emacs/eshell/autoload/evil" nil t)
(evil-define-command +eshell:run (command bang)
"TODO"
(interactive "<fsh><!>")
(let ((buffer (+eshell-last-buffer))
(command (+evil*resolve-vim-path command)))
(cond (buffer
(select-window (get-buffer-window buffer))
(+eshell-run-command command buffer))
(bang (+eshell/open nil command))
((+eshell/open-popup nil command)))))
;;;###autoload
(defun +eshell|goto-prompt-on-insert ()
"Move cursor to the prompt when switching to insert mode (if point isn't
already there)."
(when (< (point) eshell-last-output-end)
(goto-char
(if (memq this-command '(evil-append evil-append-line))
(point-max)
eshell-last-output-end))))
;;;###autoload
(defun +eshell/goto-end-of-prompt ()
"Move cursor to the prompt when switching to insert mode (if point isn't
already there)."
(interactive)
(goto-char (point-max))
(evil-append 1))
;;;###autoload (autoload '+eshell/evil-change "emacs/eshell/autoload/evil" nil t)
(evil-define-operator +eshell/evil-change (beg end type register yank-handler delete-func)
"Like `evil-change' but will not delete/copy the prompt."
(interactive "<R><x><y>")
(save-restriction
(narrow-to-region eshell-last-output-end (point-max))
(evil-change (max beg (point-min))
(if (eq type 'line) (point-max) (min (or end (point-max)) (point-max)))
type register yank-handler delete-func)))
;;;###autoload (autoload '+eshell/evil-change-line "emacs/eshell/autoload/evil" nil t)
(evil-define-operator +eshell/evil-change-line (beg end type register yank-handler)
"Change to end of line."
:motion evil-end-of-line
(interactive "<R><x><y>")
(+eshell/evil-change beg end type register yank-handler #'evil-delete-line))
;;;###autoload (autoload '+eshell/evil-delete "emacs/eshell/autoload/evil" nil t)
(evil-define-operator +eshell/evil-delete (beg end type register yank-handler)
"Like `evil-delete' but will not delete/copy the prompt."
(interactive "<R><x><y>")
(save-restriction
(narrow-to-region eshell-last-output-end (point-max))
(evil-delete (if beg (max beg (point-min)) (point-min))
(if (eq type 'line) (point-max) (min (or end (point-max)) (point-max)))
type register yank-handler)))
;;;###autoload (autoload '+eshell/evil-delete-line "emacs/eshell/autoload/evil" nil t)
(evil-define-operator +eshell/evil-delete-line (_beg end type register yank-handler)
"Change to end of line."
:motion nil
:keep-visual t
(interactive "<R><x>")
(+eshell/evil-delete (point) end type register yank-handler))

View file

@ -0,0 +1,34 @@
;;; emacs/eshell/autoload/prompts.el -*- lexical-binding: t; -*-
;;;###autoload
(defface +eshell-prompt-pwd '((t :inherit font-lock-constant-face))
"TODO"
:group 'eshell)
;;;###autoload
(defface +eshell-prompt-git-branch '((t :inherit font-lock-builtin-face))
"TODO"
:group 'eshell)
(defun +eshell--current-git-branch ()
(let ((branch (car (cl-loop for match in (split-string (shell-command-to-string "git branch") "\n")
if (string-match-p "^\*" match)
collect match))))
(if (not (eq branch nil))
(format " [%s]" (substring branch 2))
"")))
;;;###autoload
(defun +eshell-default-prompt ()
"Generate the prompt string for eshell. Use for `eshell-prompt-function'."
(concat (if (bobp) "" "\n")
(let ((pwd (eshell/pwd)))
(propertize (if (equal pwd "~")
pwd
(abbreviate-file-name (shrink-path-file pwd)))
'face '+eshell-prompt-pwd))
(propertize (+eshell--current-git-branch)
'face '+eshell-prompt-git-branch)
(propertize " λ" 'face (if (zerop eshell-last-command-status) 'success 'error))
" "))

View file

@ -0,0 +1,20 @@
;;; emacs/eshell/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-eshell-alias! (&rest aliases)
"Define aliases for eshell."
(or (cl-evenp (length aliases))
(signal 'wrong-number-of-arguments (list 'even (length aliases))))
(after! eshell
(while aliases
(let ((alias (pop aliases))
(command (pop aliases)))
(if-let* ((oldval (assoc alias +eshell-aliases)))
(setcdr oldval (list command))
(push (list alias command) +eshell-aliases))))
(when (boundp 'eshell-command-aliases-list)
(if +eshell--default-aliases
(setq eshell-command-aliases-list
(append +eshell--default-aliases
+eshell-aliases))
(setq eshell-command-aliases-list +eshell-aliases)))))

View file

@ -0,0 +1,165 @@
;;; emacs/eshell/config.el -*- lexical-binding: t; -*-
;; see:
;; + `+eshell/open': open in current buffer
;; + `+eshell/open-popup': open in a popup
;; + `+eshell/open-fullscreen': open eshell fullscreen (will restore window
;; config when quitting the last eshell buffer)
(defvar +eshell-config-dir
(expand-file-name "eshell/" doom-private-dir)
"Where to store eshell configuration files, as opposed to
`eshell-directory-name', which is where Doom will store temporary/data files.")
(defvar +eshell-enable-new-shell-on-split t
"If non-nil, spawn a new eshell session after splitting from an eshell
buffer.")
(defvar +eshell-kill-window-on-exit nil
"If non-nil, eshell will close windows along with its eshell buffers.")
(defvar +eshell-aliases
'(("q" "exit") ; built-in
("f" "find-file $1")
("bd" "eshell-up $1") ; `eshell-up'
("rg" "rg --color=always $*")
("ag" "ag --color=always $*")
("l" "ls -lh")
("ll" "ls -lah")
("clear" "clear-scrollback")) ; more sensible than default
"An alist of default eshell aliases, meant to emulate useful shell utilities,
like fasd and bd. Note that you may overwrite these in your
`eshell-aliases-file'. This is here to provide an alternative, elisp-centric way
to define your aliases.
You should use `det-eshell-alias!' to change this.")
;;
(defvar eshell-directory-name (concat doom-etc-dir "eshell"))
;; These files are exceptions, because they may contain configuration
(defvar eshell-aliases-file (concat +eshell-config-dir "alias"))
(defvar eshell-rc-script (concat +eshell-config-dir "profile"))
(defvar eshell-login-script (concat +eshell-config-dir "login"))
(defvar +eshell--default-aliases nil)
;;
;; Packages
(after! eshell ; built-in
(setq eshell-banner-message
'(format "%s %s\n"
(propertize (format " %s " (string-trim (buffer-name)))
'face 'mode-line-highlight)
(propertize (current-time-string)
'face 'font-lock-keyword-face))
eshell-scroll-to-bottom-on-input 'all
eshell-scroll-to-bottom-on-output 'all
eshell-buffer-shorthand t
eshell-kill-processes-on-exit t
eshell-hist-ignoredups t
;; don't record command in history if prefixed with whitespace
eshell-input-filter #'eshell-input-filter-initial-space
;; em-prompt
eshell-prompt-regexp "^.* λ "
eshell-prompt-function #'+eshell-default-prompt
;; em-glob
eshell-glob-case-insensitive t
eshell-error-if-no-glob t)
;; Consider eshell buffers real
(add-hook 'eshell-mode-hook #'doom|mark-buffer-as-real)
;; Keep track of open eshell buffers
(add-hook 'eshell-mode-hook #'+eshell|init)
(add-hook 'eshell-exit-hook #'+eshell|cleanup)
;; Enable autopairing in eshell
(add-hook 'eshell-mode-hook #'smartparens-mode)
;; Persp-mode/workspaces integration
(when (featurep! :feature workspaces)
(add-hook 'persp-activated-functions #'+eshell|switch-workspace)
(add-hook 'persp-before-switch-functions #'+eshell|save-workspace))
;; UI enhancements
(defun +eshell|remove-fringes ()
(set-window-fringes nil 0 0)
(set-window-margins nil 1 nil))
(add-hook 'eshell-mode-hook #'+eshell|remove-fringes)
(defun +eshell|enable-text-wrapping ()
(visual-line-mode +1)
(set-display-table-slot standard-display-table 0 ?\ ))
(add-hook 'eshell-mode-hook #'+eshell|enable-text-wrapping)
(add-hook 'eshell-mode-hook #'hide-mode-line-mode)
;; Don't auto-write our aliases! Let us manage our own `eshell-aliases-file'
;; or configure `+eshell-aliases' via elisp.
(advice-add #'eshell-write-aliases-list :override #'ignore)
;; Visual commands require a proper terminal. Eshell can't handle that, so
;; it delegates these commands to a term buffer.
(after! em-term
(dolist (cmd '("tmux" "htop" "vim" "nvim" "ncmpcpp"))
(add-to-list 'eshell-visual-commands cmd)))
(defun +eshell|init-aliases ()
(setq +eshell--default-aliases eshell-command-aliases-list
eshell-command-aliases-list
(append eshell-command-aliases-list
+eshell-aliases)))
(add-hook 'eshell-alias-load-hook #'+eshell|init-aliases)
(when (featurep! :feature evil +everywhere)
(add-hook 'eshell-mode-hook #'+eshell|init-evil))
(defun +eshell|init-keymap ()
"Setup eshell keybindings. This must be done in a hook because eshell-mode
redefines its keys every time `eshell-mode' is enabled."
(map! :map eshell-mode-map
:n [return] #'+eshell/goto-end-of-prompt
:n "c" #'+eshell/evil-change
:n "C" #'+eshell/evil-change-line
:n "d" #'+eshell/evil-delete
:n "D" #'+eshell/evil-delete-line
:i [tab] #'+eshell/pcomplete
:i "C-j" #'evil-window-down
:i "C-k" #'evil-window-up
:i "C-h" #'evil-window-left
:i "C-l" #'evil-window-right
:i "C-d" #'+eshell/quit-or-delete-char
:i "C-p" #'eshell-previous-input
:i "C-n" #'eshell-next-input
"C-s" #'+eshell/search-history
"C-c s" #'+eshell/split-below
"C-c v" #'+eshell/split-right
"C-c x" #'+eshell/kill-and-close
[remap split-window-below] #'+eshell/split-below
[remap split-window-right] #'+eshell/split-right
[remap doom/backward-to-bol-or-indent] #'eshell-bol
[remap doom/backward-kill-to-bol-and-indent] #'eshell-kill-input
[remap evil-window-split] #'+eshell/split-below
[remap evil-window-vsplit] #'+eshell/split-right))
(add-hook 'eshell-first-time-mode-hook #'+eshell|init-keymap))
(def-package! eshell-up
:commands (eshell-up eshell-up-peek))
(def-package! shrink-path
:commands shrink-path-file)
(def-package! eshell-z
:after eshell
:config
;; Use zsh's db if it exists, otherwise, store it in `doom-cache-dir'
(unless (file-exists-p eshell-z-freq-dir-hash-table-file-name)
(setq eshell-z-freq-dir-hash-table-file-name
(expand-file-name "z" eshell-directory-name))))

View file

@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; emacs/eshell/packages.el
(package! eshell-up)
(package! eshell-z)
(package! shrink-path)

View file

@ -0,0 +1,11 @@
;;; emacs/imenu/config.el -*- lexical-binding: t; -*-
;; `imenu-anywhere'
(setq imenu-anywhere-delimiter ": ")
(after! imenu-list
(setq imenu-list-idle-update-delay 0.5)
(set-popup-rule! "^\\*Ilist"
:side 'right :size 35 :quit nil :select nil :ttl 0))

View file

@ -1,5 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; tools/imenu/packages.el
;;; emacs/imenu/packages.el
(package! imenu-anywhere)
(package! imenu-list)

View file

@ -0,0 +1,33 @@
;;; emacs/term/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +term/open (arg)
"Open a terminal buffer in the current window. If ARG (universal argument) is
non-nil, cd into the current project's root."
(interactive "P")
(let ((default-directory
(if arg
(or (doom-project-root) default-directory)
default-directory)))
;; Doom's switch-buffer hooks prevent themselves from triggering when
;; switching from buffer A back to A. Because `multi-term' uses `set-buffer'
;; before `switch-to-buffer', the hooks don't trigger, so we use this
;; roundabout way to trigger them properly.
(switch-to-buffer (save-window-excursion (multi-term)))))
;;;###autoload
(defun +term/open-popup (arg)
"Open a terminal popup window. If ARG (universal argument) is
non-nil, cd into the current project's root."
(interactive "P")
(let ((default-directory
(if arg
(or (doom-project-root) default-directory)
default-directory)))
(pop-to-buffer (save-window-excursion (multi-term)))))
;;;###autoload
(defun +term/open-popup-in-project ()
"Open a terminal popup window in the root of the current project."
(interactive)
(+term/open-popup t))

View file

@ -0,0 +1,8 @@
;;; emacs/term/config.el -*- lexical-binding: t; -*-
;; `multi-term'
(setq multi-term-dedicated-window-height 20
multi-term-switch-after-close 'PREVIOUS)
;; `term' (built-in)
(add-hook 'term-mode-hook #'doom|mark-buffer-as-real)

View file

@ -1,4 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/term/packages.el
;;; emacs/term/packages.el
(package! multi-term)

View file

@ -0,0 +1,8 @@
;;; emacs/vc/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :feature evil)
;;;###autoload (autoload '+vc:git-browse "emacs/vc/autoload/evil" nil t)
(evil-define-command +vc:git-browse (bang)
"Ex interface to `+vc/git-browse-region-or-line'."
(interactive "<!>")
(+vc/git-browse-region-or-line bang))

Some files were not shown because too many files have changed in this diff Show more