Skip to content

Commit 656676c

Browse files
committed
Persist and Use MRU Backend History
1 parent 3f3c3f2 commit 656676c

2 files changed

Lines changed: 85 additions & 12 deletions

File tree

ai-code-backends.el

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,32 @@ configuration paths, upgrade commands, and skill-install commands."
423423
(const :tag "Not supported" nil))))
424424
:group 'ai-code)
425425

426+
(defvar ai-code-backends-history-file
427+
(expand-file-name "ai-code-backends-history.el" user-emacs-directory)
428+
"File path to store the MRU history of selected backends.")
429+
430+
(defun ai-code--load-backends-history ()
431+
"Load the MRU backend history from `ai-code-backends-history-file'."
432+
(if (file-exists-p ai-code-backends-history-file)
433+
(condition-case nil
434+
(with-temp-buffer
435+
(insert-file-contents ai-code-backends-history-file)
436+
(let ((content (buffer-string)))
437+
(unless (string-empty-p content)
438+
(read content))))
439+
(error nil))
440+
nil))
441+
442+
(defun ai-code--save-backend-history (backend)
443+
"Save BACKEND to the MRU history list file."
444+
(let* ((history (ai-code--load-backends-history))
445+
(updated-history (cons backend (seq-remove (lambda (x) (eq x backend)) history))))
446+
(condition-case nil
447+
(with-temp-file ai-code-backends-history-file
448+
(insert (let ((print-circle nil))
449+
(prin1-to-string updated-history))))
450+
(error nil))))
451+
426452
(defvar ai-code-selected-backend 'claude-code
427453
"Currently selected backend key from `ai-code-backends'.")
428454

@@ -432,7 +458,8 @@ configuration paths, upgrade commands, and skill-install commands."
432458
(user-error "Unknown backend: %s" new-backend))
433459
(setq ai-code-selected-backend new-backend)
434460
(ai-code--apply-backend new-backend)
435-
(ai-code--remember-current-backend-for-repo))
461+
(ai-code--remember-current-backend-for-repo)
462+
(ai-code--save-backend-history new-backend))
436463

437464
(defun ai-code--backend-spec (key)
438465
"Return backend plist for KEY from `ai-code-backends'."
@@ -519,19 +546,22 @@ invoke `ai-code-cli-resume'; otherwise call `ai-code-cli-start'."
519546
(cons (format "%s" label) key)))
520547
ai-code-backends))
521548
(effective-backend (ai-code--effective-backend))
522-
(current-label (car (seq-find (lambda (it)
523-
(eq (cdr it) effective-backend))
524-
choices)))
525-
(ordered-choices (if current-label
526-
(let ((current (assoc current-label choices)))
527-
(cons current
528-
(seq-remove (lambda (it)
529-
(equal (car it) current-label))
530-
choices)))
531-
choices))
549+
(history (ai-code--load-backends-history))
550+
(history-choices (seq-filter #'identity
551+
(mapcar (lambda (key)
552+
(seq-find (lambda (c) (eq (cdr c) key)) choices))
553+
history)))
554+
(other-choices (seq-remove (lambda (c) (member c history-choices)) choices))
555+
(sorted-choices (append history-choices other-choices))
556+
(current-choice (seq-find (lambda (it) (eq (cdr it) effective-backend)) sorted-choices))
557+
(ordered-choices (if current-choice
558+
(cons current-choice
559+
(seq-remove (lambda (it) (eq (cdr it) effective-backend))
560+
sorted-choices))
561+
sorted-choices))
532562
(choice (completing-read "Select backend: "
533563
(mapcar #'car ordered-choices)
534-
nil t nil nil current-label))
564+
nil t nil nil (car current-choice)))
535565
(key (cdr (assoc choice ordered-choices))))
536566
(ai-code-set-backend key)
537567
(when (fboundp 'ai-code-onboarding-show-backend-switch-hint)

test/test_ai-code-backends.el

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,49 @@
405405
(should spec)
406406
(should-not (plist-get (cdr spec) :install-skills))))
407407

408+
(ert-deftest ai-code-test-select-backend-mru-ordering ()
409+
"Test that backend selection ordering follows the MRU list loaded from the history file, and updates it upon selection."
410+
(let* ((temp-file (make-temp-file "ai-code-backends-history-"))
411+
(ai-code-backends-history-file temp-file)
412+
(ai-code-backends '((backend-a :label "Backend A" :start ignore :switch ignore :send ignore)
413+
(backend-b :label "Backend B" :start ignore :switch ignore :send ignore)
414+
(backend-c :label "Backend C" :start ignore :switch ignore :send ignore)))
415+
(ai-code-selected-backend 'backend-a)
416+
(captured-candidates nil)
417+
(selected-choice "Backend B"))
418+
(unwind-protect
419+
(progn
420+
;; Write initial history: backend-c was used recently, backend-b before that
421+
(with-temp-file temp-file
422+
(insert (prin1-to-string '(backend-c backend-b))))
423+
424+
;; Spy completing-read to capture the candidates offered to the user
425+
(cl-letf (((symbol-function 'completing-read)
426+
(lambda (_prompt candidates &rest _args)
427+
(setq captured-candidates candidates)
428+
selected-choice))
429+
((symbol-function 'ai-code-onboarding-show-backend-switch-hint) #'ignore)
430+
((symbol-function 'message) #'ignore))
431+
(ai-code-select-backend)
432+
433+
;; 1. The selected backend should be switched to backend-b
434+
(should (eq ai-code-selected-backend 'backend-b))
435+
436+
;; 2. The candidates list passed to completing-read should have:
437+
;; - First: Current effective backend (backend-a)
438+
;; - Then: history items in order (backend-c, backend-b) excluding current
439+
;; So candidates should be '("Backend A" "Backend C" "Backend B")
440+
(should (equal captured-candidates '("Backend A" "Backend C" "Backend B")))
441+
442+
;; 3. The history file should have been updated after selection:
443+
;; New selection B is now at the top, C is next
444+
(let ((new-history (with-temp-buffer
445+
(insert-file-contents temp-file)
446+
(read (buffer-string)))))
447+
(should (equal new-history '(backend-b backend-c))))))
448+
(when (file-exists-p temp-file)
449+
(delete-file temp-file)))))
450+
408451
(provide 'test_ai-code-backends)
409452

410453
;;; test_ai-code-backends.el ends here

0 commit comments

Comments
 (0)