diff --git a/.gitignore b/.gitignore index 2775076..470ef45 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ projects eshell parinfer-rust .mc-lists.el -codex-sessions \ No newline at end of file +codex-sessions +codex-sessions-history \ No newline at end of file diff --git a/codex.el b/codex.el index 09aa264..0c07c9e 100644 --- a/codex.el +++ b/codex.el @@ -63,6 +63,12 @@ :type 'string :group 'codex) +(defcustom codex-sessions-history-dir + (concat (file-name-parent-directory user-init-file) "codex-sessions-history/") + "Directory where Codex session histories are stored." + :type 'directory + :group 'codex) + (defvar codex--session-id nil) (defvar codex-mode-map @@ -75,16 +81,54 @@ map) "Keymap for `codex-mode'.") +(defconst codex--font-lock-keywords + '(("^---$" . 'codex-prompt-delimiter-face) + ("^User:$" . 'codex-user-prompt-face) + ("^Codex:$" . 'codex-assistant-prompt-face))) + (define-derived-mode codex-mode fundamental-mode "Codex" "Major mode for chatting with Codex." (setq-local buffer-read-only nil) (setq-local truncate-lines (not codex-wrap-lines)) + (setq-local font-lock-defaults '(codex--font-lock-keywords)) (setq-local codex--busy nil)) (defvar-local codex--prompt-start nil) -(defun codex--propertize-label (text face) - (propertize text 'face face 'rear-nonsticky '(face))) +(defun codex--ensure-history-dir () + (make-directory codex-sessions-history-dir t)) + +(defun codex--history-file () + (when (and codex--session-id (not (string-empty-p codex--session-id))) + (expand-file-name codex--session-id codex-sessions-history-dir))) + +(defun codex--set-prompt-start-from-buffer () + (let* ((haystack (buffer-string)) + (needle "---\nUser:\n") + (index (cl-search needle haystack :from-end t))) + (when index + (setq codex--prompt-start (copy-marker (+ (length needle) index))) + t))) + +(defun codex--save-session-history () + (with-codex-buffer + (let ((history-file (codex--history-file))) + (when history-file + (codex--ensure-history-dir) + (write-region (point-min) (point-max) history-file nil 'silent))))) + +(defun codex--load-session-history () + (with-codex-buffer + (let ((history-file (codex--history-file))) + (setq codex--prompt-start nil) + (erase-buffer) + (if (and history-file (file-exists-p history-file)) + (progn + (insert-file-contents history-file) + (goto-char (point-max)) + (unless (codex--set-prompt-start-from-buffer) + (user-prompt))) + (user-prompt))))) (defun list->hash-set (list &optional test) "Return a hash table whose keys are the elements of LIST." @@ -98,22 +142,23 @@ (defun prompt-delimiter () (newline) - (insert (codex--propertize-label "---" 'codex-prompt-delimiter-face)) + (insert "---") (newline)) (defun user-prompt () (prompt-delimiter) - (insert (codex--propertize-label "User:" 'codex-user-prompt-face)) + (insert "User:") (newline) (setq codex--prompt-start (point-marker))) (defun codex--write-to-chat (msg) (with-codex-buffer (newline) - (insert (codex--propertize-label "Codex:" 'codex-assistant-prompt-face)) + (insert "Codex:") (newline) (insert msg) - (user-prompt))) + (user-prompt) + (codex--save-session-history))) (defun codex--parse-session-id (jsons) (let ((thread-started-message @@ -185,12 +230,8 @@ (marker-position codex--prompt-start)))) ;; fallback in case we somehow lost where our user prompt starts (unless prompt-start - (let* ((haystack (buffer-string)) - (needle "---\nUser:\n") - (index (cl-search needle haystack :from-end t))) - (when index - (setq prompt-start (+ (length needle) index)) - (setq codex--prompt-start (copy-marker prompt-start))))) + (when (codex--set-prompt-start-from-buffer) + (setq prompt-start (marker-position codex--prompt-start)))) (unless prompt-start (error "No current prompt start")) (buffer-substring-no-properties prompt-start (point-max))))) @@ -203,6 +244,7 @@ (error "Prompt is empty!") (newline) (insert "---") + (codex--save-session-history) (codex--send prompt))))) ;;;###autoload @@ -216,7 +258,7 @@ (pop-to-buffer buf) (unless (derived-mode-p 'codex-mode) (codex-mode)) - (user-prompt) + (codex--load-session-history) (goto-char (point-max))))) (defun codex--read-sessions-file () @@ -240,13 +282,9 @@ (interactive (list (completing-read "Session: " (cons '("New") (codex--read-sessions-file))))) (let ((session-id (cdr (assoc new-session (codex--read-sessions-file))))) + (codex--save-session-history) (setf codex--session-id session-id) - (with-codex-buffer - (prompt-delimiter) - (if session-id - (insert (concat "Switching to session " session-id)) - (insert "Switching to new session")) - (user-prompt)))) + (codex--load-session-history))) (defun codex-rename-current-session (name) (interactive "sNew name: ")