lem icon indicating copy to clipboard operation
lem copied to clipboard

Add `lem-dashboard`

Open jfaz1 opened this issue 1 year ago • 10 comments

This PR adds a customizable dashboard to lem, inspired by how distributions like doom/spacemacs/lazyvim ship out of the box. Besides a more friendly greeting than a *tmp* buffer, it ships with useful shortcuts like starting a lisp scratch or seeing recent files/projects at a glance.

image

The dashboard is simply a list of dashboard-items, of which a few are included by default, like a splash/footer message, commands/urls, working directory, and recent projects/files.

When making a dashboard-item, the only method you need to implement is draw-dashboard-item, which is called whenever the dashboard is (re)drawn.

Let's say you want to add a dashboard-item that prints a random fact/quote from an http endpoint, you can make it like this (multithreading is overkill here, just for fun):

(defclass dashboard-random-fact (dashboard-item)
  ((fact :initform nil :accessor fact)
   (fetching :initform nil :accessor fetching))
  (:documentation "Creates a dashboard item that displays a random fact"))

(defmethod draw-dashboard-item ((item dashboard-random-fact) point)
  ;; Display initial or current fact
  (insert-string point
                 (create-centered-string (or (fact item) "Loading fact..."))
                 :attribute (item-attribute item))
  (unless (or (fact item) (fetching item))
    (setf (fetching item) t)
    ;; Start a background thread to fetch the fact
    (bt2:make-thread
     (lambda ()
       (handler-case
           (let* ((response (dexador:get "https://uselessfacts.jsph.pl/api/v2/facts/random"))
                  (json-data (json:decode-json-from-string response))
                  (fact-text (cdr (assoc :text json-data))))
             (send-event (lambda () 
                           (setf (fact item) fact-text)
                           (redraw-dashboard))))
         (error (e)
           (send-event (lambda () (message (format nil "Error fetching fact: ~A" e))))))
       :name "random-fact-fetcher"))))

To make a custom dashboard layout, all you have to do is call set-dashboard and pass a list of items:

(in-package :lem-dashboard)

