From 4e756aeaa15bde82f7b94afebe3722da80609755 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Fri, 8 May 2026 21:14:32 -0400 Subject: [PATCH] =?UTF-8?q?v0.7.2:=20self-help=20=E2=80=94=20/help=20=20reads=20USER=5FMANUAL.org=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit self-help-lookup parses USER_MANUAL.org org headlines, matches topic substring (case-insensitive) against section titles, and returns content previews. /help now displays the actual manual content instead of just echoing the topic. - channel-tui-main: self-help-lookup fn, updated /help handler + 1 test verifies Configuration section returns .env - TUI Main: 102/102 --- lisp/channel-tui-main.lisp | 63 ++++++++++++++++++++++++++++++++++++-- org/channel-tui-main.org | 63 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/lisp/channel-tui-main.lisp b/lisp/channel-tui-main.lisp index 94fdd6c..192487a 100644 --- a/lisp/channel-tui-main.lisp +++ b/lisp/channel-tui-main.lisp @@ -340,8 +340,17 @@ (add-msg :system "Usage: /resume ")))) ;; /help — search user manual ((and (>= (length text) 6) (string-equal (subseq text 0 6) "/help ")) - (let ((topic (string-trim '(#\Space) (subseq text 6)))) - (add-msg :system (format nil "Topic: ~a — use read-file to query USER_MANUAL.org" topic)))) + (let ((topic (string-trim '(#\Space) (subseq text 6))) + (sections (self-help-lookup (string-trim '(#\Space) (subseq text 6))))) + (if sections + (dolist (entry sections) + (let* ((title (car entry)) + (content (cdr entry)) + (preview (if (> (length content) 300) + (concatenate 'string (subseq content 0 297) "...") + content))) + (add-msg :system (format nil "~a: ~a" title preview)))) + (add-msg :system (format nil "No manual section found for '~a'" topic))))) ((string-equal text "/help") (add-msg :system "/eval Evaluate Lisp") (add-msg :system "/undo Undo last operation") @@ -558,6 +567,47 @@ (setf (st :dirty) (list nil t nil)) (loop-finish))) +;; v0.7.2 — self-help-lookup: read USER_MANUAL.org and find matching sections +(defun self-help-lookup (topic) + "Search USER_MANUAL.org for headlines matching TOPIC, return content previews." + (let* ((manual-path (merge-pathnames "projects/passepartout/docs/USER_MANUAL.org" + (merge-pathnames "memex/" (user-homedir-pathname)))) + (results nil)) + (handler-case + (let* ((text (uiop:read-file-string manual-path)) + (lines (uiop:split-string text :separator '(#\Newline))) + (in-section nil) + (section-content nil)) + (dolist (line lines) + (let ((trimmed (string-trim '(#\Space #\Tab) line))) + (cond + ;; New headline + ((and (>= (length trimmed) 2) (eql (char trimmed 0) #\*)) + ;; Flush previous section if in one + (when (and in-section section-content) + (push (cons in-section (string-trim '(#\Space #\Newline) + (format nil "~{~a~^ ~}" (reverse section-content)))) + results)) + ;; Check if this headline matches topic + (let ((title (string-trim '(#\Space #\*) trimmed))) + (if (search topic title :test #'char-equal) + (setf in-section title + section-content nil) + (setf in-section nil + section-content nil)))) + ;; Content line in matching section + (in-section + (when (and (> (length trimmed) 0) + (not (eql (char trimmed 0) #\#))) + (push trimmed section-content)))))) + ;; Flush last section + (when (and in-section section-content) + (push (cons in-section (string-trim '(#\Space #\Newline) + (format nil "~{~a~^ ~}" (reverse section-content)))) + results)) + (nreverse results)) + (error (c) (list (cons "Error" (format nil "Cannot read manual: ~a" c))))))) + (defun on-daemon-msg (msg) (let* ((payload (getf msg :payload)) (text (getf payload :text)) @@ -1220,3 +1270,12 @@ (fiveam:is (some (lambda (m) (search "IDENTITY" (getf m :content))) msgs)) (fiveam:is (some (lambda (m) (search "LOGS" (getf m :content))) msgs)) (fiveam:is (some (lambda (m) (search "TOOLS" (getf m :content))) msgs)))) + +(fiveam:test test-help-topic-lookup + "Contract v0.7.2: /help reads and searches USER_MANUAL.org." + (init-state) + (dolist (ch (coerce "/help configuration" 'list)) + (on-key (char-code ch))) + (on-key 13) + (let ((msgs (st :messages))) + (fiveam:is (some (lambda (m) (search ".env" (getf m :content))) msgs)))) diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index 6c58e82..590814c 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -374,8 +374,17 @@ Event handlers + daemon I/O + main loop. (add-msg :system "Usage: /resume ")))) ;; /help — search user manual ((and (>= (length text) 6) (string-equal (subseq text 0 6) "/help ")) - (let ((topic (string-trim '(#\Space) (subseq text 6)))) - (add-msg :system (format nil "Topic: ~a — use read-file to query USER_MANUAL.org" topic)))) + (let ((topic (string-trim '(#\Space) (subseq text 6))) + (sections (self-help-lookup (string-trim '(#\Space) (subseq text 6))))) + (if sections + (dolist (entry sections) + (let* ((title (car entry)) + (content (cdr entry)) + (preview (if (> (length content) 300) + (concatenate 'string (subseq content 0 297) "...") + content))) + (add-msg :system (format nil "~a: ~a" title preview)))) + (add-msg :system (format nil "No manual section found for '~a'" topic))))) ((string-equal text "/help") (add-msg :system "/eval Evaluate Lisp") (add-msg :system "/undo Undo last operation") @@ -592,6 +601,47 @@ Event handlers + daemon I/O + main loop. (setf (st :dirty) (list nil t nil)) (loop-finish))) +;; v0.7.2 — self-help-lookup: read USER_MANUAL.org and find matching sections +(defun self-help-lookup (topic) + "Search USER_MANUAL.org for headlines matching TOPIC, return content previews." + (let* ((manual-path (merge-pathnames "projects/passepartout/docs/USER_MANUAL.org" + (merge-pathnames "memex/" (user-homedir-pathname)))) + (results nil)) + (handler-case + (let* ((text (uiop:read-file-string manual-path)) + (lines (uiop:split-string text :separator '(#\Newline))) + (in-section nil) + (section-content nil)) + (dolist (line lines) + (let ((trimmed (string-trim '(#\Space #\Tab) line))) + (cond + ;; New headline + ((and (>= (length trimmed) 2) (eql (char trimmed 0) #\*)) + ;; Flush previous section if in one + (when (and in-section section-content) + (push (cons in-section (string-trim '(#\Space #\Newline) + (format nil "~{~a~^ ~}" (reverse section-content)))) + results)) + ;; Check if this headline matches topic + (let ((title (string-trim '(#\Space #\*) trimmed))) + (if (search topic title :test #'char-equal) + (setf in-section title + section-content nil) + (setf in-section nil + section-content nil)))) + ;; Content line in matching section + (in-section + (when (and (> (length trimmed) 0) + (not (eql (char trimmed 0) #\#))) + (push trimmed section-content)))))) + ;; Flush last section + (when (and in-section section-content) + (push (cons in-section (string-trim '(#\Space #\Newline) + (format nil "~{~a~^ ~}" (reverse section-content)))) + results)) + (nreverse results)) + (error (c) (list (cons "Error" (format nil "Cannot read manual: ~a" c))))))) + (defun on-daemon-msg (msg) (let* ((payload (getf msg :payload)) (text (getf payload :text)) @@ -1267,4 +1317,13 @@ Event handlers + daemon I/O + main loop. (fiveam:is (some (lambda (m) (search "IDENTITY" (getf m :content))) msgs)) (fiveam:is (some (lambda (m) (search "LOGS" (getf m :content))) msgs)) (fiveam:is (some (lambda (m) (search "TOOLS" (getf m :content))) msgs)))) + +(fiveam:test test-help-topic-lookup + "Contract v0.7.2: /help reads and searches USER_MANUAL.org." + (init-state) + (dolist (ch (coerce "/help configuration" 'list)) + (on-key (char-code ch))) + (on-key 13) + (let ((msgs (st :messages))) + (fiveam:is (some (lambda (m) (search ".env" (getf m :content))) msgs)))) #+end_src