Skip to content

Commit 7b191bd

Browse files
committed
feat: add robust GPTel integration and improve sidebar toggle handling
- Add context-navigator--reinit-after-reload to reinstall hooks and resubscribe after reload, ensuring UI shows correct project state immediately. - Improve context-navigator-gptel-bridge: * Soft-require GPTel on demand and prefer authoritative context sources for robust retrieval of GPTel context entries. * Add fallback raw-items construction when conversion yields no items but raw GPTel data exists, supporting better presence detection. * Implement detection and fallback reset in apply function when pull is empty but raw GPTel context is non-empty. * Add idempotent advice and variable watchers to publish :gptel-change events, keeping UI indicators up-to-date. * Harden enabling/disabling items and context synchronization, ignoring disabled items on adds and doing full reset when buffers are involved in diffs. - Enhance sidebar toggle-enabled command: * Rename from toggle-gptel for clarity; toggle model item enabled state and, if auto-push enabled, apply changes immediately to GPTel. * Immediately refresh cached GPTel keys snapshot to update sidebar indicators and display transient messages about new enabled state. - Refine rendering of sidebar items: * Always show green/gray indicator bullets reflecting actual GPTel presence, even when keys snapshot is absent (gray), and apply subdued face for disabled item names. - Add extensive tests covering: * GPTel apply fallback reset when pull is empty but raw data present. * Conversion of basic GPTel entries including files and buffer regions. * Sidebar toggle-enabled command functionality with push on and off scenarios. * Rendering disabled items with appropriate face. * Rendering of indicators when GPTel keys snapshot is missing. - Minor refactors and enhanced logging for debugging and tracing GPTel state and UI sync. This work improves robustness and user experience of the Context Navigator plugin when integrating with GPTel contexts and manages toggle operations consistently across model and GPTel state.
1 parent 40fa1bb commit 7b191bd

10 files changed

Lines changed: 540 additions & 109 deletions

context-navigator-core.el

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,5 +1066,44 @@ Strategy:
10661066
(buffer-list))))
10671067
(and buf-any (context-navigator-project-current-root buf-any))))))
10681068

