diff --git a/evil-commands.el b/evil-commands.el index e7fb345d..44306c36 100644 --- a/evil-commands.el +++ b/evil-commands.el @@ -5172,6 +5172,23 @@ event that triggered the execution of this command." (push event unread-command-events))) (evil-declare-ignore-repeat 'evil-exit-visual-and-repeat) +(evil-define-command evil-retab (tabstop) + "Convert all tabs to spaces or the other way around. +Replace all sequences of white-space containing a with new +strings of white-space using the new TABSTOP value given. +If you do not specify a new TABSTOP size or it is zero, Evil uses the +current value of `tab-width'." + (interactive "") + (unless (or (not tabstop) (string-match-p "^[0-9]*$" tabstop)) + (user-error "Invalid argument: %s" tabstop)) + (let ((beg (if (use-region-p) (region-beginning) (point-min))) + (end (if (use-region-p) (region-end) (point-max))) + (retab (if indent-tabs-mode #'tabify #'untabify)) + (tab-width (cond ((not tabstop) tab-width) + ((equal tabstop "0") tab-width) + (t (string-to-number tabstop))))) + (funcall retab beg end))) + (provide 'evil-commands) ;;; evil-commands.el ends here diff --git a/evil-maps.el b/evil-maps.el index 2834d5a8..4387d8b2 100644 --- a/evil-maps.el +++ b/evil-maps.el @@ -583,6 +583,7 @@ included in `evil-insert-state-bindings' by default." (evil-ex-define-cmd "nu[mber]" 'evil-ex-numbered-print) (evil-ex-define-cmd "#" 'evil-ex-numbered-print) (evil-ex-define-cmd "z" 'evil-ex-z) +(evil-ex-define-cmd "ret[ab]" 'evil-retab) (when (featurep 'tab-bar) (evil-ex-define-cmd "tabnew" 'tab-bar-new-tab) diff --git a/evil-tests.el b/evil-tests.el index e7bf0809..f3c85055 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -9809,6 +9809,49 @@ main(argc, argv) char **argv; { (should (equal evil-input-method "german-prefix")))) (remove-hook 'text-mode-hook #'use-german-input-method)))) +(ert-deftest evil-retab () + "Test the :retab command" + :tags '(evil) + (ert-info ("From tabs to spaces") + (evil-test-buffer + "def foo():\n\twhile True:\n\t\treturn\n" + (setq indent-tabs-mode nil) + (setq tab-width 4) + (":retab" [return]) + "def foo():\n while True:\n return\n")) + (ert-info ("From spaces to tabs") + (evil-test-buffer + "def foo():\n while True:\n return\n" + (setq indent-tabs-mode t) + (setq tab-width 4) + (":retab" [return]) + "def foo():\n\twhile True:\n\t\treturn\n")) + (ert-info ("Specify tab width as an argument") + (evil-test-buffer + "def foo():\n\twhile True:\n\t\treturn\n" + (setq indent-tabs-mode nil) + (setq tab-width 4) + (":retab 2" [return]) + "def foo():\n while True:\n return\n")) + (ert-info ("Invalid argument") + (evil-test-buffer + "def foo():\n\twhile True:\n\t\treturn\n" + (should-error (execute-kbd-macro ":retab foo" [return]))))) + +(ert-deftest evil-retab-visual () + "Test the :retab command on visual line selection. +This test fails in the batch mode (and therefore in the GitHub CI) +but it works fine in `M-x ert-run-tests-interactively'." + :tags '(evil visual) + (skip-unless (not noninteractive)) + (ert-info ("Retab only selected lines") + (evil-test-buffer + "def foo():\n\twhile True:\n\t\treturn\n" + (setq indent-tabs-mode nil) + (setq tab-width 4) + ("Vj" ":retab" [return]) + "def foo():\n while True:\n\t\treturn\n"))) + (provide 'evil-tests) ;;; evil-tests.el ends here