2019-12-12 17:30:29 +01:00
|
|
|
|
;;; dashboard.el --- A startup screen extracted from Spacemacs -*- lexical-binding: t -*-
|
|
|
|
|
|
2020-02-22 12:54:34 +01:00
|
|
|
|
;; Copyright (c) 2016-2020 Rakan Al-Hneiti & Contributors
|
2019-12-12 17:30:29 +01:00
|
|
|
|
;;
|
|
|
|
|
;; Author: Rakan Al-Hneiti
|
|
|
|
|
;; URL: https://github.com/emacs-dashboard/emacs-dashboard
|
|
|
|
|
;;
|
|
|
|
|
;; This file is not part of GNU Emacs.
|
|
|
|
|
;;
|
|
|
|
|
;;; License: GPLv3
|
|
|
|
|
;;
|
|
|
|
|
;; Created: October 05, 2016
|
2020-02-22 12:54:34 +01:00
|
|
|
|
;; Package-Version: 1.8.0-SNAPSHOT
|
2019-12-12 17:30:29 +01:00
|
|
|
|
;; Keywords: startup, screen, tools, dashboard
|
|
|
|
|
;; Package-Requires: ((emacs "25.3") (page-break-lines "0.11"))
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; An extensible Emacs dashboard, with sections for
|
|
|
|
|
;; bookmarks, projectile projects, org-agenda and more.
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(require 'seq)
|
|
|
|
|
(require 'page-break-lines)
|
|
|
|
|
(require 'recentf)
|
|
|
|
|
|
|
|
|
|
(require 'dashboard-widgets)
|
|
|
|
|
|
|
|
|
|
;; Custom splash screen
|
|
|
|
|
(defvar dashboard-mode-map
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map (kbd "C-p") 'dashboard-previous-line)
|
|
|
|
|
(define-key map (kbd "C-n") 'dashboard-next-line)
|
|
|
|
|
(define-key map (kbd "<up>") 'dashboard-previous-line)
|
|
|
|
|
(define-key map (kbd "<down>") 'dashboard-next-line)
|
|
|
|
|
(define-key map (kbd "k") 'dashboard-previous-line)
|
|
|
|
|
(define-key map (kbd "j") 'dashboard-next-line)
|
|
|
|
|
(define-key map [tab] 'widget-forward)
|
|
|
|
|
(define-key map (kbd "C-i") 'widget-forward)
|
|
|
|
|
(define-key map [backtab] 'widget-backward)
|
|
|
|
|
(define-key map (kbd "RET") 'dashboard-return)
|
|
|
|
|
(define-key map [down-mouse-1] 'widget-button-click)
|
|
|
|
|
(define-key map (kbd "g") #'dashboard-refresh-buffer)
|
|
|
|
|
(define-key map (kbd "}") #'dashboard-next-section)
|
|
|
|
|
(define-key map (kbd "{") #'dashboard-previous-section)
|
|
|
|
|
map)
|
|
|
|
|
"Keymap for dashboard mode.")
|
|
|
|
|
|
|
|
|
|
(define-derived-mode dashboard-mode special-mode "Dashboard"
|
|
|
|
|
"Dashboard major mode for startup screen.
|
|
|
|
|
\\<dashboard-mode-map>
|
|
|
|
|
"
|
|
|
|
|
:group 'dashboard
|
|
|
|
|
:syntax-table nil
|
|
|
|
|
:abbrev-table nil
|
2020-01-29 18:18:31 +01:00
|
|
|
|
(buffer-disable-undo)
|
2019-12-12 17:30:29 +01:00
|
|
|
|
(whitespace-mode -1)
|
|
|
|
|
(linum-mode -1)
|
2020-02-25 18:47:32 +01:00
|
|
|
|
(when (>= emacs-major-version 26)
|
|
|
|
|
(display-line-numbers-mode -1))
|
2019-12-12 17:30:29 +01:00
|
|
|
|
(page-break-lines-mode 1)
|
|
|
|
|
(setq inhibit-startup-screen t)
|
|
|
|
|
(setq buffer-read-only t
|
|
|
|
|
truncate-lines t))
|
|
|
|
|
|
|
|
|
|
(defgroup dashboard nil
|
|
|
|
|
"Extensible startup screen."
|
|
|
|
|
:group 'applications)
|
|
|
|
|
|
|
|
|
|
(defcustom dashboard-center-content nil
|
|
|
|
|
"Whether to center content within the window."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'dashboard)
|
|
|
|
|
|
|
|
|
|
(defconst dashboard-buffer-name "*dashboard*"
|
|
|
|
|
"Dashboard's buffer name.")
|
|
|
|
|
|
|
|
|
|
(defvar dashboard--section-starts nil
|
|
|
|
|
"List of section starting positions.")
|
|
|
|
|
|
|
|
|
|
(defun dashboard-previous-section ()
|
|
|
|
|
"Navigate back to previous section."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((current-section-start nil)
|
|
|
|
|
(current-position (point))
|
|
|
|
|
(previous-section-start nil))
|
|
|
|
|
(dolist (elt dashboard--section-starts)
|
|
|
|
|
(when (and current-section-start
|
|
|
|
|
(not previous-section-start))
|
|
|
|
|
(setq previous-section-start elt))
|
|
|
|
|
(when (and (not current-section-start)
|
|
|
|
|
(< elt current-position))
|
|
|
|
|
(setq current-section-start elt)))
|
|
|
|
|
(goto-char (if (eq current-position current-section-start)
|
|
|
|
|
previous-section-start
|
|
|
|
|
current-section-start))))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-next-section ()
|
|
|
|
|
"Navigate forward to next section."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((current-position (point))
|
|
|
|
|
(next-section-start nil)
|
|
|
|
|
(section-starts (reverse dashboard--section-starts)))
|
|
|
|
|
(dolist (elt section-starts)
|
|
|
|
|
(when (and (not next-section-start)
|
|
|
|
|
(> elt current-position))
|
|
|
|
|
(setq next-section-start elt)))
|
|
|
|
|
(when next-section-start
|
|
|
|
|
(goto-char next-section-start))))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-previous-line (arg)
|
|
|
|
|
"Move point up and position it at that line’s item.
|
|
|
|
|
Optional prefix ARG says how many lines to move; default is one line."
|
|
|
|
|
(interactive "^p")
|
|
|
|
|
(dashboard-next-line (- arg)))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-next-line (arg)
|
|
|
|
|
"Move point down and position it at that line’s item.
|
|
|
|
|
Optional prefix ARG says how many lines to move; default is one line."
|
|
|
|
|
;; code heavily inspired by `dired-next-line'
|
|
|
|
|
(interactive "^p")
|
|
|
|
|
(let ((line-move-visual nil)
|
|
|
|
|
(goal-column nil))
|
|
|
|
|
(line-move arg t))
|
|
|
|
|
;; We never want to move point into an invisible line. Dashboard doesn’t
|
|
|
|
|
;; use invisible text currently but when it does we’re ready!
|
|
|
|
|
(while (and (invisible-p (point))
|
|
|
|
|
(not (if (and arg (< arg 0)) (bobp) (eobp))))
|
|
|
|
|
(forward-char (if (and arg (< arg 0)) -1 1)))
|
|
|
|
|
(beginning-of-line-text))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-return ()
|
|
|
|
|
"Hit return key in dashboard buffer."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((start-ln (line-number-at-pos))
|
|
|
|
|
(fd-cnt 0)
|
|
|
|
|
(diff-line nil)
|
|
|
|
|
(entry-pt nil))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(while (and (not diff-line)
|
|
|
|
|
(not (= (point) (point-min)))
|
|
|
|
|
(not (get-char-property (point) 'button))
|
|
|
|
|
(not (= (point) (point-max))))
|
|
|
|
|
(forward-char 1)
|
|
|
|
|
(setq fd-cnt (1+ fd-cnt))
|
|
|
|
|
(unless (= start-ln (line-number-at-pos))
|
|
|
|
|
(setq diff-line t)))
|
|
|
|
|
(unless (= (point) (point-max))
|
|
|
|
|
(setq entry-pt (point))))
|
|
|
|
|
(when (= fd-cnt 1)
|
|
|
|
|
(setq entry-pt (1- (point))))
|
|
|
|
|
(if entry-pt
|
|
|
|
|
(widget-button-press entry-pt)
|
|
|
|
|
(call-interactively #'widget-button-press))))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-maximum-section-length ()
|
|
|
|
|
"For the just-inserted section, calculate the length of the longest line."
|
|
|
|
|
(let ((max-line-length 0))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(dashboard-previous-section)
|
|
|
|
|
(while (not (eobp))
|
|
|
|
|
(setq max-line-length
|
|
|
|
|
(max max-line-length
|
|
|
|
|
(- (line-end-position) (line-beginning-position))))
|
|
|
|
|
(forward-line)))
|
|
|
|
|
max-line-length))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-insert-startupify-lists ()
|
|
|
|
|
"Insert the list of widgets into the buffer."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((buffer-exists (buffer-live-p (get-buffer dashboard-buffer-name)))
|
|
|
|
|
(recentf-is-on (recentf-enabled-p))
|
|
|
|
|
(origial-recentf-list recentf-list)
|
|
|
|
|
(dashboard-num-recents (or (cdr (assoc 'recents dashboard-items)) 0))
|
|
|
|
|
(max-line-length 0))
|
|
|
|
|
;; disable recentf mode,
|
|
|
|
|
;; so we don't flood the recent files list with org mode files
|
|
|
|
|
;; do this by making a copy of the part of the list we'll use
|
|
|
|
|
;; let dashboard widgets change that
|
|
|
|
|
;; then restore the orginal list afterwards
|
|
|
|
|
;; (this avoids many saves/loads that would result from
|
|
|
|
|
;; disabling/enabling recentf-mode)
|
|
|
|
|
(if recentf-is-on
|
|
|
|
|
(setq recentf-list (seq-take recentf-list dashboard-num-recents)))
|
|
|
|
|
(when (or (not (eq dashboard-buffer-last-width (window-width)))
|
|
|
|
|
(not buffer-exists))
|
|
|
|
|
(setq dashboard-banner-length (window-width)
|
|
|
|
|
dashboard-buffer-last-width dashboard-banner-length)
|
|
|
|
|
(with-current-buffer (get-buffer-create dashboard-buffer-name)
|
|
|
|
|
(let ((buffer-read-only nil))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(dashboard-insert-banner)
|
|
|
|
|
(dashboard-insert-page-break)
|
|
|
|
|
(setq dashboard--section-starts nil)
|
|
|
|
|
(mapc (lambda (els)
|
|
|
|
|
(let* ((el (or (car-safe els) els))
|
|
|
|
|
(list-size
|
|
|
|
|
(or (cdr-safe els)
|
|
|
|
|
dashboard-items-default-length))
|
|
|
|
|
(item-generator
|
|
|
|
|
(cdr-safe (assoc el dashboard-item-generators))))
|
|
|
|
|
(add-to-list 'dashboard--section-starts (point))
|
|
|
|
|
(funcall item-generator list-size)
|
|
|
|
|
(setq max-line-length
|
|
|
|
|
(max max-line-length (dashboard-maximum-section-length)))
|
|
|
|
|
(dashboard-insert-page-break)))
|
|
|
|
|
dashboard-items)
|
|
|
|
|
(when dashboard-center-content
|
2020-02-17 23:07:13 +01:00
|
|
|
|
(when dashboard--section-starts
|
|
|
|
|
(goto-char (car (last dashboard--section-starts))))
|
2019-12-12 17:30:29 +01:00
|
|
|
|
(let ((margin (floor (/ (max (- (window-width) max-line-length) 0) 2))))
|
|
|
|
|
(while (not (eobp))
|
|
|
|
|
(and (not (eq ? (char-after)))
|
|
|
|
|
(insert (make-string margin ?\ )))
|
|
|
|
|
(forward-line 1))))
|
|
|
|
|
(dashboard-insert-footer))
|
|
|
|
|
(dashboard-mode)
|
|
|
|
|
(goto-char (point-min))))
|
|
|
|
|
(if recentf-is-on
|
|
|
|
|
(setq recentf-list origial-recentf-list))))
|
|
|
|
|
|
|
|
|
|
(add-hook 'window-setup-hook
|
|
|
|
|
(lambda ()
|
|
|
|
|
(add-hook 'window-size-change-functions 'dashboard-resize-on-hook)
|
|
|
|
|
(dashboard-resize-on-hook)))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-refresh-buffer ()
|
|
|
|
|
"Refresh buffer."
|
|
|
|
|
(interactive)
|
|
|
|
|
(kill-buffer dashboard-buffer-name)
|
|
|
|
|
(dashboard-insert-startupify-lists)
|
|
|
|
|
(switch-to-buffer dashboard-buffer-name))
|
|
|
|
|
|
|
|
|
|
(defun dashboard-resize-on-hook (&optional _)
|
|
|
|
|
"Re-render dashboard on window size change."
|
|
|
|
|
(let ((space-win (get-buffer-window dashboard-buffer-name))
|
|
|
|
|
(frame-win (frame-selected-window)))
|
|
|
|
|
(when (and space-win
|
|
|
|
|
(not (window-minibuffer-p frame-win)))
|
|
|
|
|
(with-selected-window space-win
|
|
|
|
|
(dashboard-insert-startupify-lists)))))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun dashboard-setup-startup-hook ()
|
|
|
|
|
"Setup post initialization hooks.
|
|
|
|
|
If a command line argument is provided,
|
|
|
|
|
assume a filename and skip displaying Dashboard."
|
|
|
|
|
(when (< (length command-line-args) 2 )
|
|
|
|
|
(add-hook 'after-init-hook (lambda ()
|
|
|
|
|
;; Display useful lists of items
|
|
|
|
|
(dashboard-insert-startupify-lists)))
|
|
|
|
|
(add-hook 'emacs-startup-hook '(lambda ()
|
|
|
|
|
(switch-to-buffer "*dashboard*")
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(redisplay)))))
|
|
|
|
|
|
|
|
|
|
(provide 'dashboard)
|
|
|
|
|
;;; dashboard.el ends here
|