]> git.eshelyaron.com Git - sweep.git/commitdiff
ADDED: command for renaming a variable in Prolog term
authorEshel Yaron <me@eshelyaron.com>
Sun, 29 Jan 2023 21:35:33 +0000 (23:35 +0200)
committerEshel Yaron <me@eshelyaron.com>
Sun, 29 Jan 2023 21:35:33 +0000 (23:35 +0200)
* sweeprolog.el (sweeprolog-rename-variable): new command.
(sweeprolog-context-menu-rename-variable): new command used in context
menus for variables.
(sweeprolog-context-menu-for-variable): new function, used in...
(sweeprolog-context-menu-functions): add it to the list.
(sweeprolog-mode-map): bind it to C-c C-r.
* README.org (Renaming Variables): new section.

README.org
sweeprolog-tests.el
sweeprolog.el

index 784c4175708d8a84200cb3ce84cec1460a875bfe..db2fd018f90929481170f961408e23e224f19bb7 100644 (file)
@@ -1646,7 +1646,7 @@ backward to the previous match.
 #+CINDEX: right click menu
 In addition to the keybindings that Sweep provides for invoking its
 commands, it integrates with Emacs's standard Context Menu minor mode
-to provide contextual menus that you operate with the mouse.
+to provide contextual menus that you interact with using the mouse.
 
 - Command: context-menu-mode :: Toggle Context Menu mode.  When
   enabled, clicking the mouse button ~down-mouse-3~ (i.e. right-click)
@@ -1668,7 +1668,8 @@ the buffer.
 If you right-click on a Prolog file specification or module name,
 Sweep suggests visiting it either in the current window or in another.
 If you right-click on a predicate, it lets you view its documentation
