(beginning-of-line)))))
(defun ruby-move-to-block (n)
- "Move to the beginning (N < 0) or the end (N > 0) of the current block
-or blocks containing the current block."
- ;; TODO: Make this work for n > 1,
- ;; make it not loop for n = 0,
- ;; document body
+ "Move to the beginning (N < 0) or the end (N > 0) of the
+current block, a sibling block, or an outer block. Do that (abs N) times."
(let ((orig (point))
(start (ruby-calculate-indent))
- (down (looking-at (if (< n 0) ruby-block-end-re
- (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))))
- pos done)
- (while (and (not done) (not (if (< n 0) (bobp) (eobp))))
- (forward-line n)
- (cond
- ((looking-at "^\\s *$"))
- ((looking-at "^\\s *#"))
- ((and (> n 0) (looking-at "^=begin\\>"))
- (re-search-forward "^=end\\>"))
- ((and (< n 0) (looking-at "^=end\\>"))
- (re-search-backward "^=begin\\>"))
- (t
- (setq pos (current-indentation))
+ (signum (if (> n 0) 1 -1))
+ (backward (< n 0))
+ down pos done)
+ (dotimes (_ (abs n))
+ (setq done nil)
+ (setq down (save-excursion
+ (back-to-indentation)
+ ;; There is a block start or block end keyword on this
+ ;; line, don't need to look for another block.
+ (and (re-search-forward
+ (if backward ruby-block-end-re
+ (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
+ (line-end-position) t)
+ (not (nth 8 (syntax-ppss))))))
+ (while (and (not done) (not (if backward (bobp) (eobp))))
+ (forward-line signum)
(cond
- ((< start pos)
- (setq down t))
- ((and down (= pos start))
- (setq done t))
- ((> start pos)
- (setq done t)))))
- (if done
- (save-excursion
- (back-to-indentation)
- (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>"))
- (setq done nil)))))
+ ;; Skip empty and commented out lines.
+ ((looking-at "^\\s *$"))
+ ((looking-at "^\\s *#"))
+ ;; Skip block comments;
+ ((and (not backward) (looking-at "^=begin\\>"))
+ (re-search-forward "^=end\\>"))
+ ((and backward (looking-at "^=end\\>"))
+ (re-search-backward "^=begin\\>"))
+ (t
+ (setq pos (current-indentation))
+ (cond
+ ;; Deeper intendation, we found a block.
+ ;; FIXME: We can't recognize empty blocks this way.
+ ((< start pos)
+ (setq down t))
+ ;; Block found, and same indentation as when started, stop.
+ ((and down (= pos start))
+ (setq done t))
+ ;; Shallower indentation, means outer block, can stop now.
+ ((> start pos)
+ (setq done t)))))
+ (if done
+ (save-excursion
+ (back-to-indentation)
+ ;; Not really at the first or last line of the block, move on.
+ (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>"))
+ (setq done nil))))))
(back-to-indentation)))
(defun ruby-beginning-of-block (&optional arg)
(defun ruby-end-of-block (&optional arg)
"Move forward to the end of the current block.
With ARG, move out of multiple blocks."
- ;; Passing a value > 1 to ruby-move-to-block currently doesn't work.
- (interactive)
+ (interactive "p")
(ruby-move-to-block (or arg 1)))
(defun ruby-forward-sexp (&optional arg)
(should (string= (ruby-add-log-current-method)
(format "M::C%s" value)))))))
+(defvar ruby-block-test-example
+ (ruby-test-string
+ "class C
+ | def foo
+ | 1
+ | end
+ |
+ | def bar
+ | 2
+ | end
+ |
+ | def baz
+ | some do
+ | end
+ | end
+ |end"))
+
+(defmacro ruby-deftest-move-to-block (name &rest body)
+ `(ert-deftest ,(intern (format "ruby-move-to-block-%s" name)) ()
+ (with-temp-buffer
+ (insert ruby-block-test-example)
+ (ruby-mode)
+ ,@body)))
+
+(put 'ruby-deftest-move-to-block 'lisp-indent-function 'defun)
+
+(ruby-deftest-move-to-block works-on-do
+ (goto-line 11)
+ (ruby-end-of-block)
+ (should (= 12 (line-number-at-pos)))
+ (ruby-beginning-of-block)
+ (should (= 11 (line-number-at-pos))))
+
+(ruby-deftest-move-to-block zero-is-noop
+ (goto-line 5)
+ (ruby-move-to-block 0)
+ (should (= 5 (line-number-at-pos))))
+
+(ruby-deftest-move-to-block ok-with-three
+ (goto-line 2)
+ (ruby-move-to-block 3)
+ (should (= 13 (line-number-at-pos))))
+
+(ruby-deftest-move-to-block ok-with-minus-two
+ (goto-line 10)
+ (ruby-move-to-block -2)
+ (should (= 2 (line-number-at-pos))))
+
(provide 'ruby-mode-tests)
;;; ruby-mode-tests.el ends here