;;; dashboard.el --- A startup screen extracted from Spacemacs -*- lexical-binding: t -*- ;; Copyright (c) 2016-2020 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.8.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 "") 'dashboard-previous-line) (define-key map (kbd "") '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. \\ " :group 'dashboard :syntax-table nil :abbrev-table nil (buffer-disable-undo) (whitespace-mode -1) (linum-mode -1) (when (>= emacs-major-version 26) (display-line-numbers-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