aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/dashboard-20220409.620/dashboard-widgets.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/dashboard-20220409.620/dashboard-widgets.el')
-rw-r--r--elpa/dashboard-20220409.620/dashboard-widgets.el1267
1 files changed, 1267 insertions, 0 deletions
diff --git a/elpa/dashboard-20220409.620/dashboard-widgets.el b/elpa/dashboard-20220409.620/dashboard-widgets.el
new file mode 100644
index 0000000..8168afe
--- /dev/null
+++ b/elpa/dashboard-20220409.620/dashboard-widgets.el
@@ -0,0 +1,1267 @@
+;;; dashboard-widgets.el --- A startup screen extracted from Spacemacs -*- lexical-binding: t -*-
+
+;; Copyright (c) 2016-2022 emacs-dashboard maintainers
+;;
+;; Author : Rakan Al-Hneiti <rakan.alhneiti@gmail.com>
+;; Maintainer : Jesús Martínez <jesusmartinez93@gmail.com>
+;; Shen, Jen-Chieh <jcs090218@gmail.com>
+;; URL : https://github.com/emacs-dashboard/emacs-dashboard
+;;
+;; This file is not part of GNU Emacs.
+;;
+;;; License: GPLv3
+;;
+;; Created: October 05, 2016
+;; Package-Version: 1.8.0-SNAPSHOT
+;; Keywords: startup, screen, tools, dashboard
+;; Package-Requires: ((emacs "26.1"))
+;;; Commentary:
+
+;; An extensible Emacs dashboard, with sections for
+;; bookmarks, projects (projectile or project.el), org-agenda and more.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'subr-x)
+(require 'image)
+
+;; Compiler pacifier
+(declare-function all-the-icons-icon-for-dir "ext:all-the-icons.el")
+(declare-function all-the-icons-icon-for-file "ext:all-the-icons.el")
+(declare-function all-the-icons-fileicon "ext:data-fileicons.el")
+(declare-function all-the-icons-octicon "ext:data-octicons.el")
+(declare-function bookmark-get-filename "ext:bookmark.el")
+(declare-function bookmark-all-names "ext:bookmark.el")
+(declare-function calendar-date-compare "ext:calendar.el")
+(declare-function projectile-cleanup-known-projects "ext:projectile.el")
+(declare-function projectile-load-known-projects "ext:projectile.el")
+(declare-function projectile-mode "ext:projectile.el")
+(declare-function projectile-relevant-known-projects "ext:projectile.el")
+;;; project.el in Emacs 26 does not contain this function
+(declare-function project-known-project-roots "ext:project.el" nil t)
+(declare-function project-forget-zombie-projects "ext:project.el" nil t)
+(declare-function org-agenda-format-item "ext:org-agenda.el")
+(declare-function org-compile-prefix-format "ext:org-agenda.el")
+(declare-function org-entry-is-done-p "ext:org.el")
+(declare-function org-in-archived-heading-p "ext:org.el")
+(declare-function org-get-category "ext:org.el")
+(declare-function org-get-deadline-time "ext:org.el")
+(declare-function org-get-heading "ext:org.el")
+(declare-function org-get-scheduled-time "ext:org.el")
+(declare-function org-get-tags "ext:org.el")
+(declare-function org-map-entries "ext:org.el")
+(declare-function org-outline-level "ext:org.el")
+(declare-function org-today "ext:org.el")
+(declare-function org-get-todo-face "ext:org.el")
+(declare-function org-get-todo-state "ext:org.el")
+(declare-function org-entry-is-todo-p "ext:org.el")
+(declare-function org-release-buffers "ext:org.el")
+(declare-function recentf-cleanup "ext:recentf.el")
+(defalias 'org-time-less-p 'time-less-p)
+(defvar org-level-faces)
+(defvar org-agenda-new-buffers)
+(defvar org-agenda-prefix-format)
+(defvar org-agenda-todo-keyword-format)
+(defvar org-todo-keywords-1)
+(defvar all-the-icons-dir-icon-alist)
+(defvar package-activated-list)
+
+(defcustom dashboard-page-separator "\n\n"
+ "Separator to use between the different pages."
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-image-banner-max-height 0
+ "Maximum height of banner image.
+
+This setting applies only if Emacs supports image transforms or
+compiled with Imagemagick support. When value is non-zero the image
+banner will be resized to the specified height in pixels, with aspect
+ratio preserved."
+ :type 'integer
+ :group 'dashboard)
+
+(defcustom dashboard-image-banner-max-width 0
+ "Maximum width of banner image.
+
+This setting applies if Emacs supports image transforms or compiled
+with Imagemagick support. When value is non-zero the image banner
+will be resized to the specified width in pixels, with aspect ratio
+preserved."
+ :type 'integer
+ :group 'dashboard)
+
+(defcustom dashboard-set-heading-icons nil
+ "When non nil, heading sections will have icons."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-set-file-icons nil
+ "When non nil, file lists will have icons."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-set-navigator nil
+ "When non nil, a navigator will be displayed under the banner."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-set-init-info t
+ "When non nil, init info will be displayed under the banner."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-set-footer t
+ "When non nil, a footer will be displayed at the bottom."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-footer-messages
+ '("The one true editor, Emacs!"
+ "Who the hell uses VIM anyway? Go Evil!"
+ "Free as free speech, free as free Beer"
+ "Happy coding!"
+ "Vi Vi Vi, the editor of the beast"
+ "Welcome to the church of Emacs"
+ "While any text editor can save your files, only Emacs can save your soul"
+ "I showed you my source code, pls respond")
+ "A list of messages, one of which dashboard chooses to display."
+ :type 'list
+ :group 'dashboard)
+
+(defcustom dashboard-show-shortcuts t
+ "Whether to show shortcut keys for each section."
+ :type 'boolean
+ :group 'dashboard)
+
+(defconst dashboard-banners-directory
+ (concat (file-name-directory (locate-library "dashboard")) "banners/")
+ "Default banner directory.")
+
+(defconst dashboard-banner-official-png
+ (concat dashboard-banners-directory "emacs.png")
+ "Emacs banner image.")
+
+(defconst dashboard-banner-logo-png
+ (concat dashboard-banners-directory "logo.png")
+ "Emacs banner image.")
+
+(defconst dashboard-banner-length 75
+ "Width of a banner.")
+
+(defcustom dashboard-banner-logo-title "Welcome to Emacs!"
+ "Specify the startup banner."
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-navigator-buttons nil
+ "Specify the navigator buttons.
+The format is: 'icon title help action face prefix suffix'.
+
+Example:
+'((\"☆\" \"Star\" \"Show stars\" (lambda (&rest _)
+ (show-stars)) 'warning \"[\" \"]\"))"
+ :type '(repeat (repeat (list string string string function symbol string string)))
+ :group 'dashboard)
+
+(defcustom dashboard-init-info
+ (lambda ()
+ (let ((package-count 0) (time (emacs-init-time)))
+ (when (bound-and-true-p package-alist)
+ (setq package-count (length package-activated-list)))
+ (when (boundp 'straight--profile-cache)
+ (setq package-count (+ (hash-table-count straight--profile-cache) package-count)))
+ (if (zerop package-count)
+ (format "Emacs started in %s" time)
+ (format "%d packages loaded in %s" package-count time))))
+ "Init info with packages loaded and init time."
+ :type '(function string)
+ :group 'dashboard)
+
+(defcustom dashboard-footer
+ (nth (random (1- (1+ (length dashboard-footer-messages)))) dashboard-footer-messages)
+ "A footer with some short message."
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-footer-icon
+ (if (and (display-graphic-p)
+ (or (fboundp 'all-the-icons-fileicon)
+ (require 'all-the-icons nil 'noerror)))
+ (all-the-icons-fileicon "emacs"
+ :height 1.1
+ :v-adjust -0.05
+ :face 'font-lock-keyword-face)
+ (propertize ">" 'face 'dashboard-footer))
+ "Footer's icon."
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-startup-banner 'official
+ "Specify the startup banner.
+Default value is `official', it displays the Emacs logo. `logo' displays Emacs
+alternative logo. An integer value is the index of text banner. A string
+value must be a path to a .PNG or .TXT file. If the value is nil then no banner
+is displayed."
+ :type '(choice (const :tag "offical" official)
+ (const :tag "logo" logo)
+ (string :tag "a png or txt path"))
+ :group 'dashboard)
+
+(defcustom dashboard-buffer-last-width nil
+ "Previous width of dashboard-buffer."
+ :type 'integer
+ :group 'dashboard)
+
+(defcustom dashboard-item-generators
+ '((recents . dashboard-insert-recents)
+ (bookmarks . dashboard-insert-bookmarks)
+ (projects . dashboard-insert-projects)
+ (agenda . dashboard-insert-agenda)
+ (registers . dashboard-insert-registers))
+ "Association list of items to how to generate in the startup buffer.
+Will be of the form `(list-type . list-function)'.
+Possible values for list-type are: `recents', `bookmarks', `projects',
+`agenda' ,`registers'."
+ :type '(repeat (alist :key-type symbol :value-type function))
+ :group 'dashboard)
+
+(defcustom dashboard-projects-backend 'projectile
+ "The package that supplies the list of recent projects.
+With the value `projectile', the projects widget uses the package
+projectile (available in MELPA). With the value `project-el',
+the widget uses the package project (available in GNU ELPA).
+
+To activate the projects widget, add e.g. `(projects . 10)' to
+`dashboard-items' after making sure the necessary package is
+installed."
+ :type '(choice (const :tag "Use projectile" projectile)
+ (const :tag "Use project.el" project-el))
+ :group 'dashboard)
+
+(defcustom dashboard-items
+ '((recents . 5)
+ (bookmarks . 5)
+ (agenda . 5))
+ "Association list of items to show in the startup buffer.
+Will be of the form `(list-type . list-size)'.
+If nil it is disabled. Possible values for list-type are:
+`recents' `bookmarks' `projects' `agenda' `registers'."
+ :type '(repeat (alist :key-type symbol :value-type integer))
+ :group 'dashboard)
+
+(defcustom dashboard-item-shortcuts
+ '((recents . "r")
+ (bookmarks . "m")
+ (projects . "p")
+ (agenda . "a")
+ (registers . "e"))
+ "Association list of items and their corresponding shortcuts.
+Will be of the form `(list-type . keys)' as understood by `(kbd keys)'.
+If nil, shortcuts are disabled. If an entry's value is nil, that item's
+shortcut is disbaled. See `dashboard-items' for possible values of list-type.'"
+ :type '(repeat (alist :key-type symbol :value-type string))
+ :group 'dashboard)
+
+(defcustom dashboard-item-names nil
+ "Association list of item heading names.
+When an item is nil or not present, the default name is used.
+Will be of the form `(default-name . new-name)'."
+ :type '(alist :key-type string :value-type string)
+ :options '("Recent Files:" "Bookmarks:" "Agenda for today:"
+ "Agenda for the coming week:" "Registers:" "Projects:")
+ :group 'dashboard)
+
+(defcustom dashboard-items-default-length 20
+ "Length used for startup lists with otherwise unspecified bounds.
+Set to nil for unbounded."
+ :type 'integer
+ :group 'dashboard)
+
+(defcustom dashboard-heading-icons
+ '((recents . "history")
+ (bookmarks . "bookmark")
+ (agenda . "calendar")
+ (projects . "rocket")
+ (registers . "database"))
+ "Association list for the icons of the heading sections.
+Will be of the form `(list-type . icon-name-string)`.
+If nil it is disabled. Possible values for list-type are:
+`recents' `bookmarks' `projects' `agenda' `registers'"
+ :type '(repeat (alist :key-type symbol :value-type string))
+ :group 'dashboard)
+
+(defcustom dashboard-path-style nil
+ "Style to display path."
+ :type '(choice
+ (const :tag "No specify" nil)
+ (const :tag "Truncate the beginning part of the path" truncate-beginning)
+ (const :tag "Truncate the middle part of the path" truncate-middle)
+ (const :tag "Truncate the end part of the path" truncate-end))
+ :group 'dashboard)
+
+(defcustom dashboard-path-max-length 70
+ "Maximum length for path to display."
+ :type 'integer
+ :group 'dashboard)
+
+(defcustom dashboard-path-shorten-string "..."
+ "String the that displays in the center of the path."
+ :type 'string
+ :group 'dashboard)
+
+(defvar recentf-list nil)
+
+(defvar dashboard-buffer-name)
+
+;;
+;; Faces
+;;
+(defface dashboard-text-banner
+ '((t (:inherit font-lock-keyword-face)))
+ "Face used for text banners."
+ :group 'dashboard)
+
+(defface dashboard-banner-logo-title
+ '((t :inherit default))
+ "Face used for the banner title."
+ :group 'dashboard)
+
+(defface dashboard-navigator
+ '((t (:inherit font-lock-keyword-face)))
+ "Face used for the navigator."
+ :group 'dashboard)
+
+(defface dashboard-heading
+ '((t (:inherit font-lock-keyword-face)))
+ "Face used for widget headings."
+ :group 'dashboard)
+
+(defface dashboard-items-face
+ '((t (:inherit widget-button)))
+ "Face used for items."
+ :group 'dashboard)
+
+(defface dashboard-no-items-face
+ '((t (:inherit widget-button)))
+ "Face used for no items."
+ :group 'dashboard)
+
+(defface dashboard-footer
+ '((t (:inherit font-lock-doc-face)))
+ "Face used for widget headings."
+ :group 'dashboard)
+
+(define-obsolete-face-alias
+ 'dashboard-text-banner-face 'dashboard-text-banner "1.2.6")
+(define-obsolete-face-alias
+ 'dashboard-banner-logo-title-face 'dashboard-banner-logo-title "1.2.6")
+(define-obsolete-face-alias
+ 'dashboard-heading-face 'dashboard-heading "1.2.6")
+
+;;
+;; Util
+;;
+(defmacro dashboard-mute-apply (&rest body)
+ "Execute BODY without message."
+ (declare (indent 0) (debug t))
+ `(let (message-log-max)
+ (with-temp-message (or (current-message) nil)
+ (let ((inhibit-message t)) ,@body))))
+
+(defun dashboard-funcall-fboundp (fnc &rest args)
+ "Call FNC with ARGS if exists."
+ (when (fboundp fnc) (if args (funcall fnc args) (funcall fnc))))
+
+;;
+;; Generic widget helpers
+;;
+(defun dashboard-subseq (seq end)
+ "Return the subsequence of SEQ from 0 to END."
+ (let ((len (length seq)))
+ (butlast seq (- len (min len end)))))
+
+(defun dashboard-get-shortcut-name (item)
+ "Get the shortcut name to be used for ITEM."
+ (let ((elem (rassoc item dashboard-item-shortcuts)))
+ (and elem (car elem))))
+
+(defun dashboard-get-shortcut (item)
+ "Get the shortcut to be used for ITEM."
+ (let ((elem (assq item dashboard-item-shortcuts)))
+ (and elem (cdr elem))))
+
+(defmacro dashboard-insert-shortcut (shortcut-id
+ shortcut-char
+ search-label
+ &optional no-next-line)
+ "Insert a shortcut SHORTCUT-CHAR for a given SEARCH-LABEL.
+Optionally, provide NO-NEXT-LINE to move the cursor forward a line."
+ (let* (;; Ensure punctuation and upper case in search string is not
+ ;; used to construct the `defun'
+ (name (downcase (replace-regexp-in-string "[[:punct:]]+" "" (format "%s" search-label))))
+ ;; remove symbol quote
+ (sym (intern (replace-regexp-in-string "'" "" (format "dashboard-jump-to-%s" shortcut-id)))))
+ `(progn
+ (eval-when-compile (defvar dashboard-mode-map))
+ (defun ,sym nil
+ ,(concat "Jump to " name ". This code is dynamically generated in `dashboard-insert-shortcut'.")
+ (interactive)
+ (unless (search-forward ,search-label (point-max) t)
+ (search-backward ,search-label (point-min) t))
+ ,@(unless no-next-line '((forward-line 1)))
+ (back-to-indentation))
+ (eval-after-load 'dashboard
+ (define-key dashboard-mode-map ,shortcut-char ',sym)))))
+
+(defun dashboard-append (msg &optional _messagebuf)
+ "Append MSG to dashboard buffer.
+If MESSAGEBUF is not nil then MSG is also written in message buffer."
+ (with-current-buffer (get-buffer-create dashboard-buffer-name)
+ (goto-char (point-max))
+ (let (buffer-read-only) (insert msg))))
+
+(defun dashboard-modify-heading-icons (alist)
+ "Append ALIST items to `dashboard-heading-icons' to modify icons."
+ (dolist (icon alist)
+ (add-to-list 'dashboard-heading-icons icon)))
+
+(defun dashboard-insert-page-break ()
+ "Insert a page break line in dashboard buffer."
+ (dashboard-append dashboard-page-separator))
+
+(defun dashboard-insert-heading (heading &optional shortcut)
+ "Insert a widget HEADING in dashboard buffer, adding SHORTCUT if provided."
+ (when (and (display-graphic-p) dashboard-set-heading-icons)
+ ;; Try loading `all-the-icons'
+ (unless (or (fboundp 'all-the-icons-octicon)
+ (require 'all-the-icons nil 'noerror))
+ (error "Package `all-the-icons' isn't installed"))
+
+ (insert (cond
+ ((string-equal heading "Recent Files:")
+ (all-the-icons-octicon (cdr (assoc 'recents dashboard-heading-icons))
+ :height 1.2 :v-adjust 0.0 :face 'dashboard-heading))
+ ((string-equal heading "Bookmarks:")
+ (all-the-icons-octicon (cdr (assoc 'bookmarks dashboard-heading-icons))
+ :height 1.2 :v-adjust 0.0 :face 'dashboard-heading))
+ ((or (string-equal heading "Agenda for today:")
+ (string-equal heading "Agenda for the coming week:"))
+ (all-the-icons-octicon (cdr (assoc 'agenda dashboard-heading-icons))
+ :height 1.2 :v-adjust 0.0 :face 'dashboard-heading))
+ ((string-equal heading "Registers:")
+ (all-the-icons-octicon (cdr (assoc 'registers dashboard-heading-icons))
+ :height 1.2 :v-adjust 0.0 :face 'dashboard-heading))
+ ((string-equal heading "Projects:")
+ (all-the-icons-octicon (cdr (assoc 'projects dashboard-heading-icons))
+ :height 1.2 :v-adjust 0.0 :face 'dashboard-heading))
+ (t " ")))
+ (insert " "))
+
+ (insert (propertize heading 'face 'dashboard-heading))
+
+ ;; Turn the inserted heading into an overlay, so that we may freely change
+ ;; its name without breaking any of the functions that expect the default name.
+ ;; If there isn't a suitable entry in `dashboard-item-names',
+ ;; we fallback to using HEADING. In that case we still want it to be an
+ ;; overlay to maintain consistent behavior (such as the point movement)
+ ;; between modified and default headings.
+ (let ((ov (make-overlay (- (point) (length heading)) (point) nil t)))
+ (overlay-put ov 'display (or (cdr (assoc heading dashboard-item-names)) heading))
+ (overlay-put ov 'face 'dashboard-heading))
+ (when shortcut (insert (format " (%s)" shortcut))))
+
+(defun dashboard-center-line (string)
+ "Center a STRING accoring to it's size."
+ (insert (make-string (max 0 (floor (/ (- dashboard-banner-length
+ (+ (length string) 1)) 2))) ?\ )))
+
+;;
+;; BANNER
+;;
+(defun dashboard-insert-ascii-banner-centered (file)
+ "Insert banner from FILE."
+ (let ((ascii-banner
+ (with-temp-buffer
+ (insert-file-contents file)
+ (let ((banner-width 0))
+ (while (not (eobp))
+ (let ((line-length (- (line-end-position) (line-beginning-position))))
+ (if (< banner-width line-length)
+ (setq banner-width line-length)))
+ (forward-line 1))
+ (goto-char 0)
+ (let ((margin
+ (max 0 (floor (/ (- dashboard-banner-length banner-width) 2)))))
+ (while (not (eobp))
+ (insert (make-string margin ?\ ))
+ (forward-line 1))))
+ (buffer-string))))
+ (put-text-property 0 (length ascii-banner) 'face 'dashboard-text-banner ascii-banner)
+ (insert ascii-banner)))
+
+(defun dashboard--type-is-gif-p (image-path)
+ "Return if image is a gif.
+String -> bool.
+Argument IMAGE-PATH path to the image."
+ (eq 'gif (image-type image-path)))
+
+(defun dashboard-insert-image-banner (banner)
+ "Display an image BANNER."
+ (when (file-exists-p banner)
+ (let* ((title dashboard-banner-logo-title)
+ (size-props
+ (append (when (> dashboard-image-banner-max-width 0)
+ (list :max-width dashboard-image-banner-max-width))
+ (when (> dashboard-image-banner-max-height 0)
+ (list :max-height dashboard-image-banner-max-height))))
+ (spec
+ (cond ((dashboard--type-is-gif-p banner)
+ (create-image banner))
+ ((image-type-available-p 'imagemagick)
+ (apply 'create-image banner 'imagemagick nil size-props))
+ (t
+ (apply 'create-image banner nil nil
+ (when (and (fboundp 'image-transforms-p)
+ (memq 'scale (funcall 'image-transforms-p)))
+ size-props)))))
+ ;; TODO: For some reason, `elisp-lint' is reporting error void
+ ;; function `image-size'.
+ (size (when (fboundp 'image-size) (image-size spec)))
+ (width (car size))
+ (left-margin (max 0 (floor (- dashboard-banner-length width) 2))))
+ (goto-char (point-min))
+ (insert "\n")
+ (insert (make-string left-margin ?\ ))
+ (insert-image spec)
+ (when (dashboard--type-is-gif-p banner) (image-animate spec 0 t))
+ (insert "\n\n")
+ (when title
+ (dashboard-center-line title)
+ (insert (format "%s\n\n" (propertize title 'face 'dashboard-banner-logo-title)))))))
+
+;;
+;; INIT INFO
+;;
+(defun dashboard-insert-init-info ()
+ "Insert init info when `dashboard-set-init-info' is t."
+ (when dashboard-set-init-info
+ (let ((init-info (if (functionp dashboard-init-info)
+ (funcall dashboard-init-info)
+ dashboard-init-info)))
+ (dashboard-center-line init-info)
+ (insert (propertize init-info 'face 'font-lock-comment-face)))))
+
+(defun dashboard-get-banner-path (index)
+ "Return the full path to banner with index INDEX."
+ (concat dashboard-banners-directory (format "%d.txt" index)))
+
+(defun dashboard-choose-banner ()
+ "Return the full path of a banner based on the dotfile value."
+ (when dashboard-startup-banner
+ (cond ((eq 'official dashboard-startup-banner)
+ (if (and (display-graphic-p) (image-type-available-p 'png))
+ dashboard-banner-official-png
+ (dashboard-get-banner-path 1)))
+ ((eq 'logo dashboard-startup-banner)
+ (if (and (display-graphic-p) (image-type-available-p 'png))
+ dashboard-banner-logo-png
+ (dashboard-get-banner-path 1)))
+ ((integerp dashboard-startup-banner)
+ (dashboard-get-banner-path dashboard-startup-banner))
+ ((stringp dashboard-startup-banner)
+ (if (and (file-exists-p dashboard-startup-banner)
+ (or (string-suffix-p ".txt" dashboard-startup-banner)
+ (and (display-graphic-p)
+ (image-type-available-p (intern (file-name-extension
+ dashboard-startup-banner))))))
+ dashboard-startup-banner
+ (message "could not find banner %s, use default instead" dashboard-startup-banner)
+ (dashboard-get-banner-path 1)))
+ (t (dashboard-get-banner-path 1)))))
+
+(defun dashboard-insert-banner ()
+ "Insert Banner at the top of the dashboard."
+ (goto-char (point-max))
+ (let ((banner (dashboard-choose-banner)) buffer-read-only)
+ (when banner
+ (if (image-type-available-p (intern (file-name-extension banner)))
+ (dashboard-insert-image-banner banner)
+ (dashboard-insert-ascii-banner-centered banner))
+ (dashboard-insert-navigator)
+ (dashboard-insert-init-info))))
+
+(defun dashboard-insert-navigator ()
+ "Insert Navigator of the dashboard."
+ (when (and dashboard-set-navigator dashboard-navigator-buttons)
+ (dolist (line dashboard-navigator-buttons)
+ (dolist (btn line)
+ (let* ((icon (car btn))
+ (title (cadr btn))
+ (help (or (cadr (cdr btn)) ""))
+ (action (or (cadr (cddr btn)) #'ignore))
+ (face (or (cadr (cddr (cdr btn))) 'dashboard-navigator))
+ (prefix (or (cadr (cddr (cddr btn))) (propertize "[" 'face face)))
+ (suffix (or (cadr (cddr (cddr (cdr btn)))) (propertize "]" 'face face))))
+ (widget-create 'item
+ :tag (concat
+ (when icon
+ (propertize icon 'face
+ (let ((prop-face (get-text-property 0 'face icon)))
+ (if prop-face
+ `(:inherit ,prop-face :inherit ,face)
+ `(:inherit ,face)))))
+ (when (and icon title
+ (not (string-equal icon ""))
+ (not (string-equal title "")))
+ (propertize " " 'face 'variable-pitch))
+ (when title (propertize title 'face face)))
+ :help-echo help
+ :action action
+ :button-face 'dashboard-items-face
+ :mouse-face 'highlight
+ :button-prefix prefix
+ :button-suffix suffix
+ :format "%[%t%]")
+ (insert " ")))
+ (let* ((width (current-column)))
+ (beginning-of-line)
+ (dashboard-center-line (make-string width ?\s))
+ (end-of-line))
+ (insert "\n"))
+ (insert "\n")))
+
+(defmacro dashboard-insert-section (section-name list list-size shortcut-id shortcut-char action &rest widget-params)
+ "Add a section with SECTION-NAME and LIST of LIST-SIZE items to the dashboard.
+SHORTCUT-CHAR is the keyboard shortcut used to access the section.
+ACTION is theaction taken when the user activates the widget button.
+WIDGET-PARAMS are passed to the \"widget-create\" function."
+ `(progn
+ (dashboard-insert-heading ,section-name
+ (if (and ,list ,shortcut-char dashboard-show-shortcuts) ,shortcut-char))
+ (if ,list
+ (when (and (dashboard-insert-section-list
+ ,section-name
+ (dashboard-subseq ,list ,list-size)
+ ,action
+ ,@widget-params)
+ ,shortcut-id ,shortcut-char)
+ (dashboard-insert-shortcut ,shortcut-id ,shortcut-char ,section-name))
+ (insert (propertize "\n --- No items ---" 'face 'dashboard-no-items-face)))))
+
+;;
+;; Section list
+;;
+(defmacro dashboard-insert-section-list (section-name list action &rest rest)
+ "Insert into SECTION-NAME a LIST of items, expanding ACTION and passing REST
+to widget creation."
+ `(when (car ,list)
+ (mapc
+ (lambda (el)
+ (let ((tag ,@rest))
+ (insert "\n ")
+
+ (when (and (display-graphic-p)
+ dashboard-set-file-icons
+ (or (fboundp 'all-the-icons-icon-for-dir)
+ (require 'all-the-icons nil 'noerror)))
+ (let* ((path (car (last (split-string ,@rest " - "))))
+ (icon (if (and (not (file-remote-p path))
+ (file-directory-p path))
+ (all-the-icons-icon-for-dir path nil "")
+ (cond
+ ((or (string-equal ,section-name "Agenda for today:")
+ (string-equal ,section-name "Agenda for the coming week:"))
+ (all-the-icons-octicon "primitive-dot" :height 1.0 :v-adjust 0.01))
+ ((file-remote-p path)
+ (all-the-icons-octicon "radio-tower" :height 1.0 :v-adjust 0.01))
+ (t (all-the-icons-icon-for-file (file-name-nondirectory path)
+ :v-adjust -0.05))))))
+ (setq tag (concat icon " " ,@rest))))
+
+ (widget-create 'item
+ :tag tag
+ :action ,action
+ :button-face 'dashboard-items-face
+ :mouse-face 'highlight
+ :button-prefix ""
+ :button-suffix ""
+ :format "%[%t%]")))
+ ,list)))
+
+;; Footer
+(defun dashboard-random-footer ()
+ "Return a random footer from `dashboard-footer-messages'."
+ (nth (random (length dashboard-footer-messages)) dashboard-footer-messages))
+
+(defun dashboard-insert-footer ()
+ "Insert footer of dashboard."
+ (when-let ((footer (and dashboard-set-footer (dashboard-random-footer))))
+ (insert "\n")
+ (dashboard-center-line footer)
+ (insert dashboard-footer-icon)
+ (insert " ")
+ (insert (propertize footer 'face 'dashboard-footer))
+ (insert "\n")))
+
+;;
+;; Truncate
+;;
+(defcustom dashboard-shorten-by-window-width nil
+ "Shorten path by window edges."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-shorten-path-offset 0
+ "Shorten path offset on the edges."
+ :type 'integer
+ :group 'dashboard)
+
+(defun dashboard-f-filename (path)
+ "Return file name from PATH."
+ (file-name-nondirectory path))
+
+(defun dashboard-f-base (path)
+ "Return directory name from PATH."
+ (file-name-nondirectory (directory-file-name (file-name-directory path))))
+
+(defun dashboard-shorten-path-beginning (path)
+ "Shorten PATH from beginning if exceeding maximum length."
+ (let* ((len-path (length path)) (len-rep (length dashboard-path-shorten-string))
+ (len-total (- dashboard-path-max-length len-rep))
+ front)
+ (if (<= len-path dashboard-path-max-length) path
+ (setq front (ignore-errors (substring path (- len-path len-total) len-path)))
+ (if front (concat dashboard-path-shorten-string front) ""))))
+
+(defun dashboard-shorten-path-middle (path)
+ "Shorten PATH from middle if exceeding maximum length."
+ (let* ((len-path (length path)) (len-rep (length dashboard-path-shorten-string))
+ (len-total (- dashboard-path-max-length len-rep))
+ (center (/ len-total 2))
+ (end-back center)
+ (start-front (- len-path center))
+ back front)
+ (if (<= len-path dashboard-path-max-length) path
+ (setq back (substring path 0 end-back)
+ front (ignore-errors (substring path start-front len-path)))
+ (if front (concat back dashboard-path-shorten-string front) ""))))
+
+(defun dashboard-shorten-path-end (path)
+ "Shorten PATH from end if exceeding maximum length."
+ (let* ((len-path (length path)) (len-rep (length dashboard-path-shorten-string))
+ (len-total (- dashboard-path-max-length len-rep))
+ back)
+ (if (<= len-path dashboard-path-max-length) path
+ (setq back (ignore-errors (substring path 0 len-total)))
+ (if (and back (< 0 dashboard-path-max-length))
+ (concat back dashboard-path-shorten-string) ""))))
+
+(defun dashboard--get-base-length (path type)
+ "Return the length of the base from the PATH by TYPE."
+ (let* ((is-dir (file-directory-p path))
+ (base (if is-dir (dashboard-f-base path) (dashboard-f-filename path)))
+ (option (cl-case type
+ (recents 'dashboard-recentf-show-base)
+ (bookmarks 'dashboard-bookmarks-show-base)
+ (projects 'dashboard-projects-show-base)))
+ (option-val (symbol-value option))
+ base-len)
+ (cl-case option-val
+ (`align (setq base-len (dashboard--align-length-by-type type)))
+ (`nil (setq base-len 0))
+ (t (setq base-len (length base))))
+ base-len))
+
+(defun dashboard-shorten-path (path type)
+ "Shorten the PATH by TYPE."
+ (setq path (abbreviate-file-name path))
+ (let ((dashboard-path-max-length
+ (if (and dashboard-path-style dashboard-shorten-by-window-width)
+ (- (window-width) (dashboard--get-base-length path type)
+ dashboard-shorten-path-offset)
+ dashboard-path-max-length)))
+ (cl-case dashboard-path-style
+ (truncate-beginning (dashboard-shorten-path-beginning path))
+ (truncate-middle (dashboard-shorten-path-middle path))
+ (truncate-end (dashboard-shorten-path-end path))
+ (t path))))
+
+(defun dashboard-shorten-paths (paths alist type)
+ "Shorten all path from PATHS by TYPE and store it to ALIST."
+ (let (lst-display abbrev (index 0))
+ (setf (symbol-value alist) nil) ; reset
+ (dolist (item paths)
+ (setq abbrev (dashboard-shorten-path item type)
+ ;; Add salt here, and use for extraction.
+ ;; See function `dashboard-extract-key-path-alist'.
+ abbrev (format "%s|%s" index abbrev))
+ ;; store `abbrev' as id; and `item' with value
+ (push (cons abbrev item) (symbol-value alist))
+ (push abbrev lst-display)
+ (cl-incf index))
+ (reverse lst-display)))
+
+(defun dashboard-extract-key-path-alist (key alist)
+ "Remove salt from KEY, and return true shorten path from ALIST."
+ (let* ((key (car (assoc key alist))) (split (split-string key "|")))
+ (nth 1 split)))
+
+(defun dashboard-expand-path-alist (key alist)
+ "Get the full path (un-shorten) using KEY from ALIST."
+ (cdr (assoc key alist)))
+
+(defun dashboard--generate-align-format (fmt len)
+ "Return FMT after inserting align LEN."
+ (let ((pos (1+ (string-match-p "%s" fmt))))
+ (concat (substring fmt 0 pos)
+ (concat "-" (number-to-string len))
+ (substring fmt pos (length fmt)))))
+
+(defun dashboard--align-length-by-type (type)
+ "Return the align length by TYPE of the section."
+ (let ((len-item (cdr (assoc type dashboard-items))) (count 0) (align-length -1)
+ len-list base)
+ (cl-case type
+ (`recents
+ (require 'recentf)
+ (setq len-list (length recentf-list))
+ (while (and (< count len-item) (< count len-list))
+ (setq base (nth count recentf-list)
+ align-length (max align-length (length (dashboard-f-filename base))))
+ (cl-incf count)))
+ (`bookmarks
+ (let ((bookmarks-lst (bookmark-all-names)))
+ (setq len-list (length bookmarks-lst))
+ (while (and (< count len-item) (< count len-list))
+ (setq base (nth count bookmarks-lst)
+ align-length (max align-length (length base)))
+ (cl-incf count))))
+ (`projects
+ (let ((projects-lst (dashboard-projects-backend-load-projects)))
+ (setq len-list (length projects-lst))
+ (while (and (< count len-item) (< count len-list))
+ (setq base (nth count projects-lst)
+ align-length (max align-length (length (dashboard-f-base base))))
+ (cl-incf count))))
+ (t (error "Unknown type for align length: %s" type)))
+ align-length))
+
+;;
+;; Recentf
+;;
+(defcustom dashboard-recentf-show-base nil
+ "Show the base file name infront of it's path."
+ :type '(choice
+ (const :tag "Don't show the base infront" nil)
+ (const :tag "Respect format" t)
+ (const :tag "Align the from base" align))
+ :group 'dashboard)
+
+(defcustom dashboard-recentf-item-format "%s %s"
+ "Format to use when showing the base of the file name."
+ :type 'string
+ :group 'dashboard)
+
+(defvar dashboard-recentf-alist nil
+ "Alist records shorten's recent files and it's full paths.")
+
+(defvar dashboard--recentf-cache-item-format nil
+ "Cache to record the new generated align format.")
+
+(defun dashboard-insert-recents (list-size)
+ "Add the list of LIST-SIZE items from recently edited files."
+ (setq dashboard--recentf-cache-item-format nil)
+ (recentf-mode)
+ (dashboard-mute-apply (recentf-cleanup))
+ (dashboard-insert-section
+ "Recent Files:"
+ (dashboard-shorten-paths recentf-list 'dashboard-recentf-alist 'recents)
+ list-size
+ 'recents
+ (dashboard-get-shortcut 'recents)
+ `(lambda (&rest _)
+ (find-file-existing (dashboard-expand-path-alist ,el dashboard-recentf-alist)))
+ (let* ((file (dashboard-expand-path-alist el dashboard-recentf-alist))
+ (filename (dashboard-f-filename file))
+ (path (dashboard-extract-key-path-alist el dashboard-recentf-alist)))
+ (cl-case dashboard-recentf-show-base
+ (`align
+ (unless dashboard--recentf-cache-item-format
+ (let* ((len-align (dashboard--align-length-by-type 'recents))
+ (new-fmt (dashboard--generate-align-format
+ dashboard-recentf-item-format len-align)))
+ (setq dashboard--recentf-cache-item-format new-fmt)))
+ (format dashboard--recentf-cache-item-format filename path))
+ (`nil path)
+ (t (format dashboard-recentf-item-format filename path))))))
+
+;;
+;; Bookmarks
+;;
+(defcustom dashboard-bookmarks-show-base t
+ "Show the base file name infront of it's path."
+ :type '(choice
+ (const :tag "Don't show the base infront" nil)
+ (const :tag "Respect format" t)
+ (const :tag "Align the from base" align))
+ :group 'dashboard)
+
+(defcustom dashboard-bookmarks-item-format "%s - %s"
+ "Format to use when showing the base of the file name."
+ :type 'string
+ :group 'dashboard)
+
+(defvar dashboard--bookmarks-cache-item-format nil
+ "Cache to record the new generated align format.")
+
+(defun dashboard-insert-bookmarks (list-size)
+ "Add the list of LIST-SIZE items of bookmarks."
+ (require 'bookmark)
+ (dashboard-insert-section
+ "Bookmarks:"
+ (dashboard-subseq (bookmark-all-names) list-size)
+ list-size
+ 'bookmarks
+ (dashboard-get-shortcut 'bookmarks)
+ `(lambda (&rest _) (bookmark-jump ,el))
+ (if-let* ((filename el)
+ (path (bookmark-get-filename el))
+ (path-shorten (dashboard-shorten-path path 'bookmarks)))
+ (cl-case dashboard-bookmarks-show-base
+ (`align
+ (unless dashboard--bookmarks-cache-item-format
+ (let* ((len-align (dashboard--align-length-by-type 'bookmarks))
+ (new-fmt (dashboard--generate-align-format
+ dashboard-bookmarks-item-format len-align)))
+ (setq dashboard--bookmarks-cache-item-format new-fmt)))
+ (format dashboard--bookmarks-cache-item-format filename path-shorten))
+ (`nil path-shorten)
+ (t (format dashboard-bookmarks-item-format filename path-shorten)))
+ el)))
+
+;;
+;; Projects
+;;
+(defcustom dashboard-projects-switch-function
+ nil
+ "Custom function to switch to projects from dashboard.
+If non-NIL, should be bound to a function with one argument. The function will
+be called with the root directory of the project to switch to."
+ :type '(choice (const :tag "Default" nil) function)
+ :group 'dashboard)
+
+(defcustom dashboard-projects-show-base nil
+ "Show the project name infront of it's path."
+ :type '(choice
+ (const :tag "Don't show the base infront" nil)
+ (const :tag "Respect format" t)
+ (const :tag "Align the from base" align))
+ :group 'dashboard)
+
+(defcustom dashboard-projects-item-format "%s %s"
+ "Format to use when showing the base of the project name."
+ :type 'string
+ :group 'dashboard)
+
+(defvar dashboard-projects-alist nil
+ "Alist records the shorten's project paths and it's full paths.")
+
+(defvar dashboard--projects-cache-item-format nil
+ "Cache to record the new generated align format.")
+
+(defun dashboard-insert-projects (list-size)
+ "Add the list of LIST-SIZE items of projects."
+ (setq dashboard--projects-cache-item-format nil)
+ (dashboard-insert-section
+ "Projects:"
+ (dashboard-shorten-paths
+ (dashboard-subseq (dashboard-projects-backend-load-projects) list-size)
+ 'dashboard-projects-alist 'projects)
+ list-size
+ 'projects
+ (dashboard-get-shortcut 'projects)
+ `(lambda (&rest _)
+ (funcall (dashboard-projects-backend-switch-function)
+ (dashboard-expand-path-alist ,el dashboard-projects-alist)))
+ (let* ((file (dashboard-expand-path-alist el dashboard-projects-alist))
+ (filename (dashboard-f-base file))
+ (path (dashboard-extract-key-path-alist el dashboard-projects-alist)))
+ (cl-case dashboard-projects-show-base
+ (`align
+ (unless dashboard--projects-cache-item-format
+ (let* ((len-align (dashboard--align-length-by-type 'projects))
+ (new-fmt (dashboard--generate-align-format
+ dashboard-projects-item-format len-align)))
+ (setq dashboard--projects-cache-item-format new-fmt)))
+ (format dashboard--projects-cache-item-format filename path))
+ (`nil path)
+ (t (format dashboard-projects-item-format filename path))))))
+
+(defun dashboard-projects-backend-load-projects ()
+ "Depending on `dashboard-projects-backend' load corresponding backend.
+Return function that returns a list of projects."
+ (cl-case dashboard-projects-backend
+ (`projectile
+ (require 'projectile)
+ (dashboard-mute-apply (projectile-cleanup-known-projects))
+ (projectile-load-known-projects))
+ (`project-el
+ (require 'project)
+ (dashboard-mute-apply (dashboard-funcall-fboundp #'project-forget-zombie-projects))
+ (project-known-project-roots))
+ (t
+ (display-warning '(dashboard)
+ "Invalid value for `dashboard-projects-backend'"
+ :error))))
+
+(defun dashboard-projects-backend-switch-function ()
+ "Return the function to switch to a project.
+Custom variable `dashboard-projects-switch-function' variable takes preference
+over custom backends."
+ (or dashboard-projects-switch-function
+ (cl-case dashboard-projects-backend
+ (`projectile 'projectile-switch-project-by-name)
+ (`project-el
+ (lambda (project)
+ "This function is used to switch to `PROJECT'."
+ (let ((default-directory project))
+ (project-find-file))))
+ (t
+ (display-warning '(dashboard)
+ "Invalid value for `dashboard-projects-backend'"
+ :error)))))
+
+;;
+;; Org Agenda
+;;
+(defcustom dashboard-week-agenda t
+ "Show agenda weekly if its not nil."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-agenda-time-string-format "%Y-%m-%d"
+ "Format time of agenda entries."
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-match-agenda-entry nil
+ "Match agenda to extra filter.
+It is the MATCH attribute for `org-map-entries'"
+ :type 'string
+ :group 'dashboard)
+
+(defcustom dashboard-agenda-release-buffers nil
+ "If not nil use `org-release-buffers' after getting the entries."
+ :type 'boolean
+ :group 'dashboard)
+
+(defcustom dashboard-filter-agenda-entry 'dashboard-filter-agenda-by-time
+ "Function to filter `org-agenda' entries."
+ :type '(choice
+ (const :tag "No filter" dashboard-no-filter-agenda)
+ (const :tag "Filter by time" dashboard-filter-agenda-by-time)
+ (const :tag "Filter by todo" dashboard-filter-agenda-by-todo)
+ (function :tag "Custom function"))
+ :group 'dashboard)
+
+(defcustom dashboard-agenda-sort-strategy nil
+ "A list of strategies to sort the agenda. If nil agenda is not sorted."
+ :type '(repeat (choice (const time-up) (const time-down)
+ (const todo-state-up) (const todo-state-down)))
+ :group 'dashboard)
+
+(defcustom dashboard-agenda-prefix-format " %i %-12:c %s "
+ "Format for each entry in the agenda.
+When the dashboard-agenda is created this format is inserted into
+`org-agenda-prefix-format' as `dashboard-agenda' and compiled with
+`org-compile-prefix-format' previous calling `dashboard-agenda-entry-format' for
+each agenda entry."
+ :type 'string
+ :group 'dashboard)
+
+(defun dashboard-agenda-entry-format ()
+ "Format agenda entry to show it on dashboard."
+ (let* ((scheduled-time (org-get-scheduled-time (point)))
+ (deadline-time (org-get-deadline-time (point)))
+ (entry-time (or scheduled-time deadline-time))
+ (item (org-agenda-format-item
+ (dashboard-agenda--formatted-time)
+ (dashboard-agenda--formatted-headline)
+ (org-outline-level)
+ (org-get-category)
+ (org-get-tags)))
+ (todo-state (org-get-todo-state))
+ (todo-index (and todo-state
+ (length (member todo-state org-todo-keywords-1))))
+ (entry-data (list 'dashboard-agenda-time entry-time
+ 'dashboard-agenda-todo-index todo-index
+ 'dashboard-agenda-file (buffer-file-name)
+ 'dashboard-agenda-loc (point))))
+ (add-text-properties 0 (length item) entry-data item)
+ item))
+
+(defun dashboard-agenda--formatted-headline ()
+ "Set agenda faces to `HEADLINE' when face text property is nil."
+ (let* ((headline (org-get-heading t t t t))
+ (todo (or (org-get-todo-state) ""))
+ (org-level-face (nth (- (org-outline-level) 1) org-level-faces))
+ (todo-state (format org-agenda-todo-keyword-format todo)))
+ (when (null (get-text-property 0 'face headline))
+ (add-face-text-property 0 (length headline) org-level-face t headline))
+ (when (null (get-text-property 0 'face todo-state))
+ (add-face-text-property 0 (length todo-state) (org-get-todo-face todo) t todo-state))
+ (concat todo-state " " headline)))
+
+(defun dashboard-agenda--formatted-time ()
+ "Get the scheduled or dead time of an entry. If no time is found return nil."
+ (when-let ((time (or (org-get-scheduled-time (point)) (org-get-deadline-time (point)))))
+ (format-time-string dashboard-agenda-time-string-format time)))
+
+(defun dashboard-due-date-for-agenda ()
+ "Return due-date for agenda period."
+ (if dashboard-week-agenda
+ (time-add (current-time) (* 86400 8))
+ (time-add (current-time) 86400)))
+
+(defun dashboard-filter-agenda-by-time ()
+ "Include entry if it has a scheduled-time or deadline-time in the future.
+An entry is included if this function returns nil and excluded if returns a
+point."
+ (let ((scheduled-time (org-get-scheduled-time (point)))
+ (deadline-time (org-get-deadline-time (point)))
+ (due-date (dashboard-due-date-for-agenda)))
+ (unless (and (not (org-entry-is-done-p))
+ (not (org-in-archived-heading-p))
+ (or (and scheduled-time
+ (org-time-less-p scheduled-time due-date))
+ (and deadline-time
+ (org-time-less-p deadline-time due-date))))
+ (point))))
+
+(defun dashboard-filter-agenda-by-todo ()
+ "Include entry if it is todo and not done.
+An entry is included if this function returns nil and excluded
+if returns a point."
+ (unless (and (org-entry-is-todo-p)
+ (not (org-entry-is-done-p))
+ (not (org-in-archived-heading-p)))
+ (point)))
+
+(defun dashboard-no-filter-agenda ()
+ "No filter agenda entries."
+ (when (org-entry-is-done-p) (point)))
+
+(defun dashboard-get-agenda ()
+ "Get agenda items for today or for a week from now."
+ (if-let ((prefix-format (assoc 'dashboard-agenda org-agenda-prefix-format)))
+ (setcdr prefix-format dashboard-agenda-prefix-format)
+ (push (cons 'dashboard-agenda dashboard-agenda-prefix-format) org-agenda-prefix-format))
+ (org-compile-prefix-format 'dashboard-agenda)
+ (prog1 (org-map-entries 'dashboard-agenda-entry-format
+ dashboard-match-agenda-entry
+ 'agenda
+ dashboard-filter-agenda-entry)
+ (dashboard-agenda--release-buffers)))
+
+(defun dashboard-agenda--release-buffers ()
+ "Release agenda buffers buffers.
+This is what `org-agenda-exit' do."
+ (when dashboard-agenda-release-buffers
+ (org-release-buffers org-agenda-new-buffers)
+ (setq org-agenda-new-buffers nil)))
+
+(defun dashboard-agenda--sorted-agenda ()
+ "Return agenda sorted by time.
+For now, it only works when dashboard-agenda has been filter by time
+and dashboard-agenda-sort is not nil."
+ (let ((agenda (dashboard-get-agenda))
+ (sort-function (dashboard-agenda--sort-function)))
+ (sort agenda sort-function)))
+
+(defun dashboard-agenda--sort-function ()
+ "Get the function use to sorted the agenda.
+Depending on the list `dashboard-agenda-sorting-strategy' use this strategies to
+build a predicate to compare each enty.
+This is similar as `org-entries-lessp' but with a different aproach."
+ (dashboard-agenda--build-sort-function dashboard-agenda-sort-strategy))
+
+(defun dashboard-agenda--build-sort-function (strategies)
+ "Build a predicate to sort the dashboard agenda.
+If `STRATEGIES' is nil then sort using the nil predicate. Look for the strategy
+predicate, the attributes of the entry and compare entries. If no predicate is
+found for the strategy it uses nil predicate."
+ (if (null strategies) (lambda (_dont _care) nil)
+ (let ((predicate (dashboard-agenda--build-sort-function-predicate
+ (car strategies)))
+ (attribute (dashboard-agenda--build-sort-function-attribute
+ (car strategies))))
+ (if (null predicate) (lambda (_dont _care) nil)
+ (lambda (entry1 entry2)
+ (dashboard-agenda--compare-entries entry1 entry2 (cdr strategies)
+ predicate attribute))))))
+
+(defun dashboard-agenda--build-sort-function-predicate (strategy)
+ "Return the predicate to compare two entryes depending on the `STRATEGY'."
+ (cl-case strategy
+ (`time-up 'org-time-less-p)
+ (`time-down (lambda (a b) (org-time-less-p b a)))
+ (`todo-state-up '>)
+ (`todo-state-down '<)))
+
+(defun dashboard-agenda--build-sort-function-attribute (strategy)
+ "Return the argument to compare two entries depending to the `STRATEGY'."
+ (cond
+ ((memq strategy '(time-up time-down)) 'dashboard-agenda-time)
+ ((memq strategy '(todo-state-up todo-state-down)) 'dashboard-agenda-todo-index)
+ (t nil)))
+
+(defun dashboard-agenda--compare-entries (entry1 entry2 strategies predicate attribute)
+ "Compare `ENTRY1' and `ENTRY2' by `ATTRIBUTE' using `PREDICATE'.
+If both attributes are nil or equals the next strategy in `STRATEGIES' is used
+to compare."
+ (let ((arg1 (get-text-property 0 attribute entry1))
+ (arg2 (get-text-property 0 attribute entry2)))
+ (cond
+ ((or (and (null arg1) (null arg2)) (equal arg1 arg2))
+ (apply (dashboard-agenda--build-sort-function strategies) (list entry1 entry2)))
+ ((null arg1) nil)
+ ((null arg2) t)
+ (t (apply predicate (list arg1 arg2))))))
+
+(defun dashboard-insert-agenda (list-size)
+ "Add the list of LIST-SIZE items of agenda."
+ (require 'org-agenda)
+ (dashboard-insert-section
+ (if dashboard-week-agenda
+ "Agenda for the coming week:"
+ "Agenda for today:")
+ (dashboard-agenda--sorted-agenda)
+ list-size
+ 'agenda
+ (dashboard-get-shortcut 'agenda)
+ `(lambda (&rest _)
+ (let ((buffer (find-file-other-window (get-text-property 0 'dashboard-agenda-file ,el))))
+ (with-current-buffer buffer
+ (goto-char (get-text-property 0 'dashboard-agenda-loc ,el))
+ (switch-to-buffer buffer))))
+ (format "%s" el)))
+
+;;
+;; Registers
+;;
+(defun dashboard-insert-registers (list-size)
+ "Add the list of LIST-SIZE items of registers."
+ (require 'register)
+ (dashboard-insert-section
+ "Registers:"
+ register-alist
+ list-size
+ 'registers
+ (dashboard-get-shortcut 'registers)
+ (lambda (&rest _) (jump-to-register (car el)))
+ (format "%c - %s" (car el) (register-describe-oneline (car el)))))
+
+(provide 'dashboard-widgets)
+;;; dashboard-widgets.el ends here