256 lines
9.2 KiB
EmacsLisp
256 lines
9.2 KiB
EmacsLisp
;;; dashboard.el --- A startup screen extracted from Spacemacs -*- lexical-binding: t -*-
|
||
|
||
;; Copyright (c) 2016-2019 Rakan Al-Hneiti & Contributors
|
||
;;
|
||
;; 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
|
||
;; Package-Version: 1.7.0-SNAPSHOT
|
||
;; 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
|
||
(buffer-disable-undo)
|
||
(whitespace-mode -1)
|
||
(linum-mode -1)
|
||
(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
|
||
(when dashboard--section-starts
|
||
(goto-char (car (last dashboard--section-starts))))
|
||
(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
|