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)