;;; syd-text.el -*- lexical-binding: t; -*-

;;;###autoload
(defun syd-region-active-p ()
  "Return non-nil if selection is active.
Detects evil visual mode as well."
  (declare (side-effect-free t))
  (or (use-region-p)
      (and (bound-and-true-p evil-local-mode)
           (evil-visual-state-p))))

;;;###autoload
(defun syd-region-beginning ()
  "Return beginning position of selection.
Uses `evil-visual-beginning' if available."
  (declare (side-effect-free t))
  (or (and (bound-and-true-p evil-local-mode)
           (evil-visual-state-p)
           (markerp evil-visual-beginning)
           (marker-position evil-visual-beginning))
      (region-beginning)))

;;;###autoload
(defun syd-region-end ()
  "Return end position of selection.
Uses `evil-visual-end' if available."
  (declare (side-effect-free t))
  (or (and (bound-and-true-p evil-local-mode)
           (evil-visual-state-p)
           (markerp evil-visual-end)
           (marker-position evil-visual-end))
      (region-end)))

;;;###autoload
(cl-defun syd-thing-at-point-or-region (&optional thing &key prompt)
  "Grab the current selection, THING at point, or xref identifier at point.

Returns THING if it is a string. Otherwise, if nothing is found at point and
PROMPT is non-nil, prompt for a string (if PROMPT is a string it'll be used as
the prompting string). Returns nil if all else fails.

NOTE: Don't use THING for grabbing symbol-at-point. The xref fallback is smarter
in some cases."
  (declare (side-effect-free t))
  (cond ((stringp thing)
         thing)
        ((syd-region-active-p)
         (buffer-substring-no-properties
          (syd-region-beginning)
          (syd-region-end)))
        (thing
         (thing-at-point thing t))
        ((require 'xref nil t)
         ;; Eglot, nox (a fork of eglot), and elpy implementations for
         ;; `xref-backend-identifier-at-point' betray the documented purpose of
         ;; the interface.  Eglot/nox return a hardcoded string and elpy
         ;; prepends the line number to the symbol.
         (let ((backend (xref-find-backend)))
           (if (memq backend '(eglot elpy nox))
               (thing-at-point 'symbol t)
             ;; A little smarter than using `symbol-at-point', though in most
             ;; cases, xref ends up using `symbol-at-point' anyway.
             (if-let ((ident (xref-backend-identifier-at-point backend)))
                 ;; REVIEW: `xref-backend-identifier' seems to have some special
                 ;; uses of text properties.  Are we sure we want to remove
                 ;; them?
                 (substring-no-properties ident)))))
        (prompt
         (read-string (if (stringp prompt) prompt "")))))

;;;###autoload
(defun syd-insert-newline-above (count)
  "Insert a blank line below the current line."
  (interactive "p")
  (dotimes (_ count)
    (let ((point-was-at-bol-p (= (current-column) 0)))
      (save-excursion
        (evil-insert-newline-above))
      ;; Special case: with `syd-insert-newline-above' is called with point at
      ;; BOL, the point unexpectedly fails to "stick" to its original position.
      (when point-was-at-bol-p
        (next-line)))))

;;;###autoload
(defun syd-insert-newline-below (count)
  "Insert a blank line below the current line."
  (interactive "p")
  (dotimes (_ count)
    (save-excursion (evil-insert-newline-below))))

;;;###autoload
(defun syd-render-ansi-escape-codes (beg end)
  (interactive "r")
  (require 'ansi-color)
  (if (region-active-p)
      (ansi-color-apply-on-region beg end)
    (ansi-color-apply-on-region (point-min) (point-max))))

(defun syd-evil-paste (before-p arg &optional register yank-handler)
  "Like `evil-paste-after', but a 'C-u' prefix argument will instead act like
':put' (i.e. the register will be pasted onto a new line)."
  (if (consp arg)
      (evil-ex-put (syd-region-beginning) (syd-region-end)
		   ;; `evil-ex-put' wants a string, but it is immediately
		   ;; converted back to a char. }xP
		   (and register (string register))
		   before-p)
    (funcall (if before-p #'evil-paste-before #'evil-paste-after)
	     arg register yank-handler)))

;;;###autoload
(evil-define-command syd-evil-paste-after (arg &optional register yank-handler)
  "See `syd-evil-paste'.'"
    (interactive "P<x><y>")
    (syd-evil-paste nil arg register yank-handler))

;;;###autoload
(evil-define-command syd-evil-paste-before (arg &optional register yank-handler)
  "See `syd-evil-paste'.'"
  (interactive "P<x><y>")
  (syd-evil-paste t arg register yank-handler))

;;;###autoload
(defun syd-pcre-quote (str)
  "Like `reqexp-quote', but for PCREs."
  (let ((special '(?. ?^ ?$ ?* ?+ ?? ?{ ?\\ ?\[ ?\| ?\())
        (quoted nil))
    (mapc (lambda (c)
            (when (memq c special)
              (push ?\\ quoted))
            (push c quoted))
          str)
    (concat (nreverse quoted))))

(provide 'syd-text)
