From f3d8bff17e011cf8eb4dfd2c9247af54f823bbe7 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sun, 29 Jan 2023 23:35:33 +0200 Subject: [PATCH] ADDED: command for renaming a variable in Prolog term * 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 | 29 ++++++++++- sweeprolog-tests.el | 15 ++++++ sweeprolog.el | 119 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 155 insertions(+), 8 deletions(-) diff --git a/README.org b/README.org index 784c417..db2fd01 100644 --- a/README.org +++ b/README.org @@ -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 diff --git a/sweeprolog-tests.el b/sweeprolog-tests.el index c1551c3..b766af1 100644 --- a/sweeprolog-tests.el +++ b/sweeprolog-tests.el @@ -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" diff --git a/sweeprolog.el b/sweeprolog.el index cddbb4f..e40d9cf 100644 --- a/sweeprolog.el +++ b/sweeprolog.el @@ -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) -- 2.39.2