summaryrefslogtreecommitdiff
path: root/eshell-conf.el
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2026-10-12 14:59:56 +0800
committerJSDurand <mmemmew@gmail.com>2026-10-12 15:08:50 +0800
commit5ce77bff2f05fff62aa5b117f82b76864d36af08 (patch)
tree8d998e7b4f881e1ad428d0276ca060a61796e291 /eshell-conf.el
parent30d4f2070d5a022a1bb6f43f939d292b2cfe17c0 (diff)
eshell: Refactor j to a better design.
* eshell-conf.el (eshell/j): Refactor to use more friendly option style and add the option for listing candidates. ("~/scripts/claude/claude-session.el"): Load the function for getting claude session information. (eshell-j): Refactor to accomodate the changes of the calling interface, and add the functionality of listing candidates.
Diffstat (limited to 'eshell-conf.el')
-rw-r--r--eshell-conf.el262
1 files changed, 147 insertions, 115 deletions
diff --git a/eshell-conf.el b/eshell-conf.el
index 1013383..3e95e8d 100644
--- a/eshell-conf.el
+++ b/eshell-conf.el
@@ -247,16 +247,37 @@ See `eshell-j' for the actual functionality and for the format of
ARGS."
(eshell-eval-using-options
"j" args
- '((?r "recent" exclusive use-recent-p
- "Find in recent directories instead of symbolic links.")
- (?a "all" nil use-recent-p
- "Find in recent directories, \
-symbolic links, and in claude session directories.")
+ '((?m "marks" nil use-marks-p
+ "Find in the marks directory (the default).")
+ (?r "recent" nil use-recent-p
+ "Find in recent directories.")
(?s "session" nil use-session-p
"Find in directories of Claude sessions.")
+ (?a "all" nil use-all-p "All sources included.")
+ (?l "list" nil list-p
+ "List the candidates instead of jumping.")
(?h "help" nil nil "Print this help message.")
- :usage "[-hras] [short-cut]")
- (eshell-j args use-recent-p use-session-p)))
+ :usage "[-hmrsal] [short-cut]"
+ :post-usage "Without any of [-mrsa], only marks are the sources.
+For listing candidates, the style of a line is determiney by the source:
+\"NAME -> PATH\" for a mark,
+\"PATH\" for a recent directory, and
+\"TITLE: PATH\" for a claude session.")
+ (eshell-j
+ args
+ (cond
+ (use-all-p (list 'marks 'recent 'session))
+ ((or use-marks-p use-recent-p use-session-p)
+ (delq nil (list (cond (use-marks-p 'marks))
+ (cond (use-recent-p 'recent))
+ (cond (use-session-p 'session)))))
+ ((list 'marks)))
+ list-p)))
+
+(defface eshell-j-mark-name
+ '((((background dark)) :foreground "sky blue")
+ (t :foreground "DeepSkyBlue4"))
+ "Face for the display of names of marks as candidates for `eshell-j'.")
;;;###autoload
(defun eshell/mark (&rest args)
@@ -343,111 +364,131 @@ supposed to leave the global state alone as well.
(defvar eshell-mark-directory (expand-file-name "~/.marks")
"The directory that stores links to other directories.")
+(load-file "~/scripts/claude/claude-session.el")
+
;;;###autoload
-(defun eshell-j (&optional short-cut use-recent-p use-session-p)
- "Jump to SHORT-CUT.
-Where this jumps to is determined by the symbolic links in the
-directory `eshell-mark-directory'. If USE-RECENT-P is non-nil, then
-also include recent directories in the list of candidates. Moreover,
-if USE-RECENT-P is 'exclusive, then only list the recent
-directories as candidates, unless there are no recent
-directories, in which case it falls back to use the marks as the
-candidates.
-
-If USE-SESSION-P and USE-RECENT-P are non-nil, then also include the
-claude session directories in the list of candidates.
-
-If USE-RECENT-P is non-nil and USE-RECENT-P is nil, only the claude
-session directories are in the list of candidates."
- (let* ((all-p (eq use-recent-p t))
- (mark-directory eshell-mark-directory)
+(defun eshell-j (&optional short-cut sources list-p)
+ "Jump to SHORT-CUT, or list the candidates if LIST-P is non-nil.
+SOURCES is a list of the symbols `marks', `recent', and `session',
+specifying the sources of candidates. Unknown symbols are ignored. If
+no known symbol is present, use `marks' as the default source.
+
+The sources are described below:
+
+- `marks': the symbolic links in the `eshell-mark-directory'.
+- `recent': the directories in `eshell-last-dir-ring'.
+- `session': the directories of claude sessions. See
+ `claude-session-candidates' to be precise.
+
+Marks and recent directories are matched with SHORT-CUT by the paths,
+and sessions are matched by the display, including the title. If all
+the candidates are in the same directory, jump to that directory
+directly; otherwise use `completing-read' to choose one destination.
+
+If LIST-P is non-nil, return a string listing one line for one
+candidate, in the following format:
+
+\"NAME -> PATH\" for a mark,
+\"PATH\" for a recent directory, and
+\"TITLE: PATH\" for a claude session."
+ (let* ((accepted-sources (list 'marks 'recent 'session))
+ ;; filter out unknown source symbols.
+ (sources (delq
+ nil
+ (mapcar
+ (lambda (source)
+ (cond ((memq source accepted-sources) source)))
+ sources)))
+ (sources (cond (sources) ((cons 'marks nil))))
(short-cut (eshell-flatten-and-stringify short-cut))
- (links (delq nil
- (mapcar
- (function
- (lambda (name)
- (cond
- ((or (string= "." (file-name-nondirectory name))
- (string= ".." (file-name-nondirectory name)))
- nil)
- (name))))
- (directory-files mark-directory t short-cut t))))
- (candidates (cond
- ((and use-recent-p
- (not (eq use-recent-p 'exclusive))
- (ring-p eshell-last-dir-ring)
- (not (ring-empty-p eshell-last-dir-ring)))
- (append (mapcar (function
- (lambda (file)
- (cons (file-name-nondirectory file)
- file)))
- links)
- (cond
- ((string-match-p short-cut mark-directory)
- (list (cons mark-directory mark-directory))))
- (mapcar (function (lambda (file) (cons file file)))
- (ring-elements eshell-last-dir-ring))))
- ((and use-recent-p
- (eq use-recent-p 'exclusive)
- (ring-p eshell-last-dir-ring)
- (not (ring-empty-p eshell-last-dir-ring)))
- (mapcar (function (lambda (file) (cons file file)))
- (ring-elements eshell-last-dir-ring)))
- ((append
- (mapcar (function
- (lambda (file)
- (cons (file-name-nondirectory file)
- file)))
- links)
- (cond
- ((string-match-p short-cut mark-directory)
- (list (cons mark-directory mark-directory))))))))
- ;; Delete duplicate items
- (candidates
- (durand-eshell-delete-dups
- candidates
- :test #'string=
- ;; In Haskell this woule be a simple function composition.
- :key (lambda (ls) (file-truename (cdr ls)))))
- ;; Delete non-matching ones
- (candidates
- (delq
- nil
- (mapcar
- (lambda (cand)
- (cond ((string-match-p short-cut (cdr cand)) cand)))
- candidates)))
- ;; also include session candidates if required
- (sessions
+ (mark-directory eshell-mark-directory)
+ (directory-key (lambda (cand)
+ (directory-file-name
+ (file-truename (cdr cand)))))
+ (path-matcher
+ (lambda (cand) (string-match-p short-cut (cdr cand))))
+ (marks
+ (cond
+ ((memq 'marks sources)
+ (let ((links
+ (delq
+ nil
+ (mapcar
+ (lambda (name)
+ (cond
+ ((or (string=
+ "." (file-name-nondirectory name))
+ (string=
+ ".." (file-name-nondirectory name)))
+ nil)
+ (name)))
+ (directory-files
+ mark-directory t short-cut t)))))
+ (append
+ (mapcar
+ (lambda (file)
+ (cons
+ (propertize
+ (file-name-nondirectory file)
+ 'face 'eshell-j-mark-name)
+ file))
+ links)
+ (cond
+ ((string-match-p short-cut mark-directory)
+ (cons
+ (cons mark-directory mark-directory)
+ nil))))))))
+ (recent
(cond
- ((or all-p use-session-p) (claude-session-candidates))))
- ;; filter sessions
+ ((and (memq 'recent sources)
+ (ring-p eshell-last-dir-ring)
+ (not (ring-empty-p eshell-last-dir-ring)))
+ (delq
+ nil
+ (mapcar
+ (lambda (file)
+ (cond
+ ((string-match-p short-cut file)
+ (cons file file))))
+ (ring-elements eshell-last-dir-ring))))))
+ (recent (durand-eshell-delete-dups
+ recent :test #'string= :key directory-key))
(sessions
- (delq
- nil
- (mapcar
- (lambda (session)
- (cond
- ((string-match-p short-cut (car session)) session)))
- sessions)))
- ;; merging
- (candidates
(cond
- (use-recent-p (append candidates sessions))
- (use-session-p sessions)
- (candidates))))
+ ((memq 'session sources)
+ (delq
+ nil
+ (mapcar
+ (lambda (session)
+ (cond
+ ((string-match-p short-cut (car session)) session)))
+ (claude-session-candidates))))))
+ (candidates (append marks recent sessions)))
(cond
((null candidates)
(user-error "No candidates matching %s found" short-cut))
- ((let ((dirs-of-cands
- (durand-eshell-delete-dups
- candidates
- :key (lambda (cand)
- (directory-file-name
- (file-truename (cdr cand))))
- :test #'string=)))
- (null (cdr dirs-of-cands)))
- ;; Only one directory candidate
+ (list-p
+ (concat
+ (mapconcat
+ (lambda (cand)
+ (concat
+ (car cand)
+ " -> "
+ (file-truename (cdr cand))
+ "\n"))
+ marks "")
+ (mapconcat
+ (lambda (cand) (concat (car cand) "\n"))
+ recent "")
+ (mapconcat
+ (lambda (cand) (concat (car cand) "\n"))
+ sessions "")))
+ ;; jump if only one destination is present
+ ((null
+ (cdr
+ (durand-eshell-delete-dups
+ candidates
+ :key directory-key :test #'string=)))
(let ((eshell-last-arguments
(cons (file-truename (cdar candidates)) nil)))
(eshell/cd (car eshell-last-arguments))))
@@ -456,17 +497,8 @@ session directories are in the list of candidates."
(file-truename
(cdr
(assoc
- (let ((candidates
- (delq
- nil
- (mapcar
- (lambda (cand)
- (cond
- ((string-match-p short-cut (car cand))
- cand)))
- candidates))))
- (completing-read
- "Choose a link: " candidates nil t))
+ (completing-read
+ "Choose a link: " candidates nil t)
candidates #'string=)))
nil)))
(eshell/cd (car eshell-last-arguments)))))))