Advertisement
6clk4mW8KYjKb5Af

rotate phrases emacs

Dec 17th, 2019
469
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lisp 7.43 KB | None | 0 0
  1. ;;; rotfr.el --- Rotate phrases -*- lexical-binding: t -*-
  2.  
  3. ;; Copyright (C) 2019  Free Software Foundation, Inc.
  4.  
  5. ;; This file is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation; either version 3, or (at your option)
  8. ;; any later version.
  9.  
  10. ;; This program is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. ;; GNU General Public License for more details.
  14.  
  15. ;; For a full copy of the GNU General Public License
  16. ;; see <http://www.gnu.org/licenses/>.
  17.  
  18. ;;; Commentary:
  19. ;; Rotate among sets of phrases.
  20.  
  21. ;;; Code:
  22. (require 'dash)
  23. (require 'cl-lib)
  24.  
  25. (defvar rotfr-rotation-sets
  26.   '((:rot ("yes" "no" "maybe so"))
  27.     (:mode php-mode :rot ("public" "protected" "private"
  28.                           "public static" "protected static" "private static"))
  29.     (:mode php-mode :rot ("class" "abstract class" "interface"))
  30.     (:mode php-mode :rot ("namespace" "use"))
  31.     (:mode php-mode :rot ("==" "===" "!=" "!==" "<" "<=" ">" ">="))
  32.     (:mode php-mode :rot ("self::" "$this->"))
  33.     (:mode emacs-lisp-mode :rot ("cl-labels" "cl-flet"))
  34.     (:mode emacs-lisp-mode :rot ("let" "let*"))
  35.     (:mode prog-mode :rot ("&&" "||"))
  36.     (:mode text-mode :rot ("and" "or" "either")))
  37.   "Rotate phrase phrase rotations.
  38.  
  39. List of plists where each plist must have:
  40. :rot  List of words or phrases to cycle through.
  41.  
  42. Plists may have the keys:
  43. :mode  symbol or list of symbols that must match the major mode of the
  44.       buffer.
  45. :bounds  ensures phrase has boundries that match thing.  By default
  46.         thing is 'same-syntax.")
  47.  
  48. (defun rotfr--rotate-match-mode-p (group)
  49.   "Determine if the mode of the current buffer is a match for the GROUP."
  50.   (cl-labels ((matchp (mode)
  51.                       (cl-typecase mode
  52.                         (cons (-any #'matchp mode))
  53.                         (symbol (derived-mode-p mode))
  54.                         (t (error "Unknown group mode: %S" (type-of mode))))))
  55.     (let ((mode (plist-get group :mode)))
  56.       (if mode
  57.           (matchp (plist-get group :mode))
  58.         t))))
  59.  
  60. (defun rotfr--phrase-match-point-p (search-point phrase)
  61.   "Determine if there is a match of the PHRASE around the area at SEARCH-POINT.
  62.  
  63. Returns start and end position as a list."
  64.   (let* ((len (length phrase))
  65.          (start (max (point-min) (- search-point (1- len))))
  66.          (end (min (+ search-point len) (point-max)))
  67.          (ss (buffer-substring-no-properties start end))
  68.          (pos (cl-search phrase ss)))
  69.     (when pos
  70.       (list (+ start pos) (+ start pos len)))))
  71.  
  72. (defun rotfr--bounds-match-thing (start end thing)
  73.   "Ensure bounds of phrase match are bound by THING.
  74.  
  75. Check that the phrase captured between START and END are also
  76. bound at the endpoints according to THING."
  77.   (let ((start-bounds (save-excursion (goto-char start) (bounds-of-thing-at-point thing)))
  78.         (end-bounds (save-excursion (goto-char (1- end)) (bounds-of-thing-at-point thing))))
  79.     (and (equal start (car start-bounds))
  80.          (equal end (cdr end-bounds)))))
  81.  
  82. (defun rotfr--find-match-for-group (group &optional reversed)
  83.   "Attempt to match a GROUP around the current point.
  84.  
  85. Will rotate REVERSED when non nil.
  86.  
  87. On success, returns as a list the phrase start point, end point,
  88. and replacement phrase."
  89.   (when (rotfr--rotate-match-mode-p group)
  90.     (let* ((search-point (point))
  91.            (rot (plist-get group :rot))
  92.            (bounds-full (plist-member group :bounds))
  93.            (bounds (if bounds-full (cadr bounds-full) 'same-syntax))
  94.            best-start best-end best-length best-next-word)
  95.       (when reversed
  96.         (setq rot (reverse rot)))
  97.       (cl-labels
  98.           ((possible-better-match (word)
  99.                                   ;; Can only match if no existing match or this
  100.                                   ;; match is longer
  101.                                   (or (not best-next-word)
  102.                                       (< best-length (length word))))
  103.            (update-matches (word next-word)
  104.                            (when-let (((possible-better-match word))
  105.                                       (found (rotfr--phrase-match-point-p search-point word))
  106.                                       ((or (not bounds)
  107.                                            (rotfr--bounds-match-thing
  108.                                             (elt found 0) (elt found 1) bounds))))
  109.                              (setq best-start (elt found 0)
  110.                                    best-end (elt found 1)
  111.                                    best-length (length word)
  112.                                    best-next-word next-word))))
  113.         (map nil #'update-matches rot (cdr (-cycle rot))))
  114.       (when best-next-word
  115.         (list best-start best-end best-next-word)))))
  116.  
  117. (setq rotfr--last-used nil)
  118.  
  119. (defun rotfr--maintain-relative-point (start-point end-point new-phrase-len)
  120.   "Determine what the new point should be after replacement.
  121.  
  122. Maintains the old positioning so that repeated calls do not move
  123. point in a random fashion.  New position is based off the
  124. position of point in comparison with its original relative position.
  125.  
  126. START-POINT and END-POINT being the points where the replacement
  127. will be done.  NEW-PHRASE-LEN is the length of the new
  128. replacement.  Returns where point should be moved to."
  129.   (let (from-end relative-len)
  130.     (if (and (eql start-point (elt rotfr--last-used 0))
  131.              (eql (point) (elt rotfr--last-used 1)))
  132.         (setq relative-len (elt rotfr--last-used 2)
  133.               from-end (elt rotfr--last-used 3))
  134.       (let* ((old-len (- end-point start-point))
  135.              (len-from-start (- (point) start-point))
  136.              (len-from-end (- end-point (point) 1)))
  137.         (if (<= len-from-start len-from-end)
  138.             (setq relative-len (/ len-from-start (float old-len)))
  139.           (setq from-end t
  140.                 relative-len (/ len-from-end (float old-len))))))
  141.     (let* ((new-relative-pos (min (1- new-phrase-len)
  142.                                   (floor (* (abs relative-len) new-phrase-len))))
  143.            (new-point (if from-end
  144.                           (- (+ start-point new-phrase-len) new-relative-pos 1)
  145.                         (+ start-point new-relative-pos))))
  146.       (setq rotfr--last-used (list start-point new-point relative-len from-end))
  147.       new-point)))
  148.  
  149. ;;;###autoload
  150. (defun rotfr-rotate-this (&optional reversed)
  151.   "Rotate the current word among selections.
  152.  
  153. Will rotate REVERSED when non nil."
  154.   (interactive)
  155.   (cl-flet ((frepl (start end new-word)
  156.                    ;; Try to keep point around relative position
  157.                    (let* ((new-point
  158.                            (rotfr--maintain-relative-point
  159.                             start end (length new-word))))
  160.                      (delete-region start end)
  161.                      (goto-char start)
  162.                      (insert new-word)
  163.                      (goto-char new-point))))
  164.     (cl-loop for group in rotfr-rotation-sets
  165.              for match = (rotfr--find-match-for-group group reversed)
  166.              if match do (apply #'frepl match) and return t)))
  167.  
  168. ;;;###autoload
  169. (defun rotfr-rotate-this-reversed ()
  170.   "Rotate the current word among selections in reverse order."
  171.   (interactive)
  172.   (rotfr-rotate-this t))
  173.  
  174. (provide 'rotfr)
  175.  
  176. ;;; rotfr.el ends here
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement