-
-
Notifications
You must be signed in to change notification settings - Fork 220
Add lem-dashboard
#1495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add lem-dashboard
#1495
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
1328e38
Add `lem-dashboard`
jfaz1 5ce2c73
Load `lem-dashboard` with lem
jfaz1 a704d21
Initial dashboard attempt with dummy data
jfaz1 e81caf5
Center project list
jfaz1 0a9f71a
Export `saved-projects`
jfaz1 24721aa
Use real projects
jfaz1 ec4be1f
Add project opening functionality
jfaz1 c52c1bd
Add next/previous shortcuts
jfaz1 72c7c24
Add recent files
jfaz1 7acdae9
Set maximum project limit
jfaz1 4a77ebc
Add project/file limit variables
jfaz1 75a7374
Add icons, format a bit better, misc. changes
jfaz1 49081a2
Add attributes + divider
jfaz1 f07562b
Add open-lem-docs/open-lem-github shortcuts, make buttons
jfaz1 90171d3
Remove divider
jfaz1 9dbb4a7
Add working dir
jfaz1 d84ec76
Add vi keybinds
jfaz1 3b147ad
Add footer message/MOTD
jfaz1 51d2165
Add startup hook + resizing
jfaz1 7948d4c
Add custom layout, user variables
jfaz1 ada2363
Invert recent file list
jfaz1 10c5dda
Export `lisp-scratch`
jfaz1 9b1907e
Add `dashboard-enable` var
jfaz1 c3504c7
Generalize dashboard features with CLOS
jfaz1 fdf356a
Cache footer/splash messages for resizing
jfaz1 3f90e47
Refactor, move default dashboard to its own file
jfaz1 f909ab6
Fix missing icons at build time
jfaz1 476f24a
Add default actions
jfaz1 974e8a2
Add default dashboard creation function
jfaz1 e77103f
Break `vertical-padding` into top/bottom margin
jfaz1 3bccc4d
Use window size for create-centered-string
jfaz1 8a53cb5
Move dashboard-items to its own file
jfaz1 8cfdef4
Remove keybind/keybind-command from dashboard-item
jfaz1 35f3423
Use `*splash-function*` instead of init hook
jfaz1 bc9e51c
Fix scratch/docs icon
jfaz1 3d578bc
Use str, minor refactor
jfaz1 cb68a56
Use smaller ascii art
jfaz1 9415e8a
Refactor (Use local-nicknames, use with-buffer-read-only, add dashboa…
jfaz1 0060f1e
Move default dashboard keybinds
jfaz1 4d0d9a9
Even smaller ascii art
jfaz1 0e1c92a
Switch to project before finding file
jfaz1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
(in-package :lem-dashboard) | ||
|
||
;; Splash | ||
(defclass dashboard-splash (dashboard-item) | ||
((splash-texts :initarg :splash-texts :accessor splash-texts | ||
:initform '("Welcome!")) | ||
(selected-splash :initarg :selected-splash :accessor selected-splash | ||
:initform nil)) | ||
(:documentation "Randomly displays one of SPLASH-TEXTS") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute)) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-splash) point) | ||
(unless (selected-splash item) | ||
(setf (selected-splash item) | ||
(nth (random (length (splash-texts item))) (splash-texts item)))) | ||
(dolist (line (str:lines (selected-splash item))) | ||
(insert-string point (create-centered-string line) :attribute (item-attribute item)) | ||
(insert-character point #\Newline))) | ||
|
||
;; Url | ||
(defclass dashboard-url (dashboard-item) | ||
((url :initarg :url :accessor url) | ||
(display-text :initarg :display-text :accessor display-text)) | ||
(:documentation "Creates link/button with DISPLAY-TEXT that opens URL externally.") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute)) | ||
|
||
(defmethod initialize-instance :after ((item dashboard-url) &key) | ||
(with-slots (url action) item | ||
(setf action (lambda () (open-external-file url))))) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-url) point) | ||
(button:insert-button point | ||
(create-centered-string (display-text item)) | ||
(lambda () (open-external-file (url item))) | ||
:attribute (item-attribute item))) | ||
|
||
;; Working dir | ||
(defclass dashboard-working-dir (dashboard-item) | ||
() | ||
(:documentation "Prints current working directory") | ||
(:default-initargs | ||
:item-attribute 'document-header4-attribute)) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-working-dir) point) | ||
(let ((working-dir (format nil "> ~A" (buffer-directory)))) | ||
(insert-string point (create-centered-string working-dir) :attribute 'document-header4-attribute) | ||
(insert-character point #\Newline))) | ||
|
||
;; Footer | ||
(defclass dashboard-footer-message (dashboard-item) | ||
((messages :initarg :messages :accessor messages :initform '("Happy Coding!")) | ||
(selected-message :initarg :selected-message :accessor selected-message | ||
:initform nil)) | ||
(:documentation "Randomly displays one of the passed-in MESSAGES") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute)) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-footer-message) point) | ||
(unless (selected-message item) | ||
(setf (selected-message item) | ||
(nth (random (length (messages item))) (messages item)))) | ||
(insert-string point | ||
(create-centered-string (format nil "> ~A" (selected-message item))) | ||
:attribute (item-attribute item))) | ||
|
||
;; Command | ||
(defclass dashboard-command (dashboard-item) | ||
((display-text :initarg :display-text :accessor display-text) | ||
(action-command :initarg :action-command :accessor action-command)) | ||
(:documentation "Creates a link/button with DISPLAY-TEXT that executes ACTION-COMMAND when clicked.") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute)) | ||
|
||
(defmethod initialize-instance :after ((item dashboard-command) &key) | ||
(with-slots (action-command action) item | ||
(setf action (lambda () (funcall action-command))))) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-command) point) | ||
(button:insert-button point | ||
(create-centered-string (display-text item)) | ||
(lambda () (funcall (action-command item))) | ||
:attribute (item-attribute item))) | ||
|
||
;; Recent projects | ||
(defclass dashboard-recent-projects (dashboard-item) | ||
((project-count :initarg :project-count :accessor project-count :initform *dashboard-project-count*)) | ||
(:documentation "Displays a list of recent projects, limited to the last PROJECT-COUNT.") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute | ||
:action (lambda () | ||
(let ((project (str:trim (line-string (current-point))))) | ||
(when project | ||
(project:project-find-file project)))))) | ||
|
||
(define-command dashboard-move-to-recent-projects () () | ||
(let ((point (buffer-point (current-buffer)))) | ||
(buffer-start point) | ||
(search-forward-regexp point "Recent Projects") | ||
(line-offset point 2) | ||
(move-to-beginning-of-line))) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-recent-projects) point) | ||
(let* ((title (format nil "~A Recent Projects (r)" (icon-string "package"))) | ||
(title-line (create-centered-string title))) | ||
(insert-string point title-line :attribute 'document-header1-attribute) | ||
(insert-character point #\Newline) | ||
(insert-character point #\Newline) | ||
(let* ((projects (reverse (project:saved-projects))) | ||
(display-projects (subseq projects 0 (min (project-count item) (length projects))))) | ||
(when display-projects | ||
(let* ((longest-project (reduce #'(lambda (a b) (if (> (length a) (length b)) a b)) display-projects)) | ||
(max-length (length longest-project)) | ||
(left-padding (floor (- (window-width (current-window)) max-length) 2))) | ||
(loop for project in display-projects | ||
do (insert-string point (format nil "~v@{~A~:*~}" left-padding " ")) | ||
(insert-string point (format nil "~A~%" project)))))))) | ||
|
||
;; Recent files | ||
(defclass dashboard-recent-files (dashboard-item) | ||
((file-count :initarg :file-count :accessor file-count :initform *dashboard-file-count*)) | ||
(:documentation "Displays a list of recent files, limited to the last FILE-COUNT.") | ||
(:default-initargs | ||
:item-attribute 'document-text-attribute | ||
:action (lambda () | ||
(let ((file (str:trim (line-string (current-point))))) | ||
(when file | ||
(find-file file)))))) | ||
|
||
(define-command dashboard-move-to-recent-files () () | ||
(let ((point (buffer-point (current-buffer)))) | ||
(buffer-start point) | ||
(search-forward-regexp point "Recent Files") | ||
(line-offset point 2) | ||
(move-to-beginning-of-line))) | ||
|
||
(defmethod draw-dashboard-item ((item dashboard-recent-files) point) | ||
(let* ((title (format nil "~A Recent Files (f)" (icon-string "file-text"))) | ||
(title-line (create-centered-string title)) | ||
(recent-files (reverse (history:history-data-list (file:file-history))))) | ||
(insert-string point title-line :attribute 'document-header1-attribute) | ||
(insert-character point #\Newline) | ||
(insert-character point #\Newline) | ||
(let ((display-files (subseq recent-files 0 (min (file-count item) (length recent-files))))) | ||
(when display-files | ||
(let* ((longest-file (reduce #'(lambda (a b) (if (> (length a) (length b)) a b)) display-files)) | ||
(max-length (length longest-file)) | ||
(left-padding (floor (- (window-width (current-window)) max-length) 2))) | ||
(loop for file in display-files | ||
do (insert-string point (str:fit left-padding " ")) | ||
(insert-string point (format nil "~A~%" file)))))))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
(in-package :lem-dashboard) | ||
|
||
(defvar *default-footer-messages* '("Happy Coding!" | ||
"ほげほげ" | ||
"In Lisp we trust, for bugs we adjust" | ||
"Read, Evaluate, Print, Love" | ||
"May your parentheses always be balanced" | ||
"(setf productivity 'high)" | ||
"<M-x> load-library <RET> tetris" | ||
"Lem Editor Modules? Lisp EMacs? Lem's Not Emacs?" | ||
"(cons 'fun 'programming)")) | ||
|
||
(defvar *default-splash* '(" | ||
----------------------- | ||
[ Welcome to Lem! ] | ||
----------------------- | ||
|
||
#*++====== | ||
%#*=============== | ||
%%*=================== | ||
%%%%%%%%%%%*+============ | ||
%%%%%%%%%%%%% +=========== | ||
%%%%%%%%%%%%%% -=========== | ||
%%%%%%%%%%%%%#*+=-:::========== | ||
%%%%%%%%%%%%=:::::::::-==-:% * | ||
%%%%%%%%%%*::::::::::::::::::==: | ||
%%%%%%%%%*-+*##-::::::::::::: | ||
%%%%%%%%%%%%%#----- | ||
%%%%%%%%%%%%%=::::: | ||
%%%%%%%%%%%*::::::: | ||
%%%%%%%%%%=::::::::: | ||
%%%%%%%%%*::::::::::::::: | ||
%%%%%%%%%*:::::::::::: | ||
%%%%%%%%%#-:::::: | ||
%%%%%%%%%*-::::: | ||
#+=-:::::::: ")) | ||
|
||
(define-command open-lem-docs () () | ||
(open-external-file "https://lem-project.github.io/usage/usage/")) | ||
|
||
(define-command open-lem-github () () | ||
(open-external-file "https://github.com/lem-project/lem")) | ||
|
||
(defun set-default-dashboard (&key | ||
(project-count 5) | ||
(file-count 5) | ||
(splash *default-splash*) | ||
(footer-messages *default-footer-messages*) | ||
hide-links) | ||
(let ((dashboard-items | ||
(list (make-instance 'dashboard-splash | ||
:item-attribute 'document-metadata-attribute | ||
:splash-texts splash) | ||
(make-instance 'dashboard-working-dir) | ||
(make-instance 'dashboard-recent-projects | ||
:project-count project-count | ||
:bottom-margin 1) | ||
(make-instance 'dashboard-recent-files | ||
:file-count file-count | ||
:bottom-margin 1) | ||
(make-instance 'dashboard-command | ||
:display-text " New Lisp Scratch Buffer (l)" | ||
:action-command 'lem-lisp-mode:lisp-scratch | ||
:item-attribute 'document-header2-attribute | ||
:bottom-margin 2)))) | ||
(unless hide-links | ||
(setf dashboard-items | ||
(append dashboard-items | ||
(list (make-instance 'dashboard-url | ||
:display-text " Getting Started (s)" | ||
:url "https://lem-project.github.io/usage/usage/" | ||
:item-attribute 'document-header3-attribute) | ||
(make-instance 'dashboard-url | ||
:display-text " GitHub (g)" | ||
:url "https://github.com/lem-project/lem" | ||
:item-attribute 'document-header3-attribute | ||
:bottom-margin 2)))) | ||
(define-key *dashboard-mode-keymap* "s" 'open-lem-docs) | ||
(define-key *dashboard-mode-keymap* "g" 'open-lem-github)) | ||
|
||
(setf dashboard-items | ||
(append dashboard-items | ||
(list (make-instance 'dashboard-footer-message | ||
:item-attribute 'document-blockquote-attribute | ||
:messages footer-messages)))) | ||
|
||
(define-key *dashboard-mode-keymap* "r" 'dashboard-move-to-recent-projects) | ||
(define-key *dashboard-mode-keymap* "f" 'dashboard-move-to-recent-files) | ||
(define-key *dashboard-mode-keymap* "l" 'lem-lisp-mode/internal:lisp-scratch) | ||
|
||
(set-dashboard dashboard-items))) | ||
|
||
(set-default-dashboard) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
(defsystem "lem-dashboard" | ||
:depends-on (:lem) | ||
:serial t | ||
:components ((:file "lem-dashboard") | ||
(:file "dashboard-items") | ||
(:file "default-dashboard"))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
(defpackage :lem-dashboard | ||
(:use :cl :lem) | ||
(:export :open-dashboard | ||
:*dashboard-mode-keymap* | ||
:*dashboard-enable* | ||
:dashboard-move-to-recent-projects | ||
:dashboard-move-to-recent-files | ||
:set-dashboard | ||
:set-default-dashboard | ||
:create-centered-string) | ||
(:local-nicknames (:button :lem/button) | ||
(:history :lem/common/history) | ||
(:project :lem-core/commands/project) | ||
(:file :lem-core/commands/file))) | ||
|
||
(in-package :lem-dashboard) | ||
|
||
(defvar *dashboard-buffer-name* "*dashboard*") | ||
(defvar *dashboard-enable* t) | ||
(defvar *dashboard-mode-keymap* (make-keymap :name '*dashboard-mode-keymap* :parent *global-keymap*)) | ||
(defvar *dashboard-layout* nil | ||
"List of dashboard-item instances; will be drawn in order.") | ||
|
||
(defun create-centered-string (str) | ||
"Creates a 'centered' string by padding with space to fill the screen width halfway." | ||
(let* ((padding (max 0 (floor (- (window-width (current-window)) (length str)) 2))) | ||
(spaces (make-string padding :initial-element #\Space))) | ||
(concatenate 'string spaces str))) | ||
|
||
(defclass dashboard-item () | ||
((item-attribute | ||
:initarg :item-attribute | ||
:accessor item-attribute | ||
:initform 'document-text-attribute | ||
:documentation "Attribute to use when drawing this item.") | ||
(top-margin | ||
:initarg :top-margin | ||
:accessor top-margin | ||
:initform 0 | ||
:documentation "The amount of vertical space (lines) to apply before the item.") | ||
(bottom-margin | ||
:initarg :bottom-margin | ||
:accessor bottom-margin | ||
:initform 1 | ||
:documentation "The amount of vertical space (lines) to apply after the item.") | ||
(action | ||
:initarg :action | ||
:accessor action | ||
:initform nil | ||
:documentation "Function to execute when <return> is pressed over this item.")) | ||
(:documentation "Base class for all dashboard items.")) | ||
|
||
(defgeneric draw-dashboard-item (item point) | ||
(:documentation "Called to draw the dashboard item.") | ||
(:method :before ((item dashboard-item) point) | ||
(dotimes (i (top-margin item)) | ||
(insert-character point #\Newline))) | ||
(:method :after ((item dashboard-item) point) | ||
(dotimes (i (bottom-margin item)) | ||
(insert-character point #\Newline)))) | ||
|
||
(define-command dashboard-open-selected-item () () | ||
"Execute action on selected dashboard item." | ||
(let* ((point (current-point)) | ||
(item (text-property-at point :dashboard-item))) | ||
(when (and item (action item)) | ||
(funcall (action item))))) | ||
|
||
(defmethod draw-dashboard-item :around ((item dashboard-item) point) | ||
"Inserts a :dashboard-item text property in between the starting and ending POINT, useful for tracking." | ||
(let ((start (copy-point point :temporary))) | ||
(call-next-method) | ||
(let ((end (copy-point point :temporary))) | ||
(put-text-property start end :dashboard-item item) | ||
(delete-point start) | ||
(delete-point end)))) | ||
jfaz1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
(define-major-mode dashboard-mode () | ||
(:name "Dashboard" | ||
:keymap *dashboard-mode-keymap*)) | ||
|
||
(defun create-dashboard-buffer () | ||
"Creates the dashboard buffer." | ||
(or (get-buffer *dashboard-buffer-name*) | ||
(make-buffer *dashboard-buffer-name* | ||
:enable-undo-p nil | ||
:read-only-p t))) | ||
|
||
(defun redraw-dashboard () | ||
"Redraws the dashboard, clearing and redrawing all content while attempting to maintain point position." | ||
(let* ((buffer (create-dashboard-buffer)) | ||
(old-line (line-number-at-point (buffer-point buffer))) | ||
(old-column (point-column (buffer-point buffer)))) | ||
(with-buffer-read-only buffer nil | ||
(erase-buffer buffer) | ||
(let ((point (buffer-point buffer))) | ||
(dolist (item *dashboard-layout*) | ||
(draw-dashboard-item item point))) | ||
(change-buffer-mode buffer 'dashboard-mode) | ||
(move-to-line (buffer-point buffer) old-line) | ||
(move-to-column (buffer-point buffer) old-column)))) | ||
|
||
(define-command open-dashboard () () | ||
"Opens the dashboard if it doesn't exist, or switches to it if it does." | ||
(when *dashboard-enable* | ||
(if (get-buffer *dashboard-buffer-name*) | ||
(switch-to-buffer (get-buffer *dashboard-buffer-name*)) | ||
(progn | ||
(redraw-dashboard) | ||
(switch-to-buffer (get-buffer *dashboard-buffer-name*)))))) | ||
|
||
(defun set-dashboard (dashboard-items) | ||
"Sets the new dashboard layout to DASHBOARD-ITEMS list and applies new keymap." | ||
(when dashboard-items | ||
(setf *dashboard-layout* dashboard-items) | ||
(when (get-buffer *dashboard-buffer-name*) | ||
(redraw-dashboard)))) | ||
|
||
(defun handle-resize (window) | ||
"Handle resizing; in this case, redraw the dashboard to keep it centered." | ||
(when (string= (buffer-name (window-buffer window)) *dashboard-buffer-name*) | ||
(redraw-dashboard))) | ||
|
||
(define-key *dashboard-mode-keymap* "n" 'next-line) | ||
(define-key *dashboard-mode-keymap* "p" 'previous-line) | ||
(define-key *dashboard-mode-keymap* "j" 'next-line) | ||
(define-key *dashboard-mode-keymap* "k" 'previous-line) | ||
(define-key *dashboard-mode-keymap* "Return" 'dashboard-open-selected-item) | ||
|
||
(setf lem:*splash-function* #'open-dashboard) | ||
(add-hook *window-size-change-functions* 'handle-resize) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed this code, is there any reason why it is not defined at the top level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I had bindings previously worked a bit differently. The idea was to not apply the keybind until it's used in case the user has a dashboard that doesn't contain an item like the docs/github link. That being said, after changing the way the default dashboard is created, it doesn't work since it gets called at compile time, so further calls to
set-default-dashboard
won't remove the stale binds.I went ahead and moved it to the top-level, users can just replace the stale bind in their config if they want to bind something else there.