From c009a0a6f73bb61d2bd37f4acb8435b335ed2fa0 Mon Sep 17 00:00:00 2001 From: Mauro Aranda Date: Thu, 22 Oct 2020 13:52:42 +0200 Subject: [PATCH] Allow moving members of editable-list widget, via delete+insert * etc/NEWS (Widget): Announce the feature (bug#6419). * lisp/wid-edit.el (widget-editable-list-delete-at): Save into a new widget property, :last-deleted, the WIDGET to be deleted. Add docstring. (widget-editable-list-insert-before): If there is a recently deleted child for the editable list, insert that one, instead of a new default widget. Add docstring. (insert-button widget): Make :help-echo a function to avoid the help-echo string become too long. (delete-button widget): Tweak the :help-echo string, to document this behavior. * test/lisp/wid-edit-tests.el (widget-test-moving-editable-list-item): Test the feature. --- etc/NEWS | 7 +++++++ lisp/wid-edit.el | 33 +++++++++++++++++++++++++++++---- test/lisp/wid-edit-tests.el | 19 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 2aed5751595..9e8182a2dae 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1166,6 +1166,13 @@ window after starting). This variable defaults to nil. +++ *** 'widget-choose' now supports menus in extended format. +--- +*** The 'editable-list' widget now supports moving items up and down. +You can now move items up and down by deleting and then reinserting +them, using the DEL and INS buttons respectively. This is useful in +Custom buffers, for example, to change the order of the elements in a +list. + ** Miscellaneous --- diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el index 6568cd2c8f1..c3366b62cdb 100644 --- a/lisp/wid-edit.el +++ b/lisp/wid-edit.el @@ -2721,7 +2721,10 @@ Return an alist of (TYPE MATCH)." (define-widget 'insert-button 'push-button "An insert button for the `editable-list' widget." :tag "INS" - :help-echo "Insert a new item into the list at this position." + :help-echo (lambda (widget) + (if (widget-get (widget-get widget :parent) :last-deleted) + "Insert back the last deleted item from this list, at this position." + "Insert a new item into the list at this position.")) :action 'widget-insert-button-action) (defun widget-insert-button-action (widget &optional _event) @@ -2734,7 +2737,7 @@ Return an alist of (TYPE MATCH)." (define-widget 'delete-button 'push-button "A delete button for the `editable-list' widget." :tag "DEL" - :help-echo "Delete this item from the list." + :help-echo "Delete this item from the list, saving it for later reinsertion." :action 'widget-delete-button-action) (defun widget-delete-button-action (widget &optional _event) @@ -2824,9 +2827,18 @@ Return an alist of (TYPE MATCH)." (cons found value))) (defun widget-editable-list-insert-before (widget before) - ;; Insert a new child in the list of children. + "Insert a new widget as a child of WIDGET. + +If there is a recently deleted child, the new widget is that deleted child. +Otherwise, the new widget is the default child of WIDGET. + +The new widget gets inserted at the position of the BEFORE child." (save-excursion (let ((children (widget-get widget :children)) + (last-deleted (when-let ((lst (widget-get widget :last-deleted))) + (prog1 + (pop lst) + (widget-put widget :last-deleted lst)))) (inhibit-read-only t) (inhibit-modification-hooks t)) (cond (before @@ -2834,7 +2846,11 @@ Return an alist of (TYPE MATCH)." (t (goto-char (widget-get widget :value-pos)))) (let ((child (widget-editable-list-entry-create - widget nil nil))) + widget (and last-deleted + (widget-apply last-deleted + :value-to-external + (widget-get last-deleted :value))) + last-deleted))) (when (< (widget-get child :entry-from) (widget-get widget :from)) (set-marker (widget-get widget :from) (widget-get child :entry-from))) @@ -2847,6 +2863,15 @@ Return an alist of (TYPE MATCH)." (widget-apply widget :notify widget)) (defun widget-editable-list-delete-at (widget child) + "Delete the widget CHILD from the known children of widget WIDGET. + +Save CHILD into the :last-deleted list, so it can be inserted later." + ;; Save the current value of CHILD, to use if the user later inserts the + ;; widget. + (widget-put child :value (widget-apply child :value-get)) + (let ((lst (widget-get widget :last-deleted))) + (push child lst) + (widget-put widget :last-deleted lst)) ;; Delete child from list of children. (save-excursion (let ((buttons (copy-sequence (widget-get widget :buttons))) diff --git a/test/lisp/wid-edit-tests.el b/test/lisp/wid-edit-tests.el index df49ffc8224..4508b680232 100644 --- a/test/lisp/wid-edit-tests.el +++ b/test/lisp/wid-edit-tests.el @@ -129,4 +129,23 @@ (widget-insert "And some non-widget text.") (should (string= (widget-apply wid :value-get) ""))))) +(ert-deftest widget-test-moving-editable-list-item () + "Check that we can move an editable list item up or down, via delete+insert." + (with-temp-buffer + (widget-insert "Testing editable-list.\n\n") + (let ((lst (widget-create 'editable-list + :value '("beg" "end" "middle") + '(editable-field :value "unknown")))) + (use-local-map widget-keymap) + (widget-setup) + ;; Go to the DEL button for the 2nd element and action it. + (goto-char (widget-get (nth 2 (widget-get lst :buttons)) :from)) + (widget-apply-action (widget-at)) + ;; Go to the INS button and action it. + (goto-char (widget-get lst :to)) + (widget-backward 1) + (widget-apply-action (widget-at)) + ;; Check that we effectively moved the item to the last position. + (should (equal (widget-value lst) '("beg" "middle" "end")))))) + ;;; wid-edit-tests.el ends here -- 2.39.2