doomemacs/modules/ui/doom-dashboard/config.el

526 lines
22 KiB
EmacsLisp
Raw Normal View History

;;; ui/doom-dashboard/config.el -*- lexical-binding: t; -*-
2017-02-20 16:33:14 -05:00
(defvar +doom-dashboard-name "*doom*"
2017-09-13 20:11:16 +02:00
"The name to use for the dashboard buffer.")
2017-02-20 16:33:14 -05:00
2018-07-03 20:00:37 +02:00
(defvar +doom-dashboard-functions
'(doom-dashboard-widget-banner
doom-dashboard-widget-shortmenu
doom-dashboard-widget-loaded
doom-dashboard-widget-footer)
"List of widget functions to run in the dashboard buffer to construct the
dashboard. These functions take no arguments and the dashboard buffer is current
while they run.")
2017-02-20 16:33:14 -05:00
(defvar +doom-dashboard-banner-file "default.png"
"The path to the image file to be used in on the dashboard. The path is
relative to `+doom-dashboard-banner-dir'. If nil, always use the ASCII banner.")
(defvar +doom-dashboard-banner-dir (concat (dir!) "/banners/")
"Where to look for `+doom-dashboard-banner-file'.")
(defvar +doom-dashboard-ascii-banner-fn #'doom-dashboard-draw-ascii-banner-fn
"The function used to generate the ASCII banner on Doom's dashboard.")
(defvar +doom-dashboard-banner-padding '(0 . 4)
"Number of newlines to pad the banner with, above and below, respectively.")
(defvar +doom-dashboard-inhibit-refresh nil
"If non-nil, the doom buffer won't be refreshed.")
(defvar +doom-dashboard-inhibit-functions ()
"A list of functions which take no arguments. If any of them return non-nil,
dashboard reloading is inhibited.")
(defvar +doom-dashboard-pwd-policy 'last-project
"The policy to use when setting the `default-directory' in the dashboard.
Possible values:
'last-project The `doom-project-root' of the last open buffer. Falls back
to `default-directory' if not in a project.
'last The `default-directory' of the last open buffer
a FUNCTION A function run with the `default-directory' of the last
open buffer, that returns a directory path
a STRING A fixed path
nil `default-directory' will never change")
2018-07-03 20:00:37 +02:00
(defvar +doom-dashboard-menu-sections
'(("Reload last session"
:icon (all-the-icons-octicon "history" :face 'doom-dashboard-menu-title)
:when (cond ((featurep! :ui workspaces)
(file-exists-p (expand-file-name persp-auto-save-fname persp-save-dir)))
((require 'desktop nil t)
(file-exists-p (desktop-full-file-name))))
:face (:inherit (doom-dashboard-menu-title bold))
:action doom/quickload-session)
("Open org-agenda"
:icon (all-the-icons-octicon "calendar" :face 'doom-dashboard-menu-title)
:when (fboundp 'org-agenda)
:action org-agenda)
2018-07-03 20:00:37 +02:00
("Recently opened files"
:icon (all-the-icons-octicon "file-text" :face 'doom-dashboard-menu-title)
:action recentf-open-files)
2018-07-03 20:00:37 +02:00
("Open project"
:icon (all-the-icons-octicon "briefcase" :face 'doom-dashboard-menu-title)
:action projectile-switch-project)
2018-07-03 20:00:37 +02:00
("Jump to bookmark"
:icon (all-the-icons-octicon "bookmark" :face 'doom-dashboard-menu-title)
:action bookmark-jump)
2018-07-03 20:00:37 +02:00
("Open private configuration"
:icon (all-the-icons-octicon "tools" :face 'doom-dashboard-menu-title)
2018-07-03 20:00:37 +02:00
:when (file-directory-p doom-private-dir)
:action doom/open-private-config)
("Open documentation"
:icon (all-the-icons-octicon "book" :face 'doom-dashboard-menu-title)
:action doom/help))
2018-07-03 20:00:37 +02:00
"An alist of menu buttons used by `doom-dashboard-widget-shortmenu'. Each
element is a cons cell (LABEL . PLIST). LABEL is a string to display after the
icon and before the key string.
PLIST can have the following properties:
:icon FORM
Uses the return value of FORM as an icon (can be literal string).
:key STRING
The keybind displayed next to the button.
:when FORM
If FORM returns nil, don't display this button.
:face FACE
Displays the icon and text with FACE (a face symbol).
:action FORM
Run FORM when the button is pushed.")
;;
(defvar +doom-dashboard--last-cwd nil)
(defvar +doom-dashboard--width 80)
2017-09-13 20:11:16 +02:00
(defvar +doom-dashboard--old-fringe-indicator fringe-indicator-alist)
(defvar +doom-dashboard--pwd-alist ())
(defvar +doom-dashboard--reload-timer nil)
2017-09-13 20:11:16 +02:00
(defvar all-the-icons-scale-factor)
(defvar all-the-icons-default-adjust)
2017-09-13 20:11:16 +02:00
;;
;;; Bootstrap
(defun +doom-dashboard-init-h ()
"Initializes Doom's dashboard."
(unless noninteractive
;; Ensure the dashboard becomes Emacs' go-to buffer when there's nothing
;; else to show.
(setq doom-fallback-buffer-name +doom-dashboard-name
initial-buffer-choice #'doom-fallback-buffer)
(unless fancy-splash-image
(setq fancy-splash-image
(expand-file-name +doom-dashboard-banner-file
+doom-dashboard-banner-dir)))
(when (equal (buffer-name) "*scratch*")
(set-window-buffer nil (doom-fallback-buffer))
(+doom-dashboard-reload))
(add-hook 'doom-load-theme-hook #'+doom-dashboard-reload-on-theme-change-h)
;; Ensure the dashboard is up-to-date whenever it is switched to or resized.
(add-hook 'window-configuration-change-hook #'+doom-dashboard-resize-h)
(add-hook 'window-size-change-functions #'+doom-dashboard-resize-h)
(add-hook 'doom-switch-buffer-hook #'+doom-dashboard-reload-maybe-h)
(add-hook 'delete-frame-functions #'+doom-dashboard-reload-frame-h)
;; `persp-mode' integration: update `default-directory' when switching perspectives
(add-hook 'persp-created-functions #'+doom-dashboard--persp-record-project-h)
(add-hook 'persp-activated-functions #'+doom-dashboard--persp-detect-project-h)
;; HACK Fix #2219 where, in GUI daemon frames, the dashboard loses center
;; alignment after switching (or killing) workspaces.
(when (daemonp)
(add-hook 'persp-activated-functions #'+doom-dashboard-reload-maybe-h))
(add-hook 'persp-before-switch-functions #'+doom-dashboard--persp-record-project-h)))
(add-hook 'doom-init-ui-hook #'+doom-dashboard-init-h 'append)
;;
;;; Faces
(defgroup doom-dashboard nil
"Manage how doom-dashboard is coloured and themed."
:prefix "doom-dashboard"
:group 'doom-themes)
(defface doom-dashboard-banner '((t (:inherit font-lock-comment-face)))
"Face used for the DOOM banner on the dashboard"
:group 'doom-dashboard)
(defface doom-dashboard-footer '((t (:inherit font-lock-keyword-face)))
"Face used for the footer on the dashboard"
:group 'doom-dashboard)
(defface doom-dashboard-footer-icon '((t (:inherit all-the-icons-green)))
"Face used for the icon of the footer on the dashboard"
:group 'doom-dashboard)
(defface doom-dashboard-loaded '((t (:inherit font-lock-comment-face)))
"Face used for the loaded packages benchmark"
:group 'doom-dashboard)
(defface doom-dashboard-menu-desc '((t (:inherit font-lock-constant-face)))
"Face used for the key description of menu widgets on the dashboard"
:group 'doom-dashboard)
(defface doom-dashboard-menu-title '((t (:inherit font-lock-keyword-face)))
"Face used for the title of menu widgets on the dashboard"
:group 'doom-dashboard)
2020-03-27 21:39:27 -04:00
;;
;;; Major mode
2017-06-25 02:01:05 +02:00
(define-derived-mode +doom-dashboard-mode special-mode
(format "DOOM v%s" doom-version)
"Major mode for the DOOM dashboard buffer."
2018-03-18 02:36:12 -04:00
:syntax-table nil
:abbrev-table nil
2019-04-26 17:42:44 -04:00
(buffer-disable-undo)
(setq truncate-lines t)
(setq-local whitespace-style nil)
(setq-local show-trailing-whitespace nil)
(setq-local hscroll-margin 0)
2018-07-03 20:00:37 +02:00
(setq-local tab-width 2)
;; Don't scroll to follow cursor
(setq-local scroll-preserve-screen-position nil)
(setq-local auto-hscroll-mode nil)
;; Line numbers are ugly with large margins
(setq-local display-line-numbers-type nil)
2017-06-25 02:01:05 +02:00
(cl-loop for (car . _cdr) in fringe-indicator-alist
collect (cons car nil) into alist
finally do (setq-local fringe-indicator-alist alist))
;; Ensure point is always on a button
2021-03-27 14:05:28 -04:00
(add-hook 'post-command-hook #'+doom-dashboard-reposition-point-h nil 'local)
;; hl-line produces an ugly cut-off line highlight in the dashboard, so don't
;; activate it there (by pretending it's already active).
(setq-local hl-line-mode t))
(define-key! +doom-dashboard-mode-map
[left-margin mouse-1] #'ignore
[remap forward-button] #'+doom-dashboard/forward-button
[remap backward-button] #'+doom-dashboard/backward-button
"n" #'forward-button
"p" #'backward-button
Introduce general.el & rewrite map! + Now uses an overriding keymap for leader keys, so that it is always available, even outside of normal/visual states. In insert/emacs states, or in sessions where evil is absent, an alternative prefix is used for leader/localleader keys. See these variables: + doom-leader-prefix + doom-leader-alt-prefix + doom-localleader-prefix + doom-localleader-alt-prefix + Keybinds now support alternative prefixes through the new :alt-prefix property. This is useful for non-evil users and non-normal evil states. By default, this is M-SPC (leader) and M-SPC m (localleader). + Removed +evil-commands flag from config/default (moved to feature/evil/+commands.el). + config/default/+bindings.el has been split into config/default/+{evil,emacs}-bindings.el, which one is loaded depends on whether evil is present or not. The latter is blank, but will soon be populated with a keybinding scheme for non-evil users (perhaps inspired by #641). + The define-key! macro has been replaced; it is now an alias for general-def. + Added unmap! as an alias for general-unbind. + The following modifier key conventions are now enforced for consistency, across all OSes: alt/option = meta windows/command = super It used to be alt/option = alt windows/command = meta Many of the default keybinds have been updated to reflect this switch, but it is likely to affect personal meta/super keybinds! The map! macro has also been rewritten to use general-define-key. Here is what has been changed: + map! no longer works with characters, e.g. (map! ?x #'do-something) is no longer supported. Keys must be kbd-able strings like "C-c x" or vectors like [?C-c ?x]. + The :map and :map* properties are now the same thing. If specified keymaps aren't defined when binding keys, it is automatically deferred. + The way you bind local keybinds has changed: ;; Don't do this (map! :l "a" #'func-a :l "b" #'func-b) ;; Do this (map! :map 'local "a" #'func-a "b" #'func-b) + map! now supports the following new blocks: + (:if COND THEN-FORM ELSE-FORM...) + (:alt-prefix PREFIX KEYS...) -- this prefix will be used for non-normal evil states. Equivalent to :non-normal-prefix in general. + The way you declare a which-key label for a prefix key has changed: ;; before (map! :desc "label" :prefix "a" ...) ;; now (map! :prefix ("a" . "label") ...) + It used to be that map! supported binding a key to a key sequence, like so: (map! "a" [?x]) ; pressing a is like pressing x This functionality was removed *temporarily* while I figure out the implementation. Addresses: #448, #814, #860 Mentioned in: #940
2018-12-22 03:30:04 -05:00
"C-n" #'forward-button
"C-p" #'backward-button
[down] #'forward-button
[up] #'backward-button
[tab] #'forward-button
[backtab] #'backward-button
;; Evil remaps
[remap evil-next-line] #'forward-button
[remap evil-previous-line] #'backward-button
[remap evil-next-visual-line] #'forward-button
[remap evil-previous-visual-line] #'backward-button
[remap evil-paste-pop-next] #'forward-button
[remap evil-paste-pop] #'backward-button
[remap evil-delete] #'ignore
[remap evil-delete-line] #'ignore
[remap evil-insert] #'ignore
[remap evil-append] #'ignore
[remap evil-replace] #'ignore
[remap evil-replace-state] #'ignore
[remap evil-change] #'ignore
[remap evil-change-line] #'ignore
[remap evil-visual-char] #'ignore
[remap evil-visual-line] #'ignore)
;;
;;; Hooks
(defun +doom-dashboard-reposition-point-h ()
"Trap the point in the buttons."
(when (region-active-p)
(setq deactivate-mark t)
(when (bound-and-true-p evil-local-mode)
(evil-change-to-previous-state)))
(or (ignore-errors
(if (button-at (point))
(forward-button 0)
(backward-button 1)))
(ignore-errors
(goto-char (point-min))
(forward-button 1))))
(defun +doom-dashboard-reload-maybe-h (&rest _)
"Reload the dashboard or its state.
If this isn't a dashboard buffer, move along, but record its `default-directory'
if the buffer is real. See `doom-real-buffer-p' for an explanation for what
'real' means.
If this is the dashboard buffer, reload it completely."
(cond ((+doom-dashboard-p (current-buffer))
(let (+doom-dashboard-inhibit-refresh)
(ignore-errors (+doom-dashboard-reload))))
((and (not (file-remote-p default-directory))
(doom-real-buffer-p (current-buffer)))
(setq +doom-dashboard--last-cwd default-directory)
2021-04-06 00:36:34 -04:00
(+doom-dashboard-update-pwd-h))))
(defun +doom-dashboard-reload-frame-h (_frame)
"Reload the dashboard after a brief pause. This is necessary for new frames,
whose dimensions may not be fully initialized by the time this is run."
(when (timerp +doom-dashboard--reload-timer)
(cancel-timer +doom-dashboard--reload-timer)) ; in case this function is run rapidly
2021-04-06 00:36:34 -04:00
(setq +doom-dashboard--reload-timer
(run-with-timer 0.1 nil #'+doom-dashboard-reload t)))
(defun +doom-dashboard-resize-h (&rest _)
"Recenter the dashboard, and reset its margins and fringes."
(let (buffer-list-update-hook
window-configuration-change-hook
window-size-change-functions)
2021-04-06 00:36:34 -04:00
(when-let (windows (get-buffer-window-list (doom-fallback-buffer) nil t))
(dolist (win windows)
(set-window-start win 0)
(set-window-fringes win 0 0)
(set-window-margins
win (max 0 (/ (- (window-total-width win) +doom-dashboard--width) 2))))
2021-04-06 00:36:34 -04:00
(with-current-buffer (doom-fallback-buffer)
(save-excursion
(with-silent-modifications
(goto-char (point-min))
(delete-region (line-beginning-position)
(save-excursion (skip-chars-forward "\n")
(point)))
(insert (make-string
(+ (max 0 (- (/ (window-height (get-buffer-window)) 2)
(round (/ (count-lines (point-min) (point-max))
2))))
(car +doom-dashboard-banner-padding))
?\n))))))))
(defun +doom-dashboard--persp-detect-project-h (&rest _)
2021-04-06 00:36:34 -04:00
"Set dashboard's PWD to current persp's `last-project-root', if it exists.
2021-04-06 00:36:34 -04:00
This and `+doom-dashboard--persp-record-project-h' provides `persp-mode'
integration with the Doom dashboard. It ensures that the dashboard is always in
the correct project (which may be different across perspective)."
(when (bound-and-true-p persp-mode)
(when-let (pwd (persp-parameter 'last-project-root))
2021-04-06 00:36:34 -04:00
(+doom-dashboard-update-pwd-h pwd))))
(defun +doom-dashboard--persp-record-project-h (&optional persp &rest _)
2021-04-06 00:36:34 -04:00
"Record the last `doom-project-root' for the current persp.
See `+doom-dashboard--persp-detect-project-h' for more information."
(when (bound-and-true-p persp-mode)
(set-persp-parameter
'last-project-root (doom-project-root)
2019-04-26 17:42:44 -04:00
(if (persp-p persp)
persp
(get-current-persp)))))
;;
;;; Library
(defun +doom-dashboard-p (buffer)
"Returns t if BUFFER is the dashboard buffer."
2018-07-04 23:14:12 +02:00
(eq buffer (get-buffer +doom-dashboard-name)))
2021-04-06 00:36:34 -04:00
(defun +doom-dashboard-update-pwd-h (&optional pwd)
"Update `default-directory' in the Doom dashboard buffer.
What it is set to is controlled by `+doom-dashboard-pwd-policy'."
(if pwd
(with-current-buffer (doom-fallback-buffer)
(doom-log "Changed dashboard's PWD to %s" pwd)
(setq-local default-directory pwd))
(let ((new-pwd (+doom-dashboard--get-pwd)))
(when (and new-pwd (file-accessible-directory-p new-pwd))
2021-04-06 00:36:34 -04:00
(+doom-dashboard-update-pwd-h
(concat (directory-file-name new-pwd)
"/"))))))
(defun +doom-dashboard-reload-on-theme-change-h ()
"Forcibly reload the Doom dashboard when theme changes post-startup."
(when after-init-time
(+doom-dashboard-reload 'force)))
(defun +doom-dashboard-reload (&optional force)
"Update the DOOM scratch buffer (or create it, if it doesn't exist)."
(when (or (and (not +doom-dashboard-inhibit-refresh)
(get-buffer-window (doom-fallback-buffer))
(not (window-minibuffer-p (frame-selected-window)))
(not (run-hook-with-args-until-success '+doom-dashboard-inhibit-functions)))
force)
(with-current-buffer (doom-fallback-buffer)
(doom-log "Reloading dashboard at %s" (format-time-string "%T"))
(with-silent-modifications
(let ((pt (point)))
(unless (eq major-mode '+doom-dashboard-mode)
(+doom-dashboard-mode))
(erase-buffer)
(run-hooks '+doom-dashboard-functions)
(goto-char pt)
(+doom-dashboard-reposition-point-h))
(+doom-dashboard-resize-h)
(+doom-dashboard--persp-detect-project-h)
2021-04-06 00:36:34 -04:00
(+doom-dashboard-update-pwd-h)
(current-buffer)))))
2017-02-20 16:33:14 -05:00
;; helpers
(defun +doom-dashboard--center (len s)
2017-06-25 02:01:05 +02:00
(concat (make-string (ceiling (max 0 (- len (length s))) 2) ? )
s))
(defun +doom-dashboard--get-pwd ()
(let ((lastcwd +doom-dashboard--last-cwd)
(policy +doom-dashboard-pwd-policy))
(cond ((null policy)
default-directory)
((stringp policy)
(expand-file-name policy lastcwd))
((functionp policy)
(funcall policy lastcwd))
((null lastcwd)
default-directory)
((eq policy 'last-project)
(or (doom-project-root lastcwd)
lastcwd))
((eq policy 'last)
lastcwd)
((warn "`+doom-dashboard-pwd-policy' has an invalid value of '%s'"
policy)))))
;;
;;; Widgets
(defun doom-dashboard-draw-ascii-banner-fn ()
(let* ((banner
'("================= =============== =============== ======== ========"
"\\\\ . . . . . . .\\\\ //. . . . . . .\\\\ //. . . . . . .\\\\ \\\\. . .\\\\// . . //"
"||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\\/ . . .||"
"|| . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . . ||"
"||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . . .||"
"|| . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\\ . . . . ||"
"||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\\_ . .|. .||"
"|| . _|| || || || || ||_ . || || . _|| || || || |\\ `-_/| . ||"
"||_-' || .|/ || || \\|. || `-_|| ||_-' || .|/ || || | \\ / |-_.||"
"|| ||_-' || || `-_|| || || ||_-' || || | \\ / | `||"
"|| `' || || `' || || `' || || | \\ / | ||"
"|| .===' `===. .==='.`===. .===' /==. | \\/ | ||"
"|| .==' \\_|-_ `===. .===' _|_ `===. .===' _-|/ `== \\/ | ||"
"|| .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \\/ | ||"
"|| .==' _-' '-__\\._-' '-_./__-' `' |. /| | ||"
"||.==' _-' `' | /==.||"
"==' _-' E M A C S \\/ `=="
"\\ _-' `-_ /"
" `'' ``'"))
(longest-line (apply #'max (mapcar #'length banner))))
(put-text-property
(point)
(dolist (line banner (point))
(insert (+doom-dashboard--center
+doom-dashboard--width
(concat
line (make-string (max 0 (- longest-line (length line)))
32)))
"\n"))
'face 'doom-dashboard-banner)))
(defun doom-dashboard-widget-banner ()
(let ((point (point)))
(when (functionp +doom-dashboard-ascii-banner-fn)
(funcall +doom-dashboard-ascii-banner-fn))
(when (and (display-graphic-p)
(stringp fancy-splash-image)
(file-readable-p fancy-splash-image))
(let ((image (create-image (fancy-splash-image-file))))
(add-text-properties
point (point) `(display ,image rear-nonsticky (display)))
(save-excursion
(goto-char point)
(insert (make-string
(truncate
(max 0 (+ 1 (/ (- +doom-dashboard--width
(car (image-size image nil)))
2))))
? ))))
(insert (make-string (or (cdr +doom-dashboard-banner-padding) 0)
?\n)))))
2017-02-20 16:33:14 -05:00
(defun doom-dashboard-widget-loaded ()
2017-02-20 16:33:14 -05:00
(insert
"\n\n"
2017-02-20 16:33:14 -05:00
(propertize
(+doom-dashboard--center
2017-06-25 02:01:05 +02:00
+doom-dashboard--width
(doom-display-benchmark-h 'return))
'face 'doom-dashboard-loaded)
2018-07-03 20:00:37 +02:00
"\n"))
2017-02-20 16:33:14 -05:00
(defun doom-dashboard-widget-shortmenu ()
(let ((all-the-icons-scale-factor 1.45)
(all-the-icons-default-adjust -0.02))
(insert "\n")
2018-07-03 20:00:37 +02:00
(dolist (section +doom-dashboard-menu-sections)
(cl-destructuring-bind (label &key icon action when face key) section
(when (and (fboundp action)
(or (null when)
(eval when t)))
2018-07-03 20:00:37 +02:00
(insert
(+doom-dashboard--center
(- +doom-dashboard--width 1)
(let ((icon (if (stringp icon) icon (eval icon t))))
(format (format "%s%%s%%-10s" (if icon "%3s\t" "%3s"))
(or icon "")
(with-temp-buffer
(insert-text-button
2018-07-05 01:32:04 +02:00
label
'action
`(lambda (_)
(call-interactively (or (command-remapping #',action)
#',action)))
'face (or face 'doom-dashboard-menu-title)
2018-07-03 20:00:37 +02:00
'follow-link t
'help-echo
(format "%s (%s)" label
(propertize (symbol-name action) 'face 'doom-dashboard-menu-desc)))
2018-07-03 20:00:37 +02:00
(format "%-37s" (buffer-string)))
;; Lookup command keys dynamically
(propertize
(or key
(when-let*
((keymaps
(delq
nil (list (when (bound-and-true-p evil-local-mode)
(evil-get-auxiliary-keymap +doom-dashboard-mode-map 'normal))
+doom-dashboard-mode-map)))
(key
(or (when keymaps
(where-is-internal action keymaps t))
(where-is-internal action nil t))))
(with-temp-buffer
(save-excursion (insert (key-description key)))
(while (re-search-forward "<\\([^>]+\\)>" nil t)
(let ((str (match-string 1)))
(replace-match
(upcase (if (< (length str) 3)
str
(substring str 0 3))))))
(buffer-string)))
"")
'face 'doom-dashboard-menu-desc))))
2018-07-03 20:00:37 +02:00
(if (display-graphic-p)
"\n\n"
"\n")))))))
(defun doom-dashboard-widget-footer ()
(insert
"\n"
2018-07-03 20:00:37 +02:00
(+doom-dashboard--center
(- +doom-dashboard--width 2)
(with-temp-buffer
(insert-text-button (or (all-the-icons-octicon "octoface" :face 'doom-dashboard-footer-icon :height 1.3 :v-adjust -0.15)
(propertize "github" 'face 'doom-dashboard-footer))
2018-07-03 20:00:37 +02:00
'action (lambda (_) (browse-url "https://github.com/hlissner/doom-emacs"))
'follow-link t
2018-08-21 02:57:40 +02:00
'help-echo "Open Doom Emacs github page")
2018-07-03 20:00:37 +02:00
(buffer-string)))
"\n"))