-in dedicated buffer.
+in a dedicated buffer (see also [[#prolog-help][Prolog Help]]).  For variables, it
+suggests to renaming (see .
 
 You can further extend and customize the context menu that
 ~sweeprolog-mode~ provides by adding functions to the variable
@@ -1677,6 +1678,30 @@ receives the menu that is being created and a description of the
 clicked Prolog token, and it can extend the menu with entries before
 it's displayed.
 
+** Renaming Variables
+:PROPERTIES:
+:CUSTOM_ID: rename-variable
+:DESCRIPTION: Replacing occurrences of one Prolog variable with another
+:ALT_TITLE: Renaming Variables
+:END:
+
+You can rename a Prolog variable across the current top-term with the
+following command:
+
+- Key: C-c C-r (sweeprolog-rename-variable) :: Rename a variable
+  across the topmost Prolog term at point.
+
+The command ~sweeprolog-rename-variable~, bound to ~C-c C-r~, prompts for
+two variable names and replaces all occurrences of the first variable
+in the term at point with the second.  The prompt for the first (old)
+variable name provides completion based on the existing variable names
+in the current term, and it uses the variable at point as its default.
+
+If Context Menu mode is enabled, you can also rename variables by
+right-clicking on them with the mouse and selecting =Rename Variable=
+from the top of the context menu.  See [[#context-menu][Context Menu]] for more
+information about context menus in Sweep.
+
 * Prolog Help
 :PROPERTIES:
 :CUSTOM_ID: prolog-help
index c1551c38a6c6f2387721571768bc6f9abccd752f..b766af1eafdc3a9ce6507a5ca74f4f7afb2360d1 100644 (file)
@@ -287,6 +287,21 @@ foo(Foo) :- bar.
     (should (string= (buffer-string)
                      "foo(bar, (_->_;_), _):-_."))))
 
+(ert-deftest rename-variable ()
+  "Tests renaming varialbes."
+  (let ((temp (make-temp-file "sweeprolog-test"
+                              nil
+                              ".pl"
+                              "foo(Bar,Baz) :- spam(Baz,Bar).")))
+    (find-file-literally temp)
+    (sweeprolog-mode)
+    (goto-char (point-min))
+    (sweeprolog-rename-variable "Bar" "Spam")
+    (sweeprolog-rename-variable "Baz" "Bar")
+    (sweeprolog-rename-variable "Spam" "Baz")
+    (should (string= (buffer-string)
+                     "foo(Baz,Bar) :- spam(Bar,Baz)."))))
+
 (ert-deftest forward-many-holes ()
   "Tests jumping over holes with `sweeprolog-forward-hole'."
   (let ((temp (make-temp-file "sweeprolog-test"
index cddbb4f70c070c0b72a8786f6868561288381db8..e40d9cf95de4e8b833639a83f9fb56ef4f219a9c 100644 (file)
@@ -319,7 +319,7 @@ inserted to the input history in `sweeprolog-top-level-mode' buffers."
   "If non-nil, enable `cursor-sensor-mode' in `sweeprolog-mode'.
 
 When enabled, `sweeprolog-mode' leverages `cursor-sensor-mode' to
-highlight all occurences of the variable at point in the current
+highlight all occurrences of the variable at point in the current
 clause."
   :package-version '((sweeprolog "0.4.2"))
   :type 'boolean
@@ -401,12 +401,13 @@ token via its `help-echo' text property."
     (define-key map (kbd "C-c C-l") #'sweeprolog-load-buffer)
     (define-key map (kbd "C-c C-m") #'sweeprolog-insert-term-with-holes)
     (define-key map (kbd "C-c C-o") #'sweeprolog-find-file-at-point)
-    (define-key map (kbd "C-c C-s") #'sweeprolog-term-search)
     (define-key map (kbd "C-c C-q") #'sweeprolog-top-level-send-goal)
+    (define-key map (kbd "C-c C-r") #'sweeprolog-rename-variable)
+    (define-key map (kbd "C-c C-s") #'sweeprolog-term-search)
     (define-key map (kbd "C-c C-t") #'sweeprolog-top-level)
     (define-key map (kbd "C-c C-u") #'sweeprolog-update-dependencies)
     (define-key map (kbd "C-c C-`")
-                (if (fboundp 'flymake-show-buffer-diagnostics)  ;; Flymake 1.2.1+
+                (if (fboundp 'flymake-show-buffer-diagnostics) ;; Flymake 1.2.1+
                     #'sweeprolog-show-diagnostics
                   #'flymake-show-diagnostics-buffer))
     (define-key map (kbd "C-c C-&") #'sweeprolog-async-goal)
@@ -2673,7 +2674,7 @@ modified."
      start end)))
 
 (defun sweeprolog-highlight-variable (point &optional var)
-  "Highlight occurences of the variable VAR in the clause at POINT.
+  "Highlight occurrences of the variable VAR in the clause at POINT.
 
 If VAR is nil, clear variable highlighting in the current clause
 instead.
@@ -5277,6 +5278,9 @@ GOAL."
 
 ;;;; Right-Click Context Menu
 
+(defvar sweeprolog-context-menu-point-at-click nil
+  "Buffer position at mouse click.")
+
 (defvar sweeprolog-context-menu-file-at-click nil
   "Prolog file specification at mouse click.")
 
@@ -5286,6 +5290,9 @@ GOAL."
 (defvar sweeprolog-context-menu-predicate-at-click nil
   "Prolog predicate indicator at mouse click.")
 
+(defvar sweeprolog-context-menu-variable-at-click nil
+  "Prolog variable at mouse click.")
+
 (defun sweeprolog-context-menu-find-module ()
   "Find Prolog module at mouse click."
   (interactive)
@@ -5316,6 +5323,13 @@ GOAL."
   (interactive)
   (sweeprolog-describe-predicate sweeprolog-context-menu-predicate-at-click))
 
+(defun sweeprolog-context-menu-rename-variable ()
+  "Rename Prolog variable at mouse click."
+  (interactive)
+  (sweeprolog-rename-variable sweeprolog-context-menu-variable-at-click
+                              nil
+                              sweeprolog-context-menu-point-at-click))
+
 (defun sweeprolog-context-menu-for-predicate (menu tok _beg _end _point)
   "Extend MENU with predicate-related commands if TOK describes one."
   (pcase tok
@@ -5369,10 +5383,30 @@ GOAL."
                              :help ,(format "Find %s" file)
                              :keys "\\[sweeprolog-find-file-at-point]")))))
 
+(defun sweeprolog-context-menu-for-variable (menu tok beg end point)
+  "Extend MENU with file-related commands if TOK specifies one.
+BEG and END are the variable's beginning and end positions, and
+POINT is the buffer position of the mouse click."
+  (pcase tok
+    ((or "var"
+         "singleton"
+         `("goal_term" "meta" variable 0))
+     (setq sweeprolog-context-menu-point-at-click point
+           sweeprolog-context-menu-variable-at-click
+           (buffer-substring-no-properties beg end))
+     (define-key menu [sweeprolog-rename-variable]
+                 `(menu-item "Rename Variable"
+                             sweeprolog-context-menu-rename-variable
+                             :help ,(concat "Rename variable "
+                                            (propertize sweeprolog-context-menu-variable-at-click
+                                                        'face (sweeprolog-variable-face)))
+                             :keys "\\[sweeprolog-rename-variable]")))))
+
 (defvar sweeprolog-context-menu-functions
   '(sweeprolog-context-menu-for-file
     sweeprolog-context-menu-for-module
-    sweeprolog-context-menu-for-predicate)
+    sweeprolog-context-menu-for-predicate
+    sweeprolog-context-menu-for-variable)
   "Functions that create context menu entries for Prolog tokens.
 Each function receives as its arguments the menu, the Prolog
 token's description, its start position, its end position, and
@@ -5387,7 +5421,7 @@ the position for which the menu is created.")
        (lambda (beg end tok)
          (when (<= beg point end)
            (run-hook-with-args 'sweeprolog-context-menu-functions
-                               menu tok beg point end))))))
+                               menu tok beg end point))))))
   menu)
 
 
