;;; editor/evil/autoload/unimpaired.el -*- lexical-binding: t; -*-

;; These are ported from vim-unimpaired https://github.com/tpope/vim-unimpaired
;; and bound in the :config default module (in +evil-bindings.el).

;;
;;; Next/Previous commands

;;;###autoload
(defun +evil/next-beginning-of-method (count)
  "Jump to the beginning of the COUNT-th method/function after point."
  (interactive "p")
  (beginning-of-defun (- count)))

;;;###autoload
(defun +evil/previous-beginning-of-method (count)
  "Jump to the beginning of the COUNT-th method/function before point."
  (interactive "p")
  (beginning-of-defun count))

;;;###autoload
(defalias #'+evil/next-end-of-method #'end-of-defun
  "Jump to the end of the COUNT-th method/function after point.")

;;;###autoload
(defun +evil/previous-end-of-method (count)
  "Jump to the end of the COUNT-th method/function before point."
  (interactive "p")
  (end-of-defun (- count)))

;;;###autoload
(defun +evil/next-preproc-directive (count)
  "Jump to the COUNT-th preprocessor directive after point.

By default, this only recognizes C preproc directives. To change this see
`+evil-preprocessor-regexp'."
  (interactive "p")
  ;; TODO More generalized search, to support directives in other languages?
  (if (re-search-forward +evil-preprocessor-regexp nil t count)
      (goto-char (match-beginning 0))
    (user-error "No preprocessor directives %s point"
                (if (> count 0) "after" "before"))))

;;;###autoload
(defun +evil/previous-preproc-directive (count)
  "Jump to the COUNT-th preprocessor directive before point.

See `+evil/next-preproc-directive' for details."
  (interactive "p")
  (+evil/next-preproc-statement (- count)))

;;;###autoload
(defun +evil/next-comment (count)
  "Jump to the beginning of the COUNT-th commented region after point."
  (interactive "p")
  (let ((orig-pt (point)))
    (require 'newcomment)
    (dotimes (_ (abs count))
      (cond ((> count 0)
             (while (and (not (eobp)) (sp-point-in-comment))
               (forward-line 1))
             (unless (comment-search-forward (point-max) 'noerror)
               (goto-char orig-pt)
               (user-error "No comment after point")))
            (t
             (while (and (not (bobp)) (sp-point-in-comment))
               (forward-line -1))
             (unless (comment-search-backward nil 'noerror)
               (goto-char orig-pt)
               (user-error "No comment before point")))))))

;;;###autoload
(defun +evil/previous-comment (count)
  "Jump to the beginning of the COUNT-th commented region before point."
  (interactive "p")
  (+evil/next-comment (- count)))

;;; ] SPC / [ SPC
;;;###autoload
(defun +evil/insert-newline-below (count)
  "Insert COUNT blank line(s) below current line. Does not change modes."
  (interactive "p")
  (dotimes (_ count)
    (save-excursion (evil-insert-newline-below))))

;;;###autoload
(defun +evil/insert-newline-above (count)
  "Insert COUNT blank line(s) above current line. Does not change modes."
  (interactive "p")
  (dotimes (_ count)
    (save-excursion (evil-insert-newline-above))))

;;; ]t / [t
;;;###autoload
(defun +evil/next-frame (count)
  "Focus next frame."
  (interactive "p")
  (dotimes (_ (abs count))
    (let ((frame (if (> count 0) (next-frame) (previous-frame))))
      (if (eq frame (selected-frame))
          (user-error "No other frame")
        (select-frame-set-input-focus frame)))))

;;;###autoload
(defun +evil/previous-frame (count)
  "Focus previous frame."
  (interactive "p")
  (+evil/next-frame (- count)))

;;; ]f / [f
(defun +evil--next-file (n)
  (unless buffer-file-name
    (user-error "Must be called from a file-visiting buffer"))
  (let* ((directory (file-name-directory buffer-file-name))
         (filename (file-name-nondirectory buffer-file-name))
         (files (doom-glob (file-name-directory buffer-file-name) "[!.]*"))
         (index (cl-position filename files :test #'file-equal-p)))
    (when (null index)
      (user-error "Couldn't find this file in current directory"))
    (let ((index (+ index n)))
      (cond ((>= index (length files))
             (user-error "No files after this one"))
            ((< index 0)
             (user-error "No files before this one"))
            ((expand-file-name (nth index files) directory))))))

;;;###autoload
(defun +evil/next-file (count)
  "Open file following this one, alphabetically, in the same directory."
  (interactive "p")
  (find-file (+evil--next-file count)))

;;;###autoload
(defun +evil/previous-file (count)
  "Open file preceding this one, alphabetically, in the same directory."
  (interactive "p")
  (find-file (+evil--next-file (- count))))


;;
;;; Encoding/Decoding

;; NOTE For ]x / [x see :lang web
;; - `+web:encode-html-entities'
;; - `+web:decode-html-entities'

(defun +evil--encode (beg end fn)
  (save-excursion
    (goto-char beg)
    (let* ((end (if (eq evil-this-type 'line) (1- end) end))
           (text (buffer-substring-no-properties beg end)))
      (delete-region beg end)
      (insert (funcall fn text)))))

;;; ]u / [u
;;;###autoload (autoload '+evil:url-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-encode (_count &optional beg end)
  "TODO"
  (interactive "<c><r>")
  (+evil--encode beg end #'url-encode-url))

;;;###autoload (autoload '+evil:url-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-decode (_count &optional beg end)
  "TODO"
  (interactive "<c><r>")
  (+evil--encode beg end #'url-unhex-string))

;;; ]y / [y
;;;###autoload (autoload '+evil:c-string-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-encode (_count &optional beg end)
  "TODO"
  (interactive "<c><r>")
  (+evil--encode
   beg end
   (lambda (text)
     (replace-regexp-in-string "[\"\\]" (lambda (ch) (concat "\\" ch)) text))))

;;;###autoload (autoload '+evil:c-string-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-decode (_count &optional beg end)
  "TODO"
  (interactive "<c><r>")
  (+evil--encode
   beg end
   (lambda (text)
     (replace-regexp-in-string "\\\\[\"\\]" (lambda (str) (substring str 1)) text))))


;;
;;; Standalone

;;; gp
;;;###autoload
(defun +evil/reselect-paste ()
  "Return to visual mode and reselect the last pasted region."
  (interactive)
  (cl-destructuring-bind (_ _ _ beg end &optional _)
      evil-last-paste
    (evil-visual-make-selection
     (save-excursion (goto-char beg) (point-marker))
     end)))