(set-dashboard (list (make-instance 'dashboard-splash
                                    :item-attribute 'document-metadata-attribute
                                    :splash-texts '("Welcome!" "Second splash message!")
                                    :top-margin 4
                                    :bottom-margin 2)
                     (make-instance 'dashboard-working-dir)
                     (make-instance 'dashboard-recent-files 
                                    :file-count 5
                                    :bottom-margin 1)
                     (make-instance 'dashboard-random-fact
                                    :bottom-margin 2
                                    :item-attribute 'document-header3-attribute)
                     (make-instance 'dashboard-command
                                    :display-text " New Lisp Scratch Buffer (l)"
                                    :command 'lem-lisp-mode/internal:lisp-scratch 
                                    :item-attribute 'document-header2-attribute
                                    :bottom-margin 2)))

image

Users might want to keep most of the default dashboard's functionality, but customize it a bit (e.g. change the splash, number of projects, etc.). There's a set-default-dashboard available to make it easy so they don't have to re-do the whole layout:

(lem-dashboard:set-default-dashboard :project-count 10 :file-count 8 :hide-links t :splash '("hello" "hello2"))

Notes:

  • Set *dashboard-enable* to nil to disable
  • Only vertical stacking is currently supported. If you want to stack multiple elements horizontally, you'll have to make a dashboard item that does so. At some point I'd like to revisit this, but the way of centering elements here is just adding space padding. I think maybe lem itself should have line attributes, like left-adjust/right-adjust/center. Then we also wouldn't need to redraw on resize.
  • I threw in some cheesy footer messages, feel free to add/remove whatever you want from there :laughing:
  • @vindarel let me know if you want me to do the docs for this one

jfaz1 avatar Aug 24 '24 06:08 jfaz1

Wow, it's super cool :rocket:

LGTM (only a minor request with the defvar)

vindarel avatar Aug 24 '24 12:08 vindarel

user feedback:

  • watch out for the logo vertical size. On my screen, not a small one, I can see the logo, the cwd, "recent projects", then I have to scroll to see the rest. Not very ergonomic.
  • have the cursor on a meaningful by default?
  • have the ascii logo for ncurses, but the beautiful image in the GUI?
  • more shortcuts like M-n and M-p to move between items?

last but not least:

  • it may be a big win to display a short help, like C-x C-f to open files, M-x (alt-x) help to read some help or M-x documentation-describe-bindings.
    • the link to the online manual is great but not self-contained.

vindarel avatar Aug 24 '24 12:08 vindarel

@vindarel let me know if you want me to do the docs for this one

accepted, thanks. I can see a short description with screenshot in the usage page, then link to a more detailed page of its own.

Also, what about a dashboard-help? (where we see the lack of more self-documentation features)

vindarel avatar Aug 24 '24 12:08 vindarel

That's great!

It just seems a little buggy. For example, I get the following error at startup

invalid number of arguments: 0
Backtrace for: #<SB-THREAD:THREAD tid=3075 "editor" RUNNING {7005B23643}>
0: ((LAMBDA (LEM-DASHBOARD::A LEM-DASHBOARD::B) :IN LEM-DASHBOARD::DRAW-DASHBOARD-ITEM)) [external]
1: (REDUCE #<FUNCTION (LAMBDA (LEM-DASHBOARD::A LEM-DASHBOARD::B) :IN LEM-DASHBOARD::DRAW-DASHBOARD-ITEM) {700CF7260B}> NIL)
2: ((:METHOD LEM-DASHBOARD::DRAW-DASHBOARD-ITEM (LEM-DASHBOARD::DASHBOARD-RECENT-PROJECTS T)) #<LEM-DASHBOARD::DASHBOARD-RECENT-PROJECTS {700CFD9353}> #<LEM-CORE:CURSOR (30, 0) "" {700CC28693}>) [fast-method]
3: ((SB-PCL::EMF LEM-DASHBOARD::DRAW-DASHBOARD-ITEM) #<unused argument> #<unused argument> #<LEM-DASHBOARD::DASHBOARD-RECENT-PROJECTS {700CFD9353}> #<LEM-CORE:CURSOR (30, 0) "" {700CC28693}>)
4: ((:METHOD LEM-DASHBOARD::DRAW-DASHBOARD-ITEM :AROUND (LEM-DASHBOARD::DASHBOARD-ITEM T)) #<LEM-DASHBOARD::DASHBOARD-RECENT-PROJECTS {700CFD9353}> #<LEM-CORE:CURSOR (30, 0) "" {700CC28693}>) [fast-method]
5: (LEM-DASHBOARD::REDRAW-DASHBOARD)
6: (LEM-DASHBOARD:OPEN-DASHBOARD)
7: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUNCALL #<FUNCTION LEM-DASHBOARD:OPEN-DASHBOARD>) #<NULL-LEXENV>)
8: (EVAL (FUNCALL #<FUNCTION LEM-DASHBOARD:OPEN-DASHBOARD>))
9: (LEM-CORE::APPLY-ARGS #S(LEM-CORE::COMMAND-LINE-ARGUMENTS :ARGS (#1="~/.lem/log" (FUNCALL #<FUNCTION LEM-DASHBOARD:OPEN-DASHBOARD>)) :DEBUG NIL :LOG-FILENAME #1# :NO-INIT-FILE NIL :INTERFACE NIL))
10: (LEM-CORE::TOPLEVEL-COMMAND-LOOP #<FUNCTION (LAMBDA NIL :IN LEM-CORE::RUN-EDITOR-THREAD) {700B0E58FB}>)
11: ((LAMBDA NIL :IN LEM-CORE::RUN-EDITOR-THREAD))
12: ((LAMBDA NIL :IN LEM-CORE::RUN-EDITOR-THREAD))
13: ((FLET BORDEAUX-THREADS-2::RUN-FUNCTION :IN BORDEAUX-THREADS-2::ESTABLISH-DYNAMIC-ENV))
14: ((LABELS BORDEAUX-THREADS-2::%ESTABLISH-DYNAMIC-ENV-WRAPPER :IN BORDEAUX-THREADS-2::ESTABLISH-DYNAMIC-ENV))
15: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
16: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
17: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
18: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
19: (SB-THREAD::RUN)

I was able to open the dashboard with M-x open-dashboard. However, I randomly get the above error.

cxxxr avatar Aug 25 '24 07:08 cxxxr

Your screenshot seems to be different from mine. Do you have a special setting? スクリーンショット 2024-08-25 16 35 15

cxxxr avatar Aug 25 '24 07:08 cxxxr

For example, I get the following error at startup,,

Bah my bad, I lost logic I had there in a commit I reverted. Thanks for pointing that out.

Your screenshot seems to be different from mine. Do you have a special setting?

~~That's due to the above error, I'll fix it now.~~ Should work now

jfaz1 avatar Aug 25 '24 07:08 jfaz1

@cxxxr I see what vindarel was talking about with the default font making the ascii art look too big. I can try making it smaller.

This is the smallest I think I can make it without it starting to look strange: image

Should I commit it?

jfaz1 avatar Aug 25 '24 19:08 jfaz1

I think it's good

cxxxr avatar Aug 25 '24 19:08 cxxxr

User feedback:

  • (minor) ncurses version, I see "NIL recent projects (r)": "NIL" instead of an icon or a character
  • bug: on any line of the recent projects, pressing Enter makes me open… files from the cwd, not the project my cursor is on.

vindarel avatar Aug 28 '24 22:08 vindarel

  • (minor) ncurses version, I see "NIL recent projects (r)": "NIL" instead of an icon or a character

Hmm I don't see that, I'm guessing I just happen to have the font it needs installed? Is there a way to detect if the font is available and then just not show it if it's not? I guess worst-case scenario I can omit icons if on ncurses.

  • bug: on any line of the recent projects, pressing Enter makes me open… files from the cwd, not the project my cursor is on.

Whoops, that's my bad. I tested in same folder so I missed that :laughing: The arg in project-find-file is unused so I had to switch first. Fixed now, thanks.

jfaz1 avatar Aug 28 '24 23:08 jfaz1

alright the PR looks great to me and cxxxr approved the changes. @jfaz1 you're done, I/we merge?

vindarel avatar Sep 02 '24 21:09 vindarel

Looks good, so I'll merge it.

cxxxr avatar Sep 03 '24 07:09 cxxxr