Moving to Doom Emacs!

This commit is contained in:
Derek Taylor
2019-12-16 20:21:19 -06:00
parent d9f2f456f1
commit d4b4c33550
683 changed files with 51877 additions and 100 deletions

View File

@@ -0,0 +1,66 @@
#+TITLE: app/calendar
#+DATE: January 13, 2018
#+SINCE: v2.1
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#packages][Packages]]
- [[#configuration][Configuration]]
- [[#changing-calendar-sources][Changing calendar sources]]
- [[#synchronizing-org-and-google-calendar][Synchronizing Org and Google Calendar]]
* Description
This module adds a calendar view for Emacs, with org and google calendar sync
support.
** Module Flags
This module provides no flags.
** Packages
+ [[https://github.com/kiwanami/emacs-calfw][calfw]]
+ [[https://github.com/kiwanami/emacs-calfw][calfw-org]]
+ [[https://github.com/myuhe/org-gcal.el][org-gcal]]
* Configuration
** Changing calendar sources
By defining your own calendar commands, you can control what sources to pull
calendar data from:
#+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
To control what org files ~clfw:org-create-source~ will use, ~let~-bind
~org-agenda-files~ around a call to ~+calendar/open-calendar~ like so:
#+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
The [[https://github.com/kiwanami/emacs-calfw][kiwanami/emacs-calfw]] project readme contains more examples.
** Synchronizing Org and Google Calendar
The [[https://github.com/myuhe/org-gcal.el][myuhe/org-gcal.el]] project README contains more detailed instructions on how
to link your calendar with Google calendars.

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! :ui 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! :ui 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 (face-foreground 'default)) ; orgmode source
)))
;;;###autoload
(defun +calendar-cfw:render-button-a (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,51 @@
;;; app/calendar/config.el -*- lexical-binding: t; -*-
(defvar +calendar-open-function #'+calendar/open-calendar
"TODO")
;;
;; Packages
(use-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-h)
(add-hook 'cfw:calendar-mode-hook 'hide-mode-line-mode)
(advice-add #'cfw:render-button :override #'+calendar-cfw:render-button-a))
(use-package! calfw-org
:commands (cfw:open-org-calendar
cfw:org-create-source
cfw:open-org-calendar-withkevin
my-open-calendar))
(use-package! org-gcal
:commands (org-gcal-sync
org-gcal-fetch
org-gcal-post-at-point
org-gcal-delete-at-point)
:config
;; hack to avoid the deferred.el error
(defun org-gcal--notify (title mes)
(message "org-gcal::%s - %s" title mes)))
;; (use-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,141 @@
#+TITLE: app/irc
#+DATE: June 11, 2017
#+SINCE: v2.0.3
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#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 requires =gnutls-cli= or =openssl= for secure connections.
* 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-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:
** 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/pass/README.org][:tools pass]] module you get an elisp API through which to access your
password store.
~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-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:
#+BEGIN_SRC emacs-lisp :tangle no
(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.
#+BEGIN_SRC emacs-lisp :tangle no
(setq auth-sources '("~/.authinfo.gpg"))
(defun my-fetch-password (&rest params)
(require 'auth-source)
(let ((match (car (apply #'auth-source-search params))))
(if match
(let ((secret (plist-get match :secret)))
(if (functionp secret)
(funcall secret)
secret))
(error "Password not found for %S" params))))
(defun my-nickserv-password (server)
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
(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

@@ -0,0 +1,123 @@
;;; app/irc/autoload/email.el -*- lexical-binding: t; -*-
(defvar +irc--workspace-name "*IRC*")
(defun +irc-setup-wconf (&optional inhibit-workspace)
(unless inhibit-workspace
(+workspace-switch +irc--workspace-name t))
(let ((buffers (doom-buffers-in-mode 'circe-mode nil t)))
(if buffers
(ignore (switch-to-buffer (car buffers)))
(require 'circe)
(delete-other-windows)
(switch-to-buffer (doom-fallback-buffer))
t)))
;;;###autoload
(defun =irc (&optional inhibit-workspace)
"Connect to IRC and auto-connect to all registered networks.
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(cond ((and (featurep! :ui 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)
"Connect to a specific registered server.
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(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."
(interactive)
(if (y-or-n-p "Really kill IRC session?")
(let (circe-channel-killed-confirmation
circe-server-killed-confirmation)
(when +irc--defer-timer
(cancel-timer +irc--defer-timer))
(disable-circe-notifications)
(mapc #'kill-buffer (doom-buffers-in-mode 'circe-mode (buffer-list) t))
(when (equal (+workspace-current-name) +irc--workspace-name)
(+workspace/delete +irc--workspace-name)))
(message "Aborted")))
;;;###autoload
(defun +irc/ivy-jump-to-channel (&optional this-server)
"Jump to an open channel or server buffer with ivy. If THIS-SERVER (universal
argument) is non-nil only show channels in current server."
(interactive "P")
(if (not (circe-server-buffers))
(message "No circe buffers available")
(when (and this-server (not circe-server-buffer))
(setq this-server nil))
(ivy-read (format "Jump to%s: " (if this-server (format " (%s)" (buffer-name circe-server-buffer)) ""))
(cl-loop with servers = (if this-server (list circe-server-buffer) (circe-server-buffers))
with current-buffer = (current-buffer)
for server in servers
collect (buffer-name server)
nconc
(with-current-buffer server
(cl-loop for buf in (circe-server-chat-buffers)
unless (eq buf current-buffer)
collect (format " %s" (buffer-name buf)))))
:action #'+irc--ivy-switch-to-buffer-action
:preselect (buffer-name (current-buffer))
:keymap ivy-switch-buffer-map
:caller '+irc/ivy-jump-to-channel)))
(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)))
;;
;;; Hooks/fns
;;;###autoload
(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))))
;;;###autoload
(defun +irc--add-circe-buffer-to-persp-h ()
(when (bound-and-true-p persp-mode)
(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)))))

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

@@ -0,0 +1,235 @@
;;; app/irc/config.el -*- lexical-binding: t; -*-
(defvar +irc-left-padding 13
"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.")
(defvar +irc-scroll-to-bottom-on-commands
'(self-insert-command yank hilit-yank)
"If these commands are called pre prompt the buffer will scroll to `point-max'.")
(defvar +irc-disconnect-hook nil
"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")
"Nicks listed have `circe-fool-face' applied and will not be tracked.")
(defvar +irc-time-stamp-format "%H:%M"
"The format of time stamps.
See `format-time-string' for a full description of available
formatting directives. ")
(defvar +irc-notifications-watch-strings nil
"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.")
(defvar +irc--defer-timer nil)
(defsubst +irc--pad (left right)
(format (format "%%%ds | %%s" +irc-left-padding)
(concat "*** " left) right))
;;
;; Packages
(use-package! circe
:commands circe circe-server-buffers
:init (setq circe-network-defaults nil)
:config
(setq circe-default-quit-message nil
circe-default-part-message nil
circe-use-cycle-completion t
circe-reduce-lurker-spam t
circe-format-say (format "{nick:+%ss} │ {body}" +irc-left-padding)
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
(+irc--pad "Join" "{nick} ({userinfo}) joined {channel}")
circe-format-server-join
(+irc--pad "Join" "{nick} ({userinfo})")
circe-format-server-part
(+irc--pad "Part" "{nick} ({userhost}) left {channel}: {reason}")
circe-format-server-quit
(+irc--pad "Quit" "{nick} ({userhost}) left IRC: {reason}]")
circe-format-server-quit-channel
(+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
(+irc--pad "Nick" "You are now known as {new-nick} ({old-nick})")
circe-format-server-nick-change-self
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
circe-format-server-mode-change
(+irc--pad "Mode" "{change} on {target} by {setter} ({userhost})")
circe-format-server-lurker-activity
(+irc--pad "Lurk" "{nick} joined {joindelta} ago"))
(add-hook 'doom-real-buffer-functions #'+circe-buffer-p)
(add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode)
(add-hook 'circe-mode-hook #'+irc--add-circe-buffer-to-persp-h)
(defadvice! +irc--circe-run-disconnect-hook-a (&rest _)
"Runs `+irc-disconnect-hook' after circe disconnects."
:after #'circe--irc-conn-disconnected
(run-hooks '+irc-disconnect-hook))
(add-hook! 'lui-pre-output-hook
(defun +irc-circe-truncate-nicks-h ()
"Truncate long nicknames in chat output non-destructively."
(when-let (beg (text-property-any (point-min) (point-max) 'lui-format-argument 'nick))
(goto-char beg)
(let ((end (next-single-property-change beg 'lui-format-argument))
(nick (plist-get (plist-get (text-properties-at beg) 'lui-keywords)
:nick)))
(when (> (length nick) +irc-left-padding)
(compose-region (+ beg +irc-left-padding -1) end
+irc-truncate-nick-char))))))
(add-hook! 'circe-message-option-functions
(defun +irc-circe-message-option-bot-h (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))))))
;; 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)))
(use-package! circe-color-nicks
:hook (circe-channel-mode . enable-circe-color-nicks)
:config
(setq circe-color-nicks-min-constrast-ratio 4.5
circe-color-nicks-everywhere t))
(use-package! circe-new-day-notifier
:after circe
:config
(enable-circe-new-day-notifier)
(setq circe-new-day-notifier-format-message
(+irc--pad "Day" "Date changed [{day}]")))
(use-package! circe-notifications
:commands enable-circe-notifications
:init
(if +irc-defer-notifications
(add-hook! 'circe-server-connected-hook
(setq +irc--defer-timer
(run-at-time +irc-defer-notifications nil
#'enable-circe-notifications)))
(add-hook 'circe-server-connected-hook #'enable-circe-notifications))
:config
(setq circe-notifications-watch-strings +irc-notifications-watch-strings
circe-notifications-emacs-focused nil
circe-notifications-alert-style
(cond (IS-MAC 'osx-notifier)
(IS-LINUX 'libnotify)
(circe-notifications-alert-style))))
(use-package! lui
:commands lui-mode
:config
(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-h ()
"Ensure entering insert mode will put us at the prompt, unless editing
after prompt marker."
(when (> (marker-position lui-input-marker) (point))
(goto-char (point-max))))
(add-hook! 'lui-mode-hook
(add-hook 'evil-insert-state-entry-hook #'+irc-evil-insert-h
nil 'local))
(mapc (lambda (cmd) (push cmd +irc-scroll-to-bottom-on-commands))
'(evil-paste-after evil-paste-before evil-open-above evil-open-below)))
(defun +irc-preinput-scroll-to-bottom-h ()
"Go to the end of the buffer in all windows showing it.
Courtesy of esh-mode.el"
(when (memq this-command +irc-scroll-to-bottom-on-commands)
(let* ((selected (selected-window))
(current (current-buffer)))
(when (> (marker-position lui-input-marker) (point))
(walk-windows
(function
(lambda (window)
(when (eq (window-buffer window) current)
(select-window window)
(goto-char (point-max))
(select-window selected))))
nil t)))))
(add-hook! 'lui-mode-hook
(add-hook 'pre-command-hook #'+irc-preinput-scroll-to-bottom-h nil t))
;; enable a horizontal line marking the last read message
(add-hook 'lui-mode-hook #'enable-lui-track-bar)
(add-hook! 'lui-mode-hook
(defun +irc-init-lui-margins-h ()
(setq lui-time-stamp-position 'right-margin
lui-time-stamp-format +irc-time-stamp-format
right-margin-width (length (format-time-string lui-time-stamp-format))))
(defun +irc-init-lui-wrapping-a ()
(setq fringes-outside-margins t
word-wrap t
wrap-prefix (make-string (+ +irc-left-padding 3) ? )))))
(use-package! lui-logging
:after lui
:config (enable-lui-logging))
(use-package! lui-autopaste
:hook (circe-channel-mode . enable-lui-autopaste))

View File

@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; app/irc/packages.el
(package! circe)
(package! circe-notifications)

View File

@@ -0,0 +1,14 @@
;;; app/regex/autoload/export.el
;;;###autoload
(defun +regex/export () (interactive)) ; TODO +regex/export
;;
(defun +regex-export-python ()) ; import (re|regex)
(defun +regex-export-php ()) ; preg_(match(_all)?|replace)
(defun +regex-export-ruby ()) ; %r[.+]
(defun +regex-export-js ()) ; /.+/

View File

@@ -0,0 +1,272 @@
;;; app/regex/autoload/regex.el
(defvar +regex--text-buffer nil)
(defvar +regex--text-replace-buffer nil)
(defvar +regex--expr-buffer nil)
(defvar +regex--expr-replace-buffer nil)
(defvar +regex--groups-buffer nil)
(defvar +regex--replace-buffer nil)
;;
(defface +regex-match-0-face
`((t (:foreground "Black" :background ,(doom-color 'magenta) :bold t)))
"TODO"
:group 'faces)
(defface +regex-match-1-face
`((t (:foreground "Black" :background ,(doom-color 'blue) :bold t)))
"TODO"
:group 'faces)
(defface +regex-match-2-face
`((t (:foreground "Black" :background ,(doom-color 'green) :bold t)))
"TODO"
:group 'faces)
(defvar +regex-faces
'(+regex-match-0-face +regex-match-1-face +regex-match-2-face)
"TODO")
;;
(defvar +regex-mode-map
(let ((map (make-sparse-keymap)))
(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 kill-current-buffer] #'+regex/quit)
(define-key map [remap kill-buffer] #'+regex/quit)
map)
"TODO")
;;;###autoload
(define-minor-mode +regex-mode
"TODO"
:init-value nil
:global nil
:lighter ""
:keymap +regex-mode-map
(if +regex-mode
(add-hook 'after-change-functions #'+regex-update-buffers nil t)
(remove-hook 'after-change-functions #'+regex-update-buffers t)))
;;;###autoload
(defun =regex (&optional dummy-text)
"Start the Regex IDE."
(interactive "P")
(unless (buffer-live-p +regex--expr-buffer)
(condition-case ex
(progn
(setq +regex--expr-buffer (get-buffer-create "*doom-regex*")
+regex--text-buffer (if dummy-text (get-buffer-create "*doom-regex-text*") (current-buffer))
+regex--groups-buffer (get-buffer-create "*doom-regex-groups*"))
(when dummy-text
(+workspace-switch +regex-workspace-name t)
(switch-to-buffer +regex--text-buffer)
(with-current-buffer +regex--text-buffer
(insert +regex-dummy-text)))
(pop-to-buffer +regex--groups-buffer)
(pop-to-buffer +regex--expr-buffer)
(with-current-buffer +regex--expr-buffer
(conf-mode)
(rainbow-delimiters-mode +1)
(doom/toggle-line-numbers +1)
(setq-local require-final-newline nil)
(+regex-mode +1)
(text-scale-set 3)))
('error
(+regex/quit)
(error "Failed to open the Regexp IDE: %s" ex)))))
;;;###autoload
(defun =regex/replace ()
(interactive)
(unless (buffer-live-p +regex--replace-buffer)
(let (text)
(=regex t)
(with-selected-window (get-buffer-window +regex--text-buffer)
(setq text (buffer-string))
(select-window (split-window-right))
(switch-to-buffer (get-buffer-create "*doom-regex-text-repl*"))
(erase-buffer)
(insert text)
(read-only-mode +1)
(setq +regex--text-replace-buffer (current-buffer)))
(with-current-buffer +regex--expr-buffer
(select-window (split-window-right))
(switch-to-buffer (get-buffer-create "*doom-regex-repl*"))
(conf-mode)
(rainbow-delimiters-mode +1)
(doom/toggle-line-numbers -1)
(setq-local require-final-newline nil)
(setq mode-line-format nil
+regex--expr-replace-buffer (current-buffer))
(+regex-mode +1)
(text-scale-set 3)))))
;;;###autoload
(defun +regex/quit ()
"TODO"
(interactive)
(when (and +regex--text-buffer (buffer-live-p +regex--text-buffer))
(with-current-buffer +regex--text-buffer
(+regex-mode -1)
(remove-overlays nil nil 'category '+regex))
(when (equal (buffer-name +regex--text-buffer) "*doom-regex-text*")
(kill-buffer +regex--text-buffer)))
(when (equal (+workspace-current-name) +regex-workspace-name)
(+workspace/delete +regex-workspace-name))
(mapc (lambda (bufname)
(let ((buf (symbol-value bufname)))
(when (and buf (buffer-live-p buf))
(kill-buffer buf)
(set bufname nil))))
(list '+regex--text-replace-buffer
'+regex--expr-replace-buffer
'+regex--expr-buffer
'+regex--groups-buffer
'+regex--replace-buffer)))
(defun +regex--expr ()
(when (buffer-live-p +regex--expr-buffer)
(with-current-buffer +regex--expr-buffer
(string-trim (buffer-string)))))
(defun +regex--expr-replace ()
(when (buffer-live-p +regex--expr-replace-buffer)
(with-current-buffer +regex--expr-replace-buffer
(string-trim (buffer-string)))))
;;;###autoload
(defun +regex-update-buffers (&optional beg end len)
(interactive)
(let* ((inhibit-read-only t)
(regex (or (+regex--expr) ""))
(replace (or (+regex--expr-replace) ""))
(str (or (with-current-buffer +regex--text-buffer (buffer-string)) "")))
(with-current-buffer +regex--groups-buffer
(erase-buffer))
(with-current-buffer +regex--text-buffer
(remove-overlays nil nil 'category '+regex)
(when (> (length regex) 0)
(save-excursion
(goto-char (point-min))
(pcase +regex-default-backend
('emacs (+regex-backend-emacs regex replace str))
('perl (+regex-backend-perl regex replace str))))))
(with-current-buffer +regex--groups-buffer
(goto-char (point-min)))))
;; --- backends ---------------------------
(defun +regex--render-perl (regex text)
"From <https://github.com/jwiegley/regex-tool>"
(with-temp-buffer
(insert (format "@lines = <DATA>;
$line = join(\"\", @lines);
print \"(\";
while ($line =~ m/%s/gm) {
print \"(\", length($`), \" \", length($&), \" \";
for $i (1 .. 20) {
if ($$i) {
my $group = $$i;
$group =~ s/([\\\\\"])/\\\\\\1/g;
print \"(\", $i, \" . \\\"\", $group, \"\\\") \";
}
}
print \")\";
}
print \")\";
__DATA__
%s" (replace-regexp-in-string "/" "\\/" regex nil t) text))
(call-process-region (point-min) (point-max) "perl" t t)
(goto-char (point-min))
(read (current-buffer))))
(defun +regex--replace-perl (regex replace text)
(unless (or (string-empty-p regex)
(string-empty-p replace)
(string-empty-p text))
(with-temp-buffer
(insert (format "@lines = <DATA>;
$line = join(\"\", @lines);
$line =~ s/%s/%s/gm;
print $line;
__DATA__
%s" (replace-regexp-in-string "/" "\\/" regex nil t) (replace-regexp-in-string "/" "\\/" replace nil t) text))
(call-process-region (point-min) (point-max) "perl" t t)
(buffer-string))))
;;;###autoload
(defun +regex-backend-perl (regex replace str)
"TODO"
(cl-assert (stringp regex))
(cl-assert (stringp replace))
(cl-assert (stringp str))
(let ((i 0)
(results (+regex--render-perl regex str)))
(when (buffer-live-p +regex--text-replace-buffer)
(let ((replace (+regex--replace-perl regex replace str)))
(with-current-buffer +regex--text-replace-buffer
(erase-buffer)
(insert
(if (and (listp results)
replace
(not (string-empty-p replace)))
replace
str)))))
(dolist (result (if (listp results) results))
(let* ((offset (nth 0 result))
(length (nth 1 result))
(matches (nthcdr 2 result))
(ov (make-overlay (1+ offset) (+ offset length 1))))
(overlay-put ov 'face (nth (mod i 3) +regex-faces))
(overlay-put ov 'category '+regex)
(cl-incf i)
(let* ((match-zero (buffer-substring (1+ offset) (+ offset length 1)))
(line (format "Match: %s\n" (propertize match-zero 'face 'font-lock-string-face))))
(with-current-buffer +regex--groups-buffer
(insert line)))
(dolist (match matches)
(with-current-buffer +regex--groups-buffer
(goto-char (point-max))
(insert (format "Group %d: %s\n"
(propertize (car match) 'face 'font-lock-constant-face)
(propertize (cdr match) 'face 'font-lock-string-face)))))
(with-current-buffer +regex--groups-buffer
(insert ?\n))))))
;;;###autoload
(defun +regex-backend-emacs (regex replace str)
"TODO"
(cl-assert (stringp regex))
(cl-assert (stringp replace))
(cl-assert (stringp str))
(let ((i 0)
pos)
(when (buffer-live-p +regex--text-replace-buffer)
(with-current-buffer +regex--text-replace-buffer
(erase-buffer)
(insert str)
(when (and (listp results) (string-empty-p replace))
(replace-regexp regex replace))))
(while (and (setq pos (point))
(re-search-forward regex nil t))
(if (= (point) pos)
(forward-char 1)
(let ((ov (make-overlay (match-beginning 0) (match-end 0))))
(overlay-put ov 'face (nth (mod i 3) +regex-faces))
(overlay-put ov 'category '+regex))
(cl-incf i)
(dotimes (i 10)
(when-let (text (match-string i))
(save-match-data
(with-current-buffer +regex--groups-buffer
(goto-char (point-max))
(insert
(format "Group %d: %s\n"
(propertize i 'face 'font-lock-constant-face)
(propertize text 'face 'font-lock-string-face)))))))
(with-current-buffer +regex--groups-buffer
(insert ?\n))))))

View File

@@ -0,0 +1,51 @@
;;; app/regex/config.el
;; Often, I find myself writing regular expressions that could terrify seasoned
;; programmers (or little children). To hone my regex fu, I need a regex
;; playground. Sure, there's regexr.com, but don't be silly, that's not Emacs.
;;
;; Sadly, the Emacs' regex syntax is niche and lacks support for a few
;; questionably useful features, like lookaround assertions, conditionals, case
;; modifiers or backreferences, among others. No, I want PCRE. I am going to
;; have my cake and eat it too, damn it!
;;
;; Workflow:
;; + Invoke `=regex' (if opened with C-u, opens in separate workspace with a
;; dummy text buffer).
;; + A regex window will popup up. Any matches will be highlighted in the
;; original buffer.
;; + C-c C-k to close it
;; + TODO C-c C-e to export to various langauges
;;
;; WARNING: THIS IS A WORK IN PROGRESS
(defvar +regex-workspace-name "*regex*"
"TODO")
(defvar +regex-default-backend 'perl
"The backend used to process regular expressions.
The `emacs' backend handles regular expressions directly.
The `perl' backend talks to a perl subprocess to do the handling.")
(defvar +regex-dummy-text
"Welcome to DOOM Emacs, proudly hosted by the demons of hell!
Edit the Expression & Text to see matches. Roll over matches or the expression
for details. Undo mistakes with ctrl-z. Save Favorites & Share expressions with
friends or the Community. Explore your results with Tools. A full Reference &
Help is available in the Library, or watch the video Tutorial.
Sample text for testing:
abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789 _+-.,!@#$%^&*();\/|<>\"'
12345 -98.7 3.141 .6180 9,000 +42
555.123.4567 +1-(800)-555-2468
foo@demo.net bar.ba@test.co.uk
www.demo.com http://foo.co.uk/
http://regexr.com/foo.html?q=bar
https://mediatemple.net"
"TODO")
(set-popup-rules!
'(("^\\*doom-regex\\*$" :size 4 :quit nil)
("^\\*doom-regex-groups" :side 'left :size 28 :select nil :quit nil)))

View File

@@ -0,0 +1,117 @@
;;; app/rss/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defalias '=rss #'elfeed
"Activate (or switch to) `elfeed' in its workspace.")
;;;###autoload
(defun +rss/delete-pane ()
"Delete the *elfeed-entry* split pane."
(interactive)
(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)
"Display the currently selected item in a buffer."
(interactive (list (elfeed-search-selected :ignore-region)))
(when (elfeed-entry-p entry)
(elfeed-untag entry 'unread)
(elfeed-search-update-entry entry)
(elfeed-show-entry entry)))
;;;###autoload
(defun +rss/next ()
"Show the next item in the elfeed-search buffer."
(interactive)
(funcall elfeed-show-entry-delete)
(with-current-buffer (elfeed-search-buffer)
(forward-line)
(call-interactively '+rss/open)))
;;;###autoload
(defun +rss/previous ()
"Show the previous item in the elfeed-search buffer."
(interactive)
(funcall elfeed-show-entry-delete)
(with-current-buffer (elfeed-search-buffer)
(forward-line -1)
(call-interactively '+rss/open)))
;;
;; Hooks
;;;###autoload
(defun +rss-elfeed-wrap-h ()
"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-h ()
"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-h :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."
(let* ((years (or years 1.0))
(living-feeds (make-hash-table :test 'equal))
(seconds (* years 365.0 24 60 60))
(threshold (- (float-time) seconds)))
(with-elfeed-db-visit (entry feed)
(let ((date (elfeed-entry-date entry)))
(when (> date threshold)
(setf (gethash (elfeed-feed-url feed) living-feeds) t))))
(cl-loop for url in (elfeed-feed-list)
unless (gethash url living-feeds)
collect url)))
;;;###autoload
(defun +rss-put-sliced-image-fn (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-fn (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

@@ -0,0 +1,74 @@
;;; app/rss/config.el -*- lexical-binding: t; -*-
;; This is an opinionated workflow that turns Emacs into an RSS reader, inspired
;; 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 "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
(use-package! elfeed
:commands elfeed
:config
(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 #'pop-to-buffer
elfeed-show-entry-delete #'+rss/delete-pane
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
(add-hook! 'doom-real-buffer-functions
(defun +rss-buffer-p (buf)
(string-match-p "^\\*elfeed" (buffer-name buf))))
;; Enhance readability of a post
(add-hook 'elfeed-show-mode-hook #'+rss-elfeed-wrap-h)
(add-hook! 'elfeed-search-mode-hook
(add-hook 'kill-buffer-hook #'+rss-cleanup-h nil 'local))
;; 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-fn
shr-external-rendering-functions '((img . +rss-render-image-tag-without-underline-fn))))
;; Keybindings
(after! elfeed-show
(define-key! elfeed-show-mode-map
[remap next-buffer] #'+rss/next
[remap previous-buffer] #'+rss/previous))
(when (featurep! :editor 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)))
(use-package! elfeed-org
:when (featurep! +org)
:after elfeed
:config
(let ((default-directory org-directory))
(setq rmh-elfeed-org-files
(mapcar #'expand-file-name +rss-elfeed-files)))
(elfeed-org))

View File

@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; app/rss/packages.el
(package! elfeed)
(package! elfeed-org)

View File

@@ -0,0 +1,96 @@
#+TITLE: app/twitter
#+DATE: October 11, 2019
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
- [[#appendix][Appendix]]
- [[#commands--keybindings][Commands & Keybindings]]
* Description
Enjoy twitter from emacs.
+ View various timelines side by side, e.g. user's timeline, home, etc.
+ Post new tweets
+ Send direct messages
+ Retweet
+ Follow and un-follow users
+ Favorite tweets
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/hayamiz/twittering-mode][twittering-mode]]
+ [[https://github.com/abo-abo/avy][avy]]
** TODO Hacks
{A list of internal modifications to included packages}
* Prerequisites
+ For SSL connection (required by Twitter's API), one of the followings is required:
+ [[http://curl.haxx.se/][cURL]]
+ [[http://www.gnu.org/software/wget/][GNU Wget]]
+ [[http://www.openssl.org/][OpenSSL]]
+ [[http://www.gnu.org/software/gnutls/][GnuTLS]]
+ [[http://www.gnupg.org/][GnuPG]] is required for keeping the OAuth token encrypted in local storage.
+ ~twittering-icon-mode~ converts retrieved icons into XPM by default. To
achieve this and for displaying icons in formats that are not supported by
Emacs as well as for resizing icon images, [[http://www.imagemagick.org/][ImageMagick]] is required.
To build emacs with ImageMagick support the ~--with-imagemagick~ flag needs to
be passed to the ~configure~ script, e.g. ~./configure --with-imagemagick~.
For detailed instruction on how to build Emacs from source please refer to
[[https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL][INSTALL]] in Emacs' savannah repository.
+ For keeping retrieved icons in local storage, [[http://www.gzip.org/][gzip]] is required.
* TODO Features
An in-depth list of features, how to use them, and their dependencies.
* TODO Configuration
How to configure this module, including common problems and how to address them.
* Troubleshooting
Currently ~twittering-mode~ binds =SPC=, breaking its functionality as a leader
key. To work around this issue you may use =M-SPC= instead when in
~twittering-mode~.
* Appendix
** Commands & Keybindings
Here is a list of available commands and their default keybindings (defined in
[[./config.el][config.el]]).
| command | key / ex command | description |
|---------------------+------------------+-------------------------------------------------------------|
| ~+twitter/quit~ | =q= | Close current window |
| ~+twitter/quit-all~ | =Q= | Close all twitter windows and buffers, and delete workspace |
And when ~:editor evil +everywhere~ is active:
| command | key / ex command | description |
|--------------------------------------------------+------------------+------------------------------------------------------------------|
| ~twittering-favorite~ | =f= | Favorite/Like a tweet |
| ~twittering-unfavorite~ | =F= | Un-favorite/Un-like a tweet |
| ~twittering-follow~ | =C-f= | Follow user |
| ~twittering-unfollow~ | =C-F= | Un-follow user |
| ~twittering-delete-status~ | =d= | Delete a tweet |
| ~twittering-retweet~ | =r= | Retweet |
| ~twittering-toggle-or-retrieve-replied-statuses~ | =R= | Toggle or retrieve replies |
| ~twittering-update-status-interactive~ | =o= | Update tweets |
| ~+twitter/ace-link~ | =O= | Open some visible link from a ~twittering-mode~ buffer using ace |
| ~twittering-search~ | =/= | Search |
| ~twittering-goto-next-status~ | =J= | Go to next tweet |
| ~twittering-goto-previous-status~ | =K= | Go to previous tweet |
| ~twittering-goto-first-status~ | =gg= | Go to first tweet |
| ~twittering-goto-last-status~ | =G= | Go to last tweet |
| ~twittering-goto-next-status-of-user~ | =gj= | Go to next tweet of user |
| ~twittering-goto-previous-status-of-user)))~ | =gk= | Go to previous tweet of user |

View File

@@ -0,0 +1,103 @@
;;; app/twitter/autoload.el -*- lexical-binding: t; -*-
(defvar +twitter-workspace-name "*Twitter*"
"The name to use for the twitter workspace.")
;;;###autoload
(defun +twitter-display-buffer-fn (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! :ui 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"))
(switch-to-buffer (car twittering-initial-timeline-spec-string))
(dolist (name (cdr twittering-initial-timeline-spec-string))
(split-window-horizontally)
(switch-to-buffer name))
(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)
(cond ((one-window-p) (+twitter/quit-all))
((featurep! :ui 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)
(when (featurep! :ui 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 (buffer-list) t))
(with-current-buffer 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)
(require 'avy)
(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

@@ -0,0 +1,79 @@
;;; app/twitter/config.el -*- lexical-binding: t; -*-
(use-package! twittering-mode
:commands twit
:config
(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 "%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-rule! "^\\*twittering-edit" :size 15 :ttl nil :quit nil :select t)
(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-fn))
(after! solaire-mode
(add-hook 'twittering-mode-hook #'solaire-mode))
;; Custom header-line for twitter buffers
(add-hook! 'twittering-mode-hook
(defun +twitter-switch-mode-and-header-line-h ()
(setq header-line-format mode-line-format
mode-line-format nil)))
;; `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! :editor 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,5 @@
;; -*- no-byte-compile: t; -*-
;;; app/twitter/packages.el
(package! twittering-mode)
(package! avy)

View File

@@ -0,0 +1,131 @@
#+TITLE: app/write
#+DATE: October 10, 2019
#+SINCE: v1.3
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#language-tool][Language Tool]]
- [[#wordnut][Wordnut]]
- [[#features][Features]]
- [[#m-x-write-mode][~M-x +write-mode~]]
- [[#language-tool-langtool][Language Tool ~+langtool~]]
- [[#commands][Commands]]
- [[#wordnut-wordnut][Wordnut ~+wordnut~]]
- [[#commands-1][Commands]]
- [[#synosaurus][Synosaurus]]
- [[#commands-2][Commands]]
- [[#configuration][Configuration]]
- [[#mixed-pitch-mode][mixed-pitch-mode]]
- [[#appendix][Appendix]]
- [[#minor-modes][Minor modes]]
- [[#commands-3][Commands]]
* Description
Adds word processing tools and the ~+write-mode~ minor mode, which converts
Emacs into a more comfortable writing environment.
** Module Flags
This module provides two module flags:
- ~+langtool~ Enables language tool integration.
- ~+wordnut~ Enables wordnet integration.
** Plugins
+ [[https://github.com/hpdeifel/synosaurus][synosaurus]]
+ [[https://gitlab.com/jabranham/mixed-pitch][mixed-pitch]]
+ [[https://github.com/joostkremers/visual-fill-column][visual-fill-column]]
+ [[https://github.com/mhayashi1120/Emacs-langtool][langtool]]* (=+langtool=)
+ [[https://github.com/gromnitsky/wordnut][wordnut]]* (=+wordnut=)
* 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
* Features
** ~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~
* 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

@@ -0,0 +1,43 @@
;;; app/write/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(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-h ()
"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-h ()
(display-line-numbers-mode (if +write-mode +1 -1)))
;;;###autoload
(defun +write-init-mixed-pitch-h ()
(mixed-pitch-mode (if +write-mode +1 -1)))
;;;###autoload
(defun +write-init-visual-fill-column-h ()
(visual-fill-column-mode (if +write-mode +1 -1)))
;;;###autoload
(add-hook! '+write-mode-hook
#'(flyspell-mode
visual-line-mode
+write-init-mixed-pitch-h
+write-init-visual-fill-column-h
+write-init-line-numbers-h
+write-init-org-mode-h))

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
(use-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 2)))
(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,12 @@
;; -*- no-byte-compile: t; -*-
;;; app/write/packages.el
(package! synosaurus)
(package! mixed-pitch)
(when (featurep! +langtool)
(package! langtool))
(when (featurep! +wordnut)
(package! wordnut))
(package! visual-fill-column)

View File

@@ -0,0 +1,131 @@
#+TITLE: completion/company
#+DATE: February 19, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* 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-company-backends-in-certain-modes][Enable company backend(s) in certain modes]]
- [[#troubleshooting][Troubleshooting]]
- [[#x-mode-doesnt-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.]]
* 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.
https://assets.doomemacs.org/completion/company/overlay.png
** Module Flags
+ =+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+ and is incompatible with the =+tng= flag*
+ =+tng= Enables completion using only ~TAB~. Pressing ~TAB~ will select the
next completion suggestion, while ~S-TAB~ will select the previous one. *This
is incompatible with the =+childframe= flag*
** 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]]* (=+childframe=)
* Prerequisites
This module has no direct prerequisites.
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.
* Features
** Code completion
By default, completion is triggered after a short idle period or with the
=C-SPC= key. While the popup is visible, the following keys are available:
| Keybind | Description |
|---------+------------------------------------------|
| =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 |
** 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 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
(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 code completion isn't working for you, consider the following common causes
before you file a bug report:
** 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

@@ -0,0 +1,156 @@
;;; completion/company/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar +company-backend-alist
'((text-mode company-dabbrev company-yasnippet company-ispell)
(prog-mode company-capf company-yasnippet)
(conf-mode 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"
(declare (indent defun))
(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)
backends))))
;;
;;; Library
(defun +company--backends ()
(let (backends)
(let ((mode major-mode)
(modes (list major-mode)))
(while (setq mode (get mode 'derived-mode-parent))
(push mode modes))
(dolist (mode modes)
(dolist (backend (append (cdr (assq mode +company-backend-alist))
(default-value 'company-backends)))
(push backend backends)))
(delete-dups
(append (cl-loop for (mode . backends) in +company-backend-alist
if (or (eq major-mode mode) ; major modes
(and (boundp mode)
(symbol-value mode))) ; minor modes
append backends)
(nreverse backends))))))
;;
;;; Hooks
;;;###autoload
(defun +company-init-backends-h ()
"Set `company-backends' for the current buffer."
(if (not company-mode)
(remove-hook 'change-major-mode-after-body-hook #'+company-init-backends-h 'local)
(unless (eq major-mode 'fundamental-mode)
(setq-local company-backends (+company--backends)))
(add-hook 'change-major-mode-after-body-hook #'+company-init-backends-h nil 'local)))
(put '+company-init-backends-h '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
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
(all-completions
arg
(delete-dups
(split-string
(replace-regexp-in-string
"^[\t\s]+" ""
(concat (buffer-substring-no-properties (point-min) (line-beginning-position))
(buffer-substring-no-properties (line-end-position) (point-max))))
"\\(\r\n\\|[\n\r]\\)" t))))))
;;;###autoload
(defun +company/dict-or-keywords ()
"`company-mode' completion combining `company-dict' and `company-keywords'."
(interactive)
(require 'company-dict)
(require 'company-keywords)
(let ((company-backends '((company-keywords company-dict))))
(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)
(company-select-previous-or-abort)))

View File

@@ -0,0 +1,133 @@
;;; completion/company/config.el -*- lexical-binding: t; -*-
(use-package! company
:commands company-complete-common company-manual-begin company-grab-line
:after-call pre-command-hook after-find-file
:init
(setq company-minimum-prefix-length 2
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 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
(when (featurep! :editor evil)
(add-hook 'company-mode-hook #'evil-normalize-keymaps)
;; Allow users to switch between backends on the fly. E.g. C-x C-s followed
;; by C-x C-n, will switch from `company-yasnippet' to
;; `company-dabbrev-code'.
(defadvice! +company--abort-previous-a (&rest _)
:before #'company-begin-backend
(company-abort)))
(add-hook 'company-mode-hook #'+company-init-backends-h)
(global-company-mode +1))
(use-package! company-tng
:when (featurep! +tng)
:after-call post-self-insert-hook
:config
(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
(after! company-files
(pushnew! company-files--regexps
"file:\\(\\(?:\\.\\{1,2\\}/\\|~/\\|/\\)[^\]\n]*\\)"))
(use-package! company-prescient
:hook (company-mode . company-prescient-mode)
:config
;; NOTE prescient config duplicated with `ivy'
(setq prescient-save-file (concat doom-cache-dir "prescient-save.el"))
(prescient-persist-mode +1))
(use-package! company-box
:when (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-fn
company-box-icons--lsp
+company-box-icons--elisp-fn
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-fn (candidate)
(when (get-text-property 0 'yas-annotation candidate)
'Yasnippet))
(defun +company-box-icons--elisp-fn (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))))))
(use-package! company-dict
:defer t
:config
(setq company-dict-dir (expand-file-name "dicts" doom-private-dir))
(add-hook! 'doom-project-hook
(defun +company-enable-project-dicts-h (mode &rest _)
"Enable per-project dictionaries."
(if (symbol-value mode)
(add-to-list 'company-dict-minor-mode-list mode nil #'eq)
(setq company-dict-minor-mode-list (delq mode company-dict-minor-mode-list))))))

View File

@@ -0,0 +1,8 @@
;; -*- no-byte-compile: t; -*-
;;; completion/company/packages.el
(package! company)
(package! company-dict)
(package! company-prescient)
(when (featurep! +childframe)
(package! company-box))

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! '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! '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! '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! 'prog-mode 'old)
(set-company-backend! 'emacs-lisp-mode 'child)
(set-company-backend! 'prog-mode nil)
(expect (backends 'emacs-lisp-mode) :to-equal '(child t)))
(it "overwrites past cumulative backends"
(set-company-backend! '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! 'prog-mode 'base)
(set-company-backend! 'emacs-lisp-mode 'child)
(set-company-backend! 'prog-mode 'new)
(expect (backends 'emacs-lisp-mode) :to-equal '(child new t)))))

View File

@@ -0,0 +1,42 @@
;;; completion/helm/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+helm:project-search "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:project-search (all-files-p query)
"Ex interface for `+helm/grep'"
(interactive "<!><a>")
(+helm/project-search all-files-p query))
;;;###autoload (autoload '+helm:project-search-from-cwd "completion/helm/autoload/evil" nil t)
(evil-define-command +helm:project-search-from-cwd (query &optional recurse-p)
"Ex interface for `+helm/grep-from-cwd'."
(interactive "<a><!>")
(+helm/project-search-from-cwd (not recurse-p) query))
;;;###autoload
(defun +helm--set-prompt-display (pos)
"TODO"
(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,96 @@
;;; completion/helm/autoload/helm.el -*- lexical-binding: t; -*-
;;;###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! :ui workspaces)
(user-error "This command requires the :ui 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! :ui workspaces)
(user-error "This command requires the :ui workspaces module"))
(with-no-warnings
(with-persp-buffer-list nil (helm-mini))))
;;
;;; Project search
;;;###autoload
(cl-defun +helm-file-search (&key query in all-files (recursive t))
"Conduct a file search using ripgrep.
: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))
(unless (executable-find "rg")
(user-error "Couldn't find ripgrep in your PATH"))
(require 'helm-rg)
(let ((helm-rg-default-directory (or in (doom-project-root) default-directory))
(helm-rg-default-extra-args
(delq nil (list (when all-files "-z -uu")
(unless recursive "--maxdepth 1")))))
(helm-rg (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)
(buffer-substring-no-properties beg end))))
""))))
;;;###autoload
(defun +helm/project-search (&optional arg initial-query directory)
"Performs a project search from the project root with ripgrep.
ARG (universal argument), include all files, even hidden or compressed ones, in
the search."
(interactive "P")
(+helm-file-search
:query initial-query
:in directory
:all-files (and (not (null arg))
(listp arg))))
;;;###autoload
(defun +helm/project-search-from-cwd (&optional arg initial-query)
"Performs a project search recursively from the current directory.
If ARG (universal argument), include all files, even hidden or compressed ones."
(interactive "P")
(+helm-file-search
:query initial-query
:in default-directory
:all-files (and (not (null arg))
(listp arg))))
;;;###autoload
(defun +helm/jump-list ()
"TODO"
(interactive)
(error "not implemented yet"))

View File

@@ -0,0 +1,59 @@
;;; completion/helm/autoload/posframe.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +helm-poshandler-frame-center-near-bottom-fn (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-fn (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-h ()
"TODO"
;; Ensure focus is properly returned to the underlying window. This gives the
;; modeline a chance to refresh.
(switch-to-buffer +helm--posframe-buffer t))
(add-hook 'helm-cleanup-hook #'+helm-posframe-cleanup-h)

View File

@@ -0,0 +1,185 @@
;;; completion/helm/config.el -*- lexical-binding: t; -*-
;; Posframe (requires +childframe)
(defvar +helm-posframe-handler #'+helm-poshandler-frame-center-near-bottom-fn
"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
(use-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 locate] #'helm-locate
[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)))
(use-package! helm
: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
;; 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! :editor evil +everywhere)
(setq helm-default-prompt-display-function #'+helm--set-prompt-display))
:init
(when (featurep! +childframe)
(setq helm-display-function #'+helm-posframe-display-fn))
(let ((fuzzy (featurep! +fuzzy)))
(setq helm-M-x-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
(set-popup-rule! "^\\*helm" :vslot -100 :size 0.22 :ttl nil)
;; HACK Doom doesn't support these commands, which invite the user to install
;; the package via ELPA. Force them to use +helm/* instead, because they work
;; out of the box.
(advice-add #'helm-projectile-rg :override #'+helm/project-search)
(advice-add #'helm-projectile-ag :override #'+helm/project-search)
(advice-add #'helm-projectile-grep :override #'+helm/project-search)
;; 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)
;; Use helpful instead of describe-* to display documentation
(dolist (fn '(helm-describe-variable helm-describe-function))
(advice-add fn :around #'doom-use-helpful-a)))
(use-package! helm-flx
:when (featurep! +fuzzy)
:hook (helm-mode . helm-flx-mode)
:config (helm-flx-mode +1))
(after! helm-rg
(setq helm-rg-display-buffer-normal-method #'pop-to-buffer)
(set-popup-rule! "^helm-rg-" :ttl nil :select t :size 0.45)
(map! :map helm-rg-map
"C-c C-e" #'helm-rg--bounce)
(map! :map helm-rg--bounce-mode-map
"q" #'kill-current-buffer
"C-c C-c" (λ! (helm-rg--bounce-dump) (kill-current-buffer))
"C-x C-c" #'helm-rg--bounce-dump-current-file
"C-c C-k" #'kill-current-buffer))
;;;###package helm-bookmark
(setq helm-bookmark-show-location t)
(after! helm-files
(setq helm-boring-file-regexp-list
(append (list "\\.projects$" "\\.DS_Store$")
helm-boring-file-regexp-list)))
(defvar helm-generic-files-map (make-sparse-keymap))
(after! helm-locate
(when (and IS-MAC
(null helm-locate-command)
(executable-find "mdfind"))
(setq helm-locate-command "mdfind -name %s"))
(set-keymap-parent helm-generic-files-map helm-map))
(use-package! helm-org
:when (featurep! :lang org)
:defer t
:init
(after! helm-mode
(pushnew! helm-completing-read-handlers-alist
'(org-capture . helm-org-completing-read-tags)
'(org-set-tags . helm-org-completing-read-tags))))
(use-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
(set-keymap-parent helm-projectile-find-file-map helm-map))
(setq ivy-height 20) ; for `swiper-isearch'
(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

@@ -0,0 +1,16 @@
;; -*- no-byte-compile: t; -*-
;;; completion/helm/packages.el
(package! helm)
(package! helm-rg)
(package! helm-c-yasnippet)
(package! helm-company)
(package! helm-describe-modes :recipe (:host github :repo "emacs-helm/helm-describe-modes"))
(package! helm-projectile)
(package! swiper-helm)
(when (featurep! +fuzzy)
(package! helm-flx))
(when (featurep! +childframe)
(package! posframe))
(when (featurep! :lang org)
(package! helm-org))

View File

@@ -0,0 +1,59 @@
;;; completion/ido/config.el -*- lexical-binding: t; -*-
(defun +ido-init-h ()
(setq ido-ignore-buffers
'("\\` " "^\\*ESS\\*" "^\\*Messages\\*" "^\\*Help\\*" "^\\*Buffer"
"^\\*.*Completions\\*$" "^\\*Ediff" "^\\*tramp" "^\\*cvs-"
"_region_" " output\\*$" "^TAGS$" "^\*Ido")
ido-use-faces nil
ido-confirm-unique-completion t
ido-case-fold t
ido-enable-tramp-completion nil
ido-enable-flex-matching t
ido-create-new-buffer 'always
ido-enable-tramp-completion t
ido-enable-last-directory-history t
ido-save-directory-list-file (concat doom-cache-dir "ido.last"))
(unless (member "\\`.DS_Store$" ido-ignore-files)
(push "\\`.DS_Store$" ido-ignore-files)
(push "Icon\\?$" ido-ignore-files))
(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))))
(defadvice! +ido--sort-mtime-a ()
"Sort ido filelist by mtime instead of alphabetically."
:override #'ido-sort-mtime
(setq ido-temp-list
(sort ido-temp-list
(lambda (a b)
(time-less-p
(sixth (file-attributes (concat ido-current-directory b)))
(sixth (file-attributes (concat ido-current-directory a)))))))
(ido-to-end ;; move . files to end (again)
(cl-loop for x in ido-temp-list
if (char-equal (string-to-char x) ?.)
collect x)))
(add-hook! '(ido-make-file-list-hook ido-make-dir-list-hook)
#'ido-sort-mtime)
;;
(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-h))
;;
(add-hook 'ido-setup-hook #'+ido-init-h)

View File

@@ -0,0 +1,7 @@
;; -*- no-byte-compile: t; -*-
;;; completion/ido/packages.el
(package! flx-ido)
(package! ido-completing-read+)
(package! ido-vertical-mode)
(package! crm-custom)

View File

@@ -0,0 +1,194 @@
#+TITLE: completion/ivy
#+DATE: February 13, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* 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]]
- [[#opensuse][openSUSE]]
- [[#features][Features]]
- [[#jump-to-navigation][Jump-to navigation]]
- [[#project-search--replace][Project search & replace]]
- [[#in-buffer-searching][In-buffer searching]]
- [[#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-fuzzynon-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 ripgrep.
#+begin_quote
I prefer ivy over ido for its flexibility. I prefer ivy over helm because it's
lighter, simpler and faster in many cases.
#+end_quote
** Module Flags
+ =+fuzzy= Enables fuzzy completion for Ivy searches.
+ =+prescient= Enables prescient filtering and sorting for Ivy searches.
+ =+childframe= Causes Ivy to display in a floating child frame, above Emacs.
+ =+icons= Enables file icons for switch-{buffer,project}/find-file counsel
commands.
** 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/raxod502/prescient.el][prescient]]* (=+prescient=)
+ [[https://github.com/tumashu/ivy-posframe][ivy-posframe]]* (=+childframe=)
+ [[https://github.com/asok/all-the-icons-ivy][all-the-icons-ivy]]* (=+icons=)
** Hacks
+ Functions with ivy/counsel equivalents have been globally remapped (like
~find-file~ => ~counsel-find-file~). So a keybinding to ~find-file~ will
invoke ~counsel-find-file~ instead.
+ ~counsel-[arp]g~'s 3-character limit was reduced to 1 (mainly for the ex
command)
* Prerequisites
This module depends on:
+ [[https://github.com/BurntSushi/ripgrep][ripgrep]] (rg)
** Install
*** MacOS
#+BEGIN_SRC sh
brew install ripgrep
#+END_SRC
*** Arch Linux
#+BEGIN_SRC sh :dir /sudo::
sudo pacman --needed --noconfirm -S ripgrep
#+END_SRC
*** openSUSE
#+BEGIN_SRC sh :dir /sudo::
sudo zypper install ripgrep
#+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 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 p f=, =SPC SPC= | Jump to file in project |
| =SPC f f=, =SPC .= | Jump to file from current directory |
| =SPC s i= | Jump to symbol in file |
** Project search & replace
This module provides interactive text search and replace using ripgrep.
| Keybind | Description |
|-----------+--------------------------|
| =SPC s p= | Search project |
| =SPC s P= | Search another project |
| =SPC s d= | Search this directory |
| =SPC s D= | Search another directory |
https://assets.doomemacs.org/completion/ivy/search.png
Prefixing these keys with 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 |
|------------------------+------------------------------------------------------------------|
| ~:pg[rep][!] [QUERY]~ | Search project (if ~!~, include hidden files) |
| ~:pg[rep]d[!] [QUERY]~ | Search from current directory (if ~!~, don't search recursively) |
The optional `!` is equivalent to the universal argument for the previous
commands.
-----
These keybindings are available while a search is active:
| Keybind | Description |
|-----------+-----------------------------------------------|
| =C-c C-o= | Open a buffer with your search results |
| =C-c C-e= | 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 =C-c C-e=) can be committed
with =C-c C-c= and aborted with =C-c C-k= (alternatively =ZZ= and =ZQ=, for evil
users).
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 s s= (~swiper-isearch~)
+ =SPC s S= (~swiper-isearch-thing-at-point~)
+ =SPC s b= (~swiper~)
+ ~:sw[iper] [QUERY]~
https://assets.doomemacs.org/completion/ivy/swiper.png
A wgrep buffer can be opened from swiper with =C-c C-e=.
** 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 f=, =SPC .= | Browse from current directory |
| =SPC p f=, =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 p t= | List all TODO/FIXMEs in project |
| =SPC s b= | Search the current buffer |
| =SPC s d= | Search this directory |
| =SPC s D= | Search another directory |
| =SPC s i= | Search for symbol in current buffer |
| =SPC s p= | Search project |
| =SPC s P= | Search another project |
| =SPC s s= | Search the current buffer (incrementally) |
* Configuration
** TODO Enable fuzzy/non-fuzzy search for specific commands
** TODO Change the position of the ivy childframe
* TODO Troubleshooting

View File

@@ -0,0 +1,14 @@
;; completion/ivy/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+ivy:project-search "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:project-search (query &optional all-files-p)
"Ex interface for `+ivy/project-search'."
(interactive "<a><!>")
(+ivy/project-search all-files-p query))
;;;###autoload (autoload '+ivy:project-search-from-cwd "completion/ivy/autoload/evil" nil t)
(evil-define-command +ivy:project-search-from-cwd (query &optional recurse-p)
"Ex interface for `+ivy/project-search-from-cwd'."
(interactive "<a><!>")
(+ivy/project-search-from-cwd (not recurse-p) query))

View File

@@ -0,0 +1,33 @@
;;; completion/ivy/autoload/hydras.el -*- lexical-binding: t; -*-
;;;###if (featurep! :ui hydra)
(eval-when-compile (require 'ivy-hydra))
;;;###autoload (autoload 'hydra-ivy/body "completion/ivy/autoload/hydras" nil nil)
(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

@@ -0,0 +1,366 @@
;;; completion/ivy/autoload/ivy.el -*- lexical-binding: t; -*-
(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-standard-search (str)
"TODO"
(funcall +ivy-standard-search-fn str))
;;;###autoload
(defun +ivy-alternative-search (str)
"TODO"
(funcall +ivy-alternative-search-fn str))
;;;###autoload
(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)))
(when (null uniquify-buffer-name-style)
(setq candidate (replace-regexp-in-string "<[0-9]+>$" "" 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."
;; NOTE This is inspired by `all-the-icons-ivy-buffer-transformer', minus the
;; buffer name and extra padding as those are handled by `ivy-rich'.
(propertize "\t" 'display
(if-let* ((buffer (get-buffer candidate))
(mode (buffer-local-value 'major-mode buffer)))
(or
(all-the-icons-ivy--icon-for-mode mode)
(all-the-icons-ivy--icon-for-mode (get mode 'derived-mode-parent))
(funcall
all-the-icons-ivy-family-fallback-for-buffer
all-the-icons-ivy-name-fallback-for-buffer))
(all-the-icons-icon-for-file candidate))))
;;;###autoload
(defun +ivy-rich-describe-variable-transformer (cand)
"Previews the value of the variable in the minibuffer"
(let* ((sym (intern cand))
(val (and (boundp sym) (symbol-value sym)))
(print-level 3))
(replace-regexp-in-string
"[\n\t\^[\^M\^@\^G]" " "
(cond ((booleanp val)
(propertize (format "%s" val) 'face
(if (null val)
'font-lock-comment-face
'success)))
((symbolp val)
(propertize (format "'%s" val)
'face 'highlight-quoted-symbol))
((keymapp val)
(propertize "<keymap>" 'face 'font-lock-constant-face))
((listp val)
(prin1-to-string val))
((stringp val)
(propertize (format "%S" val) 'face 'font-lock-string-face))
((numberp val)
(propertize (format "%s" val) 'face 'highlight-numbers-number))
((format "%s" val)))
t)))
;;
;; 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
;; NOTE A clever disguise, needed for virtual buffers.
:caller #'ivy-switch-buffer)))
;;;###autoload
(defun +ivy/switch-workspace-buffer (&optional arg)
"Switch to another buffer within the current workspace.
If ARG (universal argument), open selection in other-window."
(interactive "P")
(+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))
;;;###autoload
(defun +ivy/woccur ()
"Invoke a wgrep buffer on the current ivy results, if supported."
(interactive)
(unless (window-minibuffer-p)
(user-error "No completion session is active"))
(require 'wgrep)
(let ((caller (ivy-state-caller ivy-last)))
(if-let (occur-fn (plist-get +ivy-edit-functions caller))
(ivy-exit-with-action
(lambda (_) (funcall occur-fn)))
(if-let (occur-fn (plist-get ivy--occurs-list caller))
(let ((buffer (generate-new-buffer
(format "*ivy-occur%s \"%s\"*"
(if caller (concat " " (prin1-to-string caller)) "")
ivy-text))))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(funcall occur-fn))
(setf (ivy-state-text ivy-last) ivy-text)
(setq ivy-occur-last ivy-last)
(setq-local ivy--directory ivy--directory))
(ivy-exit-with-action
`(lambda (_)
(pop-to-buffer ,buffer)
(ivy-wgrep-change-to-wgrep-mode))))
(user-error "%S doesn't support wgrep" caller)))))
;;;###autoload
(defun +ivy-yas-prompt (prompt choices &optional display-fn)
(yas-completing-prompt prompt choices display-fn #'ivy-completing-read))
;;;###autoload
(defun +ivy-git-grep-other-window-action (x)
"Opens the current candidate in another window."
(when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x)
(select-window
(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 (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)
(run-hooks 'counsel-grep-post-action-hook)
(selected-window))))))
;;;###autoload
(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 (&key query in all-files (recursive t))
"Conduct a file search using ripgrep.
: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))
(unless (executable-find "rg")
(user-error "Couldn't find ripgrep in your PATH"))
(require 'counsel)
(let* ((ivy-more-chars-alist '((t . 1)))
(project-root (or (doom-project-root) default-directory))
(directory (or in project-root))
(default-directory directory)
(args (concat (if all-files " -uu")
(unless recursive " --maxdepth 1"))))
(counsel-rg
(or (if query 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)
(let ((query (buffer-substring-no-properties beg end)))
;; Escape characters that are special to ivy searches
(replace-regexp-in-string "[! |]" (lambda (substr)
(cond ((and (string= substr " ")
(not (featurep! +fuzzy)))
" ")
((string= substr "|")
"\\\\\\\\|")
((concat "\\\\" substr))))
(rxt-quote-pcre query)))))))
directory args
(format "rg%s %s"
args
(cond ((equal directory default-directory)
"./")
((equal directory project-root)
(projectile-project-name))
((file-relative-name directory project-root)))))))
;;;###autoload
(defun +ivy/project-search (&optional arg initial-query directory)
"Performs a live project search from the project root using ripgrep.
If ARG (universal argument), include all files, even hidden or compressed ones,
in the search."
(interactive "P")
(+ivy-file-search :query initial-query :in directory :all-files arg))
;;;###autoload
(defun +ivy/project-search-from-cwd (&optional arg initial-query)
"Performs a project search recursively from the current directory.
If ARG (universal argument), include all files, even hidden or compressed ones."
(interactive "P")
(+ivy/project-search arg initial-query default-directory))
;;
;;; Wrappers around `counsel-compile'
;;;###autoload
(defun +ivy/compile ()
"Execute a compile command from the current buffer's directory."
(interactive)
(counsel-compile default-directory))
;;;###autoload
(defun +ivy/project-compile ()
"Execute a compile command from the current project's root."
(interactive)
(counsel-compile (projectile-project-root)))
;;;###autoload
(defun +ivy/jump-list ()
"Go to an entry in evil's (or better-jumper's) jumplist."
(interactive)
;; REVIEW Refactor me
(let (buffers)
(unwind-protect
(ivy-read "jumplist: "
(nreverse
(delete-dups
(delq
nil
(mapcar (lambda (mark)
(when mark
(cl-destructuring-bind (path pt _id) mark
(let ((buf (get-file-buffer path)))
(unless buf
(push (setq buf (find-file-noselect path t))
buffers))
(with-current-buffer buf
(goto-char pt)
(font-lock-fontify-region (line-beginning-position) (line-end-position))
(cons (format "%s:%d: %s"
(buffer-name)
(line-number-at-pos)
(string-trim-right (or (thing-at-point 'line) "")))
(point-marker)))))))
(cddr (better-jumper-jump-list-struct-ring
(better-jumper-get-jumps (better-jumper--get-current-context))))))))
:sort nil
:require-match t
:action (lambda (cand)
(let ((mark (cdr cand)))
(delq! (marker-buffer mark) buffers)
(mapc #'kill-buffer buffers)
(setq buffers nil)
(with-current-buffer (switch-to-buffer (marker-buffer mark))
(goto-char (marker-position mark)))))
:caller '+ivy/jump-list)
(mapc #'kill-buffer buffers))))
;;;###autoload
(defun +ivy/git-grep-other-window-action ()
"Open the current counsel-{ag,rg,git-grep} candidate in other-window."
(interactive)
(ivy-set-action #'+ivy-git-grep-other-window-action)
(setq ivy-exit 'done)
(exit-minibuffer))

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-fn (str)
"TODO"
(ivy-posframe--display str #'+ivy-poshandler-frame-center-near-bottom-fn))
;;;###autoload
(defun +ivy-poshandler-frame-center-near-bottom-fn (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

@@ -0,0 +1,350 @@
;;; completion/ivy/config.el -*- lexical-binding: t; -*-
(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-buffer-unreal-face 'font-lock-comment-face
"The face for unreal buffers in `ivy-switch-to-buffer'.")
(defvar +ivy-edit-functions nil
"A plist mapping ivy/counsel commands to commands that generate an editable
results buffer.")
(defvar +ivy-standard-search-fn
(if (featurep! +prescient)
#'+ivy-prescient-non-fuzzy
#'ivy--regex-plus)
"Function to use for non-fuzzy search commands.
This uses the standard search algorithm ivy uses (or a variant of it).")
(defvar +ivy-alternative-search-fn
(cond ((featurep! +prescient) #'ivy-prescient-re-builder)
((featurep! +fuzzy) #'ivy--regex-fuzzy)
;; Ignore order for non-fuzzy searches by default
(#'ivy--regex-ignore-order))
"Function to use for fuzzy search commands.
This uses a search algorithm other than ivy's default.")
;;
;;; Packages
(use-package! ivy
:after-call pre-command-hook
:init
(setq ivy-re-builders-alist
`((counsel-rg . +ivy-standard-search)
(swiper . +ivy-standard-search)
(swiper-isearch . +ivy-standard-search)
(t . +ivy-alternative-search)))
(define-key!
[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 evil-show-jumps] #'+ivy/jump-list)
:config
;; Counsel changes a lot of ivy's state at startup; to control for that, we
;; need to load it as early as possible. Some packages (like `ivy-prescient')
;; require this.
(require 'counsel nil t)
(setq ivy-height 17
ivy-wrap t
ivy-fixed-height-minibuffer t
projectile-completion-system 'ivy
;; disable magic slash on non-match
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 #'ignore
;; enable ability to select prompt (alternative to `ivy-immediate-done')
ivy-use-selectable-prompt t)
;; Highlight each ivy candidate including the following newline, so that it
;; extends to the right edge of the window
(setf (alist-get 't ivy-format-functions-alist)
#'ivy-format-function-line)
;; Integrate `ivy' with `better-jumper'; ensure a jump point is registered
;; before jumping to new locations with ivy
(setf (alist-get 't ivy-hooks-alist)
(lambda ()
(with-ivy-window
(setq +ivy--origin (point-marker)))))
(add-hook! 'minibuffer-exit-hook
(defun +ivy--set-jump-point-maybe-h ()
(and (markerp (bound-and-true-p +ivy--origin))
(not (equal (ignore-errors (with-ivy-window (point-marker)))
+ivy--origin))
(with-current-buffer (marker-buffer +ivy--origin)
(better-jumper-set-jump +ivy--origin)))
(setq +ivy--origin nil)))
(after! yasnippet
(add-hook 'yas-prompt-functions #'+ivy-yas-prompt))
(defadvice! +ivy--inhibit-completion-in-region-a (orig-fn &rest args)
"`ivy-completion-in-region' struggles with completing certain
evil-ex-specific constructs, so we disable it solely in evil-ex."
:around #'evil-ex
(let ((completion-in-region-function #'completion--in-region))
(apply orig-fn args)))
(define-key ivy-minibuffer-map (kbd "C-c C-e") #'+ivy/woccur)
(ivy-mode +1)
(use-package! ivy-hydra
:commands (ivy-dispatching-done ivy--matcher-desc ivy-hydra/body)
:init
(define-key! ivy-minibuffer-map
"C-o" #'ivy-dispatching-done
"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)))
(use-package! ivy-rich
:after ivy
:config
(setq ivy-rich-parse-remote-buffer nil)
(when (featurep! +icons)
(cl-pushnew '(+ivy-rich-buffer-icon)
(cadr (plist-get ivy-rich-display-transformers-list
'ivy-switch-buffer))))
;; Include variable value in `counsel-describe-variable'
(plist-put! ivy-rich-display-transformers-list
'counsel-describe-variable
'(:columns
((counsel-describe-variable-transformer (:width 40)) ; the original transformer
(+ivy-rich-describe-variable-transformer (:width 50))
(ivy-rich-counsel-variable-docstring (:face font-lock-doc-face)))))
;; 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)))
;; Apply switch buffer transformers to `counsel-projectile-switch-to-buffer' as well
(plist-put! ivy-rich-display-transformers-list
'counsel-projectile-switch-to-buffer
(plist-get ivy-rich-display-transformers-list 'ivy-switch-buffer))
(ivy-rich-mode +1))
(use-package! all-the-icons-ivy
:when (featurep! +icons)
:after ivy
:config
;; `all-the-icons-ivy' is incompatible with ivy-rich's switch-buffer
;; modifications, so we disable them and merge them ourselves
(setq all-the-icons-ivy-buffer-commands nil)
(all-the-icons-ivy-setup)
(after! counsel-projectile
(let ((all-the-icons-ivy-file-commands '(counsel-projectile
counsel-projectile-find-file
counsel-projectile-find-dir)))
(all-the-icons-ivy-setup))))
(use-package! counsel
:defer t
:init
(define-key!
[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 swiper] #'counsel-grep-or-swiper
[remap evil-ex-registers] #'counsel-evil-registers
[remap evil-show-marks] #'counsel-mark-ring
[remap yank-pop] #'counsel-yank-pop
[remap load-theme] #'counsel-load-theme
[remap locate] #'counsel-locate
[remap unicode-chars-list-chars] #'counsel-unicode-char
[remap compile] #'+ivy/compile
[remap projectile-compile-project] #'+ivy/project-compile)
:config
(set-popup-rule! "^\\*ivy-occur" :size 0.35 :ttl 0 :quit nil)
;; Don't use ^ as initial input. Set this here because `counsel' defines more
;; of its own, on top of the defaults.
(setq ivy-initial-inputs-alist nil)
;; REVIEW Move this somewhere else and perhaps generalize this so both
;; ivy/helm users can enjoy it.
(defadvice! +ivy--counsel-file-jump-use-fd-rg-a (args)
"Change `counsel-file-jump' to use fd or ripgrep, if they are available."
:override #'counsel--find-return-list
(cl-destructuring-bind (find-program . args)
(cond ((executable-find "fd")
(cons "fd" (list "-t" "f" "-E" ".git")))
((executable-find "rg")
(cons "rg" (list "--files" "--hidden" "--no-messages")))
((cons find-program args)))
(unless (listp args)
(user-error "`counsel-file-jump-args' is a list now, please customize accordingly."))
(counsel--call
(cons find-program args)
(lambda ()
(goto-char (point-min))
(let ((offset (if (member find-program '("fd" "rg")) 0 2))
files)
(while (< (point) (point-max))
(push (buffer-substring
(+ offset (line-beginning-position)) (line-end-position)) files)
(forward-line 1))
(nreverse files))))))
;; Integrate with `helpful'
(setq counsel-describe-function-function #'helpful-callable
counsel-describe-variable-function #'helpful-variable)
;; Make `counsel-compile' projectile-aware (if you prefer it over
;; `+ivy/compile' and `+ivy/project-compile')
(add-to-list 'counsel-compile-root-functions #'projectile-project-root)
(after! savehist
;; Persist `counsel-compile' history
(add-to-list 'savehist-additional-variables 'counsel-compile-history))
;; Use spotlight on mac for `counsel-locate' by default, since it doesn't need
;; any additional setup.
(when IS-MAC
(setq counsel-locate-cmd #'counsel-locate-cmd-mdfind))
;; Don't mess with font-locking on the dashboard; it causes breakages
(add-to-list 'swiper-font-lock-exclude #'+doom-dashboard-mode)
;; Record in jumplist when opening files via counsel-{ag,rg,pt,git-grep}
(add-hook 'counsel-grep-post-action-hook #'better-jumper-set-jump)
;; Configure `counsel-find-file'
(setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)")
(ivy-add-actions
'counsel-find-file
'(("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) (with-ivy-window (insert (format "[[./%s]]" (file-relative-name path default-directory)))))
"insert relative org-link")
("L" (lambda (path) (with-ivy-window (insert (format "[[%s]]" path))))
"Insert absolute org-link")))
(ivy-add-actions
'counsel-ag ; also applies to `counsel-rg'
'(("O" +ivy-git-grep-other-window-action "open in other window"))))
(use-package! counsel-projectile
:defer t
:init
(define-key!
[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
;; A more sensible `counsel-projectile-find-file' that reverts 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.
(setf (alist-get 'projectile-find-file counsel-projectile-key-bindings)
#'+ivy/projectile-find-file)
;; no highlighting visited files; slows down the filtering
(ivy-set-display-transformer #'counsel-projectile-find-file nil))
(use-package! wgrep
:commands wgrep-change-to-wgrep-mode
:config (setq wgrep-auto-save-buffer t))
(use-package! ivy-posframe
:when (featurep! +childframe)
:hook (ivy-mode . ivy-posframe-mode)
:config
(setq ivy-fixed-height-minibuffer nil
ivy-posframe-border-width 10
ivy-posframe-parameters
`((min-width . 90)
(min-height . ,ivy-height)))
;; default to posframe display function
(setf (alist-get t ivy-posframe-display-functions-alist)
#'+ivy-display-at-frame-center-near-bottom-fn)
;; posframe doesn't work well with async sources (the posframe will
;; occasionally stop responding/redrawing), and causes violent resizing of the
;; posframe.
(dolist (fn '(swiper counsel-rg counsel-grep counsel-git-grep))
(setf (alist-get fn ivy-posframe-display-functions-alist)
#'ivy-display-function-fallback)))
(use-package! flx
:when (featurep! +fuzzy)
:unless (featurep! +prescient)
:defer t ; is loaded by ivy
:init (setq ivy-flx-limit 10000))
(use-package! ivy-prescient
:hook (ivy-mode . ivy-prescient-mode)
:when (featurep! +prescient)
:init
(setq prescient-filter-method
(if (featurep! +fuzzy)
'(literal regexp initialism fuzzy)
'(literal regexp initialism))
ivy-prescient-enable-filtering nil ; we do this ourselves
ivy-prescient-retain-classic-highlighting t)
:config
(defun +ivy-prescient-non-fuzzy (str)
(let ((prescient-filter-method '(literal regexp)))
(ivy-prescient-re-builder str)))
;; NOTE prescient config duplicated with `company'
(setq prescient-save-file (concat doom-cache-dir "prescient-save.el"))
(prescient-persist-mode +1))
;;;###package swiper
(setq swiper-action-recenter t)
;;;###package amx
(setq amx-save-file (concat doom-cache-dir "amx-items")) ; used by `counsel-M-x'

View File

@@ -0,0 +1,3 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; completion/ivy/doctor.el

View File

@@ -0,0 +1,22 @@
;; -*- no-byte-compile: t; -*-
;;; completion/ivy/packages.el
(package! amx)
(package! ivy)
(package! counsel)
(package! counsel-projectile)
(package! swiper)
(package! ivy-hydra)
(package! ivy-rich)
(package! wgrep)
(if (featurep! +prescient)
(package! ivy-prescient)
(when (featurep! +fuzzy)
(package! flx)))
(when (featurep! +childframe)
(package! ivy-posframe))
(when (featurep! +icons)
(package! all-the-icons-ivy))

View File

@@ -0,0 +1,412 @@
;;; 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))
;;
;;; Autoloads
(autoload 'org-capture-goto-target "org-capture" nil t)
;;
;;; Leader keys
(map! :leader
:desc "Evaluate line/region" "e" #'+eval/line-or-region
(:prefix ("l" . "<localleader>")) ; bound locally
(:prefix ("!" . "checkers")) ; bound by flycheck
;;; <leader> f --- file
(:prefix-map ("f" . "file")
:desc "Open project editorconfig" "." #'editorconfig-find-current-editorconfig
: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 "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 "Delete this file" "K" #'doom/delete-this-file
:desc "Move this file" "m" #'doom/move-this-file
: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 "Yank filename" "y" #'+default/yank-buffer-filename
:desc "Open scratch buffer" "x" #'doom/open-scratch-buffer
:desc "Open project scratch buffer" "X" #'doom/switch-to-scratch-buffer)
;;; <leader> g --- lookup
(:when (featurep! :tools lookup)
(:prefix-map ("g" . "lookup")
"k" #'+lookup/documentation
"d" #'+lookup/definition
"D" #'+lookup/references
"f" #'+lookup/file
"o" #'+lookup/online-select
"i" #'+lookup/in-docsets
"I" #'+lookup/in-all-docsets))
;;; <leader> o --- org
"o" nil ; we need to unbind it first as Org claims this
(:prefix-map ("o". "org")
:desc "Do what I mean" "o" #'+org/dwim-at-point
:desc "Display inline images" "i" #'org-display-inline-images
:desc "Search notes for symbol" "." #'+default/search-notes-for-symbol-at-point
(: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 "Browse notes" "f" #'+default/browse-notes
:desc "Search org-directory" "s" #'+default/org-notes-search
:desc "Switch org buffers" "b" #'org-switchb
:desc "Capture" "c" #'org-capture
:desc "Goto capture" "C" #'org-capture-goto-target
:desc "Link store" "l" #'org-store-link
:desc "Sync org caldav" "S" #'org-caldav-sync)
;;; <leader> p --- project
(:prefix ("p" . "project")
:desc "Find file in other project" "F" #'doom/find-file-in-other-project
:desc "Search project" "s" #'+default/search-project
:desc "List project tasks" "t" #'magit-todos-list
:desc "Open project scratch buffer" "x" #'doom/open-project-scratch-buffer
:desc "Switch to project scratch buffer" "X" #'doom/switch-to-project-scratch-buffer
;; later expanded by projectile
(:prefix ("4" . "in other window"))
(:prefix ("5" . "in other frame")))
;;; <leader> q --- quit/restart
(:prefix-map ("q" . "quit/restart")
:desc "Quit Emacs" "q" #'kill-emacs
:desc "Save and quit Emacs" "Q" #'save-buffers-kill-terminal
(:when (featurep! :ui 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)
;;; <leader> & --- snippets
(:prefix-map ("&" . "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-map ("t" . "terminal")
(:when (featurep! :term term)
:desc "Toggle term popup" "t" #'+term/toggle
:desc "Open term here" "T" #'+term/here)
(:when (featurep! :term vterm)
:desc "Toggle vterm popup" "t" #'+vterm/toggle
:desc "Open vterm here" "T" #'+vterm/here)
(:when (featurep! :term eshell)
:desc "Toggle eshell popup" "t" #'+eshell/toggle
:desc "Open eshell here" "T" #'+eshell/here))
;;; <leader> v --- versioning
(:prefix-map ("v" . "versioning")
:desc "Git revert file" "R" #'vc-revert
:desc "Kill link to remote" "y" #'+vc/browse-at-remote-kill-file-or-region
:desc "Kill link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage
(: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 file or region" "." #'+vc/browse-at-remote-file-or-region
:desc "Browse homepage" "h" #'+vc/browse-at-remote-homepage
: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 "Fixup" "f" #'magit-commit-fixup
:desc "Issue" "i" #'forge-create-issue
:desc "Pull request" "p" #'forge-create-pullreq)))
;;; <leader> w --- workspaces/windows
(:prefix-map ("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 "Switch to other workspace" "o" #'+workspace/other
: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-final)
;;; <leader> m --- multiple cursors
(:when (featurep! :editor multiple-cursors)
(:prefix-map ("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
;;; <leader> M --- mu4e
(:when (featurep! :email mu4e)
(:prefix-map ("M" . "mu4e")
:desc "Open email app" "M" #'=mu4e
:desc "Compose email" "c" #'+mu4e/compose))
;;; <leader> I --- IRC
(:when (featurep! :app irc)
(:prefix-map ("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)))
;;; <leader> T --- twitter
(:when (featurep! :app twitter)
(:prefix-map ("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)))
;;
;;; Global & plugin 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-+" #'doom/reset-font-size
"M-=" #'doom/increase-font-size
"M--" #'doom/decrease-font-size
;;; newlines
[remap newline] #'newline-and-indent
"C-j" #'+default/newline
;;; search
(: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)
;;; objed
(:when (featurep! :editor objed +manual)
"M-SPC" #'objed-activate)
;;; buffer management
"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
;;; company-mode
"C-;" #'+company/complete
(: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)))
;;; ein notebooks
(:after ein:notebook-multilang
:map ein:notebook-multilang-mode-map
"C-c h" #'+ein/hydra/body)
;;; expand-region
"C-=" #'er/expand-region
"C--" #'er/contract-region
;;; 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)
;;; 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)
;;; ivy & counsel
(:when (featurep! :completion ivy)
(:after ivy
:map ivy-minibuffer-map
"TAB" #'ivy-alt-done
"C-g" #'keyboard-escape-quit)
(:after counsel
:map counsel-ag-map
"C-SPC" #'ivy-call-and-recenter ; preview
"M-RET" #'+ivy/git-grep-other-window-action)
"C-M-y" #'counsel-yank-pop)
;;; neotree
(:when (featurep! :ui neotree)
"<f9>" #'+neotree/open
"<C-f9>" #'+neotree/find-this-file
(: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))
;;; popups
(:when (featurep! :ui popup)
"C-x p" #'+popup/other
"C-`" #'+popup/toggle
"C-~" #'+popup/raise)
;;; repl
"C-c C-z" #'+eval/open-repl-other-window
"C-c C-S-z" #'+eval/open-repl-same-window
;;; 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)
;;; treemacs
(:when (featurep! :ui treemacs)
"<f9>" #'+treemacs/toggle
"<C-f9>" #'+treemacs/find-file)
;;; yasnippet
(:after yasnippet
:map yas-keymap ; keymap while editing an inserted snippet
"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))

View File

@@ -0,0 +1,26 @@
;;; 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)
(use-package! expand-region
:commands (er/contract-region er/mark-symbol er/mark-word)
:config
(defadvice! doom--quit-expand-region-a ()
"Properly abort an expand-region region."
:before '(evil-escape doom/escape)
(when (memq last-command '(er/expand-region er/contract-region))
(er/contract-region 0))))
;;
;;; Keybinds
(when (featurep! +bindings)
(load! "+emacs-bindings"))

View File

@@ -0,0 +1,624 @@
;;; config/default/+bindings.el -*- lexical-binding: t; -*-
(when (featurep! :editor 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! :i "C-u" #'doom/backward-kill-to-bol-and-indent
:i "C-w" #'backward-kill-word
;; Vimmish ex motion keys
:i "C-b" #'backward-word
:i "C-f" #'forward-word)
;; 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-a" #'move-beginning-of-line
"C-b" #'backward-word
"C-f" #'forward-word
"C-r" #'evil-paste-from-register
"C-u" #'doom/backward-kill-to-bol-and-indent
"C-v" #'yank
"C-w" #'backward-kill-word
"C-z" (λ! (ignore-errors (call-interactively #'undo)))
;; Scrolling lines
"C-j" #'next-line
"C-k" #'previous-line
"C-S-j" #'scroll-up-command
"C-S-k" #'scroll-down-command)
(define-key! read-expression-map
"C-j" #'next-line-or-history-element
"C-k" #'previous-line-or-history-element))
;;
;;; Global keybindings
;; Smart tab, these will only work in GUI Emacs
(map! :i [tab] (general-predicate-dispatch nil ; fall back to nearest keymap
(and (featurep! :editor 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 'evil-jump-item)
#'evil-jump-item)
:v [tab] (general-predicate-dispatch nil
(and (bound-and-true-p yas-minor-mode)
(or (eq evil-visual-selection 'line)
(not (memq (char-after) (list ?\( ?\[ ?\{ ?\} ?\] ?\))))))
#'yas-insert-snippet
(fboundp 'evil-jump-item)
#'evil-jump-item)
;; Smarter newlines
:i [remap newline] #'newline-and-indent ; auto-indent on newline
:i "C-j" #'+default/newline ; default behavior
(:after help :map help-mode-map
:n "o" #'ace-link-help)
(:after helpful :map helpful-mode-map
:n "o" #'ace-link-help)
(:after info :map Info-mode-map
:n "o" #'ace-link-info)
(:after apropos :map apropos-mode-map
:n "o" #'ace-link-help
:n "TAB" #'forward-button
:n [tab] #'forward-button
:n [backtab] #'backward-button)
(:after view :map view-mode-map
[escape] #'View-quit-all)
(:after man :map Man-mode-map
:n "q" #'kill-current-buffer)
:m "gs" #'+evil/easymotion ; lazy-load `evil-easymotion'
(:after org
:map org-mode-map
:m "gsh" #'+org/goto-visible)
(:when (featurep! :editor multiple-cursors)
: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
:v "I" #'evil-mc-make-cursor-in-visual-selection-beg
:v "A" #'evil-mc-make-cursor-in-visual-selection-end)
;; misc
:n "C-S-f" #'toggle-frame-fullscreen
:n "C-+" #'doom/reset-font-size
;; Buffer-local font resizing
:n "C-=" #'text-scale-increase
:n "C--" #'text-scale-decrease
;; Frame-local font resizing
:n "M-C-=" #'doom/increase-font-size
:n "M-C--" #'doom/decrease-font-size)
;;
;;; Module keybinds
;;; :completion
(map! (:when (featurep! :completion company)
:i "C-@" #'+company/complete
:i "C-SPC" #'+company/complete
(: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
[f1] nil)
(: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
(:after comint :map comint-mode-map
"TAB" #'company-complete
[tab] #'company-complete))
(:when (featurep! :completion ivy)
(: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-return] #'+ivy/git-grep-other-window-action))
(:when (featurep! :completion helm)
(:after helm :map helm-map
[left] #'left-char
[right] #'right-char
"C-S-f" #'helm-previous-page
"C-S-n" #'helm-next-source
"C-S-p" #'helm-previous-source
"C-S-j" #'helm-next-source
"C-S-k" #'helm-previous-source
"C-j" #'helm-next-line
"C-k" #'helm-previous-line
"C-u" #'helm-delete-minibuffer-contents
"C-s" #'helm-minibuffer-history
;; Swap TAB and C-z
"TAB" #'helm-execute-persistent-action
[tab] #'helm-execute-persistent-action
"C-z" #'helm-select-action)
(:after helm-ag :map helm-ag-map
"C--" #'+helm-do-ag-decrease-context
"C-=" #'+helm-do-ag-increase-context
[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 popup)
:n "C-`" #'+popup/toggle
:n "C-~" #'+popup/raise
:g "C-x p" #'+popup/other)
(:when (featurep! :ui workspaces)
:n "C-t" #'+workspace/new
:n "C-S-t" #'+workspace/display
: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-final
(:when IS-MAC
:g "s-t" #'+workspace/new
:g "s-T" #'+workspace/display
:n "s-1" #'+workspace/switch-to-0
:n "s-2" #'+workspace/switch-to-1
:n "s-3" #'+workspace/switch-to-2
:n "s-4" #'+workspace/switch-to-3
:n "s-5" #'+workspace/switch-to-4
:n "s-6" #'+workspace/switch-to-5
:n "s-7" #'+workspace/switch-to-6
:n "s-8" #'+workspace/switch-to-7
:n "s-9" #'+workspace/switch-to-8
:n "s-0" #'+workspace/switch-to-final)))
;;; :editor
(map! (:when (featurep! :editor format)
:n "gQ" #'+format:region)
(:when (featurep! :editor rotate-text)
:n "!" #'rotate-text)
(:when (featurep! :editor multiple-cursors)
;; 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)))
(:when (featurep! :editor snippets)
;; auto-yasnippet
:i [C-tab] #'aya-expand
:nv [C-tab] #'aya-create))
;;; :tools
(when (featurep! :tools eval)
(map! "M-r" #'+eval/buffer))
;;
;;; <leader>
(map! :leader
:desc "Eval expression" ";" #'pp-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
(:when (featurep! :ui popup)
:desc "Toggle last popup" "~" #'+popup/toggle)
:desc "Find file" "." #'find-file
:desc "Switch buffer" "," #'switch-to-buffer
(:when (featurep! :ui workspaces)
:desc "Switch workspace buffer" "," #'persp-switch-to-buffer
:desc "Switch buffer" "<" #'switch-to-buffer)
:desc "Switch to last buffer" "`" #'evil-switch-to-windows-last-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 "Jump to bookmark" "RET" #'bookmark-jump
;;; <leader> TAB --- workspace
(:when (featurep! :ui workspaces)
(:prefix-map ("TAB" . "workspace")
:desc "Display tab bar" "TAB" #'+workspace/display
:desc "Switch workspace" "." #'+workspace/switch-to
:desc "Switch to last workspace" "`" #'+workspace/other
: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 final workspace" "0" #'+workspace/switch-to-final))
;;; <leader> b --- buffer
(:prefix-map ("b" . "buffer")
:desc "Toggle narrowing" "-" #'doom/toggle-narrow-buffer
:desc "Previous buffer" "[" #'previous-buffer
:desc "Next buffer" "]" #'next-buffer
(:when (featurep! :ui workspaces)
:desc "Switch workspace buffer" "b" #'persp-switch-to-buffer
:desc "Switch buffer" "B" #'switch-to-buffer)
(:unless (featurep! :ui workspaces)
:desc "Switch buffer" "b" #'switch-to-buffer)
:desc "Kill buffer" "d" #'kill-current-buffer
:desc "ibuffer" "i" #'ibuffer
:desc "Kill buffer" "k" #'kill-current-buffer
:desc "Kill all buffers" "K" #'doom/kill-all-buffers
:desc "Switch to last buffer" "l" #'evil-switch-to-windows-last-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 "Revert buffer" "r" #'revert-buffer
:desc "Save buffer" "s" #'basic-save-buffer
:desc "Save all buffers" "S" #'evil-write-all
:desc "Pop up scratch buffer" "x" #'doom/open-scratch-buffer
:desc "Switch to scratch buffer" "X" #'doom/switch-to-scratch-buffer
:desc "Bury buffer" "z" #'bury-buffer
:desc "Kill buried buffers" "Z" #'doom/kill-buried-buffers)
;;; <leader> c --- code
(:prefix-map ("c" . "code")
:desc "Compile" "c" #'compile
:desc "Recompile" "C" #'recompile
: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 "LSP Format buffer/region" "F" #'+default/lsp-format-region-or-buffer
:desc "LSP Organize imports" "i" #'lsp-organize-imports
:desc "Jump to documentation" "k" #'+lookup/documentation
:desc "LSP Rename" "r" #'lsp-rename
:desc "Send to repl" "s" #'+eval/send-region-to-repl
:desc "Delete trailing whitespace" "w" #'delete-trailing-whitespace
:desc "Delete trailing newlines" "W" #'doom/delete-trailing-newlines
:desc "List errors" "x" #'flymake-show-diagnostics-buffer
(:when (featurep! :tools flycheck)
:desc "List errors" "x" #'flycheck-list-errors))
;;; <leader> f --- file
(:prefix-map ("f" . "file")
:desc "Open project editorconfig" "c" #'editorconfig-find-current-editorconfig
:desc "Copy this file" "C" #'doom/copy-this-file
:desc "Find directory" "d" #'dired
:desc "Delete this file" "D" #'doom/delete-this-file
:desc "Find file in emacs.d" "e" #'+default/find-in-emacsd
:desc "Browse emacs.d" "E" #'+default/browse-emacsd
:desc "Find file" "f" #'find-file
:desc "Find file from here" "F" #'+default/find-file-under-here
:desc "Locate file" "l" #'locate
: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 "Rename/move file" "R" #'doom/move-this-file
:desc "Save file" "s" #'save-buffer
:desc "Save file as..." "S" #'write-file
:desc "Sudo find file" "u" #'doom/sudo-find-file
:desc "Sudo this file" "U" #'doom/sudo-this-file
:desc "Yank filename" "y" #'+default/yank-buffer-filename)
;;; <leader> g --- git
(:prefix-map ("g" . "git")
:desc "Git revert file" "R" #'vc-revert
:desc "Copy link to remote" "y" #'+vc/browse-at-remote-kill-file-or-region
:desc "Copy link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage
(: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 switch branch" "b" #'magit-branch-checkout
:desc "Magit status" "g" #'magit-status
:desc "Magit file delete" "D" #'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 file or region" "o" #'+vc/browse-at-remote-file-or-region
:desc "Browse homepage" "h" #'+vc/browse-at-remote-homepage
: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 "Fixup" "f" #'magit-commit-fixup
:desc "Branch" "b" #'magit-branch-and-checkout
:desc "Issue" "i" #'forge-create-issue
:desc "Pull request" "p" #'forge-create-pullreq)))
;;; <leader> i --- insert
(:prefix-map ("i" . "insert")
:desc "Current file name" "f" #'+default/insert-file-path
:desc "Current file path" "F" (λ!! #'+default/insert-file-path t)
:desc "Evil ex path" "p" (λ! (evil-ex "R!echo "))
:desc "From evil register" "r" #'evil-ex-registers
:desc "Snippet" "s" #'yas-insert-snippet
:desc "Unicode" "u" #'unicode-chars-list-chars
:desc "From clipboard" "y" #'+default/yank-pop)
;;; <leader> n --- notes
(:prefix-map ("n" . "notes")
:desc "Search notes for symbol" "*" #'+default/search-notes-for-symbol-at-point
:desc "Org agenda" "a" #'org-agenda
:desc "Toggle org-clock" "c" #'+org/toggle-clock
:desc "Cancel org-clock" "C" #'org-clock-cancel
:desc "Open deft" "d" #'deft
:desc "Find file in notes" "f" #'+default/find-in-notes
:desc "Browse notes" "F" #'+default/browse-notes
:desc "Org store link" "l" #'org-store-link
:desc "Tags search" "m" #'org-tags-view
:desc "Org capture" "n" #'org-capture
:desc "Active org-clock" "o" #'org-clock-goto
:desc "Todo list" "t" #'org-todo-list
:desc "Search notes" "s" #'+default/org-notes-search
:desc "Search org agenda headlines" "S" #'+default/org-notes-headlines
:desc "View search" "v" #'org-search-view
:desc "Org export to clipboard" "y" #'+org/export-to-clipboard
:desc "Org export to clipboard as RTF" "Y" #'+org/export-to-clipboard-as-rich-text
(:when (featurep! :lang org +journal)
(:prefix ("j" . "journal")
:desc "New Entry" "j" #'org-journal-new-entry
:desc "Search Forever" "s" #'org-journal-search-forever)))
;;; <leader> o --- open
(:prefix-map ("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 "Start debugger" "d" #'+debugger/start
:desc "New frame" "f" #'make-frame
: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! :term shell)
:desc "Toggle shell popup" "t" #'+shell/toggle
:desc "Open shell here" "T" #'+shell/here)
(:when (featurep! :term term)
:desc "Toggle terminal popup" "t" #'+term/toggle
:desc "Open terminal here" "T" #'+term/here)
(:when (featurep! :term vterm)
:desc "Toggle vterm popup" "t" #'+vterm/toggle
:desc "Open vterm here" "T" #'+vterm/here)
(:when (featurep! :term eshell)
:desc "Toggle eshell popup" "e" #'+eshell/toggle
:desc "Open eshell here" "E" #'+eshell/here)
(: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))
;;; <leader> p --- project
(:prefix-map ("p" . "project")
:desc "Browse project" "." #'+default/browse-project
: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 "Repeat last command" "C" #'projectile-repeat-last-command
:desc "Remove known project" "d" #'projectile-remove-known-project
:desc "Edit project .dir-locals" "e" #'projectile-edit-dir-locals
:desc "Find file in project" "f" #'projectile-find-file
:desc "Find file in other project" "F" #'doom/find-file-in-other-project
:desc "Configure project" "g" #'projectile-configure-project
:desc "Invalidate project cache" "i" #'projectile-invalidate-cache
:desc "Kill project buffers" "k" #'projectile-kill-buffers
: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 "Run project" "R" #'projectile-run-project
:desc "Save project files" "s" #'projectile-save-project-buffers
:desc "Pop up scratch buffer" "x" #'doom/open-project-scratch-buffer
:desc "Switch to scratch buffer" "X" #'doom/switch-to-project-scratch-buffer
:desc "List project tasks" "t" #'magit-todos-list
:desc "Test project" "T" #'projectile-test-project)
;;; <leader> q --- quit/session
(:prefix-map ("q" . "quit/session")
:desc "Restart emacs server" "d" #'+default/restart-server
:desc "Delete frame" "f" #'delete-frame
:desc "Clear current frame" "F" #'doom/kill-all-buffers
:desc "Kill Emacs (and daemon)" "K" #'save-buffers-kill-emacs
: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)
;;; <leader> r --- remote
(:when (featurep! :tools upload)
(:prefix-map ("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))
;;; <leader> s --- search
(:prefix-map ("s" . "search")
:desc "Search buffer" "b" #'swiper
:desc "Search current directory" "d" #'+default/search-cwd
:desc "Search other directory" "D" #'+default/search-other-cwd
:desc "Locate file" "f" #'locate
:desc "Jump to symbol" "i" #'imenu
:desc "Jump to visible link" "l" #'ace-link
:desc "Jump to link" "L" #'ffap-menu
:desc "Jump list" "j" #'evil-show-jumps
:desc "Jump to mark" "m" #'evil-show-marks
:desc "Look up online" "o" #'+lookup/online
:desc "Look up online (w/ prompt)" "O" #'+lookup/online-select
:desc "Look up in local docsets" "k" #'+lookup/in-docsets
:desc "Look up in all docsets" "K" #'+lookup/in-all-docsets
:desc "Search project" "p" #'+default/search-project
:desc "Search other project" "P" #'+default/search-other-project
:desc "Search buffer" "s" #'swiper-isearch
:desc "Search buffer for thing at point" "S" #'swiper-isearch-thing-at-point)
;;; <leader> t --- toggle
(:prefix-map ("t" . "toggle")
:desc "Big mode" "b" #'doom-big-font-mode
:desc "Flymake" "f" #'flymake-mode
(:when (featurep! :tools flycheck)
:desc "Flycheck" "f" #'flycheck-mode)
:desc "Frame fullscreen" "F" #'toggle-frame-fullscreen
:desc "Evil goggles" "g" #'evil-goggles-mode
(:when (featurep! :ui indent-guides)
:desc "Indent guides" "i" #'highlight-indent-guides-mode)
:desc "Indent style" "I" #'doom/toggle-indent-style
:desc "Line numbers" "l" #'doom/toggle-line-numbers
(:when (featurep! :lang org +present)
:desc "org-tree-slide mode" "p" #'+org-present/start)
:desc "Read-only mode" "r" #'read-only-mode
(:when (featurep! :tools flyspell)
:desc "Flyspell" "s" #'flyspell-mode)
(:when (featurep! :lang org +pomodoro)
:desc "Pomodoro timer" "t" #'org-pomodoro)
:desc "Word-wrap mode" "w" #'+word-wrap-mode))
(after! which-key
(let ((prefix-re (regexp-opt (list doom-leader-key doom-leader-alt-key))))
(cl-pushnew `((,(format "\\`\\(?:C-w\\|%s w\\) m\\'" prefix-re))
nil . "maximize")
which-key-replacement-alist)))

View File

@@ -0,0 +1,21 @@
;;; config/default/+evil.el -*- lexical-binding: t; -*-
(defun +default-disable-delete-selection-mode-h ()
(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-h)
;;
;;; 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,46 @@
#+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]]
- [[#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
** I'm not an evil user...
That's fine. All evil configuration is ignored if =:editor 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,347 @@
;; 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/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-a ()
"A replacement for `newline-and-indent'.
Continues comments if executed from a commented line, with special support for
languages with weak native comment continuation support (like C-family
languages)."
(interactive)
(if (and (sp-point-in-comment)
comment-line-break-function)
(funcall comment-line-break-function nil)
(delete-horizontal-space 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-a (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-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 "Search directory: ")
default-directory)))
(call-interactively
(cond ((featurep! :completion ivy) #'+ivy/project-search-from-cwd)
((featurep! :completion helm) #'+helm/project-search-from-cwd)
(#'rgrep)))))
;;;###autoload
(defun +default/search-other-cwd ()
"Conduct a text search in another directory."
(interactive)
(+default/search-cwd 'other))
;;;###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 "Search 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)
(#'projectile-grep)))))
;;;###autoload
(defun +default/search-other-project ()
"Conduct a text search in a known project."
(interactive)
(+default/search-project 'other))
;;;###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
(or (and (use-region-p)
(rxt-quote-pcre
(buffer-substring-no-properties (region-beginning)
(region-end))))
(rxt-quote-pcre (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 symbol))
((featurep! :completion helm)
(+helm/project-search nil symbol))
((rgrep (regexp-quote symbol))))))
;;;###autoload
(defun +default/search-notes-for-symbol-at-point (&optional 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 (rxt-quote-pcre (or (thing-at-point 'symbol t) ""))))
(require 'org)
(let ((default-directory org-directory))
(+default/search-project-for-symbol-at-point
nil symbol)))
;;;###autoload
(defun +default/org-notes-search ()
"Perform a text search on `org-directory'."
(interactive)
(require 'org)
(let ((default-directory org-directory))
(+default/search-project-for-symbol-at-point nil "")))
;;;###autoload
(defun +default/org-notes-headlines ()
"Jump to an Org headline in `org-agenda-files'."
(interactive)
(doom-completing-read-org-headings
"Jump to org headline: " org-agenda-files 3 t))
;;;###autoload
(defun +default/lsp-format-region-or-buffer ()
"Format the buffer (or selection) with LSP."
(interactive)
(unless (bound-and-true-p lsp-mode)
(user-error "Not in an LSP buffer"))
(call-interactively
(if (use-region-p)
#'lsp-format-region
#'lsp-format-buffer)))
;;;###autoload
(defun +default/restart-server ()
"Restart the Emacs server."
(interactive)
(server-force-delete)
(while (server-running-p)
(sit-for 1))
(server-start))
;;;###autoload
(defun +default/find-file-under-here ()
"Perform a recursive file search from the current directory."
(interactive)
(if (featurep! :completion ivy)
(call-interactively #'counsel-file-jump)
(λ! (doom-project-find-file default-directory))))
;;;###autoload
(defun +default/insert-file-path (arg)
"Insert the file name (absolute path if prefix ARG).
If `buffer-file-name' isn't set, uses `default-directory'."
(interactive "P")
(let ((path (or buffer-file-name default-directory)))
(insert
(if arg
(abbreviate-file-name path)
(file-name-nondirectory path)))))

View File

@@ -0,0 +1,405 @@
;;; 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
,@(cond ((featurep! :completion ivy)
'(ivy-minibuffer-map
ivy-switch-buffer-map))
((featurep! :completion helm)
'(helm-map
helm-ag-map
helm-read-file-map))))
"A list of all the keymaps used for the minibuffer.")
;;
;;; Reasonable defaults
;;;###package avy
(setq avy-all-windows nil
avy-all-windows-alt t
avy-background t)
(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))
(use-package! drag-stuff
:defer t
:init
(map! "<M-up>" #'drag-stuff-up
"<M-down>" #'drag-stuff-down
"<M-left>" #'drag-stuff-left
"<M-right>" #'drag-stuff-right))
;;;###package tramp
(unless IS-WINDOWS
(setq tramp-default-method "ssh")) ; faster than the default scp
;;
;;; 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
;; Smartparens is broken in `cc-mode' as of Emacs 27. See
;; <https://github.com/Fuco1/smartparens/issues/963>.
(unless EMACS27+
(pushnew! sp--special-self-insert-commands 'c-electric-paren 'c-electric-brace))
;; Smartparens' navigation feature is neat, but does not justify how
;; expensive it is. It's also less useful for evil users. This may need to
;; be reactivated for non-evil users though. Needs more testing!
(add-hook! 'after-change-major-mode-hook
(defun doom-disable-smartparens-navigate-skip-match-h ()
(setq sp-navigate-skip-match nil
sp-navigate-consider-sgml-tags nil)))
;; 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)))
;; In lisps ( should open a new form if before another parenthesis
(sp-local-pair sp-lisp-modes "(" ")" :unless '(:rem 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))
;; Smartparens and cc-mode both try to autoclose angle-brackets
;; intelligently. The result isn't very intelligent (causes redundant
;; characters), so just do it ourselves.
(define-key! c++-mode-map "<" nil ">" nil)
(defun +default-cc-sp-point-is-template-p (id action context)
"Return t if point is in the right place for C++ angle-brackets."
(and (sp-in-code-p id action context)
(cond ((eq action 'insert)
(sp-point-after-word-p id action context))
((eq action 'autoskip)
(/= (char-before) 32)))))
(defun +default-cc-sp-point-after-include-p (id action context)
"Return t if point is in an #include."
(and (sp-in-code-p id action context)
(save-excursion
(goto-char (line-beginning-position))
(looking-at-p "[ ]*#include[^<]+"))))
;; ...and leave it to smartparens
(sp-local-pair '(c++-mode objc-mode)
"<" ">"
:when '(+default-cc-sp-point-is-template-p
+default-cc-sp-point-after-include-p)
:post-handlers '(("| " "SPC")))
(sp-local-pair '(c-mode c++-mode objc-mode java-mode)
"/*!" "*/"
:post-handlers '(("||\n[i]" "RET") ("[d-1]< | " "SPC"))))
;; 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-asterix-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 scala-mode)
"/*" "*/"
:actions '(insert)
:post-handlers '(("| " "SPC")
("|\n[i]*/[d-2]" "RET")
(+default-expand-asterix-doc-comment-block "*")))
(after! smartparens-ml
(sp-with-modes '(tuareg-mode fsharp-mode)
(sp-local-pair "(*" "*)" :actions nil)
(sp-local-pair "(*" "*"
:actions '(insert)
:post-handlers '(("| " "SPC") ("|\n[i]*)[d-2]" "RET")))))
(after! smartparens-markdown
(sp-with-modes '(markdown-mode gfm-mode)
(sp-local-pair "```" "```" :post-handlers '(:add ("||\n[i]" "RET")))
;; The original rules for smartparens had an odd quirk: inserting two
;; asterixex would replace nearby quotes with asterixes. These two rules
;; set out to fix this.
(sp-local-pair "**" nil :actions :rem)
(sp-local-pair "*" "*"
:actions '(insert skip)
:unless '(:rem sp-point-at-bol-p)
;; * then SPC will delete the second asterix and assume
;; you wanted a bullet point. * followed by another *
;; will produce an extra, assuming you wanted **|**.
:post-handlers '(("[d1]" "SPC") ("|*" "*"))))
;; This keybind allows * to skip over **.
(map! :map markdown-mode-map
:ig "*" (λ! (if (looking-at-p "\\*\\* *$")
(forward-char 2)
(call-interactively 'self-insert-command)))))
;; Highjacks backspace to:
;; a) balance spaces inside brackets/parentheses ( | ) -> (|)
;; b) delete up to nearest column multiple of `tab-width' 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-a))
;; Makes `newline-and-indent' continue comments (and more reliably)
(advice-add #'newline-and-indent :override #'+default--newline-indent-and-continue-comments-a))
;;
;;; 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 key-translation-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
:v "s-x" #'kill-region
;; Buffer-local font scaling
"s-+" #'doom/reset-font-size
"s-=" #'doom/increase-font-size
"s--" #'doom/decrease-font-size
;; Conventional text-editing keys & motions
"s-a" #'mark-whole-buffer
"s-/" (λ! (save-excursion (comment-line 1)))
:n "s-/" #'evilnc-comment-or-uncomment-lines
:v "s-/" #'evilnc-comment-operator
: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
"E" #'doom/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
;; 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
;; make `describe-bindings' available under the b prefix which it previously
;; occupied. Add more binding related commands under that prefix as well
"b" nil
"bb" #'describe-bindings
"bi" #'which-key-show-minor-mode-keymap
"bm" #'which-key-show-major-mode
"bt" #'which-key-show-top-level
"bf" #'which-key-show-full-keymap
"bk" #'which-key-show-keymap
;; replaces `apropos-documentation' b/c `apropos' covers this
"d" nil
"da" #'doom/help-autodefs
"db" #'doom/report-bug
"dd" #'doom/toggle-debug-mode
"df" #'doom/help-faq
"dh" #'doom/help
"dk" #'doom/goto-packages-file
"dl" #'doom/help-search-load-path
"dm" #'doom/help-modules
"dn" #'doom/help-news
"dN" #'doom/help-news-search
"di" #'doom/goto-doomblock
"dp" #'doom/help-packages
"dP" #'doom/help-package-homepage
"dc" #'doom/goto-config-file
"dC" #'doom/help-package-config
"ds" #'doom/help-search
"dx" #'doom/sandbox
"dt" #'doom/toggle-profiler
"dv" #'doom/version
;; replaces `apropos-command'
"a" #'apropos
"A" #'apropos-documentation
;; replaces `describe-copying' b/c not useful
"C-c" #'describe-coding-system
;; replaces `Info-got-emacs-command-node' b/c redundant w/ `Info-goto-node'
"F" #'describe-face
;; replaces `view-hello-file' b/c annoying
"h" nil
;; replaces `view-emacs-news' b/c it's on C-n too
"n" #'doom/help-news
;; replaces `help-with-tutorial', b/c it's less useful than `load-theme'
"t" #'load-theme
;; replaces `finder-by-keyword' b/c not useful
"p" #'doom/help-packages
;; replaces `describe-package' b/c redundant w/ `doom/describe-package'
"P" #'find-library)
(after! which-key
(let ((prefix-re (regexp-opt (list doom-leader-key doom-leader-alt-key))))
(cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) d\\'" prefix-re))
nil . "doom")
which-key-replacement-alist)
(cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) r\\'" prefix-re))
nil . "reload")
which-key-replacement-alist)
(cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) b\\'" prefix-re))
nil . "bindings")
which-key-replacement-alist)))
(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
;; Standardizes the behavior of modified RET to match the behavior of
;; other editors, particularly Atom, textedit, textmate, and vscode, in
;; which ctrl+RET will add a new "item" below the current one and
;; cmd+RET (Mac) / meta+RET (elsewhere) will add a new, blank line below
;; the current one.
:gni [C-return] #'+default/newline-below
:gni [C-S-return] #'+default/newline-above
(:when IS-MAC
:gni [s-return] #'+default/newline-below
:gni [S-s-return] #'+default/newline-above)
(:unless IS-MAC
:gni [M-return] #'+default/newline-below
:gni [M-S-return] #'+default/newline-above)))
;;
;;; Bootstrap configs
(if (featurep 'evil)
(load! "+evil")
(load! "+emacs"))

View File

@@ -0,0 +1,9 @@
;; -*- no-byte-compile: t; -*-
;;; config/default/packages.el
(package! avy)
(package! ace-link)
(package! drag-stuff)
(unless (featurep! :editor evil)
(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-h ()
"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,51 @@
;;; config/literate/init.el -*- lexical-binding: t; -*-
(defvar +literate-config-file
(concat doom-private-dir "config.org")
"The file path of your literate config file.")
(defvar +literate-config-cache-file
(concat doom-cache-dir "literate-last-compile")
"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))
(when (or (file-newer-than-file-p +literate-config-file
+literate-config-cache-file)
force-p)
(message "Compiling your literate config...")
(let* ((org (expand-file-name +literate-config-file))
(dest (concat (file-name-sans-extension +literate-config-file) ".el"))
(output (get-buffer-create "*org-tangle*")))
(unwind-protect
;; We tangle in a separate, blank process because loading it here
;; would load all of :lang org (very expensive!).
(or (and (zerop (call-process
"emacs" nil output nil
"-q" "--batch"
"-l" "ob-tangle"
"--eval" (format "(org-babel-tangle-file %S %S)"
org dest)))
(with-current-buffer output
(message "%s" (buffer-string))
t)
;; 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!"))
(kill-buffer output))))))
;; Let 'er rip!
(+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-h))

View File

@@ -0,0 +1,102 @@
;;; editor/evil/+commands.el -*- lexical-binding: t; -*-
;;
;;; Custom commands
;; Editing
(evil-ex-define-cmd "@" #'+evil:macro-on-all-lines) ; TODO Test me
(evil-ex-define-cmd "R[ead]" #'+evil:read)
(evil-ex-define-cmd "al[ign]" #'+evil:align)
(evil-ex-define-cmd "ral[ign]" #'+evil:align-right)
(evil-ex-define-cmd "enhtml" #'+web:encode-html-entities)
(evil-ex-define-cmd "dehtml" #'+web:decode-html-entities)
(evil-ex-define-cmd "mc" #'+multiple-cursors:evil-mc)
(evil-ex-define-cmd "iedit" #'evil-multiedit-ex-match)
(evil-ex-define-cmd "na[rrow]" #'+evil:narrow-buffer)
(evil-ex-define-cmd "retab" #'+evil:retab)
(evil-ex-define-cmd "rev[erse]" #'+evil:reverse-lines)
(evil-ex-define-cmd "l[ine]diff" #'evil-quick-diff)
;;; External resources
;; TODO (evil-ex-define-cmd "db" #'doom:db)
;; TODO (evil-ex-define-cmd "dbu[se]" #'doom:db-select)
;; TODO (evil-ex-define-cmd "go[ogle]" #'doom:google-search)
(evil-ex-define-cmd "lo[okup]" #'+lookup:online)
(evil-ex-define-cmd "dash" #'+lookup:dash)
(evil-ex-define-cmd "http" #'httpd-start) ; start http server
(evil-ex-define-cmd "repl" #'+eval:repl) ; invoke or send to repl
(evil-ex-define-cmd "h[elp]" #'+evil:help)
;; TODO (evil-ex-define-cmd "rx" 'doom:regex) ; open re-builder
(evil-ex-define-cmd "sh[ell]" #'+eshell:run)
(evil-ex-define-cmd "t[mux]" #'+tmux:run) ; send to tmux
(evil-ex-define-cmd "tcd" #'+tmux:cd-here) ; cd to default-directory in tmux
(evil-ex-define-cmd "pad" #'+evil:open-scratch-buffer)
;;; GIT
(evil-ex-define-cmd "gist" #'+gist:send) ; send current buffer/region to gist
(evil-ex-define-cmd "gistl" #'+gist:list) ; list gists by user
(evil-ex-define-cmd "gbrowse" #'+vc:git-browse) ; show file/region in github/gitlab
(evil-ex-define-cmd "gissues" #'forge-browse-issues) ; show github issues
(evil-ex-define-cmd "git" #'magit-status) ; open magit status window
(evil-ex-define-cmd "gstage" #'magit-stage)
(evil-ex-define-cmd "gunstage" #'magit-unstage)
(evil-ex-define-cmd "gblame" #'magit-blame)
(evil-ex-define-cmd "grevert" #'git-gutter:revert-hunk)
;;; Dealing with buffers
(evil-ex-define-cmd "k[ill]" #'doom/kill-current-buffer)
(evil-ex-define-cmd "k[ill]all" #'+evil:kill-all-buffers)
(evil-ex-define-cmd "k[ill]m" #'+evil:kill-matching-buffers)
(evil-ex-define-cmd "k[ill]o" #'doom/kill-other-buffers)
(evil-ex-define-cmd "k[ill]b" #'doom/kill-buried-buffers)
(evil-ex-define-cmd "l[ast]" #'doom/popup-restore)
(evil-ex-define-cmd "messages" #'view-echo-area-messages)
(evil-ex-define-cmd "pop[up]" #'doom/popup-this-buffer)
;;; Project navigation
(evil-ex-define-cmd "a" #'projectile-find-other-file)
(evil-ex-define-cmd "cd" #'+evil:cd)
(evil-ex-define-cmd "pwd" #'+evil:pwd)
(evil-define-command +evil:swiper (&optional search)
"Invoke `swiper' with SEARCH, otherwise with the symbol at point."
(interactive "<a>")
(swiper-isearch search))
(evil-ex-define-cmd "sw[iper]" #'+evil:swiper)
(cond ((featurep! :completion ivy)
(evil-ex-define-cmd "pg[rep]" #'+ivy:project-search)
(evil-ex-define-cmd "pg[grep]d" #'+ivy:project-search-from-cwd))
((featurep! :completion helm)
(evil-ex-define-cmd "pg[rep]" #'+helm:project-search)
(evil-ex-define-cmd "pg[grep]d" #'+helm:project-search-from-cwd)))
;;; Project tools
(evil-ex-define-cmd "compile" #'+evil:compile)
(evil-ex-define-cmd "mak[e]" #'+evil:make)
(evil-ex-define-cmd "debug" #'+debugger/start)
(evil-ex-define-cmd "er[rors]" #'flycheck-list-errors)
;;; File operations
(evil-ex-define-cmd "cp" #'+evil:copy-this-file)
(evil-ex-define-cmd "mv" #'+evil:move-this-file)
(evil-ex-define-cmd "rm" #'+evil:delete-this-file)
;;; Sessions/tabs
(evil-ex-define-cmd "sclear" #'+workspace/kill-session)
(evil-ex-define-cmd "sl[oad]" #'doom/quickload-session)
(evil-ex-define-cmd "ss[ave]" #'doom/quicksave-session)
(evil-ex-define-cmd "tabc[lose]" #'+workspace:delete)
(evil-ex-define-cmd "tabclear" #'doom/kill-all-buffers)
(evil-ex-define-cmd "tabl[ast]" #'+workspace/switch-to-last)
(evil-ex-define-cmd "tabload" #'+workspace:load)
(evil-ex-define-cmd "tabn[ew]" #'+workspace:new)
(evil-ex-define-cmd "tabn[ext]" #'+workspace:switch-next)
(evil-ex-define-cmd "tabp[rev]" #'+workspace:switch-previous)
(evil-ex-define-cmd "tabr[ename]" #'+workspace:rename)
(evil-ex-define-cmd "tabs" #'+workspace/display)
(evil-ex-define-cmd "tabsave" #'+workspace:save)
;;; Org-mode
(evil-ex-define-cmd "cap" #'org-capture)

View File

@@ -0,0 +1,238 @@
;;; editor/evil/+everywhere.el -*- lexical-binding: t; -*-
;; We load evil-collection ourselves for these reasons:
;;
;; 1. To truly lazy load it. Some of its modules, like
;; evil-collection-{elisp-mode,buff-menu} are loaded immediately, because
;; Emacs loads their packages immediately, which pulls in all of
;; evil-collection (and other packages with it, sometimes).
;; 2. This ensures a predictable load order, versus lazy loading using :defer or
;; :after-call. This means users can use (after! org ...) and be sure that
;; their changes will override evil-collection's.
;; 3. Ideally, we'd do away with evil-collection entirely. It changes too often,
;; introduces breaking bugs too frequently, and I don't agree with all their
;; design choices. Regardless, it does mork than it causes trouble, so it may
;; be here to stay.
;; 4. Adds `+evil-collection-disabled-list', to make it easier for users to
;; disable modules, and to reduce the effort required to maintain our copy of
;; `evil-collection-list' (now I can just copy it from time to time).
(defvar +evil-collection-disabled-list
'(anaconda-mode
buff-menu
comint
company
custom
eldoc
elisp-mode
ert
free-keys
help
helm
image
kotlin-mode
occur
package-menu
ruby-mode
simple
slime)
"A list of `evil-collection' modules to ignore. See the definition of this
variable for an explanation of the defaults (in comments). See
`evil-collection-mode-list' for a list of available options.")
(defvar evil-collection-setup-minibuffer nil)
;; We do this ourselves, and better.
(defvar evil-collection-want-unimpaired-p nil)
;; This has to be defined here since evil-collection doesn't autoload its own.
;; It must be updated whenever evil-collection updates theirs. Here's an easy
;; way to update it:
;;
;; (with-current-buffer
;; (url-retrieve-synchronously "https://raw.githubusercontent.com/emacs-evil/evil-collection/master/evil-collection.el" t t)
;; (goto-char (point-min))
;; (when (re-search-forward "^(defvar evil-collection--supported-modes\n[^(]+")
;; (let ((list (sexp-at-point)))
;; ;; Fixes
;; (when (assq 'pdf list)
;; (setf (alist-get 'pdf list) '(pdf-tools)))
;; (kill-new (prin1-to-string list)))))
(defvar evil-collection-mode-list
`(2048-game
ag
alchemist
anaconda-mode
apropos
arc-mode
bookmark
(buff-menu "buff-menu")
calc
calendar
cider
cmake-mode
comint
company
compile
(custom cus-edit)
cus-theme
daemons
deadgrep
debbugs
debug
diff-mode
dired
disk-usage
doc-view
docker
ebib
edbi
edebug
ediff
eglot
elfeed
elisp-mode
elisp-refs
elisp-slime-nav
emms
epa
ert
eshell
eval-sexp-fu
evil-mc
eww
flycheck
flymake
free-keys
geiser
ggtags
git-timemachine
go-mode
grep
guix
hackernews
helm
help
helpful
hg-histedit
hungry-delete
ibuffer
image
image-dired
image+
imenu-list
indium
info
ivy
js2-mode
leetcode
log-edit
log-view
lsp-ui-imenu
lua-mode
kotlin-mode
macrostep
man
magit
magit-todos
,@(if evil-collection-setup-minibuffer '(minibuffer))
monky
mu4e
mu4e-conversation
neotree
notmuch
nov
(occur replace)
omnisharp
outline
p4
(package-menu package)
pass
(pdf pdf-tools)
popup
proced
process-menu
prodigy
profiler
python
quickrun
racer
realgud
reftex
restclient
rjsx-mode
robe
rtags
ruby-mode
simple
slime
sly
tablist
(term term ansi-term multi-term)
tetris
tide
transmission
typescript-mode
vc-annotate
vc-dir
vc-git
vdiff
view
vlf
vterm
w3m
wdired
wgrep
which-key
woman
xref
youtube-dl
(ztree ztree-diff)))
(defun +evil-collection-init (module &optional disabled-list)
"Initialize evil-collection-MODULE.
Unlike `evil-collection-init', this respects `+evil-collection-disabled-list',
and complains if a module is loaded too early (during startup)."
(unless (memq (or (car-safe module) module) disabled-list)
(doom-log "Initialized evil-collection-%s %s"
(or (car-safe module) module)
(if doom-init-time "" "(too early!)"))
(with-demoted-errors "evil-collection error: %s"
(evil-collection-init (list module)))))
;;
;;; Bootstrap
;; These modes belong to packages that Emacs always loads at startup, causing
;; evil-collection to load immediately. We avoid this by loading them after
;; evil-collection has first loaded...
(with-eval-after-load 'evil-collection
(mapc #'+evil-collection-init '(comint custom help)))
;; ...or on first invokation of their associated major/minor modes.
(add-transient-hook! 'Buffer-menu-mode
(+evil-collection-init '(buff-menu "buff-menu")))
(add-transient-hook! 'image-mode
(+evil-collection-init 'image))
(add-transient-hook! 'emacs-lisp-mode
(+evil-collection-init 'elisp-mode))
(add-transient-hook! 'occur-mode
(+evil-collection-init 'replace))
(evil-define-key* 'normal process-menu-mode-map
"q" #'kill-current-buffer
"d" #'process-menu-delete-process)
;; Don't overwrite the leader keys
(setq evil-collection-key-blacklist
(list doom-leader-key doom-localleader-key
doom-leader-alt-key doom-localleader-alt-key))
;; Load the rest
(dolist (mode evil-collection-mode-list)
(dolist (req (or (cdr-safe mode) (list mode)))
(with-eval-after-load req
(+evil-collection-init mode +evil-collection-disabled-list))))

View File

@@ -0,0 +1,176 @@
#+TITLE: feature/evil
#+DATE: February 2, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#ported-vim-plugins][Ported vim plugins]]
- [[#custom-text-objects][Custom Text Objects]]
- [[#custom-ex-commands][Custom Ex Commands]]
- [[#configuration][Configuration]]
- [[#removing-evil-mode][Removing evil-mode]]
- [[#restoring-old-substitution-behavior-on-ss][Restoring old substitution behavior on s/S]]
* Description
This holy module brings the vim experience to Emacs.
** Module Flags
+ =+everywhere= Enables evilified keybinds everywhere possible. Uses the
[[https://github.com/emacs-evil/evil-collection][evil-collection]] plugin as a foundation.
** Plugins
+ [[https://github.com/emacs-evil/evil][evil]]
+ [[https://github.com/wcsmith/evil-args][evil-args]]
+ [[https://github.com/PythonNut/evil-easymotion][evil-easymotion]]
+ [[https://github.com/cute-jumper/evil-embrace.el][evil-embrace]]
+ [[https://github.com/syl20bnr/evil-escape][evil-escape]]
+ [[https://github.com/Dewdrops/evil-exchange][evil-exchange]]
+ [[https://github.com/TheBB/evil-indent-plus][evil-indent-plus]]
+ [[https://github.com/redguardtoo/evil-nerd-commenter][evil-nerd-commentary]]
+ [[https://github.com/redguardtoo/evil-matchit][evil-matchit]]
+ [[https://github.com/cofi/evil-numbers][evil-numbers]]
+ [[https://github.com/noctuid/evil-textobj-anyblock][evil-textobj-anyblock]]
+ [[https://github.com/hlissner/evil-snipe][evil-snipe]]
+ [[https://github.com/emacs-evil/evil-surround][evil-surround]]
+ [[https://github.com/alexmurray/evil-vimish-fold][evil-vimish-fold]]
+ [[https://github.com/bling/evil-visualstar][evil-visualstar]]
+ [[https://github.com/ninrod/exato][exato]]
+ [[https://github.com/emacs-evil/evil-collection][evil-collection]]*
+ [[https://www.github.com/rgrinberg/evil-quick-diff][evil-quick-diff]]
** Hacks
+ The o/O keys will respect and continue commented lines (can be disabled by
setting ~+evil-want-o/O-to-continue-comments~ to ~nil~).
+ In visual mode, =*= and =#= will search for the current selection instead of
the word-at-point.
+ The ~:g[lobal]~ ex command has been modified to highlight matches.
+ More of vim's filename modifiers are supported in ex commands (like ~:p~,
~:p:h~ or ~:t~) than vanilla evil-mode offers.
+ A custom filename modifier is available in Doom: ~:P~, which expands to the
project root (throws an error if not in a project).
* Prerequisites
This module has no external prerequisites.
* Features
** Ported vim plugins
The following vim plugins have been ported to evil:
| Vim Plugin | Emacs Plugin | Keybind(s) |
|-----------------------+--------------------------------+--------------------------------------------|
| vim-commentary | evil-nerd-commenter | omap =gc= |
| vim-easymotion | evil-easymotion | omap =gs= |
| vim-seek or vim-sneak | evil-snipe | mmap =s= / =S=, omap =z= / =Z= & =x= / =X= |
| vim-surround | evil-embrace and evil-surround | vmap =S=, omap =ys= |
This module has also ported vim-unimpaired keybinds to Emacs.
In other modules:
+ The tools/neotree & tools/treemacs modules provide a =NERDTree= equivalent.
+ The editor/multiple-cursors module contains functionality equal to the
following vim plugins:
+ evil-multiedit => vim-multiedit
+ evil-mc => vim-multiple-cursors
** Custom Text Objects
This module provides a couple extra text objects, along with the built-in ones.
For posterity, here are the built-in ones:
+ =w W= words
+ =s= sentences
+ =p= paragraphs
+ =b= parenthesized blocks
+ =b ( ) { } [ ] < >= braces, parentheses and brackets
+ =' " `= quotes
+ =t= tags
+ =o= symbols
And these are text objects added by this module:
+ =a= C-style function arguments (provided by ~evil-args~)
+ =B= any block delimited by braces, parentheses or brackets (provided by
~evil-textobj-anyblock~)
+ =i j k= by indentation (=k= includes one line above; =j= includes one line
above and below) (provided by ~evil-indent-plus~)
+ =x= XML attributes (provided by ~exato~)
** Custom Ex Commands
| Ex Command | Description |
|-----------------------+--------------------------------------------------------------------------------------|
| ~:@~ | Apply macro on selected lines |
| ~:ag[!] REGEXP~ | Perform a project search with ag |
| ~:agcwd[!] REGEXP~ | Perform a project search with ag from the current directory |
| ~:al[ign][!] REGEXP~ | Align text to the first match of REGEXP. If BANG, align all matches on each line |
| ~:cp[!] NEWPATH~ | Copy the current file to NEWPATH |
| ~:dash QUERY~ | Look up QUERY (or the symbol at point) in dash docsets |
| ~:dehtml [INPUT]~ | HTML decode selected text / inserts result if INPUT is given |
| ~:enhtml [INPUT]~ | HTML encode selected text / inserts result if INPUT is given |
| ~:grep[!]~ | Perform a project search with git-grep |
| ~:grepcwd[!]~ | Perform a project search with git-grep from the current directory |
| ~:iedit REGEXP~ | Invoke iedit on all matches for REGEXP |
| ~:k[ill]all[!]~ | Kill all buffers (if BANG, affect buffer across workspaces) |
| ~:k[ill]b~ | Kill all buried buffers |
| ~:k[ill]m[!] REGEXP~ | Kill buffers whose name matches REGEXP (if BANG, affect buffers across workspaces) |
| ~:k[ill]o~ | Kill all other buffers besides the selected one |
| ~:k[ill]~ | Kill the current buffer |
| ~:lo[okup] QUERY~ | Look up QUERY on an online search engine |
| ~:mc REGEXP~ | Invoke multiple cursors on all matches for REGEXP |
| ~:mv[!] NEWPATH~ | Move the current file to NEWPATH |
| ~:na[rrow]~ | Narrow the buffer to the selection |
| ~:pad~ | Open a scratch pad for running code quickly |
| ~:ral[ign][!] REGEXP~ | Right-Align text that matches REGEXP. If BANG, align all matches on each line |
| ~:repl~ | Open a REPL and/or copy the current selection to it |
| ~:retab~ | Convert indentation to the default within the selection |
| ~:rev[erse]~ | Reverse the selected lines |
| ~:rg[!]~ | Perform a project search with ripgrep |
| ~:rgcwd[!]~ | Perform a project search with ripgrep from the current directory |
| ~:rm[!] [PATH]~ | Delete the current buffer's file and buffer |
| ~:tcd[!]~ | Send =cd X= to tmux. X = the project root if BANG, X = ~default-directory~ otherwise |
* Configuration
** Removing evil-mode
You must do two things to remove Evil:
1. Remove =:editor evil= from =~/.doom.d/init.el=,
2. Run ~doom refresh~ to clean up lingering dependencies and refresh your
autoloads files.
3. [OPTIONAL] You may want to assign new values to ~doom-leader-alt-key~ and
~doom-localleader-alt-key~. These are bound to =C-c= and =C-c l= by default.
#+begin_quote
Ignore ~doom-leader-key~ and ~doom-localleader-key~, they don't apply to
non-evil sessions.
#+end_quote
Evil-specific configuration and keybindings (defined with ~map!~) will be
ignored without =:editor evil= present (and omitted when byte-compiling).
Keep in mind that, at the time of this writing, Doom was designed by a vimmer,
for vimmers. Little consideration has been put into designing a keybind scheme
for vanilla Emacs users (though it's being worked on!).
That means that much of Doom's functionality will be orphaned in an evil-less
setup. You'll have to set your own keybinds.
I suggest studying [[file:../../config/default/+emacs-bindings.el][config/default/+emacs-bindings.el]] to see what keybinds are
available for non-evil users. Otherwise, you may find inspiration [[file:../../../docs/example_configs.org][on the example
Doom configurations page]].
** Restoring old substitution behavior on s/S
Doom replaces the =s= and =S= keys with the =evil-snipe= package (a port of
vim-seek/vim-sneak for 2-character versions of f/F/t/T).
To disable evil-snipe on s/S, you can either:
1. Disable ~evil-snipe-mode~ by adding ~(after! evil-snipe (evil-snipe-mode
-1))~ to =$DOOMDIR/config.el=,
2. Or disable =evil-snipe= completely with ~(package! evil-snipe :disable t)~
added to =$DOOMDIR/packages.el=, but this will also disable incremental
highlighting for the f/F/t/T motions keys.
3. Or use =cl= and =cc=, respectively; they do the same thing.

View File

@@ -0,0 +1,198 @@
;;; editor/evil/autoload/advice.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +evil-escape-a (&rest _)
"Call `doom/escape' if `evil-force-normal-state' is called interactively."
(when (called-interactively-p 'any)
(call-interactively #'doom/escape)))
;;;###autoload
(defun +evil-resolve-vim-path-a (file-name)
"Take a path and resolve any vim-like filename modifiers in it. This adds
support for most vim file modifiers, as well as:
%:P Resolves to `doom-project-root'.
See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers for
more information on modifiers."
(let* (case-fold-search
(regexp (concat "\\(?:^\\|[^\\\\]\\)"
"\\([#%]\\)"
"\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)"))
(matches
(cl-loop with i = 0
while (and (< i (length file-name))
(string-match regexp file-name i))
do (setq i (1+ (match-beginning 0)))
and collect
(cl-loop for j to (/ (length (match-data)) 2)
collect (match-string j file-name)))))
(dolist (match matches)
(let ((flags (split-string (car (cdr (cdr match))) ":" t))
(path (and buffer-file-name
(pcase (car (cdr match))
("%" (file-relative-name buffer-file-name))
("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name))))))
flag global)
(if (not path)
(setq path "")
(while flags
(setq flag (pop flags))
(when (string-suffix-p "\\" flag)
(setq flag (concat flag (pop flags))))
(when (string-prefix-p "gs" flag)
(setq global t
flag (substring flag 1)))
(setq path
(or (pcase (substring flag 0 1)
("p" (expand-file-name path))
("~" (concat "~/" (file-relative-name path "~")))
("." (file-relative-name path default-directory))
("t" (file-name-nondirectory (directory-file-name path)))
("r" (file-name-sans-extension path))
("e" (file-name-extension path))
("S" (shell-quote-argument path))
("h"
(let ((parent (file-name-directory (expand-file-name path))))
(unless (equal (file-truename path)
(file-truename parent))
(if (file-name-absolute-p path)
(directory-file-name parent)
(file-relative-name parent)))))
("s"
(if (featurep 'evil)
(when-let (args (evil-delimited-arguments (substring flag 1) 2))
(let ((pattern (evil-transform-vim-style-regexp (car args)))
(replace (cadr args)))
(replace-regexp-in-string
(if global pattern (concat "\\(" pattern "\\).*\\'"))
(evil-transform-vim-style-regexp replace) path t t
(unless global 1))))
path))
("P"
(let ((project-root (doom-project-root (file-name-directory (expand-file-name path)))))
(unless project-root
(user-error "Not in a project"))
(abbreviate-file-name project-root)))
(_ path))
"")))
;; strip trailing slash, if applicable
(when (and (not (string= path "")) (equal (substring path -1) "/"))
(setq path (substring path 0 -1))))
(setq file-name
(replace-regexp-in-string
(format "\\(?:^\\|[^\\\\]\\)\\(%s\\)"
(regexp-quote (string-trim-left (car match))))
path file-name t t 1))))
(replace-regexp-in-string regexp "\\1" file-name t)))
(defun +evil--insert-newline (&optional above _noextranewline)
(let ((pos (save-excursion (beginning-of-line-text) (point)))
comment-auto-fill-only-comments)
(require 'smartparens)
(evil-narrow-to-field
(if above
(if (save-excursion (nth 4 (sp--syntax-ppss pos)))
(evil-save-goal-column
(setq evil-auto-indent nil)
(goto-char pos)
(let ((ws (abs (skip-chars-backward " \t"))))
;; FIXME oh god why
(save-excursion
(if comment-line-break-function
(funcall comment-line-break-function nil)
(comment-indent-new-line))
(when (and (derived-mode-p 'c-mode 'c++-mode 'objc-mode 'java-mode 'js2-mode)
(eq (char-after) ?/))
(insert "*"))
(insert
(make-string (max 0 (+ ws (skip-chars-backward " \t")))
32)))
(insert (make-string (max 1 ws) 32))))
(evil-move-beginning-of-line)
(insert (if use-hard-newlines hard-newline "\n"))
(forward-line -1)
(back-to-indentation))
(evil-move-end-of-line)
(cond ((sp-point-in-comment pos)
(setq evil-auto-indent nil)
(if comment-line-break-function
(funcall comment-line-break-function)
(comment-indent-new-line)))
;; TODO Find a better way to do this
((and (eq major-mode 'haskell-mode)
(fboundp 'haskell-indentation-newline-and-indent))
(setq evil-auto-indent nil)
(haskell-indentation-newline-and-indent))
(t
(insert (if use-hard-newlines hard-newline "\n"))
(back-to-indentation)))))))
;;;###autoload
(defun +evil--insert-newline-below-and-respect-comments-a (orig-fn count)
(if (or (not +evil-want-o/O-to-continue-comments)
(not (eq this-command 'evil-open-below))
(evil-insert-state-p))
(funcall orig-fn count)
(cl-letf (((symbol-function 'evil-insert-newline-below)
(lambda () (+evil--insert-newline))))
(let ((evil-auto-indent evil-auto-indent))
(funcall orig-fn count)))))
;;;###autoload
(defun +evil--insert-newline-above-and-respect-comments-a (orig-fn count)
(if (or (not +evil-want-o/O-to-continue-comments)
(not (eq this-command 'evil-open-above))
(evil-insert-state-p))
(funcall orig-fn count)
(cl-letf (((symbol-function 'evil-insert-newline-above)
(lambda () (+evil--insert-newline 'above))))
(let ((evil-auto-indent evil-auto-indent))
(funcall orig-fn count)))))
;;;###autoload (autoload '+evil-window-split-a "editor/evil/autoload/advice" nil t)
(evil-define-command +evil-window-split-a (&optional count file)
"Same as `evil-window-split', but correctly updates the window history."
:repeat nil
(interactive "P<f>")
;; HACK This ping-ponging between the destination and source windows is to
;; update the window focus history, so that, if you close either split
;; afterwards you won't be sent to some random window.
(let ((doom-inhibit-switch-window-hooks t)
(origwin (selected-window)))
(select-window (split-window origwin count 'below))
(unless evil-split-window-below
(select-window origwin))
(run-hooks 'doom-switch-window-hook))
(recenter)
(when (and (not count) evil-auto-balance-windows)
(balance-windows (window-parent)))
(if file (evil-edit file)))
;;;###autoload (autoload '+evil-window-vsplit-a "editor/evil/autoload/advice" nil t)
(evil-define-command +evil-window-vsplit-a (&optional count file)
"Same as `evil-window-split', but correctly updates the window history."
:repeat nil
(interactive "P<f>")
;; HACK This ping-ponging between the destination and source windows is to
;; update the window focus history, so that, if you close either split
;; afterwards you won't be sent to some random window.
(let ((doom-inhibit-switch-window-hooks t)
(origwin (selected-window)))
(select-window (split-window origwin count 'right))
(unless evil-vsplit-window-right
(select-window origwin))
(run-hooks 'doom-switch-window-hook))
(run-hooks)
(recenter)
(when (and (not count) evil-auto-balance-windows)
(balance-windows (window-parent)))
(if file (evil-edit file)))
;;;###autoload
(defun +evil--fix-dabbrev-in-minibuffer-h ()
"Make `try-expand-dabbrev' from `hippie-expand' work in minibuffer. See
`he-dabbrev-beg', so we need to redefine syntax for '/'."
(set-syntax-table (let* ((table (make-syntax-table)))
(modify-syntax-entry ?/ "." table)
table)))

View File

@@ -0,0 +1,39 @@
;;; editor/evil/autoload/embrace.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +evil--embrace-get-pair (char)
(if-let* ((pair (cdr-safe (assoc (string-to-char char) evil-surround-pairs-alist))))
pair
(if-let* ((pair (assoc-default char embrace--pairs-list)))
(if-let* ((real-pair (and (functionp (embrace-pair-struct-read-function pair))
(funcall (embrace-pair-struct-read-function pair)))))
real-pair
(cons (embrace-pair-struct-left pair) (embrace-pair-struct-right pair)))
(cons char char))))
;;;###autoload
(defun +evil--embrace-escaped ()
"Backslash-escaped surround character support for embrace."
(let ((char (read-char "\\")))
(if (eq char 27)
(cons "" "")
(let ((pair (+evil--embrace-get-pair (string char)))
(text (if (sp-point-in-string) "\\\\%s" "\\%s")))
(cons (format text (car pair))
(format text (cdr pair)))))))
;;;###autoload
(defun +evil--embrace-latex ()
"LaTeX command support for embrace."
(cons (format "\\%s{" (read-string "\\")) "}"))
;;;###autoload
(defun +evil--embrace-elisp-fn ()
"Elisp function support for embrace."
(cons (format "(%s " (or (read-string "(") "")) ")"))
;;;###autoload
(defun +evil--embrace-angle-brackets ()
"Type/generic angle brackets."
(cons (format "%s<" (or (read-string "") ""))
">"))

View File

@@ -0,0 +1,180 @@
;; editor/evil/autoload/evil.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-evil-initial-state! (modes state)
"Set the initialize STATE of MODES using `evil-set-initial-state'."
(declare (indent defun))
(after! evil
(if (listp modes)
(dolist (mode (doom-enlist modes))
(evil-set-initial-state mode state))
(evil-set-initial-state modes state))))
;;
;;; Interactive commands
;;;###autoload
(defun +evil/visual-indent ()
"vnoremap < <gv"
(interactive)
(evil-shift-right (region-beginning) (region-end))
(evil-normal-state)
(evil-visual-restore))
;;;###autoload
(defun +evil/visual-dedent ()
"vnoremap > >gv"
(interactive)
(evil-shift-left (region-beginning) (region-end))
(evil-normal-state)
(evil-visual-restore))
;;;###autoload
(defun +evil/paste-preserve-register ()
"Call `evil-paste-after' without overwriting the clipboard (by writing to the
0 register instead). This allows you to paste the same text again afterwards."
(interactive)
(let ((evil-this-register ?0))
(call-interactively #'evil-paste-after)))
(defun +evil--window-swap (direction)
"Move current window to the next window in DIRECTION.
If there are no windows there and there is only one window, split in that
direction and place this window there. If there are no windows and this isn't
the only window, use evil-window-move-* (e.g. `evil-window-move-far-left')."
(when (window-dedicated-p)
(user-error "Cannot swap a dedicated window"))
(let* ((this-window (selected-window))
(this-buffer (current-buffer))
(that-window (windmove-find-other-window direction nil this-window))
(that-buffer (window-buffer that-window)))
(when (or (minibufferp that-buffer)
(window-dedicated-p this-window))
(setq that-buffer nil that-window nil))
(if (not (or that-window (one-window-p t)))
(funcall (pcase direction
('left #'evil-window-move-far-left)
('right #'evil-window-move-far-right)
('up #'evil-window-move-very-top)
('down #'evil-window-move-very-bottom)))
(unless that-window
(setq that-window
(split-window this-window nil
(pcase direction
('up 'above)
('down 'below)
(_ direction))))
(with-selected-window that-window
(switch-to-buffer (doom-fallback-buffer)))
(setq that-buffer (window-buffer that-window)))
(with-selected-window this-window
(switch-to-buffer that-buffer))
(with-selected-window that-window
(switch-to-buffer this-buffer))
(select-window that-window))))
;;;###autoload
(defun +evil/window-move-left () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'left))
;;;###autoload
(defun +evil/window-move-right () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'right))
;;;###autoload
(defun +evil/window-move-up () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'up))
;;;###autoload
(defun +evil/window-move-down () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'down))
;;;###autoload
(defun +evil/easymotion ()
"Invoke and lazy-load `evil-easymotion' without compromising which-key
integration."
(interactive)
(let ((prefix (this-command-keys)))
(evil-define-key* 'motion 'global prefix nil)
(evilem-default-keybindings (key-description prefix))
(setq prefix-arg current-prefix-arg
unread-command-events
(mapcar (lambda (e) (cons t e))
(vconcat (when evil-this-operator
(where-is-internal evil-this-operator
evil-normal-state-map
t))
prefix)))))
;;;###autoload (autoload '+evil:apply-macro "editor/evil/autoload/evil" nil t)
(evil-define-operator +evil:apply-macro (beg end)
"Apply macro to each line."
:move-point nil
(interactive "<r>")
(let ((register (or evil-this-register (read-char)))
macro)
(cond ((or (and (eq register ?@) (eq evil-last-register ?:))
(eq register ?:))
(setq macro (lambda () (evil-ex-repeat nil))
evil-last-register ?:))
((eq register ?@)
(unless evil-last-register
(user-error "No previously executed keyboard macro."))
(setq macro (evil-get-register evil-last-register t)))
((setq macro (evil-get-register register t)
evil-last-register register)))
(unless macro
(user-error "No macro recorded in %c register" register))
(evil-change-state 'normal)
(evil-with-single-undo
(let ((lines (count-lines beg end)))
(message "Applied macro in %c register %d times" register lines)
(apply-macro-to-region-lines beg end macro)
(message "Applied macro in %c register %d times...DONE" register lines)))))
;;;###autoload (autoload '+evil:retab "editor/evil/autoload/evil" nil t)
(evil-define-operator +evil:retab (&optional beg end)
"Wrapper around `doom/retab'."
:motion nil :move-point nil :type line
(interactive "<r>")
(doom/retab beg end))
;;;###autoload (autoload '+evil:narrow-buffer "editor/evil/autoload/evil" nil t)
(evil-define-operator +evil:narrow-buffer (beg end &optional bang)
"Narrow the buffer to region between BEG and END.
Widens narrowed buffers first. If BANG, use indirect buffer clones instead."
:move-point nil
(interactive "<r><!>")
(if (not bang)
(if (buffer-narrowed-p)
(widen)
(narrow-to-region beg end))
(when (buffer-narrowed-p)
(doom/widen-indirectly-narrowed-buffer t))
(doom/narrow-buffer-indirectly beg end)))
;;;###autoload (autoload '+evil:yank-unindented "editor/evil/autoload/evil" nil t)
(evil-define-operator +evil:yank-unindented (beg end _type _register _yank-handler)
"Saves the (reindented) characters in motion into the kill-ring."
:move-point nil
:repeat nil
(interactive "<R><x><y>")
(let ((indent (save-excursion (goto-char beg) (current-indentation)))
(text (buffer-substring beg end)))
(with-temp-buffer
(insert text)
(indent-rigidly (point-min) (point-max) (- indent))
(evil-yank (point-min) (point-max)))))
;;
;;; wgrep
;;;###autoload (autoload '+evil-delete "editor/evil/autoload/evil" nil t)
(evil-define-operator +evil-delete (beg end type register yank-handler)
"A wrapper around `evil-delete' for `wgrep' buffers that will invoke
`wgrep-mark-deletion' on lines you try to delete."
(interactive "<R><x><y>")
(condition-case _ex
(evil-delete beg end type register yank-handler)
('text-read-only
(evil-apply-on-block
(lambda (beg _)
(goto-char beg)
(call-interactively #'wgrep-mark-deletion))
beg (1- end) nil))))

View File

@@ -0,0 +1,190 @@
;;; editor/evil/autoload/ex.el -*- lexical-binding: t; -*-
(defvar +evil--flag nil)
(defun +evil--ex-match-init (name &optional face update-hook)
(with-current-buffer evil-ex-current-buffer
(cond
((eq +evil--flag 'start)
(evil-ex-make-hl name
:face (or face 'evil-ex-lazy-highlight)
:update-hook (or update-hook #'evil-ex-pattern-update-ex-info))
(setq +evil--flag 'update))
((eq +evil--flag 'stop)
(evil-ex-delete-hl name)))))
(defun +evil--ex-buffer-match (arg &optional hl-name flags beg end)
(when (and (eq +evil--flag 'update)
evil-ex-substitute-highlight-all
(not (zerop (length arg))))
(condition-case lossage
(let* ((pattern (evil-ex-make-substitute-pattern
arg
(or flags (list))))
(range (or (evil-copy-range evil-ex-range)
(evil-range (or beg (line-beginning-position))
(or end (line-end-position))
'line
:expanded t))))
(evil-expand-range range)
(evil-ex-hl-set-region hl-name
(max (evil-range-beginning range) (window-start))
(min (evil-range-end range) (window-end)))
(evil-ex-hl-change hl-name pattern))
(end-of-file
(evil-ex-pattern-update-ex-info nil "incomplete replacement"))
(user-error
(evil-ex-pattern-update-ex-info nil (format "?%s" lossage))))))
;;;###autoload
(defun +evil-ex-regexp-match (flag &optional arg invert)
(let ((hl-name 'evil-ex-buffer-match)
(+evil--flag flag))
(with-selected-window (minibuffer-selected-window)
(+evil--ex-match-init hl-name)
(cl-destructuring-bind (&optional arg flags)
(evil-delimited-arguments arg 2)
(let ((evil-ex-substitute-global
(if invert
(not evil-ex-substitute-global)
evil-ex-substitute-global)))
(+evil--ex-buffer-match
arg hl-name (string-to-list flags)))))))
;;
;;; Ex Commands
;;;###autoload (autoload '+evil:align "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:align (beg end pattern &optional flags)
"Ex interface to `align-regexp'.
PATTERN is a vim-style regexp. FLAGS is an optional string of characters.
Supports the following flags:
g Repeat alignment on all matches in each line"
(interactive "<r><//>")
(align-regexp
beg end
(concat "\\(\\s-*\\)" (evil-transform-vim-style-regexp pattern))
1 1 (memq ?g flags)))
;;;###autoload (autoload '+evil:align-right "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:align-right (beg end pattern &optional flags)
"Ex interface to `align-regexp' that right-aligns matches.
PATTERN is a vim-style regexp. FLAGS is an optional string of characters.
Supports the following flags:
g Repeat alignment on all matches in each line"
(interactive "<r><//>")
(align-regexp
beg end
(concat "\\(" (evil-transform-vim-style-regexp pattern) "\\)")
-1 1 (memq ?g flags)))
;; ;;;###autoload (autoload '+evil:sort "editor/evil/autoload/ex" nil nil)
;; (evil-define-command +evil:sort (beg end &optional pattern flags reverse)
;; (interactive "<r><//><!>"))
;;;###autoload (autoload '+evil:open-scratch-buffer "editor/evil/autoload/ex" nil t)
(evil-define-operator +evil:open-scratch-buffer (bang)
(interactive "<!>")
(doom/open-scratch-buffer bang))
;;;###autoload (autoload '+evil:pwd "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:pwd (bang)
"Display the current working directory. If BANG, copy it to your clipboard."
(interactive "<!>")
(if (not bang)
(pwd)
(kill-new default-directory)
(message "Copied to clipboard")))
;;;###autoload (autoload '+evil:make "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:make (arguments &optional bang)
"Run make with ARGUMENTS.
If BANG is non-nil, open compilation output in a comint buffer.
If BANG, then run ARGUMENTS as a full command. This command understands vim file
modifiers (like %:p:h). See `+evil-resolve-vim-path-a' for details."
(interactive "<sh><!>")
(+evil:compile (format "make %s"
(evil-ex-replace-special-filenames
arguments))
bang))
;;;###autoload (autoload '+evil:compile "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:compile (arguments &optional bang)
"Run `compile-command' with ARGUMENTS.
If BANG is non-nil, open compilation output in a comint buffer.
This command understands vim file modifiers (like %:p:h). See
`+evil-resolve-vim-path-a' for details."
(interactive "<sh><!>")
(compile (evil-ex-replace-special-filenames
(format "%s %s"
(eval compile-command)
arguments))
bang))
;;;###autoload (autoload '+evil:reverse-lines "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:reverse-lines (beg end)
"Reverse lines between BEG and END."
(interactive "<r>")
(reverse-region beg end))
;;;###autoload (autoload '+evil:cd "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:cd (&optional path)
"Change `default-directory' with `cd'."
(interactive "<f>")
(let ((path (or path "~")))
(cd path)
(message "Changed directory to '%s'" (abbreviate-file-name (expand-file-name path)))))
;;;###autoload (autoload '+evil:kill-all-buffers "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:kill-all-buffers (&optional bang)
"Kill all buffers. If BANG, kill current session too."
(interactive "<!>")
(if (and bang (fboundp '+workspace/kill-session))
(+workspace/kill-session)
(call-interactively #'doom/kill-all-buffers)))
;;;###autoload (autoload '+evil:kill-matching-buffers "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:kill-matching-buffers (&optional bang pattern)
"Kill all buffers matching PATTERN regexp. If BANG, only match project
buffers."
(interactive "<a>")
(doom/kill-matching-buffers
pattern (if bang (doom-project-buffer-list))))
;;;###autoload (autoload '+evil:help "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:help (&optional bang query)
"Look up documentation for QUERY.
If QUERY is in the format of an ex command, it will map it to the underlying
function and open its documentation with `helpful-function'. Otherwise, it will
search for it with `apropos'.
If QUERY is empty, this runs the equivalent of 'M-x apropos'. If BANG is
non-nil, a search is preformed against Doom's manual (with `doom/help-search')."
(interactive "<!><a>")
(if bang
(doom/help-search query)
(save-match-data
(cond ((or (null query) (string-empty-p (string-trim query)))
(call-interactively
(or (command-remapping #'apropos)
#'apropos)))
((string-match "^ *:\\([^ ]+\\)$" query)
(helpful-function
(evil-ex-completed-binding (match-string 1 query))))
((message "Searching for %S, this may take a while..." query)
(apropos query t))))))
;;;###autoload (autoload '+evil:read "editor/evil/autoload/ex" nil t)
(evil-define-command +evil:read (count file)
"Alternative version of `evil-read' that replaces filename modifiers in FILE."
(interactive "P<fsh>")
(evil-read count (evil-ex-replace-special-filenames file)))

View File

@@ -0,0 +1,33 @@
;;; editor/evil/autoload/files.el -*- lexical-binding: t; -*-
;;;###autoload (autoload '+evil:delete-this-file "editor/evil/autoload/files" nil t)
(evil-define-command +evil:delete-this-file (&optional filename force-p)
"Delete FILENAME (defaults to the file associated with current buffer) and
kills the buffer. If FORCE-P, force the deletion (don't ask for confirmation)."
:repeat nil
(interactive "<f><!>")
(doom/delete-this-file (or filename (file-truename buffer-file-name))
force-p))
;;;###autoload (autoload '+evil:move-this-file "editor/evil/autoload/files" nil t)
(evil-define-command +evil:move-this-file (new-path &optional force-p)
"Move current buffer's file to NEW-PATH. Replaces %, # and other vim-esque
filename modifiers (see `+evil*ex-replace-special-filenames'). If FORCE-P,
overwrite the destination file if it exists, without confirmation."
:repeat nil
(interactive "<f><!>")
(when (or (not new-path) (string-empty-p new-path))
(user-error "No new path was specified"))
(doom/move-this-file new-path force-p))
;;;###autoload (autoload '+evil:copy-this-file "editor/evil/autoload/files" nil nil)
(evil-define-command +evil:copy-this-file (new-path &optional force-p)
"Copy current buffer's file to NEW-PATH. Replaces %, # and other vim-esque
filename modifiers (see `+evil*ex-replace-special-filenames'). If FORCE-P,
overwrite the destination file if it exists, without confirmation."
:repeat nil
(interactive "<f><!>")
(when (or (not new-path) (string-empty-p new-path))
(user-error "No new path was specified"))
(doom/copy-this-file new-path force-p))

View File

@@ -0,0 +1,13 @@
;;; editor/evil/autoload/textobjects.el -*- lexical-binding: t; -*-
;;;###autoload (autoload '+evil:whole-buffer-txtobj "editor/evil/autoload/textobjects" nil nil)
(evil-define-text-object +evil:whole-buffer-txtobj (count &optional _beg _end type)
"Text object to select the whole buffer."
(evil-range (point-min) (point-max) type))
;;;###autoload (autoload '+evil:defun-txtobj "editor/evil/autoload/textobjects" nil nil)
(evil-define-text-object +evil:defun-txtobj (count &optional _beg _end type)
"Text object to select the whole buffer."
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'defun)
(evil-range beg end type)))

View File

@@ -0,0 +1,200 @@
;;; editor/evil/autoload/unimpaired.el -*- lexical-binding: t; -*-
;; These are ported from vim-unimpaired https://github.com/tpope/vim-unimpaired
;; and bound in the :config default module (in +evil-bindings.el).
;;
;;; Next/Previous commands
;;;###autoload
(defun +evil/next-beginning-of-method (count)
"Jump to the beginning of the COUNT-th method/function after point."
(interactive "p")
(beginning-of-defun (- count)))
;;;###autoload
(defun +evil/previous-beginning-of-method (count)
"Jump to the beginning of the COUNT-th method/function before point."
(interactive "p")
(beginning-of-defun count))
;;;###autoload
(defalias #'+evil/next-end-of-method #'end-of-defun
"Jump to the end of the COUNT-th method/function after point.")
;;;###autoload
(defun +evil/previous-end-of-method (count)
"Jump to the end of the COUNT-th method/function before point."
(interactive "p")
(end-of-defun (- count)))
;;;###autoload
(defun +evil/next-preproc-directive (count)
"Jump to the COUNT-th preprocessor directive after point.
By default, this only recognizes C preproc directives. To change this see
`+evil-preprocessor-regexp'."
(interactive "p")
;; TODO More generalized search, to support directives in other languages?
(if (re-search-forward +evil-preprocessor-regexp nil t count)
(goto-char (match-beginning 0))
(user-error "No preprocessor directives %s point"
(if (> count 0) "after" "before"))))
;;;###autoload
(defun +evil/previous-preproc-directive (count)
"Jump to the COUNT-th preprocessor directive before point.
See `+evil/next-preproc-directive' for details."
(interactive "p")
(+evil/next-preproc-statement (- count)))
;;;###autoload
(defun +evil/next-comment (count)
"Jump to the beginning of the COUNT-th commented region after point."
(interactive "p")
(let ((orig-pt (point)))
(require 'newcomment)
(dotimes (_ (abs count))
(cond ((> count 0)
(while (and (not (eobp)) (sp-point-in-comment))
(forward-line 1))
(unless (comment-search-forward (point-max) 'noerror)
(goto-char orig-pt)
(user-error "No comment after point")))
(t
(while (and (not (bobp)) (sp-point-in-comment))
(forward-line -1))
(unless (comment-search-backward nil 'noerror)
(goto-char orig-pt)
(user-error "No comment before point")))))))
;;;###autoload
(defun +evil/previous-comment (count)
"Jump to the beginning of the COUNT-th commented region before point."
(interactive "p")
(+evil/next-comment (- count)))
;;; ] SPC / [ SPC
;;;###autoload
(defun +evil/insert-newline-below (count)
"Insert COUNT blank line(s) below current line. Does not change modes."
(interactive "p")
(dotimes (_ count)
(save-excursion (evil-insert-newline-below))))
;;;###autoload
(defun +evil/insert-newline-above (count)
"Insert COUNT blank line(s) above current line. Does not change modes."
(interactive "p")
(dotimes (_ count)
(save-excursion (evil-insert-newline-above))))
;;; ]t / [t
;;;###autoload
(defun +evil/next-frame (count)
"Focus next frame."
(interactive "p")
(dotimes (_ (abs count))
(let ((frame (if (> count 0) (next-frame) (previous-frame))))
(if (eq frame (selected-frame))
(user-error "No other frame")
(select-frame-set-input-focus frame)))))
;;;###autoload
(defun +evil/previous-frame (count)
"Focus previous frame."
(interactive "p")
(+evil/next-frame (- count)))
;;; ]f / [f
(defun +evil--next-file (n)
(unless buffer-file-name
(user-error "Must be called from a file-visiting buffer"))
(let* ((directory (file-name-directory buffer-file-name))
(filename (file-name-nondirectory buffer-file-name))
(files (doom-glob (file-name-directory buffer-file-name) "[!.]*"))
(index (cl-position filename files :test #'file-equal-p)))
(when (null index)
(user-error "Couldn't find this file in current directory"))
(let ((index (+ index n)))
(cond ((>= index (length files))
(user-error "No files after this one"))
((< index 0)
(user-error "No files before this one"))
((expand-file-name (nth index files) directory))))))
;;;###autoload
(defun +evil/next-file (count)
"Open file following this one, alphabetically, in the same directory."
(interactive "p")
(find-file (+evil--next-file count)))
;;;###autoload
(defun +evil/previous-file (count)
"Open file preceding this one, alphabetically, in the same directory."
(interactive "p")
(find-file (+evil--next-file (- count))))
;;
;;; Encoding/Decoding
;; NOTE For ]x / [x see :lang web
;; - `+web:encode-html-entities'
;; - `+web:decode-html-entities'
(defun +evil--encode (beg end fn)
(save-excursion
(goto-char beg)
(let* ((end (if (eq evil-this-type 'line) (1- end) end))
(text (buffer-substring-no-properties beg end)))
(delete-region beg end)
(insert (funcall fn text)))))
;;; ]u / [u
;;;###autoload (autoload '+evil:url-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-encode (_count &optional beg end)
"TODO"
(interactive "<c><r>")
(+evil--encode beg end #'url-encode-url))
;;;###autoload (autoload '+evil:url-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-decode (_count &optional beg end)
"TODO"
(interactive "<c><r>")
(+evil--encode beg end #'url-unhex-string))
;;; ]y / [y
;;;###autoload (autoload '+evil:c-string-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-encode (_count &optional beg end)
"TODO"
(interactive "<c><r>")
(+evil--encode
beg end
(lambda (text)
(replace-regexp-in-string "[\"\\]" (lambda (ch) (concat "\\" ch)) text))))
;;;###autoload (autoload '+evil:c-string-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-decode (_count &optional beg end)
"TODO"
(interactive "<c><r>")
(+evil--encode
beg end
(lambda (text)
(replace-regexp-in-string "\\\\[\"\\]" (lambda (str) (substring str 1)) text))))
;;
;;; Standalone
;;; gp
;;;###autoload
(defun +evil/reselect-paste ()
"Return to visual mode and reselect the last pasted region."
(interactive)
(cl-destructuring-bind (_ _ _ beg end &optional _)
evil-last-paste
(evil-visual-make-selection
(save-excursion (goto-char beg) (point-marker))
end)))

View File

@@ -0,0 +1,587 @@
;;; editor/evil/config.el -*- lexical-binding: t; -*-
;; I'm a vimmer at heart. Its modal philosophy suits me better, and this module
;; strives to make Emacs a much better vim than vim was.
(defvar +evil-repeat-keys (cons ";" ",")
"The keys to use for universal 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.")
(defvar +evil-want-o/O-to-continue-comments t
"If non-nil, the o/O keys will continue comment lines if the point is on a
line with a linewise comment.")
(defvar +evil-preprocessor-regexp "^\\s-*#[a-zA-Z0-9_]"
"The regexp used by `+evil/next-preproc-directive' and
`+evil/previous-preproc-directive' on ]# and [#, to jump between preprocessor
directives. By default, this only recognizes C directives.")
;; Set these defaults before `evil'; use `defvar' so they can be changed prior
;; to loading.
(defvar evil-want-C-i-jump (or (daemonp) (display-graphic-p)))
(defvar evil-want-C-u-scroll t)
(defvar evil-want-C-w-scroll t)
(defvar evil-want-Y-yank-to-eol t)
(defvar evil-want-abbrev-expand-on-insert-exit nil)
(use-package! evil
:hook (doom-init-modules . evil-mode)
:demand t
:preface
(setq evil-want-visual-char-semi-exclusive t
evil-magic t
evil-echo-state t
evil-indent-convert-tabs t
evil-ex-search-vim-style-regexp t
evil-ex-substitute-global t
evil-ex-visual-char-range t ; column range for ex commands
evil-insert-skip-empty-lines t
evil-mode-line-format 'nil
evil-respect-visual-line-mode t
;; more vim-like behavior
evil-symbol-word-search t
;; cursor appearance
evil-default-cursor '+evil-default-cursor-fn
evil-normal-state-cursor 'box
evil-emacs-state-cursor '(box +evil-emacs-cursor-fn)
evil-insert-state-cursor 'bar
evil-visual-state-cursor 'hollow
;; must be set before evil/evil-collection is loaded
evil-want-keybinding (not (featurep! +everywhere))
;; Only do highlighting in selected window so that Emacs has less work
;; to do highlighting them all.
evil-ex-interactive-search-highlight 'selected-window)
:config
(evil-select-search-module 'evil-search-module 'evil-search)
(put 'evil-define-key* 'lisp-indent-function 'defun)
;; stop copying each visual state move to the clipboard:
;; https://bitbucket.org/lyro/evil/issue/336/osx-visual-state-copies-the-region-on
;; grokked from:
;; http://stackoverflow.com/questions/15873346/elisp-rename-macro
(advice-add #'evil-visual-update-x-selection :override #'ignore)
;; Start help-with-tutorial in emacs state
(advice-add #'help-with-tutorial :after (lambda (&rest _) (evil-emacs-state +1)))
;; Allows you to click buttons without initiating a selection
(define-key evil-motion-state-map [down-mouse-1] nil)
;; Done in a hook to ensure the popup rules load as late as possible
(add-hook! 'doom-init-modules-hook
(defun +evil--init-popup-rules-h ()
(set-popup-rules!
'(("^\\*evil-registers" :size 0.3)
("^\\*Command Line" :size 8)))))
;; Change the cursor color in emacs state. We do it this roundabout way
;; instead of changing `evil-default-cursor' (or `evil-emacs-state-cursor') so
;; it won't interfere with users who have changed these variables.
(defvar +evil--default-cursor-color "#ffffff")
(defvar +evil--emacs-cursor-color "#ff9999")
(add-hook! 'doom-load-theme-hook
(defun +evil-update-cursor-color-h ()
(setq +evil--default-cursor-color (face-background 'cursor)
+evil--emacs-cursor-color (face-foreground 'warning))))
(defun +evil-default-cursor-fn ()
(evil-set-cursor-color +evil--default-cursor-color))
(defun +evil-emacs-cursor-fn ()
(evil-set-cursor-color +evil--emacs-cursor-color))
(setq-hook! 'after-change-major-mode-hook evil-shift-width tab-width)
;; --- keybind fixes ----------------------
(after! wgrep
;; A wrapper that invokes `wgrep-mark-deletion' across lines you use
;; `evil-delete' in wgrep buffers.
(define-key wgrep-mode-map [remap evil-delete] #'+evil-delete))
(add-hook! 'doom-escape-hook
(defun +evil-disable-ex-highlights-h ()
"Disable ex search buffer highlights."
(when (evil-ex-hl-active-p 'evil-ex-search)
(evil-ex-nohighlight)
t)))
;; --- evil hacks -------------------------
(unless noninteractive
(setq save-silently t)
(add-hook! 'after-save-hook
(defun +evil-display-vimlike-save-message-h ()
"Shorter, vim-esque save messages."
(message "\"%s\" %dL, %dC written"
(if buffer-file-name
(file-relative-name (file-truename buffer-file-name) (doom-project-root))
(buffer-name))
(count-lines (point-min) (point-max))
(buffer-size)))))
;; 'gq' moves the cursor to the beginning of selection. Disable this, since
;; it's more disruptive than helpful.
(defadvice! +evil--dont-move-cursor-a (orig-fn &rest args)
:around #'evil-indent
(save-excursion (apply orig-fn args)))
;; In evil, registers 2-9 are buffer-local. In vim, they're global, so...
(defadvice! +evil--make-numbered-markers-global-a (_arg)
:after-until #'evil-global-marker-p
(and (>= char ?2) (<= char ?9)))
;; Make ESC (from normal mode) the universal escaper. See `doom-escape-hook'.
(advice-add #'evil-force-normal-state :after #'+evil-escape-a)
;; monkey patch `evil-ex-replace-special-filenames' to improve support for
;; file modifiers like %:p:h. This adds support for most of vim's modifiers,
;; and one custom one: %:P (expand to the project root).
(advice-add #'evil-ex-replace-special-filenames :override #'+evil-resolve-vim-path-a)
;; make `try-expand-dabbrev' (from `hippie-expand') work in minibuffer
(add-hook 'minibuffer-inactive-mode-hook #'+evil--fix-dabbrev-in-minibuffer-h)
;; Focus and recenter new splits
(advice-add #'evil-window-split :override #'+evil-window-split-a)
(advice-add #'evil-window-vsplit :override #'+evil-window-vsplit-a)
;; Make o/O continue comments (see `+evil-want-o/O-to-continue-comments')
(advice-add #'evil-open-above :around #'+evil--insert-newline-above-and-respect-comments-a)
(advice-add #'evil-open-below :around #'+evil--insert-newline-below-and-respect-comments-a)
;; Recenter screen after most searches
(dolist (fn '(evil-visualstar/begin-search-forward
evil-visualstar/begin-search-backward
evil-ex-search-word-backward
evil-ex-search-word-backward
evil-ex-search-forward
evil-ex-search-backward))
(advice-add fn :after #'doom-recenter-a))
;; --- custom interactive codes -----------
;; These arg types will highlight matches in the current buffer
(evil-ex-define-argument-type regexp-match
:runner (lambda (flag &optional arg) (+evil-ex-regexp-match flag arg 'inverted)))
(evil-ex-define-argument-type regexp-global-match
:runner +evil-ex-regexp-match)
(defun +evil--regexp-match-args (arg)
(when (evil-ex-p)
(cl-destructuring-bind (&optional arg flags)
(evil-delimited-arguments arg 2)
(list arg (string-to-list flags)))))
;; Other commands can make use of this
(evil-define-interactive-code "<//>"
:ex-arg regexp-match
(+evil--regexp-match-args evil-ex-argument))
(evil-define-interactive-code "<//!>"
:ex-arg regexp-global-match
(+evil--regexp-match-args evil-ex-argument))
;; Forward declare these so that ex completion works, even if the autoloaded
;; functions aren't loaded yet.
(evil-add-command-properties '+evil:align :ex-arg 'regexp-match)
(evil-add-command-properties '+evil:align-right :ex-arg 'regexp-match)
(evil-add-command-properties '+multiple-cursors:evil-mc :ex-arg 'regexp-global-match)
;; Lazy load evil ex commands
(delq! 'evil-ex features)
(add-transient-hook! 'evil-ex (provide 'evil-ex))
(after! evil-ex (load! "+commands")))
;;
;;; Packages
(use-package! evil-easymotion
:commands evilem-create evilem-default-keybindings
:config
;; Use evil-search backend, instead of isearch
(evilem-make-motion evilem-motion-search-next #'evil-ex-search-next
:bind ((evil-ex-search-highlight-all nil)))
(evilem-make-motion evilem-motion-search-previous #'evil-ex-search-previous
:bind ((evil-ex-search-highlight-all nil)))
(evilem-make-motion evilem-motion-search-word-forward #'evil-ex-search-word-forward
:bind ((evil-ex-search-highlight-all nil)))
(evilem-make-motion evilem-motion-search-word-backward #'evil-ex-search-word-backward
:bind ((evil-ex-search-highlight-all nil))))
(use-package! evil-embrace
:commands embrace-add-pair embrace-add-pair-regexp
:hook (LaTeX-mode . embrace-LaTeX-mode-hook)
:hook (org-mode . embrace-org-mode-hook)
:hook ((ruby-mode enh-ruby-mode) . embrace-ruby-mode-hook)
:hook (emacs-lisp-mode . embrace-emacs-lisp-mode-hook)
:hook ((lisp-mode emacs-lisp-mode clojure-mode racket-mode)
. +evil-embrace-lisp-mode-hook-h)
:hook ((org-mode LaTeX-mode) . +evil-embrace-latex-mode-hook-h)
:hook ((c++-mode rust-mode rustic-mode csharp-mode java-mode swift-mode typescript-mode)
. +evil-embrace-angle-bracket-modes-hook-h)
:init
(after! evil-surround
(evil-embrace-enable-evil-surround-integration))
:config
(setq evil-embrace-show-help-p nil)
(defun +evil-embrace-latex-mode-hook-h ()
(embrace-add-pair-regexp ?l "\\[a-z]+{" "}" #'+evil--embrace-latex))
(defun +evil-embrace-lisp-mode-hook-h ()
(push (cons ?f (make-embrace-pair-struct
:key ?f
:read-function #'+evil--embrace-elisp-fn
:left-regexp "([^ ]+ "
:right-regexp ")"))
embrace--pairs-list))
(defun +evil-embrace-angle-bracket-modes-hook-h ()
(set (make-local-variable 'evil-embrace-evil-surround-keys)
(delq ?< evil-embrace-evil-surround-keys))
(push (cons ?< (make-embrace-pair-struct
:key ?<
:read-function #'+evil--embrace-angle-brackets
:left-regexp "\\[a-z]+<"
:right-regexp ">"))
embrace--pairs-list))
;; Add escaped-sequence support to embrace
(setf (alist-get ?\\ (default-value 'embrace--pairs-list))
(make-embrace-pair-struct
:key ?\\
:read-function #'+evil--embrace-escaped
:left-regexp "\\[[{(]"
:right-regexp "\\[]})]")))
(use-package! evil-escape
:commands evil-escape
:after-call pre-command-hook
:init
(setq evil-escape-excluded-states '(normal visual multiedit emacs motion)
evil-escape-excluded-major-modes '(neotree-mode treemacs-mode vterm-mode)
evil-escape-key-sequence "jk"
evil-escape-delay 0.15)
(evil-define-key* '(insert replace visual operator) 'global "\C-g" #'evil-escape)
:config
;; no `evil-escape' in minibuffer
(add-hook 'evil-escape-inhibit-functions #'minibufferp)
;; so that evil-escape-mode-hook runs, and can be toggled by evil-mc
(evil-escape-mode +1))
(use-package! evil-exchange
:commands evil-exchange
:config
(add-hook! 'doom-escape-hook
(defun +evil--escape-exchange-h ()
(when evil-exchange--overlays
(evil-exchange-cancel)
t))))
(use-package! evil-quick-diff
:commands (evil-quick-diff evil-quick-diff-cancel))
(use-package! evil-nerd-commenter
:commands (evilnc-comment-operator
evilnc-inner-comment
evilnc-outer-commenter))
(use-package! evil-snipe
:commands (evil-snipe-mode
evil-snipe-override-mode
evil-snipe-local-mode
evil-snipe-override-local-mode)
:after-call pre-command-hook
:init
(setq evil-snipe-smart-case t
evil-snipe-scope 'line
evil-snipe-repeat-scope 'visible
evil-snipe-char-fold t)
:config
(pushnew! evil-snipe-disabled-modes 'Info-mode 'calc-mode)
(evil-snipe-mode +1)
(evil-snipe-override-mode +1))
(use-package! evil-surround
:commands (global-evil-surround-mode
evil-surround-edit
evil-Surround-edit
evil-surround-region)
:config (global-evil-surround-mode 1))
(use-package! evil-traces
:after evil-ex
:config
(pushnew! evil-traces-argument-type-alist
'(+evil:align . evil-traces-global)
'(+evil:align-right . evil-traces-global))
(evil-traces-mode))
;; Allows you to use the selection for * and #
(use-package! evil-visualstar
:commands (evil-visualstar/begin-search
evil-visualstar/begin-search-forward
evil-visualstar/begin-search-backward)
:init
(evil-define-key* 'visual 'global
"*" #'evil-visualstar/begin-search-forward
"#" #'evil-visualstar/begin-search-backward))
;;
;;; Text object plugins
(use-package! exato
:commands evil-outer-xml-attr evil-inner-xml-attr)
;;
;;; Keybinds
(defmacro set-repeater! (command next-func prev-func)
"Makes ; and , the universal repeat-keys in evil-mode.
To change these keys see `+evil-repeat-keys'."
(let ((fn-sym (intern (format "+evil/repeat-%s" (doom-unquote command)))))
`(progn
(defun ,fn-sym (&rest _)
(when +evil-repeat-keys
(evil-define-key* 'motion 'local
(kbd (car +evil-repeat-keys)) #',next-func
(kbd (cdr +evil-repeat-keys)) #',prev-func)))
(advice-add #',command :after-while #',fn-sym))))
;; n/N
(set-repeater! evil-ex-search-next evil-ex-search-next evil-ex-search-previous)
(set-repeater! evil-ex-search-previous evil-ex-search-next evil-ex-search-previous)
(set-repeater! evil-ex-search-forward evil-ex-search-next evil-ex-search-previous)
(set-repeater! evil-ex-search-backward evil-ex-search-next evil-ex-search-previous)
;; f/F/t/T/s/S
(after! evil-snipe
(setq evil-snipe-repeat-keys nil
evil-snipe-override-evil-repeat-keys nil) ; causes problems with remapped ;
(set-repeater! evil-snipe-f evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-F evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-t evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-T evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-s evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-S evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-x evil-snipe-repeat evil-snipe-repeat-reverse)
(set-repeater! evil-snipe-X evil-snipe-repeat evil-snipe-repeat-reverse))
;; */#
(set-repeater! evil-visualstar/begin-search-forward
evil-ex-search-next evil-ex-search-previous)
(set-repeater! evil-visualstar/begin-search-backward
evil-ex-search-previous evil-ex-search-next)
;; `evil-collection'
(when (featurep! +everywhere)
(unless doom-reloading-p
(load! "+everywhere"))
(setq evil-collection-company-use-tng (featurep! :completion company +tng))
;; Don't let evil-collection interfere with certain keys
(appendq! evil-collection-key-blacklist
(append (when (featurep! :tools lookup)
'("gd" "gf" "K"))
(when (featurep! :tools eval)
'("gr" "gR"))
'("[" "]" "gz" "<escape>")))
(defadvice! +evil-collection-disable-blacklist-a (orig-fn)
:around #'evil-collection-vterm-toggle-send-escape ; allow binding to ESC
(let (evil-collection-key-blacklist)
(apply orig-fn))))
;; 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! :v "@" #'+evil:apply-macro
;; ported from vim-unimpaired
:n "] SPC" #'+evil/insert-newline-below
:n "[ SPC" #'+evil/insert-newline-above
:n "]b" #'next-buffer
:n "[b" #'previous-buffer
:n "]f" #'+evil/next-file
:n "[f" #'+evil/previous-file
:m "]u" #'+evil:url-encode
:m "[u" #'+evil:url-decode
:m "]y" #'+evil:c-string-encode
:m "[y" #'+evil:c-string-decode
(:when (featurep! :lang web)
:m "]x" #'+web:encode-html-entities
:m "[x" #'+web:decode-html-entities)
(:when (featurep! :ui vc-gutter)
:m "]d" #'git-gutter:next-hunk
:m "[d" #'git-gutter:previous-hunk)
(:when (featurep! :ui hl-todo)
:m "]t" #'hl-todo-next
:m "[t" #'hl-todo-previous)
(:when (featurep! :ui workspaces)
:n "gt" #'+workspace:switch-next
:n "gT" #'+workspace:switch-previous
:n "]w" #'+workspace/switch-right
:n "[w" #'+workspace/switch-left)
;; custom vim-unmpaired-esque keys
:m "]#" #'+evil/next-preproc-directive
:m "[#" #'+evil/previous-preproc-directive
:m "]a" #'evil-forward-arg
:m "[a" #'evil-backward-arg
:m "]c" #'+evil/next-comment
:m "[c" #'+evil/previous-comment
:m "]e" #'next-error
:m "[e" #'previous-error
:n "]F" #'+evil/next-frame
:n "[F" #'+evil/previous-frame
:m "]h" #'outline-next-visible-heading
:m "[h" #'outline-previous-visible-heading
:m "]m" #'+evil/next-beginning-of-method
:m "[m" #'+evil/previous-beginning-of-method
:m "]M" #'+evil/next-end-of-method
:m "[M" #'+evil/previous-end-of-method
:n "[o" #'+evil/insert-newline-above
:n "]o" #'+evil/insert-newline-below
:n "gp" #'+evil/reselect-paste
:v "gp" #'+evil/paste-preserve-register
:nv "g@" #'+evil:apply-macro
:nv "gc" #'evilnc-comment-operator
:nv "gx" #'evil-exchange
:nv "gy" #'+evil:yank-unindented
:n "g=" #'evil-numbers/inc-at-pt
:n "g-" #'evil-numbers/dec-at-pt
:v "g=" #'evil-numbers/inc-at-pt-incremental
:v "g-" #'evil-numbers/dec-at-pt-incremental
:v "g+" #'evil-numbers/inc-at-pt
(:when (featurep! :tools lookup)
:nv "K" #'+lookup/documentation
:nv "gd" #'+lookup/definition
:nv "gD" #'+lookup/references
:nv "gf" #'+lookup/file)
(:when (featurep! :tools eval)
:nv "gr" #'+eval:region
:n "gR" #'+eval/buffer
:v "gR" #'+eval:replace-region
;; Restore these keybinds, since the blacklisted/overwritten gr/gR will
;; undo them:
(:after dired
:map dired-mode-map
:n "gr" #'revert-buffer)
(:after notmuch
:map notmuch-common-keymap
:n "gr" #'notmuch-refresh-this-buffer
:n "gR" #'notmuch-poll-and-refresh-this-buffer)
(:after elfeed
:map elfeed-search-update--force
:n "gr" #'elfeed-search-update--force
:n "gR" #'elfeed-search-fetch))
:nv "z=" #'flyspell-correct-word-generic
;; custom evil keybinds
:nv "zn" #'+evil:narrow-buffer
:n "zN" #'doom/widen-indirectly-narrowed-buffer
:n "zx" #'kill-current-buffer
:n "ZX" #'bury-buffer
;; 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
"d" #'evil-window-delete
"C-C" #'ace-delete-window)
;; text objects
:textobj "a" #'evil-inner-arg #'evil-outer-arg
:textobj "B" #'evil-textobj-anyblock-inner-block #'evil-textobj-anyblock-a-block
:textobj "c" #'evilnc-inner-comment #'evilnc-outer-commenter
:textobj "f" #'+evil:defun-txtobj #'+evil:defun-txtobj
:textobj "g" #'+evil:whole-buffer-txtobj #'+evil:whole-buffer-txtobj
:textobj "i" #'evil-indent-plus-i-indent #'evil-indent-plus-a-indent
:textobj "j" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down
:textobj "k" #'evil-indent-plus-i-indent-up #'evil-indent-plus-a-indent-up
:textobj "x" #'evil-inner-xml-attr #'evil-outer-xml-attr
;; evil-easymotion
(:after evil-easymotion
:map evilem-map
"a" (evilem-create #'evil-forward-arg)
"A" (evilem-create #'evil-backward-arg)
"s" #'evil-avy-goto-char-2
"SPC" (λ!! #'evil-avy-goto-char-timer t)
"/" #'evil-avy-goto-char-timer)
;; 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
;; Omni-completion
(:when (featurep! :completion company)
(: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)))

View File

@@ -0,0 +1,29 @@
;; -*- no-byte-compile: t; -*-
;;; editor/evil/packages.el
(package! evil)
(package! evil-args)
(package! evil-easymotion)
(package! evil-embrace)
(package! evil-escape)
(package! evil-exchange)
(package! evil-indent-plus)
(package! evil-nerd-commenter)
(package! evil-numbers :recipe (:host github :repo "janpath/evil-numbers"))
(package! evil-snipe)
(package! evil-surround)
(package! evil-textobj-anyblock)
(package! evil-traces)
(package! evil-visualstar)
(package! exato)
(package! evil-quick-diff :recipe (:host github :repo "rgrinberg/evil-quick-diff"))
;;
(when (featurep! +everywhere)
;; `evil-collection-neotree' uses the `neotree-make-executor' macro, but this
;; requires neotree be available during byte-compilation (while installing).
(when (featurep! :ui neotree)
(package! neotree)
(autoload 'neotree-make-executor "neotree" nil nil 'macro))
(package! evil-collection))

View File

@@ -0,0 +1,69 @@
;; -*- no-byte-compile: t; -*-
;;; editor/evil/test/test-evil.el
(describe "feature/evil"
:var (resv project-root)
(require! :editor evil)
(require 'evil)
(load! "../autoload/evil")
(before-each
(fset 'resv #'+evil-resolve-vim-path-a)
(spy-on 'doom-project-root :and-call-fake (lambda () project-root)))
;; `evil-ex-replace-special-filenames' / `+evil-resolve-vim-path-a'
(describe "file modifiers"
(it "supports basic vim file modifiers"
(let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")
(default-directory "~/.emacs.d/test/modules/")
(project-root "~/.emacs.d/"))
(expect (resv "%") :to-equal "feature/test-evil.el")
(expect (resv "%:r") :to-equal "feature/test-evil")
(expect (resv "%:r.elc") :to-equal "feature/test-evil.elc")
(expect (resv "%:e") :to-equal "el")
(expect (resv "%:p") :to-equal (expand-file-name buffer-file-name))
(expect (resv "%:h") :to-equal "feature")
(expect (resv "%:t") :to-equal "test-evil.el")
(expect (resv "%:.") :to-equal "feature/test-evil.el")
(expect (resv "%:~") :to-equal "~/.emacs.d/test/modules/feature/test-evil.el")
(expect (file-truename (resv "%:p"))
:to-equal (file-truename buffer-file-name))))
(it "supports nested vim file modifiers"
(let ((buffer-file-name "~/vim/src/version.c")
(default-directory "~/vim/")
(project-root "~/vim/"))
(expect (resv "%:p") :to-equal (expand-file-name "~/vim/src/version.c"))
(expect (resv "%:p:.") :to-equal "src/version.c")
(expect (resv "%:p:~") :to-equal "~/vim/src/version.c")
(expect (resv "%:h") :to-equal "src")
(expect (resv "%:p:h") :to-equal (expand-file-name "~/vim/src"))
(expect (resv "%:p:h:h") :to-equal (expand-file-name "~/vim"))
(expect (resv "%:t") :to-equal "version.c")
(expect (resv "%:p:t") :to-equal "version.c")
(expect (resv "%:r") :to-equal "src/version")
(expect (resv "%:p:r") :to-equal (expand-file-name "~/vim/src/version"))
(expect (resv "%:t:r") :to-equal "version")))
(it "cleans up empty file modifiers"
(let (buffer-file-name default-directory)
(expect (resv "%") :to-equal "")
(expect (resv "%:r") :to-equal "")
(expect (resv "%:e") :to-equal "")
(expect (resv "%:h") :to-equal "")
(expect (resv "%:t") :to-equal "")
(expect (resv "%:.") :to-equal "")
(expect (resv "%:~") :to-equal "")
(expect (resv "%:P") :to-equal "")))
(it "supports substitution modifiers"
(let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")
(default-directory "~/.emacs.d/test/modules/"))
(expect (resv "%:s?e?x?") :to-equal "fxature/test-evil.el")
(expect (resv "%:gs?e?x?") :to-equal "fxaturx/txst-xvil.xl")))
(it "cleans up empty substitution modifiers"
(let (buffer-file-name default-directory)
(expect (resv "%:s?e?x?") :to-equal "")
(expect (resv "%:gs?e?x?") :to-equal "")))))

View File

@@ -0,0 +1,79 @@
#+TITLE: editor/file-templates
#+DATE: February 11, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC_2:noexport:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[Usage][Usage]]
- [[Inserting OSS licenses][Inserting OSS licenses]]
- [[Configuration][Configuration]]
- [[Registering a new file template][Registering a new file template]]
- [[Changing existing file templates][Changing existing file templates]]
- [[Adding new OSS licenses][Adding new OSS licenses]]
- [[Troubleshooting][Troubleshooting]]
- [[Appendix][Appendix]]
- [[API][API]]
- [[Commands][Commands]]
- [[Variables][Variables]]
* Description
This module adds file templates for blank files, powered by yasnippet.
** Module Flags
This module provides no flags.
** Plugins
This module installs no plugins.
* Prerequisites
This module has no prerequisites.
* Usage
File templates are automatically expanded when opening empty files.
They are also regular yasnippet snippets, which can be expanded by typing their
trigger and pressing =TAB=. By convention, the triggers for file templates are
prefixed with two underscores ~__~.
** Inserting OSS licenses
A special command is available for inserting software licenses: ~M-x
+file-templates/insert-license~.
#+begin_quote
Licenses with a ~-bp~ suffix are boilerplate templates; shorter versions meant
for comment headers in code.
#+end_quote
* Configuration
** TODO Registering a new file template
** TODO Changing existing file templates
** Adding new OSS licenses
The ~+file-templates/insert-license~ command searches for snippets under
~text-mode~ that are named ~__license-ABC~, where ABC is the short name of the
license. e.g. ~__license-mit~.
So long as these files exist, ~+file-templates/insert-license~ will recognize
them.
* Troubleshooting
If a file template isn't expanding where you expect it to, run ~M-x
+file-templates/debug~. This will report to you what file template rule would
apply for the correct file.
* Appendix
** API
+ ~set-file-template! PRED &rest PLIST~
+ ~set-file-templates! &rest TEMPLATES~
** Commands
+ ~+file-templates/insert-license~
+ ~+file-templates/debug~
** Variables
+ ~+file-templates-dir~
+ ~+file-templates-default-trigger~
+ ~+file-templates-alist~

View File

@@ -0,0 +1,118 @@
;;; editor/file-templates/autoload.el -*- lexical-binding: t; -*-
(defun +file-templates--set (pred plist)
(if (null (car-safe plist))
(setq +file-templates-alist
(delq (assoc pred +file-templates-alist)
+file-templates-alist))
(push `(,pred ,@plist) +file-templates-alist)))
;;;###autodef
(defun set-file-template! (pred &rest plist)
"Register a file template.
PRED can either be a regexp string or a major mode symbol. PLIST may contain
these properties:
:when FUNCTION
Provides a secondary predicate. This function takes no arguments and is
executed from within the target buffer. If it returns nil, this rule will be
skipped over.
:trigger STRING|FUNCTION
If a string, this is the yasnippet trigger keyword used to trigger the
target snippet.
If a function, this function will be run in the context of the buffer to
insert a file template into. It is given no arguments and must insert text
into the current buffer manually.
If omitted, `+file-templates-default-trigger' is used.
:mode SYMBOL
What mode to get the yasnippet snippet from. If omitted, either PRED (if
it's a major-mode symbol) or the mode of the buffer is used.
:project BOOL
If non-nil, ignore this template if this buffer isn't in a project.
:ignore BOOL
If non-nil, don't expand any template for this file and don't test any other
file template rule against this buffer.
\(fn PRED &key WHEN TRIGGER MODE PROJECT IGNORE)"
(declare (indent defun))
(defer-until! (boundp '+file-templates-alist)
(+file-templates--set pred plist)))
;;;###autodef
(defun set-file-templates! (&rest templates)
"Like `set-file-templates!', but can register multiple file templates at once.
\(fn &rest (PRED &key WHEN TRIGGER MODE PROJECT IGNORE))"
(defer-until! (boundp '+file-templates-alist)
(dolist (template templates)
(+file-templates--set (car template) (cdr template)))))
;;
;;; Library
;;;###autoload
(cl-defun +file-templates--expand (pred &key project mode trigger ignore _when)
"Auto insert a yasnippet snippet into current file and enter insert mode (if
evil is loaded and enabled)."
(when (and pred (not ignore))
(when (if project (doom-project-p) t)
(unless mode
(setq mode (if (symbolp pred) pred major-mode)))
(unless mode
(user-error "Couldn't determine mode for %s file template" pred))
(unless trigger
(setq trigger +file-templates-default-trigger))
(if (functionp trigger)
(funcall trigger)
(require 'yasnippet)
(unless yas-minor-mode
(yas-minor-mode-on))
(when (and yas-minor-mode
(when-let
(template (cl-find trigger (yas--all-templates (yas--get-snippet-tables mode))
:key #'yas--template-key :test #'equal))
(yas-expand-snippet (yas--template-content template)))
(and (featurep 'evil) evil-local-mode)
(and yas--active-field-overlay
(overlay-buffer yas--active-field-overlay)
(overlay-get yas--active-field-overlay 'yas--field)))
(evil-initialize-state 'insert))))))
;;;###autoload
(defun +file-templates-get-short-path ()
"Fetches a short file path for the header in Doom module templates."
(let ((path (file-truename (or buffer-file-name default-directory))))
(save-match-data
(cond ((string-match "/modules/\\(.+\\)$" path)
(match-string 1 path))
((file-in-directory-p path doom-emacs-dir)
(file-relative-name path doom-emacs-dir))
((abbreviate-file-name path))))))
;;
;;; Commands
;;;###autoload
(defun +file-templates/insert-license ()
"Insert a license file template into the current file."
(interactive)
(require 'yasnippet)
(let ((templates
(let (yas-choose-tables-first ; avoid prompts
yas-choose-keys-first)
(cl-loop for tpl in (yas--all-templates (yas--get-snippet-tables 'text-mode))
for uuid = (yas--template-uuid tpl)
if (string-prefix-p "__license-" uuid)
collect (cons (string-remove-prefix "__license-" uuid) tpl)))))
(when-let (uuid (yas-choose-value (mapcar #'car templates)))
(yas-expand-snippet (cdr (assoc uuid templates))))))
;;;###autoload
(defun +file-templates/debug ()
"Tests the current buffer and outputs the file template rule most appropriate
for it. This is used for testing."
(interactive)
(message "Found %s" (cl-find-if #'+file-template-p +file-templates-alist)))

View File

@@ -0,0 +1,159 @@
;;; editor/file-templates/config.el -*- lexical-binding: t; -*-
(defvar +file-templates-dir
(expand-file-name "templates/" (file-name-directory load-file-name))
"The path to a directory of yasnippet folders to use for file templates.")
(defvar +file-templates-default-trigger "__"
"The default yasnippet trigger key (a string) for file template rules that
don't have a :trigger property in `+file-templates-alist'.")
(defvar +file-templates-alist
`(;; General
(gitignore-mode)
(dockerfile-mode)
("/docker-compose\\.yml$" :mode yaml-mode)
("/Makefile$" :mode makefile-gmake-mode)
;; elisp
("/.dir-locals.el$")
("/packages\\.el$" :when +file-templates-in-emacs-dirs-p
:trigger "__doom-packages"
:mode emacs-lisp-mode)
("/doctor\\.el$" :when +file-templates-in-emacs-dirs-p
:trigger "__doom-doctor"
:mode emacs-lisp-mode)
("/test/.+\\.el$" :when +file-templates-in-emacs-dirs-p
:trigger "__doom-test"
:mode emacs-lisp-mode)
("\\.el$" :when +file-templates-in-emacs-dirs-p
:trigger "__doom-module"
:mode emacs-lisp-mode)
("-test\\.el$" :mode emacs-ert-mode)
(emacs-lisp-mode :trigger "__initfile")
(snippet-mode)
;; C/C++
("/main\\.c\\(?:c\\|pp\\)$" :trigger "__main.cpp" :mode c++-mode)
("/win32_\\.c\\(?:c\\|pp\\)$" :trigger "__winmain.cpp" :mode c++-mode)
("\\.c\\(?:c\\|pp\\)$" :trigger "__cpp" :mode c++-mode)
("\\.h\\(?:h\\|pp\\|xx\\)$" :trigger "__hpp" :mode c++-mode)
("\\.h$" :trigger "__h" :mode c-mode)
(c-mode :trigger "__c")
;; direnv
("/\\.envrc$" :trigger "__envrc" :mode direnv-envrc-mode)
;; go
("/main\\.go$" :trigger "__main.go" :mode go-mode :project t)
(go-mode :trigger "__.go")
;; web-mode
("/normalize\\.scss$" :trigger "__normalize.scss" :mode scss-mode)
("/master\\.scss$" :trigger "__master.scss" :mode scss-mode)
("\\.html$" :trigger "__.html" :mode web-mode)
(scss-mode)
;; java
("/main\\.java$" :trigger "__main" :mode java-mode)
("/build\\.gradle$" :trigger "__build.gradle" :mode android-mode)
("/src/.+\\.java$" :mode java-mode)
;; javascript
("/package\\.json$" :trigger "__package.json" :mode json-mode)
("/bower\\.json$" :trigger "__bower.json" :mode json-mode)
("/gulpfile\\.js$" :trigger "__gulpfile.js" :mode js-mode)
("/webpack\\.config\\.js$" :trigger "__webpack.config.js" :mode js-mode)
("\\.js\\(?:on\\|hintrc\\)$" :mode json-mode)
;; Lua
("/main\\.lua$" :trigger "__main.lua" :mode love-mode)
("/conf\\.lua$" :trigger "__conf.lua" :mode love-mode)
;; Markdown
(markdown-mode)
;; Markdown
(nxml-mode)
;; Nix
("/shell\\.nix$" :trigger "__shell.nix")
(nix-mode)
;; Org
("/README\\.org$"
:when +file-templates-in-emacs-dirs-p
:trigger "__doom-readme"
:mode org-mode)
("\\.org$" :trigger "__" :mode org-mode)
;; PHP
("\\.class\\.php$" :trigger "__.class.php" :mode php-mode)
(php-mode)
;; Python
;; TODO ("tests?/test_.+\\.py$" :trigger "__" :mode nose-mode)
;; TODO ("/setup\\.py$" :trigger "__setup.py" :mode python-mode)
(python-mode)
;; Ruby
("/lib/.+\\.rb$" :trigger "__module" :mode ruby-mode :project t)
("/spec_helper\\.rb$" :trigger "__helper" :mode rspec-mode :project t)
("_spec\\.rb$" :mode rspec-mode :project t)
("/\\.rspec$" :trigger "__.rspec" :mode rspec-mode :project t)
("\\.gemspec$" :trigger "__.gemspec" :mode ruby-mode :project t)
("/Gemfile$" :trigger "__Gemfile" :mode ruby-mode :project t)
("/Rakefile$" :trigger "__Rakefile" :mode ruby-mode :project t)
(ruby-mode)
;; Rust
("/Cargo.toml$" :trigger "__Cargo.toml" :mode rust-mode)
("/main\\.rs$" :trigger "__main.rs" :mode rust-mode)
;; Slim
("/\\(?:index\\|main\\)\\.slim$" :mode slim-mode)
;; Shell scripts
("\\.zunit$" :trigger "__zunit" :mode sh-mode)
(fish-mode)
(sh-mode)
;; Solidity
(solidity-mode :trigger "__sol"))
"An alist of file template rules. The CAR of each rule is either a major mode
symbol or regexp string. The CDR is a plist. See `set-file-template!' for more
information.")
;;
;; Library
(defun +file-templates-in-emacs-dirs-p (file)
"Returns t if FILE is in Doom or your private directory."
(or (file-in-directory-p file doom-private-dir)
(file-in-directory-p file doom-emacs-dir)))
(defun +file-template-p (rule)
"Return t if RULE applies to the current buffer."
(let ((pred (car rule))
(plist (cdr rule)))
(and (cond ((symbolp pred) (eq major-mode pred))
((and (stringp pred) buffer-file-name)
(string-match-p pred buffer-file-name))
((not (plist-member plist :when)) t)
((funcall (plist-get plist :when) buffer-file-name)))
rule)))
(defun +file-templates-check-h ()
"Check if the current buffer is a candidate for file template expansion. It
must be non-read-only, empty, and there must be a rule in
`+file-templates-alist' that applies to it."
(and buffer-file-name
(not buffer-read-only)
(bobp) (eobp)
(not (member (substring (buffer-name) 0 1) '("*" " ")))
(not (file-exists-p buffer-file-name))
;; Prevent file-templates from breaking org-capture when target file
;; doesn't exist and has a file template.
(or (not (fboundp 'org-capture-get))
(not (org-capture-get :new-buffer)))
(when-let (rule (cl-find-if #'+file-template-p +file-templates-alist))
(apply #'+file-templates--expand rule))))
;;
;;; Bootstrap
(after! yasnippet
(if (featurep! :editor snippets)
(add-to-list 'yas-snippet-dirs '+file-templates-dir 'append #'eq)
(setq yas-prompt-functions (delq #'yas-dropdown-prompt yas-prompt-functions)
yas-snippet-dirs '(+file-templates-dir))
;; Exit snippets on ESC from normal mode
(add-hook 'doom-escape-hook #'yas-abort-snippet)
;; Ensure file templates in `+file-templates-dir' are visible
(yas-reload-all)))
;;
(add-hook 'find-file-hook #'+file-templates-check-h)

View File

@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; editor/file-templates/packages.el
(package! yasnippet)

View File

@@ -0,0 +1,7 @@
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

View File

@@ -0,0 +1,49 @@
# -*- mode: snippet -*-
# name: build.gradle template
# condition: (eq major-mode 'groovy-mode)
# --
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
apply plugin: 'android'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['java']
resources.srcDirs = ['java']
aidl.srcDirs = ['java']
renderscript.srcDirs = ['java']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
// Move the tests to tests/java, tests/res, etc...
androidTest.setRoot('../tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
lintOptions {
abortOnError false
}
}
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#include "`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`.h"
$0

View File

@@ -0,0 +1,10 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#ifndef ${1:__`(upcase (file-name-base buffer-file-name))`_H_$(upcase yas-text)}
#define $1
$0
#endif // $1

View File

@@ -0,0 +1,13 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
$0
return 0;
}

View File

@@ -0,0 +1,12 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
$0
return 0;
}

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#include "`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`.h"
$0

View File

@@ -0,0 +1,10 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# --
#ifndef ${1:__`(upcase (file-name-base buffer-file-name))`_H_$(upcase yas-text)}
#define $1
$0
#endif // $1

View File

@@ -0,0 +1,15 @@
# -*- mode: snippet -*-
# group: file templates
# contributor: Henrik Lissner
# condition: (executable-find "nix-env")
# --
if type lorri &>/dev/null; then
echo "direnv: using lorri"
eval "$(lorri direnv)"
else
# fall back to using direnv's builtin nix support
# to prevent bootstrapping problems.
use nix
fi
`%`$0

View File

@@ -0,0 +1,4 @@
FROM ${1:phusion/baseimage:latest}
MAINTAINER ${2:`user-full-name` <`user-mail-address`>}
$0

View File

@@ -0,0 +1,3 @@
;;; `(file-name-nondirectory buffer-file-name)`
$0

View File

@@ -0,0 +1,3 @@
;;; `(+file-templates-get-short-path)` -*- lexical-binding: t; -*-
$0

View File

@@ -0,0 +1,3 @@
;;; `(+file-templates-get-short-path)` -*- lexical-binding: t; -*-
$0

View File

@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; `(+file-templates-get-short-path)`
$0

View File

@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; `(+file-templates-get-short-path)`
$0

View File

@@ -0,0 +1,6 @@
;;; `(file-name-nondirectory buffer-file-name)`${1: --- ${2:description}} -*- lexical-binding: t; -*-
$0
(provide '`(file-name-base buffer-file-name)`)
;;; `(file-name-nondirectory buffer-file-name)` ends here

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env fish
$0

View File

@@ -0,0 +1,31 @@
.DS_Store
.idea
*.log
tmp/
`(let ((type-ignore (yas-choose-value '("(none)" "python" "ruby" "java" "js"))))
(string-join
(cond ((string= type-ignore "python")
'("*.py[cod]"
"*.egg"
"build"
"htmlcov"))
((string= type-ignore "ruby")
'(".ruby-version"
".bundle"
"vendor"
"Gemfile.lock"
"coverage"))
((string= type-ignore "java")
'("*.class"
"build"))
((string= type-ignore "js")
'("*.tern-port"
"node_modules/"
"npm-debug.log*"
"yarn-debug.log*"
"yarn-error.log*"
"*.tsbuildinfo"
".npm"
".eslintcache")))
"\n"))`

View File

@@ -0,0 +1,7 @@
package ${1:main}
import (
"fmt"
)
$0

View File

@@ -0,0 +1,9 @@
package ${1:main}
import (
"fmt"
)
func main() {
$0
}

View File

@@ -0,0 +1,9 @@
(defun yas-java-project-package ()
(or (and (eq major-mode 'java-mode)
(+java-current-package))
""))
(defun yas-java-class-name ()
(or (and (eq major-mode 'java-mode)
(+java-current-class))
""))

View File

@@ -0,0 +1,9 @@
# -*- mode: snippet -*-
# name: Java file template
# --
package `(yas-java-project-package)`;
public class `(yas-java-class-name)` $1
{
$0
}

View File

@@ -0,0 +1,11 @@
# -*- mode: snippet -*-
# name: Java MAIN file template
# --
package `(yas-java-project-package)`;
public class `(yas-java-class-name)` $1
{
public static void main(String[] args) {
$0
}
}

View File

@@ -0,0 +1,4 @@
const gulp = require('gulp');
// Default task
gulp.task('default', [$1]);

View File

@@ -0,0 +1,21 @@
module.exports = {
entry: [
${1:'./app/main.js'}
],
output: {
path: __dirname + '/dist',
filename: "app.bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
// plugins: []
};

View File

@@ -0,0 +1,3 @@
[
$0
]

View File

@@ -0,0 +1,14 @@
{
"name": "${1:package-name}",
"description": "${2:description}",
"license": "MIT",
"authors": [
"`user-full-name` <`user-mail-address`>"
],
"main": "public/app.js",
"moduleType": [],
"homepage": "",
"dependencies": {
`%`$0
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "${1:package-name}",
"description": "${2:description}",
"version": "1.0.0",
"author": "`user-full-name` <`user-mail-address`>",
"license": "MIT",
"main": "${3:app/${4:main.js}}",
"scripts": {
$0
},
"dependencies": {},
"devDependencies": {}
}

View File

@@ -0,0 +1,40 @@
# -*- mode: snippet -*-
# name: love.conf
# condition: (eq major-mode 'lua-mode)
# --
function love.conf(t)
t.identity = nil -- The name of the save directory (string)
t.version = "0.9.1" -- The LÖVE version this game was made for (string)
t.console = false -- Attach a console (boolean, Windows only)
t.window.title = "${1:Untitled}" -- The window title (string)
t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
t.window.width = 800 -- The window width (number)
t.window.height = 600 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = false -- Let the window be user-resizable (boolean)
t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
t.window.fullscreen = false -- Enable fullscreen (boolean)
t.window.fullscreentype = "normal" -- Standard fullscreen or desktop fullscreen mode (string)
t.window.vsync = true -- Enable vertical sync (boolean)
t.window.fsaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.display = 1 -- Index of the monitor to show the window in (number)
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean). Added in 0.9.1
t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean). Added in 0.9.1
t.modules.audio = true -- Enable the audio module (boolean)
t.modules.event = true -- Enable the event module (boolean)
t.modules.graphics = true -- Enable the graphics module (boolean)
t.modules.image = true -- Enable the image module (boolean)
t.modules.joystick = true -- Enable the joystick module (boolean)
t.modules.keyboard = true -- Enable the keyboard module (boolean)
t.modules.math = true -- Enable the math module (boolean)
t.modules.mouse = true -- Enable the mouse module (boolean)
t.modules.physics = true -- Enable the physics module (boolean)
t.modules.sound = true -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.timer = true -- Enable the timer module (boolean)
t.modules.window = true -- Enable the window module (boolean)
t.modules.thread = true -- Enable the thread module (boolean)
end

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