<commit_before>;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause

(in-package :nyxt/prompt-buffer-mode)
(eval-when (:compile-toplevel :load-toplevel :execute)
  (trivial-package-local-nicknames:add-package-local-nickname :alex :alexandria)
  (trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum))

(define-mode prompt-buffer-mode ()
  "Mode for the prompt buffer."
  ((keymap-scheme
    (define-scheme "prompt-buffer"
      scheme:cua
      (list
       "C-n" 'select-next
       "C-p" 'select-previous
       "C-v" 'select-next-page
       "M-v" 'select-previous-page
       "M->" 'select-last
       "M-<" 'select-first
       "M-]" 'select-next-source
       "M-[" 'select-previous-source
       "return" 'return-selection
       "C-return" 'return-input         ; TODO: Bind to shift-return instead?
       "M-return" 'return-selection-over-action       ; TODO: Also bind to C-return?
       "C-j" 'run-persistent-action
       "C-space" 'prompt-buffer-toggle-mark
       "shift-space" 'prompt-buffer-toggle-mark-backwards
       "M-space" 'prompt-buffer-toggle-mark
       "M-a" 'prompt-buffer-mark-all
       "M-u" 'prompt-buffer-unmark-all
       "M-m" 'prompt-buffer-toggle-mark-all
       "C-w" 'copy-selection
       "C-v" 'prompt-buffer-paste))
    ;; TODO: We could have VI bindings for the minibuffer too.
    ;; But we need to make sure it's optional + to have an indicator
    ;; for the mode.
    )))

(define-command select-next (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next entry in prompt buffer."
  (prompter:select-next (prompter prompt-buffer))
  ;; TODO: Update display?  The library should probably decide when to update
  ;; it.  Drawback is that it maybe result in too many draws.  If the caller
  ;; decides when redraw, it has more control.
  (update-suggestion-html prompt-buffer))

(define-command select-previous (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next entry in prompt buffer."
  (prompter:select-previous (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-first (&optional (prompt-buffer (current-prompt-buffer)))
  "Select first entry in prompt buffer."
  (prompter:select-first (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-last (&optional (prompt-buffer (current-prompt-buffer)))
  "Select first entry in prompt buffer."
  (prompter:select-last (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-next-source (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next soruce in prompt buffer."
  (prompter:select-next-source (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-previous-source (&optional (prompt-buffer (current-prompt-buffer)))
  "Select previous source in prompt buffer."
  (prompter:select-previous-source (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-next-page (&key (prompt-buffer (current-prompt-buffer))
                                  (steps 1))
  "Select entry by STEPS next page in prompt buffer.
If STEPS is negative, go to previous pages instead."
  (unless (= 0 steps)
    (let ((step-page-index              ; TODO: Add multi-source support.
            (ffi-minibuffer-evaluate-javascript
             (current-window)
             (ps:ps
               (defun element-in-view-port-p (element)
                 (ps:let* ((rect (ps:chain element (get-bounding-client-rect))))
                   (if (and (>= (ps:chain rect top) 0)
                            (>= (ps:chain rect left) 0)
                            (<= (ps:chain rect right) (ps:chain window inner-width))
                            (<= (ps:chain rect bottom) (ps:chain window inner-height)))
                       t nil)))
               (defun step-row (row)
                 (ps:chain
                  (aref (ps:chain row parent-node rows)
                        (max 0
                             (min (- (ps:chain row parent-node rows length) 1)
                                  (+ (if (< 0 (ps:lisp steps)) 1 -1)
                                     (ps:chain row row-index)))))))
               (defun find-first-element-out-of-view (row)
                 (if (element-in-view-port-p row)
                     (let ((new-row (step-row row)))
                       (if (eq new-row row)
                           row
                           (find-first-element-out-of-view new-row)))
                     row))
               (-
                (ps:chain
                 (find-first-element-out-of-view (ps:chain document (get-element-by-id "cursor")))
                 row-index)
                (ps:chain (ps:chain document (get-element-by-id "cursor")) row-index))))))
      (sera:and-let* ((index-diff (parse-integer step-page-index :junk-allowed t)))
        (prompter:select-next (prompter prompt-buffer)
                              index-diff)))
    ;; TODO: Update display?  The library should probably decide when to update
    ;; it.  Drawback is that it maybe result in too many draws.  If the caller
    ;; decides when redraw, it has more control.
    (update-suggestion-html prompt-buffer)))

(define-command select-previous-page (&key (prompt-buffer (current-prompt-buffer))
                                      (steps 1))
  "Select entry by STEPS previous page in prompt buffer.
If STEPS is negative, go to next pages instead."
  (select-next-page :prompt-buffer prompt-buffer :steps (- steps)))

(define-command return-selection (&optional (prompt-buffer (current-prompt-buffer)))
  "Have the PROMT-BUFFER return the selection, then quit."
  (prompter:return-selection (prompter prompt-buffer))
  (hide-prompt-buffer prompt-buffer))

(define-command return-input (&optional (prompt-buffer (current-prompt-buffer))) ; TODO: Remove if we remove `must-match-p'?
  "Have the PROMT-BUFFER return the selection, then quit."
  (prompter:return-input (prompter prompt-buffer))
  (hide-prompt-buffer prompt-buffer))

(defun prompt-buffer-actions (&optional (window (current-window)))
  (sera:and-let* ((first-prompt-buffer (first (nyxt::active-minibuffers window))))
    (prompter:actions (prompter first-prompt-buffer))))

;; TODO: Should actions be commands?
(defun action-properties (action)
  "Return the name and documentation properties of the given ACTION symbol."
  ;; TODO: Return bindings.
  (flet ((first-line (string)
           (first (str:split (string #\newline) string))))
    `(:name ,(symbol-name action)
      :documentation ,(first-line (documentation action 'function)))))

(define-class action-source (prompter:prompter-source)
  ((prompter:name "List of actions")
   (prompter:initial-suggestions (prompt-buffer-actions))
   (prompter:suggestion-property-function #'action-properties)))

(define-command return-selection-over-action (&optional (prompt-buffer (current-prompt-buffer)))
  "Prompt for an action to run over PROMPT-BUFFER selection."
  (let ((action (prompt
                 :prompter (list
                            :prompt "Action to run on selection"
                            :sources (list (make-instance 'action-source))))))
    (when action
      (prompter:return-selection (prompter prompt-buffer) action)
      (hide-prompt-buffer prompt-buffer))))

(define-command run-persistent-action (&optional (prompt-buffer (current-prompt-buffer)))
  "Run persistent action over selected suggestion without closing PROMPT-BUFFER."
  (prompter:call-persistent-action (prompter prompt-buffer)))

(define-command cancel-input (&optional (prompt-buffer (current-prompt-buffer))) ; TODO: Rename.
  "Close the prompt-buffer without further action."
  (hide-prompt-buffer prompt-buffer))

(define-command prompt-buffer-toggle-mark (&key
                                           (prompt-buffer (current-prompt-buffer))
                                           (direction :forward))
  "Mark selection.
Only available if current prompt-buffer source `multi-selection-p' is non-nil.<commit_msg>prompt-buffer: Bind "C-c C-f" to toggle-follow.<commit_after>;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause

(in-package :nyxt/prompt-buffer-mode)
(eval-when (:compile-toplevel :load-toplevel :execute)
  (trivial-package-local-nicknames:add-package-local-nickname :alex :alexandria)
  (trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum))

(define-mode prompt-buffer-mode ()
  "Mode for the prompt buffer."
  ((keymap-scheme
    (define-scheme "prompt-buffer"
      scheme:cua
      (list
       "C-n" 'select-next
       "C-p" 'select-previous
       "C-v" 'select-next-page
       "M-v" 'select-previous-page
       "M->" 'select-last
       "M-<" 'select-first
       "M-]" 'select-next-source
       "M-[" 'select-previous-source
       "return" 'return-selection
       "C-return" 'return-input         ; TODO: Bind to shift-return instead?
       "M-return" 'return-selection-over-action       ; TODO: Also bind to C-return?
       "C-j" 'run-persistent-action
       "C-c C-f" 'toggle-follow
       "C-space" 'prompt-buffer-toggle-mark
       "shift-space" 'prompt-buffer-toggle-mark-backwards
       "M-space" 'prompt-buffer-toggle-mark
       "M-a" 'prompt-buffer-mark-all
       "M-u" 'prompt-buffer-unmark-all
       "M-m" 'prompt-buffer-toggle-mark-all
       "C-w" 'copy-selection
       "C-v" 'prompt-buffer-paste))
    ;; TODO: We could have VI bindings for the minibuffer too.
    ;; But we need to make sure it's optional + to have an indicator
    ;; for the mode.
    )))

(define-command select-next (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next entry in prompt buffer."
  (prompter:select-next (prompter prompt-buffer))
  ;; TODO: Update display?  The library should probably decide when to update
  ;; it.  Drawback is that it maybe result in too many draws.  If the caller
  ;; decides when redraw, it has more control.
  (update-suggestion-html prompt-buffer))

(define-command select-previous (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next entry in prompt buffer."
  (prompter:select-previous (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-first (&optional (prompt-buffer (current-prompt-buffer)))
  "Select first entry in prompt buffer."
  (prompter:select-first (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-last (&optional (prompt-buffer (current-prompt-buffer)))
  "Select first entry in prompt buffer."
  (prompter:select-last (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-next-source (&optional (prompt-buffer (current-prompt-buffer)))
  "Select next soruce in prompt buffer."
  (prompter:select-next-source (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-previous-source (&optional (prompt-buffer (current-prompt-buffer)))
  "Select previous source in prompt buffer."
  (prompter:select-previous-source (prompter prompt-buffer))
  (update-suggestion-html prompt-buffer))

(define-command select-next-page (&key (prompt-buffer (current-prompt-buffer))
                                  (steps 1))
  "Select entry by STEPS next page in prompt buffer.
If STEPS is negative, go to previous pages instead."
  (unless (= 0 steps)
    (let ((step-page-index              ; TODO: Add multi-source support.
            (ffi-minibuffer-evaluate-javascript
             (current-window)
             (ps:ps
               (defun element-in-view-port-p (element)
                 (ps:let* ((rect (ps:chain element (get-bounding-client-rect))))
                   (if (and (>= (ps:chain rect top) 0)
                            (>= (ps:chain rect left) 0)
                            (<= (ps:chain rect right) (ps:chain window inner-width))
                            (<= (ps:chain rect bottom) (ps:chain window inner-height)))
                       t nil)))
               (defun step-row (row)
                 (ps:chain
                  (aref (ps:chain row parent-node rows)
                        (max 0
                             (min (- (ps:chain row parent-node rows length) 1)
                                  (+ (if (< 0 (ps:lisp steps)) 1 -1)
                                     (ps:chain row row-index)))))))
               (defun find-first-element-out-of-view (row)
                 (if (element-in-view-port-p row)
                     (let ((new-row (step-row row)))
                       (if (eq new-row row)
                           row
                           (find-first-element-out-of-view new-row)))
                     row))
               (-
                (ps:chain
                 (find-first-element-out-of-view (ps:chain document (get-element-by-id "cursor")))
                 row-index)
                (ps:chain (ps:chain document (get-element-by-id "cursor")) row-index))))))
      (sera:and-let* ((index-diff (parse-integer step-page-index :junk-allowed t)))
        (prompter:select-next (prompter prompt-buffer)
                              index-diff)))
    ;; TODO: Update display?  The library should probably decide when to update
    ;; it.  Drawback is that it maybe result in too many draws.  If the caller
    ;; decides when redraw, it has more control.
    (update-suggestion-html prompt-buffer)))

(define-command select-previous-page (&key (prompt-buffer (current-prompt-buffer))
                                      (steps 1))
  "Select entry by STEPS previous page in prompt buffer.
If STEPS is negative, go to next pages instead."
  (select-next-page :prompt-buffer prompt-buffer :steps (- steps)))

(define-command return-selection (&optional (prompt-buffer (current-prompt-buffer)))
  "Have the PROMT-BUFFER return the selection, then quit."
  (prompter:return-selection (prompter prompt-buffer))
  (hide-prompt-buffer prompt-buffer))

(define-command return-input (&optional (prompt-buffer (current-prompt-buffer))) ; TODO: Remove if we remove `must-match-p'?
  "Have the PROMT-BUFFER return the selection, then quit."
  (prompter:return-input (prompter prompt-buffer))
  (hide-prompt-buffer prompt-buffer))

(defun prompt-buffer-actions (&optional (window (current-window)))
  (sera:and-let* ((first-prompt-buffer (first (nyxt::active-minibuffers window))))
    (prompter:actions (prompter first-prompt-buffer))))

;; TODO: Should actions be commands?
(defun action-properties (action)
  "Return the name and documentation properties of the given ACTION symbol."
  ;; TODO: Return bindings.
  (flet ((first-line (string)
           (first (str:split (string #\newline) string))))
    `(:name ,(symbol-name action)
      :documentation ,(first-line (documentation action 'function)))))

(define-class action-source (prompter:prompter-source)
  ((prompter:name "List of actions")
   (prompter:initial-suggestions (prompt-buffer-actions))
   (prompter:suggestion-property-function #'action-properties)))

(define-command return-selection-over-action (&optional (prompt-buffer (current-prompt-buffer)))
  "Prompt for an action to run over PROMPT-BUFFER selection."
  (let ((action (prompt
                 :prompter (list
                            :prompt "Action to run on selection"
                            :sources (list (make-instance 'action-source))))))
    (when action
      (prompter:return-selection (prompter prompt-buffer) action)
      (hide-prompt-buffer prompt-buffer))))

(define-command run-persistent-action (&optional (prompt-buffer (current-prompt-buffer)))
  "Run persistent action over selected suggestion without closing PROMPT-BUFFER."
  (prompter:call-persistent-action (prompter prompt-buffer)))

(define-command cancel-input (&optional (prompt-buffer (current-prompt-buffer))) ; TODO: Rename.
  "Close the prompt-buffer without further action."
  (hide-prompt-buffer prompt-buffer))

(define-command toggle-follow (&optional (prompt-buffer (current-prompt-buffer)))
  "Close the prompt-buffer without further action."
  (prompter:toggle-follow (prompter prompt-buffer)))

(define-command prompt-buffer-toggle-mark (&key
                                           (prompt-buffer (current-prompt-buffer))
                                           (direction :forward))
  "Mark selection.
Only available if current prompt-buffer source `multi-selection-p' is non-nil.
{'fraction_non_alphanumeric': 0.12918283220174587, 'fraction_numerical': 0.0013336566440349175, 'mean_word_length': 2.578524945770065, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '220391', 'n_tokens_mistral': 4924, 'n_tokens_neox': 4704, 'n_words': 1273}