diff --git a/CHANGELOG.md b/CHANGELOG.md index 37bb03b7..25512e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## master (unreleased) +* [#302](https://github.com/clojure-emacs/clojure-mode/pull/302) Add new sexp navigation commands. `clojure-forward-logical-sexp` and `clojure-backward-logical-sexp` consider `^hints` and `#reader.macros` to be part of the sexp that follows them. + ## 4.1.0 (20/06/2015) ### Changes diff --git a/clojure-mode.el b/clojure-mode.el index 1cb48f22..0f055a81 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -1044,6 +1044,45 @@ Returns a list pair, e.g. (\"defn\" \"abc\") or (\"deftest\" \"some-test\")." (list (match-string 1) (match-string 2)))))) + +;;; Sexp navigation +(defun clojure-forward-logical-sexp (&optional n) + "Move forward N logical sexps. +This will skip over sexps that don't represent objects, so that ^hints and +#reader.macros are considered part of the following sexp." + (interactive "p") + (if (< n 0) + (clojure-backward-logical-sexp (- n)) + (while (> n 0) + ;; Non-logical sexps. + (while (progn (forward-sexp 1) + (forward-sexp -1) + (looking-at-p "\\^\\|#[[:alpha:]]")) + (forward-sexp 1)) + ;; The actual sexp + (forward-sexp 1) + (setq n (1- n))))) + +(defun clojure-backward-logical-sexp (&optional n) + "Move backward N logical sexps. +This will skip over sexps that don't represent objects, so that ^hints and +#reader.macros are considered part of the following sexp." + (interactive "p") + (if (< n 0) + (clojure-forward-logical-sexp (- n)) + (while (> n 0) + ;; The actual sexp + (backward-sexp 1) + ;; Non-logical sexps. + (while (and (not (bobp)) + (ignore-errors + (save-excursion + (backward-sexp 1) + (looking-at-p "\\^\\|#[[:alpha:]]")))) + (backward-sexp 1)) + (setq n (1- n))))) + + ;;;###autoload (progn (add-to-list 'auto-mode-alist diff --git a/test/clojure-mode-sexp-test.el b/test/clojure-mode-sexp-test.el new file mode 100644 index 00000000..3cfcc705 --- /dev/null +++ b/test/clojure-mode-sexp-test.el @@ -0,0 +1,45 @@ +;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Artur Malabarba + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(ert-deftest test-sexp () + (with-temp-buffer + (insert "^String #macro ^dynamic reverse") + (clojure-mode) + (clojure-backward-logical-sexp 1) + (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 1) + (should (looking-back "\\^String \\#macro \\^dynamic reverse")) + (insert " ^String biverse inverse") + (clojure-backward-logical-sexp 1) + (should (looking-at-p "inverse")) + (clojure-backward-logical-sexp 2) + (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 2) + (should (looking-back "\\^String biverse")) + (clojure-backward-logical-sexp 1) + (should (looking-at-p "\\^String biverse")))) + +(provide 'clojure-mode-sexp-test) + +;;; clojure-mode-sexp-test.el ends here