@@ -5485,6 +5519,79 @@ Deletes PROC if STRING contains an end of output marker string."
             sweeprolog-async-goal-current-goal goal))
     (display-buffer buffer)))
 
+
+;;;; Refactoring
+
+(defun sweeprolog--variables-at-point ()
+  "Return information about variables in the Prolog term at point.
+
+Returns a cons cell (VAR-OCCURRENCES . VAR-AT-POINT).
+VAR-OCCURRENCES is an alist of elements (VAR . OCCURRENCES) where
+VAR is a variable name and OCCURRENCES is itself an alist of
+elements (BEG . END) describing the beginning and end of
+occurrences of this variable in buffer positions.  VAR-AT-POINT
+is the name of the variable at point, if any."
+  (let ((point (point))
+        (vars nil)
+        (var-at-point nil))
+    (sweeprolog-analyze-term-at-point
+     (lambda (beg end arg)
+       (pcase arg
+         ((or "var"
+              "singleton"
+              `("goal_term" "meta" variable 0))
+          (let ((var (buffer-substring-no-properties beg end)))
+            (push (cons beg end)
+                  (alist-get var vars nil nil #'string=))
+            (when (<= beg point end)
+              (setq var-at-point var)))))))
+    (cons vars var-at-point)))
+
+(defun sweeprolog-rename-variable (&optional old new point)
+  "Rename the variable OLD to NEW in the Prolog term at POINT.
+
+If OLD is nil, prompt for it in the minibuffer with completion.
+If NEW is nil, prompt for it as well.  If POINT is nil, it
+defaults to the current point.
+
+Interactively, OLD, NEW and POINT are nil."
+  (interactive "" sweeprolog-mode)
+  (setq point (or point (point)))
+  (save-excursion
+    (goto-char point)
+    (let* ((term-var-occurrences (sweeprolog--variables-at-point))
+           (var-occurrences (car term-var-occurrences))
+           (var-at-point (cdr term-var-occurrences)))
+      (unless var-occurrences
+        (user-error "No variables to rename here!"))
+      (let* ((old-name
+              (or old
+                  (completing-read
+                   (concat
+                    "Rename variable"
+                    (when-let ((def var-at-point))
+                      (concat " (default "
+                              (propertize def 'face (sweeprolog-variable-face))
+                              ")"))
+                    ": ")
+                   var-occurrences nil t nil nil var-at-point)))
+             (new-name
+              (or new
+                  (read-string
+                   (concat
+                    "Rename "
+                    (propertize old-name 'face (sweeprolog-variable-face))
+                    " to: ")
+                   nil nil old-name))))
+        (combine-after-change-calls
+          (dolist (old-occurrence (alist-get old-name var-occurrences
+                                             nil nil #'string=))
+            (let ((occurrence-beg (car old-occurrence))
+                  (occurrence-end (cdr old-occurrence)))
+              (delete-region occurrence-beg occurrence-end)
+              (goto-char occurrence-beg)
+              (insert new-name))))))))
+
 ;;;; Footer
 
 (provide 'sweeprolog)