1069+
(defun context-navigator--reinit-after-reload ()
1070+
"Reinstall hooks/subscriptions when file is reloaded and mode is ON.
1071+
Also triggers an immediate project switch so header shows actual project."
1072+
(when (bound-and-true-p context-navigator-mode)
1073+
;; Reset event bus to avoid duplicated subscribers from older definitions.
1074+
(ignore-errors (context-navigator-events-reset))
1075+
(setq context-navigator--event-tokens nil)
1076+
;; Ensure project hooks installed
1077+
(ignore-errors (context-navigator-project-setup-hooks))
1078+
;; Re-subscribe
1079+
(push (context-navigator-events-subscribe :project-switch #'context-navigator--on-project-switch)
1080+
context-navigator--event-tokens)
1081+
(push (context-navigator-events-subscribe
1082+
:model-refreshed
1083+
(lambda (_state)
1084+
;; Debounced autosave (latest state at execution time)
1085+
(when context-navigator-autosave
1086+
(context-navigator-events-debounce
1087+
:autosave
1088+
(or context-navigator-autosave-debounce 0.5)
1089+
(lambda ()
1090+
(let ((st (context-navigator--state-get)))
1091+
(when (and context-navigator-autosave
1092+
(context-navigator-state-p st)
1093+
(not (context-navigator-state-inhibit-autosave st)))
1094+
(let ((root (context-navigator-state-last-project-root st))
1095+
(slug (context-navigator-state-current-group-slug st))
1096+
(items (context-navigator-state-items st)))
1097+
(when (and (stringp slug) (not (string-empty-p slug)))
1098+
(ignore-errors (context-navigator-persist-save items root slug)))))))))))
1099+
context-navigator--event-tokens)
1100+
;; Trigger initial project switch so UI/header reflect actual project
1101+
(when context-navigator--auto-project-switch
1102+
(let ((root (ignore-errors (context-navigator--pick-root-for-autoproject))))
1103+
(context-navigator-events-publish :project-switch root)))))
1104+
1105+
;; Auto-reinit after reload (eval-buffer/byte-compile)
1106+
(context-navigator--reinit-after-reload)
1107+
10691108
(provide 'context-navigator-core)
10701109
;;; context-navigator-core.el ends here

context-navigator-gptel-bridge.el

Lines changed: 166 additions & 68 deletions
Large diffs are not rendered by default.

context-navigator-render.el

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
(require 'cl-lib)
1616
(require 'subr-x)
1717
(require 'context-navigator-model)
18+
(require 'context-navigator-log)
1819

1920
(defgroup context-navigator-render nil
2021
"Rendering settings for context-navigator."
@@ -37,6 +38,11 @@
3738
:type '(choice (const auto) (const icons) (const text) (const off))
3839
:group 'context-navigator-render)
3940

41+
(defface context-navigator-disabled-face
42+
'((t :foreground "gray55"))
43+
"Face used to render disabled items in the sidebar."
44+
:group 'context-navigator-render)
45+
4046
(defvar-local context-navigator-render--last-hash nil)
4147
(defvar context-navigator-render--gptel-keys nil
4248
"Optional list of stable keys of items currently present in gptel.
@@ -113,25 +119,36 @@ indicator sits centered and appears moderately large relative to item text."
113119
"Format a single ITEM into a propertized line string, using ICON-FN if non-nil.
114120
When LEFT-WIDTH is non-nil, align the left column to that width.
115121
116-
Binary indicator (when gptel keys list is provided):
117-
- green ● : item present in gptel
118-
- gray ○ : item absent in gptel."
122+
Binary indicator:
123+
- green ● : item present in gptel (when keys snapshot says so)
124+
- gray ○ : item absent in gptel or when the keys snapshot is unknown.
125+
Disabled items are rendered with a subdued face (only the name). Indicator reflects actual gptel presence (green when present, gray otherwise)."
119126
(let* ((key (context-navigator-model-item-key item))
120127
(name (context-navigator-render--truncate
121128
(or (context-navigator-item-name item) "")
122129
context-navigator-render-truncate-name))
123130
(path (or (context-navigator-item-path item) ""))
124-
;; Show indicators only when we have a non-empty keys snapshot.
125-
;; When gptel keys are absent (nil), hide indicators.
126-
(have-keys (consp context-navigator-render--gptel-keys))
127-
(present (and have-keys (member key context-navigator-render--gptel-keys)))
128-
(state-icon (and have-keys
129-
(context-navigator-render--indicator present)))
131+
(enabled (not (null (context-navigator-item-enabled item))))
132+
(keys-list context-navigator-render--gptel-keys)
133+
;; Reflect actual presence in gptel regardless of enabled flag.
134+
(present (and keys-list (member key keys-list)))
135+
;; Always render an indicator; when PRESENT is nil it will be gray
136+
(state-icon (context-navigator-render--indicator present))
137+
;; Trace why indicator is green/gray in this render row
138+
(ignore-errors
139+
(context-navigator-debug :trace :render
140+
"indicator key=%s enabled=%s present=%s keys=%d"
141+
key enabled (and present t)
142+
(length (or keys-list '()))))
130143
(icon (and (functionp icon-fn) (or (funcall icon-fn item) "")))
144+
;; Dim only the name for disabled items; do not touch indicator/icon.
145+
(name-prop (if enabled
146+
name
147+
(propertize name 'face 'context-navigator-disabled-face)))
131148
(left (context-navigator-render--left-column
132149
state-icon
133150
(and (stringp icon) icon)
134-
name))
151+
name-prop))
135152
(right (context-navigator-render--right-column path))
136153
;; Add a small left padding to each item line
137154
(line (context-navigator-render--compose-line (concat " " left) right left-width)))

context-navigator-sidebar.el

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
(require 'context-navigator-icons)
2323
(require 'context-navigator-persist)
2424
(require 'context-navigator-i18n)
25+
(require 'context-navigator-log)
2526

2627
(defcustom context-navigator-auto-open-groups-on-error t
2728
"When non-nil, automatically switch the sidebar to the groups list if a group fails to load."
@@ -1102,15 +1103,56 @@ Wraps to the bottom when no previous element is found before point."
11021103
"0.2.1")
11031104

11041105
(defun context-navigator-sidebar-toggle-enabled ()
1105-
"Toggle enabled/disabled for the item at point, apply to gptel.
1106+
"Toggle inclusion of the item at point in gptel sync (model :enabled).
11061107
1107-
Deprecated for 't' binding: use `context-navigator-sidebar-toggle-gptel' to toggle gptel membership."
1108+
Toggles the item's enabled flag and, when push→gptel is ON, applies the
1109+
updated set to gptel immediately. The new state is persisted via the
1110+
existing debounced autosave."
11081111
(interactive)
11091112
(when-let* ((item (context-navigator-sidebar--at-item)))
1110-
(let* ((key (context-navigator-model-item-key item)))
1111-
(message "Deprecated: toggling model only for %s" (or (context-navigator-item-name item) key))
1112-
(let* ((st (ignore-errors (context-navigator-toggle-item key))))
1113-
(ignore-errors st))
1113+
(let* ((key (context-navigator-model-item-key item))
1114+
(name (or (context-navigator-item-name item) key)))
1115+
;; Toggle model
1116+
(ignore-errors (context-navigator-toggle-item key))
1117+
;; Apply to gptel immediately when auto-push is ON and refresh indicators snapshot
1118+
(when (and (boundp 'context-navigator--push-to-gptel)
1119+
context-navigator--push-to-gptel)
1120+
(let* ((st (ignore-errors (context-navigator--state-get)))
1121+
(items (and st (context-navigator-state-items st))))
1122+
(let ((res (ignore-errors (context-navigator-gptel-apply (or items '())))))
1123+
(ignore-errors
1124+
(context-navigator-debug :debug :ui "toggle:t apply -> %S" res)))
1125+
;; Immediately refresh cached keys so lamps reflect actual presence now.
1126+
(let* ((lst (ignore-errors (context-navigator-gptel-pull)))
1127+
(pulled-keys (and (listp lst)
1128+
(mapcar #'context-navigator-model-item-key lst)))
1129+
(raw-keys (and (or (null pulled-keys) (= (length pulled-keys) 0))
1130+
(fboundp 'context-navigator-gptel--raw-keys)
1131+
(ignore-errors (context-navigator-gptel--raw-keys))))
1132+
(fallback-keys
1133+
(when (and (or (null pulled-keys) (= (length pulled-keys) 0))
1134+
(or (null raw-keys) (= (length raw-keys) 0)))
1135+
(and (listp items)
1136+
(mapcar #'context-navigator-model-item-key
1137+
(cl-remove-if-not #'context-navigator-item-enabled items)))))
1138+
(keys (or pulled-keys raw-keys fallback-keys '())))
1139+
(let ((h (sxhash-equal keys)))
1140+
(setq context-navigator-sidebar--gptel-keys keys
1141+
context-navigator-sidebar--gptel-keys-hash h)
1142+
(ignore-errors
1143+
(context-navigator-debug :debug :ui
1144+
"toggle:t immediate pull -> keys=%d hash=%s%s"
1145+
(length keys) h
1146+
(if (and (or (null pulled-keys) (= (length pulled-keys) 0))
1147+
(or (null raw-keys) (= (length raw-keys) 0)))
1148+
" (fallback)" "")))))))
1149+
;; Report new state briefly
1150+
(let* ((st2 (ignore-errors (context-navigator--state-get)))
1151+
(idx (and st2 (context-navigator-state-index st2)))
1152+
(it2 (and idx (gethash key idx)))
1153+
(en (and it2 (context-navigator-item-enabled it2))))
1154+
(message (if en "Enabled: %s" "Disabled: %s") name))
1155+
;; Refresh UI and advance to the next item
11141156
(context-navigator-sidebar--schedule-render)
11151157
(context-navigator-sidebar--render-if-visible)
11161158
(context-navigator-sidebar-next-item))))
@@ -1233,20 +1275,54 @@ Order of operations:
12331275
:gptel-change
12341276
(lambda (&rest _)
12351277
(let* ((lst (ignore-errors (context-navigator-gptel-pull)))
1236-
(keys (and (listp lst)
1237-
(mapcar #'context-navigator-model-item-key lst))))
1278+
(pulled-keys (and (listp lst)
1279+
(mapcar #'context-navigator-model-item-key lst)))
1280+
(raw-keys (and (or (null pulled-keys) (= (length pulled-keys) 0))
1281+
(fboundp 'context-navigator-gptel--raw-keys)
1282+
(ignore-errors (context-navigator-gptel--raw-keys))))
1283+
;; Last-resort fallback: enabled items in model only if both pulled and raw are empty
1284+
(fallback-keys
1285+
(when (and (or (null pulled-keys) (= (length pulled-keys) 0))
1286+
(or (null raw-keys) (= (length raw-keys) 0)))
1287+
(let* ((st (ignore-errors (context-navigator--state-get)))
1288+
(items (and st (context-navigator-state-items st))))
1289+
(and (listp items)
1290+
(mapcar #'context-navigator-model-item-key
1291+
(cl-remove-if-not #'context-navigator-item-enabled items))))))
1292+
(keys (or pulled-keys raw-keys fallback-keys '()))
1293+
(h (sxhash-equal keys))
1294+
(n (length keys))
1295+
(use-fallback (and (or (null pulled-keys) (= (length pulled-keys) 0))
1296+
(or (null raw-keys) (= (length raw-keys) 0))))
1297+
(sample (mapconcat (lambda (s) s)
1298+
(cl-subseq keys 0 (min 5 n))
1299+
", ")))
12381300
(setq context-navigator-sidebar--gptel-keys keys
1239-
context-navigator-sidebar--gptel-keys-hash (sxhash-equal keys)))
1301+
context-navigator-sidebar--gptel-keys-hash h)
1302+
(ignore-errors
1303+
(context-navigator-debug :debug :ui
1304+
"gptel-change: pulled %d keys, hash=%s, sample=[%s]%s"
1305+
n h sample
1306+
(if use-fallback " (fallback)" ""))))
12401307
(context-navigator-sidebar--schedule-render)))
12411308
context-navigator-sidebar--subs))
12421309

12431310
(defun context-navigator-sidebar--init-gptel-cache ()
12441311
"Initialize cached gptel keys once."
12451312
(let* ((lst (ignore-errors (context-navigator-gptel-pull)))
1246-
(keys (and (listp lst)
1247-
(mapcar #'context-navigator-model-item-key lst))))
1313+
(pulled (and (listp lst)
1314+
(mapcar #'context-navigator-model-item-key lst)))
1315+
(raw (and (or (null pulled) (= (length pulled) 0))
1316+
(fboundp 'context-navigator-gptel--raw-keys)
1317+
(ignore-errors (context-navigator-gptel--raw-keys))))
1318+
(keys (or pulled raw '()))
1319+
(h (sxhash-equal keys)))
12481320
(setq context-navigator-sidebar--gptel-keys keys
1249-
context-navigator-sidebar--gptel-keys-hash (sxhash-equal keys))))
1321+
context-navigator-sidebar--gptel-keys-hash h)
1322+
(ignore-errors
1323+
(context-navigator-debug :debug :ui
1324+
"gptel-init: pulled %d keys, hash=%s"
1325+
(length (or keys '())) h))))
12501326

12511327
(defun context-navigator-sidebar--close-hijacked-windows ()
12521328
"Close any windows previously marked as sidebar that now show a foreign buffer.
@@ -1324,12 +1400,31 @@ render."
13241400
(with-current-buffer buf
13251401
(when (get-buffer-window buf t)
13261402
(let* ((lst (ignore-errors (context-navigator-gptel-pull)))
1327-
(keys (and (listp lst)
1328-
(mapcar #'context-navigator-model-item-key lst)))
1329-
(h (sxhash-equal keys)))
1403+
(pulled-keys (and (listp lst)
1404+
(mapcar #'context-navigator-model-item-key lst)))
1405+
(raw-keys (and (or (null pulled-keys) (= (length pulled-keys) 0))
1406+
(fboundp 'context-navigator-gptel--raw-keys)
1407+
(ignore-errors (context-navigator-gptel--raw-keys))))
1408+
(fallback-keys
1409+
(when (and (or (null pulled-keys) (= (length pulled-keys) 0))
1410+
(or (null raw-keys) (= (length raw-keys) 0)))
1411+
(let* ((st (ignore-errors (context-navigator--state-get)))
1412+
(items (and st (context-navigator-state-items st))))
1413+
(and (listp items)
1414+
(mapcar #'context-navigator-model-item-key
1415+
(cl-remove-if-not #'context-navigator-item-enabled items))))))
1416+
(keys (or pulled-keys raw-keys fallback-keys '()))
1417+
(h (sxhash-equal keys))
1418+
(use-fallback (and (or (null pulled-keys) (= (length pulled-keys) 0))
1419+
(or (null raw-keys) (= (length raw-keys) 0)))))
13301420
(unless (equal h context-navigator-sidebar--gptel-keys-hash)
13311421
(setq context-navigator-sidebar--gptel-keys keys
13321422
context-navigator-sidebar--gptel-keys-hash h)
1423+
(ignore-errors
1424+
(context-navigator-debug :debug :ui
1425+
"gptel-poll: updated keys=%d hash=%s%s"
1426+
(length keys) h
1427+
(if use-fallback " (fallback)" "")))
13331428
(context-navigator-sidebar--schedule-render)))))))))))))
13341429

13351430
(defun context-navigator-sidebar--install-subs ()
@@ -1401,7 +1496,7 @@ MAP is a keymap to search for COMMAND bindings."
14011496
(context-navigator-sidebar-previous-item . :help-previous-item)
14021497
(context-navigator-sidebar-activate . :help-activate)
14031498
(context-navigator-sidebar-preview . :help-preview)
1404-
(context-navigator-sidebar-toggle-gptel . :help-toggle-gptel)
1499+
(context-navigator-sidebar-toggle-enabled . :help-toggle-gptel)
14051500
(context-navigator-sidebar-delete-dispatch . :help-delete)
14061501
(context-navigator-sidebar-refresh-dispatch . :help-refresh)
14071502
(context-navigator-sidebar-go-up . :help-go-up)
@@ -1501,7 +1596,7 @@ MAP is a keymap to search for COMMAND bindings."
15011596
(define-key m (kbd "j") #'context-navigator-sidebar-next-item)
15021597
(define-key m (kbd "k") #'context-navigator-sidebar-previous-item)
15031598
(define-key m (kbd "l") #'context-navigator-sidebar-activate)
1504-
(define-key m (kbd "t") #'context-navigator-sidebar-toggle-gptel)
1599+
(define-key m (kbd "t") #'context-navigator-sidebar-toggle-enabled)
15051600

15061601
;; TAB navigation between interactive elements
15071602
;; Bind several TAB event representations to be robust across terminals/minor-modes.
@@ -1548,7 +1643,7 @@ MAP is a keymap to search for COMMAND bindings."
15481643
;; Ensure bindings are updated after reloads (defvar won't reinitialize an existing keymap).
15491644
(when (keymapp context-navigator-sidebar-mode-map)
15501645
;; Keep legacy binding in sync
1551-
(define-key context-navigator-sidebar-mode-map (kbd "t") #'context-navigator-sidebar-toggle-gptel)
1646+
(define-key context-navigator-sidebar-mode-map (kbd "t") #'context-navigator-sidebar-toggle-enabled)
15521647
;; Make TAB robust across reloads/terminals/minor-modes
15531648
(define-key context-navigator-sidebar-mode-map (kbd "TAB") #'context-navigator-sidebar-tab-next)
15541649
(define-key context-navigator-sidebar-mode-map (kbd "<tab>") #'context-navigator-sidebar-tab-next)
@@ -1660,6 +1755,18 @@ Do not highlight header/separator lines."
16601755
"Toggle push-to-gptel session flag and refresh header immediately."
16611756
(interactive)
16621757
(ignore-errors (context-navigator-toggle-push-to-gptel))
1758+
;; Immediately refresh cached keys from gptel so indicators reflect actual presence,
1759+
;; regardless of push flag.
1760+
(let* ((lst (ignore-errors (context-navigator-gptel-pull)))
1761+
(keys (and (listp lst)
1762+
(mapcar #'context-navigator-model-item-key lst)))
1763+
(h (sxhash-equal keys)))
1764+
(setq context-navigator-sidebar--gptel-keys keys
1765+
context-navigator-sidebar--gptel-keys-hash h)
1766+
(ignore-errors
1767+
(context-navigator-debug :debug :ui
1768+
"toggle:push -> keys=%d hash=%s"
1769+
(length (or keys '())) h)))
16631770
;; Force immediate redraw for visible sidebar
16641771
(let ((buf (get-buffer context-navigator-sidebar--buffer-name)))
16651772
(when (buffer-live-p buf)

0 commit comments

Comments
 (0)