Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/application/runtime.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,31 @@ command replaced the active conversation."
:rollback-cause (nreverse rollback-failures)))
directory)))

(-> application--conversation-input-history
(conversation &key (:limit (integer 0)))
list)
(defun application--conversation-input-history
(conversation &key (limit *terminal-history-limit*))
"Return bounded editable input history recovered from durable CONVERSATION."
(let ((entries (make-deque :maximum-count limit)))
(conversation-map-records
conversation
(lambda (record)
(let ((properties (rest record)))
(case (first record)
(:message
(let ((content (getf properties :content)))
(when (and (eq (getf properties :role) ':user)
(non-empty-string-p content))
(deque-push-back entries (copy-seq content)))))
(:user-operation
(let ((source (getf properties :source)))
(when (and (member (getf properties :kind) '(:command :lisp))
(non-empty-string-p source))
(deque-push-back entries (copy-seq source)))))))))
(deque->list entries)))


(-> application--install-owned-conversation
(application conversation &key (:conversation-lease conversation-lease))
application)
Expand All @@ -1508,6 +1533,14 @@ command replaced the active conversation."
(application--configuration-for-conversation
(application-configuration application)
conversation))
(ui (application-ui application))
(history-limit
(if ui
(line-editor-history-limit (terminal-ui-editor ui))
*terminal-history-limit*))
(input-history
(application--conversation-input-history
conversation :limit history-limit))
(previous-configuration (application-configuration application))
(previous-conversation (application-conversation application))
(previous-conversation-lease
Expand Down Expand Up @@ -1572,6 +1605,8 @@ command replaced the active conversation."
(application--load-goal application)
(context-runtime-reset)
(application-publish-recovery-session application)
(when ui
(terminal-ui-load-history ui input-history))
(setf committed-p t))
(serious-condition (condition)
(setf failure condition)))
Expand Down
32 changes: 22 additions & 10 deletions src/configuration/settings.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -777,13 +777,25 @@ reasoning effort only when that effort is supported by the selected model."
(-> make-identifier () string)
(defun make-identifier ()
"Return a process-independent identifier suitable for conversations and requests."
(handler-case
(with-open-file (stream #P"/proc/sys/kernel/random/uuid"
:direction ':input
:external-format ':utf-8)
(string-trim '(#\Space #\Tab #\Newline #\Return)
(read-line stream)))
(error ()
(format nil "~36R-~16,'0X"
(get-universal-time)
(random (ash 1 64))))))
(flet ((fallback ()
(format nil "~36R-~16,'0X"
(get-universal-time)
(random (ash 1 64)))))
(handler-case
(cond
#+linux
(t
(with-open-file (stream #P"/proc/sys/kernel/random/uuid"
:direction ':input
:external-format ':utf-8)
(string-trim '(#\Space #\Tab #\Newline #\Return)
(read-line stream))))
#+(and (not linux) (or darwin macos macosx bsd))
(t
(string-trim
'(#\Space #\Tab #\Newline #\Return)
(uiop:run-program '("/usr/bin/uuidgen") :output :string)))
(t
(fallback)))
(error ()
(fallback)))))
1 change: 1 addition & 0 deletions src/core/package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
#:line-editor-text
#:line-editor-cursor
#:line-editor-history
#:line-editor-history-limit
#:line-editor-set-text
#:line-editor-clear
#:line-editor-handle-event
Expand Down
11 changes: 11 additions & 0 deletions src/terminal/protocol.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,17 @@
:accessor terminal-ui-completion-prefix
:type (option string)
:documentation "Input restored when active completion selection is cancelled.")
(completion-history-state
:initform nil
:accessor terminal-ui-completion-history-state
:type (option list)
:documentation
"Clinedi history traversal state restored when completion is cancelled.")
(completion-dismissed-p
:initform nil
:accessor terminal-ui-completion-dismissed-p
:type boolean
:documentation "Whether Escape has hidden passive completion suggestions.")
(selector
:initform nil
:accessor terminal-ui-selector
Expand Down
16 changes: 16 additions & 0 deletions src/terminal/responsive-input.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,17 @@ new input resumes them."
(application-input-controller-forced-exit-message controller)
message)))))
(when accepted-p
(let ((application
(application-input-controller-application controller)))
(when (slot-boundp application 'ui)
(handler-case
(terminal-ui-set-notice
(application-ui application)
"Interrupting request."
:duration-seconds
*application-interrupted-queue-notice-seconds*)
(error ()
nil))))
(handler-case
(application-input-controller--interrupt-main-for-turn-cancellation
controller)
Expand Down Expand Up @@ -2346,6 +2357,11 @@ may execute immediately; other Lisp waits for the idle boundary."
:force-exit-window-p t
:pause-queued-work-p t))
nil)
((and (eq event ':escape)
(terminal-ui-completion-menu-present-p ui))
(terminal-ui-process-event
ui event :queue-editing-p follow-up-editing-p)
nil)
((and (eq event ':escape)
(or
(application-input-controller--turn-cancellation-active-p
Expand Down
136 changes: 116 additions & 20 deletions src/terminal/ui.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,41 @@
:visible-count *terminal-ui-visible-completions*
:arrangement ':vertical))))


(defmacro with-terminal-ui-locked ((ui) &body body)
"Run BODY while holding UI's recursive presentation lock."
(let ((locked-ui (gensym "UI")))
`(let ((,locked-ui ,ui))
(with-recursive-lock-held ((terminal-ui-lock ,locked-ui))
,@body))))


(-> terminal-ui-load-history (terminal-ui list) terminal-ui)
(defun terminal-ui-load-history (ui entries)
"Replace UI's editable history while preserving its current draft and cursor."
(with-terminal-ui-locked (ui)
(let* ((editor (terminal-ui-editor ui))
(navigating-p
(not
(null (slot-value editor 'clinedi::history-index))))
(text
(copy-seq
(if navigating-p
(slot-value editor 'clinedi::history-stash)
(line-editor-text editor))))
(cursor
(if navigating-p
(slot-value editor 'clinedi::history-stash-cursor)
(line-editor-cursor editor)))
(history-editor
(line-editor-create
:history (mapcar #'sanitize-text entries)
:history-limit (line-editor-history-limit editor))))
(line-editor-set-text editor text :cursor cursor)
(setf (slot-value editor 'clinedi::history)
(slot-value history-editor 'clinedi::history))))
ui)

(-> terminal-ui--call-with-lock-if-available
(terminal-ui function)
(values t boolean))
Expand Down Expand Up @@ -673,11 +701,62 @@ columns: name, tally, and description."
row-width)))
(setf previous-group group)))))))

(-> terminal-ui--editor-history-navigating-p (line-editor) boolean)
(defun terminal-ui--editor-history-navigating-p (editor)
"Return true when EDITOR is currently traversing history."
(and (slot-boundp editor 'clinedi::history-index)
(not (null (slot-value editor 'clinedi::history-index)))
t))


(-> terminal-ui--snapshot-history-state (line-editor) (option list))
(defun terminal-ui--snapshot-history-state (editor)
"Return EDITOR's history traversal state, or NIL outside history recall."
(when (terminal-ui--editor-history-navigating-p editor)
(list (copy-seq (line-editor-text editor))
(line-editor-cursor editor)
(slot-value editor 'clinedi::history-index)
(copy-seq (slot-value editor 'clinedi::history-stash))
(slot-value editor 'clinedi::history-stash-cursor))))


(-> terminal-ui--restore-history-state (line-editor list) null)
(defun terminal-ui--restore-history-state (editor state)
"Restore EDITOR's history traversal STATE after completion is cancelled."
(destructuring-bind (text cursor index stash stash-cursor) state
(line-editor-set-text editor text :cursor cursor)
(setf (slot-value editor 'clinedi::history-index) index
(slot-value editor 'clinedi::history-stash) stash
(slot-value editor 'clinedi::history-stash-cursor) stash-cursor))
nil)

(-> terminal-ui--completion-offered-p (terminal-ui) boolean)
(defun terminal-ui--completion-offered-p (ui)
"Return true when UI may paint or begin command completion."
(or (terminal-ui-completion-active-p ui)
(and (not (terminal-ui-completion-dismissed-p ui))
(not (terminal-ui--editor-history-navigating-p
(terminal-ui-editor ui))))))


(-> terminal-ui-completion-menu-present-p (terminal-ui) boolean)
(defun terminal-ui-completion-menu-present-p (ui)
"Return true when UI currently renders command completion candidates."
(and (terminal-ui--completion-offered-p ui)
(not
(null
(selector-items (terminal-ui-completion-selector ui))))))


(-> terminal-ui--completion-rows (terminal-ui integer) list)
(defun terminal-ui--completion-rows (ui row-width)
"Return styled rows for UI's matching command completions."
(terminal-ui--reconcile-completions ui)
(terminal-ui--choice-rows (terminal-ui-completion-selector ui) row-width))
(block nil
(unless (terminal-ui--completion-offered-p ui)
(selector-set-items (terminal-ui-completion-selector ui) nil)
(return nil))
(terminal-ui--reconcile-completions ui)
(terminal-ui--choice-rows (terminal-ui-completion-selector ui) row-width)))

(-> terminal-ui--accept-completion (terminal-ui list) null)
(defun terminal-ui--accept-completion (ui entry)
Expand All @@ -696,45 +775,58 @@ columns: name, tally, and description."
(defun terminal-ui--begin-completion (ui)
"Begin choosing among UI's current command completion candidates."
(unless (terminal-ui-completion-active-p ui)
(setf (terminal-ui-completion-prefix ui)
(line-editor-text (terminal-ui-editor ui))
(terminal-ui-completion-active-p ui) t))
(let ((editor (terminal-ui-editor ui)))
(setf (terminal-ui-completion-prefix ui)
(line-editor-text editor)
(terminal-ui-completion-history-state ui)
(terminal-ui--snapshot-history-state editor)
(terminal-ui-completion-dismissed-p ui) nil
(terminal-ui-completion-active-p ui) t)))
nil)

(-> terminal-ui--end-completion (terminal-ui) null)
(defun terminal-ui--end-completion (ui)
"Leave UI's active command completion selection without changing input."
(setf (terminal-ui-completion-active-p ui) nil
(terminal-ui-completion-prefix ui) nil)
(terminal-ui-completion-prefix ui) nil
(terminal-ui-completion-history-state ui) nil)
nil)

(-> terminal-ui--cancel-completion (terminal-ui) null)
(defun terminal-ui--cancel-completion (ui)
"Cancel UI's completion selection and restore its original command prefix."
(let ((prefix (terminal-ui-completion-prefix ui)))
(when prefix
(line-editor-set-text (terminal-ui-editor ui) prefix)))
"Cancel completion and restore its original input or history traversal state."
(let ((prefix (terminal-ui-completion-prefix ui))
(history-state (terminal-ui-completion-history-state ui))
(editor (terminal-ui-editor ui)))
(cond
(history-state
(terminal-ui--restore-history-state editor history-state))
(prefix
(line-editor-set-text editor prefix))))
(selector-set-items (terminal-ui-completion-selector ui) nil)
(terminal-ui--end-completion ui)
(setf (terminal-ui-completion-dismissed-p ui) t)
nil)

(-> terminal-ui--handle-completion-event
(terminal-ui t)
(values (option keyword) (option string)))
(defun terminal-ui--handle-completion-event (ui event)
"Apply EVENT to UI's completion suggestions and return its action when consumed."
(terminal-ui--reconcile-completions ui)
(let ((selector (terminal-ui-completion-selector ui)))
(block nil
(block nil
(unless (or (terminal-ui--completion-offered-p ui)
(member event '(:complete :complete-previous)))
(return (values nil nil)))
(terminal-ui--reconcile-completions ui)
(let ((selector (terminal-ui-completion-selector ui)))
(unless (selector-items selector)
(return (values nil nil)))
(unless (or (terminal-ui-completion-active-p ui)
(member event
'(:up :down :history-previous :history-next
:complete :complete-previous :submit)))
(return (values nil nil)))
(when (member event '(:up :down :history-previous :history-next
:complete :complete-previous))
(unless (or (terminal-ui-completion-active-p ui)
(member event
'(:up :down :complete :complete-previous
:submit :escape)))
(return (values nil nil)))
(when (member event '(:up :down :complete :complete-previous))
(terminal-ui--begin-completion ui))
(multiple-value-bind (selector-action entry)
(selector-handle-event selector event)
Expand Down Expand Up @@ -2310,6 +2402,7 @@ frames, so this function never paints directly from a child thread."
"Replace UI's editable input with INPUT and repaint it."
(with-terminal-ui-locked (ui)
(terminal-ui--set-draft-input ui input)
(setf (terminal-ui-completion-dismissed-p ui) nil)
(terminal-ui--paint-live ui))
ui)

Expand Down Expand Up @@ -2462,6 +2555,9 @@ frames, so this function never paints directly from a child thread."
(terminal-ui--apply-editor-event ui effective-event)
(when (eq action :changed)
(terminal-ui--restore-history-images ui))
(when (and (eq action :changed)
(not (string= text-before (line-editor-text editor))))
(setf (terminal-ui-completion-dismissed-p ui) nil))
(when (and (member action '(:submit :queue))
(stringp payload))
(terminal-ui--remember-image-submission
Expand Down
Loading
Loading