├── demo.gif ├── demo-indent.gif ├── indent-tools-indentation-of.el ├── indent-tools-tests.el ├── README.org └── indent-tools.el /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vindarel/indent-tools/HEAD/demo.gif -------------------------------------------------------------------------------- /demo-indent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vindarel/indent-tools/HEAD/demo-indent.gif -------------------------------------------------------------------------------- /indent-tools-indentation-of.el: -------------------------------------------------------------------------------- 1 | ;;; indent-tools.el --- Indent, move around etc by indentation units. 2 | 3 | ;; Copyright (C) 2016-2019 wtf public licence 4 | 5 | ;; Author: vindarel 6 | ;; URL: https://gitlab.com/emacs-stuff/indent-tools/ 7 | 8 | ;; What is the indentation level of the current mode ? 9 | 10 | (defvar indent-tools-indentation-of-modes-alist '() "Given a mode, associate a function that gives this mode's indentation.") 11 | 12 | (defvar indent-tools-indentation-offset standard-indent 13 | "Indentation level to use (spaces) by default when no is found for the current mode. Defaults to 'standard-indent`.") 14 | 15 | ;; A function for every mode. 16 | (defun indent-tools-indentation-of-python () 17 | "Return Python's current indentation as an int, usually 4." 18 | (cond ((and (boundp 'python-indent-offset) 19 | (numberp python-indent-offset)) 20 | python-indent-offset))) 21 | 22 | (defun indent-tools-indentation-of-yaml () 23 | "Return Yaml's current indentation as an int." 24 | (cond ((and (boundp 'yaml-indent-offset) 25 | (numberp yaml-indent-offset)) 26 | yaml-indent-offset))) 27 | 28 | (defun indent-tools-indentation-of-json () 29 | "Rutern JSon's current indentation as an int." 30 | (if (boundp 'json-encoding-default-indentation) 31 | (length json-encoding-default-indentation))) 32 | 33 | (defun indent-tools-indentation-of-jade () 34 | "Return Jade's current indentation as an int." 35 | (cond ((and (boundp 'jade-tab-width) 36 | (numberp jade-tab-width)) 37 | jade-tab-width))) 38 | 39 | (defun indent-tools-indentation-of-web-mode-code () 40 | "In web-mode, indentation of code." 41 | (cond ((and (boundp 'web-mode-code-indent-offset) 42 | (numberp web-mode-code-indent-offset)) 43 | web-mode-code-indent-offset))) 44 | 45 | ;; The alist. 46 | (setq indent-tools-indentation-of-modes-alist 47 | '( 48 | (python-mode . indent-tools-indentation-of-python) 49 | (yaml-mode . indent-tools-indentation-of-yaml) 50 | (jade-mode . indent-tools-indentation-of-jade) 51 | (web-mode . indent-tools-indentation-of-web-mode-code) 52 | (json-mode . indent-tools-indentation-of-json) 53 | )) 54 | 55 | (defun indent-tools-indentation-of-current-mode () 56 | "Get the current mode's indentation offset by calling the function associated to this mode in the alist `indent-tools-indentation-of-modes-alist'. If not found, return the default `standard-indent'. 57 | Return an int (for python, it's usually 4)." 58 | (let ((mode-assoc (assoc major-mode indent-tools-indentation-of-modes-alist))) 59 | (if mode-assoc 60 | (funcall (cdr mode-assoc)) 61 | ;; if we don't know this major mode, return a default. 62 | indent-tools-indentation-offset))) 63 | 64 | 65 | (provide 'indent-tools-indentation-of) 66 | 67 | ;;; indent-tools-indentation-of ends here. 68 | -------------------------------------------------------------------------------- /indent-tools-tests.el: -------------------------------------------------------------------------------- 1 | ;;; indent-tools-test.el --- Tests for indent-tools 2 | 3 | ;; Copyright (C) 2016 Free Software Foundation, Inc. 4 | 5 | ;; Author: vindarel 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | ;; 24 | 25 | ;;; Code: 26 | 27 | (require 'ert) 28 | (load-file "indent-tools.el") 29 | (require 'indent-tools) 30 | 31 | (setq text-quoting-style 'grave) 32 | (message "Emacs version: %s" emacs-version) 33 | 34 | ;; Firs round for unit tests: testing movements 35 | ;; -------------------------------------------- 36 | ;; In a temp buffer, we insert a simple yaml, we put the cursor on a given line, we call some funtions and we check the point is on the expected line. 37 | ;; 38 | ;; To improve: 39 | ;; - test on other modes (like python-mode) 40 | ;; - macro ? (no more funcall & lambda) 41 | ;; - check the content of the line instead of line nb, so than we can change the yaml sample and the test declaration doesn't move. 42 | ;; - test with many and difficult yaml samples 43 | 44 | ;; - ;TODO: test indentation functions. 45 | 46 | (defun indent-tools-test-with-buffer (txt start-line func expected-line &optional mode) 47 | "Insert the given txt in a temp buffer, run function and check we're on the good line." 48 | (with-temp-buffer 49 | (yaml-mode) 50 | (insert txt) 51 | (if start-line 52 | (goto-line start-line) 53 | (goto-char (point-min))) 54 | (funcall func) 55 | (should (= (line-number-at-pos) expected-line)) 56 | )) 57 | 58 | ;; Our sample yaml file. 59 | (setq indent-test-txt-yaml "parent: 60 | # comment ! 61 | - child: l.3 62 | foo: 1 63 | - child: l.5 64 | foo: 2 65 | child: l.7 66 | parent2: 67 | other-family: 1 68 | parent3") 69 | 70 | (ert-deftest test-next-sibling () 71 | (indent-tools-test-with-buffer 72 | indent-test-txt-yaml 73 | 1 74 | (lambda () (indent-tools-goto-next-sibling)) 75 | 8)) 76 | 77 | (ert-deftest test-previous-sibling () 78 | (indent-tools-test-with-buffer 79 | indent-test-txt-yaml 80 | 5 81 | (lambda () (indent-tools-goto-previous-sibling)) 82 | 3)) 83 | 84 | (ert-deftest test-down () 85 | (indent-tools-test-with-buffer 86 | indent-test-txt-yaml 87 | 1 88 | (lambda () (indent-tools-goto-child)) 89 | 3 90 | )) 91 | 92 | (ert-deftest test-down-twice () 93 | (indent-tools-test-with-buffer 94 | indent-test-txt-yaml 95 | 1 96 | (lambda () (indent-tools-goto-child) (indent-tools-goto-child)) 97 | 4 98 | )) 99 | 100 | (ert-deftest test-up () 101 | (indent-tools-test-with-buffer 102 | indent-test-txt-yaml 103 | 3 104 | (lambda () (indent-tools-goto-parent)) 105 | 1)) 106 | 107 | (ert-deftest test-up-twice () 108 | (indent-tools-test-with-buffer 109 | indent-test-txt-yaml 110 | 4 111 | (lambda () (indent-tools-goto-parent) (indent-tools-goto-parent)) 112 | 1)) 113 | 114 | (provide 'indent-tools-tests) 115 | 116 | ;;; indent-tools-tests.el ends here 117 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * indent-tools 2 | 3 | [[https://melpa.org/#/indent-tools][file:https://melpa.org/packages/indent-tools-badge.svg]] 4 | 5 | Indent, move around and act on code based on indentation (yaml, 6 | python, jade, etc). 7 | 8 | Meant for indentation-based languages, but can be used any time with 9 | indented text. 10 | 11 | _Example:_ 12 | 13 | Navigating by blocks: 14 | 15 | #+BEGIN_HTML 16 | 17 | #+END_HTML 18 | 19 | _Indenting_: 20 | 21 | #+BEGIN_HTML 22 | 23 | #+END_HTML 24 | 25 | ** Installation 26 | 27 | Set up [[http://wikemacs.org/wiki/Package.el][MELPA]] and use [[http://wikemacs.org/wiki/Package.el][package.el]]: =M-x package-install RET indent-tools RET=. 28 | 29 | ** Usage 30 | 31 | You can activate a minor mode or use an [[https://github.com/abo-abo/hydra/][Hydra]]. 32 | 33 | We recommand you to use the hydra because it shows all available 34 | options and it allows to call an action multiple times in a row, with 35 | a single keypress: 36 | : M-x indent-tools-hydra/body 37 | 38 | To bind the Hydra at =C-c >=: 39 | #+BEGIN_SRC emacs-lisp 40 | (require 'indent-tools) 41 | (global-set-key (kbd "C-c >") 'indent-tools-hydra/body) 42 | #+END_SRC 43 | 44 | To bind this in python-mode: 45 | #+BEGIN_SRC emacs-lisp 46 | (add-hook 'python-mode-hook 47 | (lambda () (define-key python-mode-map (kbd "C-c >") 'indent-tools-hydra/body)) 48 | ) 49 | #+END_SRC 50 | (=C-c >= originally bound to indent the line). 51 | 52 | With the minor mode (=M-x indent-tools-minor-mode=), the prefix key is 53 | at =C-c >= (you can change the variable 54 | =indent-tools-keymap-prefix=). See its keymap with =C-h f 55 | indent-tools-minor-mode=. 56 | 57 | *** Indent, de-indent 58 | 59 | Indent an indentation tree: =M-x indent-tools-demote=. This indents 60 | according to the major mode (currently specially supported: python, 61 | jade, yaml. For other modes, it indents by 62 | =indent-tools-indent-offset=, 4 spaces). 63 | 64 | With the hydra, repeat this action as much as you wish. 65 | 66 | Indent by only *one space* (useful for jade-mode): =SPC= key with the hydra. 67 | 68 | Indent the *paragraph*: =M-x indent-tools-indent-paragraph=, or =P= 69 | with the hydra. 70 | 71 | See also: (de)indenting until the end of the current indentation level 72 | (i.e., indent every nodes with the same level). 73 | 74 | *** Move around 75 | 76 | Move to the *end of the current indentation*, 77 | 78 | to the *next* or the *previous sibling* (a line with the same indentation), 79 | 80 | move *one parent up* or the first *child down* (line with lesser or 81 | greater indentation), much useful for a large yaml file, but also for 82 | code navigation. 83 | 84 | *j* and *k* (with the hydra) as usual go to the next and previous line. 85 | 86 | *** Recenter screen (L) 87 | 88 | While you move around, you may want to recenter the screen, what we 89 | usually do with =C-l= ("recenter-top-bottom"). We can not bind control 90 | keys into hydras, so this is bound to =L=. Successive calls will put 91 | the point in the middle, at the top or at the bottom of the screen. 92 | 93 | *** Kill, copy, (un)comment, select, fold 94 | 95 | See the hydra. 96 | 97 | Some actions (kill, copy, comment) will ask you again what to operate 98 | upon: the indented block, the whole level, or the paragraph. 99 | 100 | Other actions (toggle fold) act straight away on the indented block. 101 | 102 | So one can: 103 | 104 | - kill something 105 | - copy 106 | - comment 107 | - uncomment (only a paragraph) 108 | - toggle the fold (of the current indentation) 109 | - call imenu to go to another function definition, keeping the hydra. 110 | 111 | 112 | There are interactive functions too: 113 | 114 | : M-x indent-tools-kill-[hydra/body, tree, level, paragraph] 115 | 116 | Copying: 117 | 118 | : M-x indent-tools-copy-[hydra/body, tree, level, paragraph] 119 | 120 | 121 | *** Undo 122 | 123 | The hydra binds =_= to =undo-tree-undo=, so we can try commands and 124 | undo anything by staying inside it. 125 | 126 | ** Configure 127 | 128 | To know the indentation of the current major mode, call 129 | =indent-tools-indentation-of-current-mode=. 130 | 131 | If it doesn't know the current mode, see the variable 132 | =indent-tools-indentation-of-modes-alist=. It is an alist which 133 | associates a mode to a function which returns the desired indentation 134 | level (an int). 135 | 136 | If no indentation level is known for this mode, it uses 137 | =indent-tools-indentation-offset=, which defaults to Emacs' 138 | =standard-indent=. 139 | 140 | Example: 141 | 142 | #+BEGIN_SRC emacs-lisp 143 | (defun indent-tools-indentation-of-python () 144 | "Return Python's current indentation as an int, usually 4." 145 | (cond ((and (boundp 'python-indent-offset) 146 | (numberp python-indent-offset)) 147 | python-indent-offset))) 148 | 149 | ;; The alist. 150 | (setq indent-tools-indentation-of-modes-alist 151 | '( 152 | (python-mode . indent-tools-indentation-of-python) 153 | (yaml-mode . indent-tools-indentation-of-yaml) 154 | (jade-mode . indent-tools-indentation-of-jade) 155 | )) 156 | #+END_SRC 157 | 158 | ** Develop 159 | 160 | To run the unit tests, go into the tests file and run *ert*: 161 | : M-x ert 162 | and either choose a specific test, either keep =t= to run all. 163 | 164 | You'll have an ert buffer with passing tests in green, failing ones in 165 | red. Use TAB end ENTER in this buffer (à la org-mode). 166 | 167 | ** Ideas, todos 168 | 169 | [X] Demote. 170 | 171 | [X] Indent according to mode. Done for python, yaml and jade. 172 | 173 | [X] Do something with the default behaviour of =M-x indent-rigidly= which 174 | lets us indent interactively. Would be useful for jade templates. => 175 | just used the Hydra feature. 176 | 177 | [X] See if the utilities functions of mine on [[https://gitlab.com/emacs-stuff/my-elisp/blob/master/yaml-utils.el][yaml-utils]] can be useful 178 | (indent all siblings at once ? Move around siblings ?). 179 | 180 | See `move-text` in melpa to move regions up and down. 181 | 182 | [X] See how [[https://github.com/zenozeng/yafolding.el/blob/master/yafolding.el][yafolding]] did. 183 | 184 | ** See also 185 | - [[http://wikemacs.org/wiki/Json#json-navigator_-_navigate_json_presented_as_a_tree][json-navigator]] - to display any JSON document as a tree, 186 | which leafs you can unfold and follow. (Emacs 25.1) Based on the 187 | more generic [[https://github.com/DamienCassou/hierarchy][hierarchy]]. 188 | ** Change log 189 | 190 | - <2018-01-24 mer.> simplify =Kill=: it kills the indented block, no 191 | more choice to kill a paragraph or all the level in a hydra, not 192 | fitting in this package. 193 | - <2017-08-03 jeu.> =L= insteal of =C-l= to recenter the screen ("recenter-top-bottom"). 194 | - <2017-07-21 ven.> undo-tree within the hydra (press "_") 195 | - <2017-07-21 ven.> small fix for ">" indent: go to the beginning of text before action. 196 | - <2017-03-22 mer.> added Uncomment the paragraph 197 | -------------------------------------------------------------------------------- /indent-tools.el: -------------------------------------------------------------------------------- 1 | ;;; indent-tools.el --- Indent, navigate (and more) by blocks of indentation: yaml, python etc. 2 | 3 | ;; Copyright (C) 2016-2019 wtf public licence 4 | 5 | ;; Author: vindarel 6 | ;; URL: https://gitlab.com/emacs-stuff/indent-tools/ 7 | ;; Version: 0.1 8 | ;; Keywords: indentation, movements, navigation, kill, fold, yaml, python 9 | ;; Package-Requires: ((s "0") (hydra "0") (yafolding "0")) 10 | 11 | ;;; Commentary: 12 | 13 | ;; Indent, move around and act on code based on indentation, by indentation units. Perfect to navigate in a big yaml file or in Python code. 14 | 15 | ;;; Code: 16 | 17 | (require 'indent-tools-indentation-of) 18 | 19 | (require 'hydra) 20 | (require 'yafolding) 21 | 22 | (defvar indent-tools-node-regexp "\"?[a-zA-Z0-9(\"'-.{]" "A regexp to match the beginning of a yaml node. Should skip comments.") ;; Should be mode specific: skip comments, etc 23 | 24 | (defun indent-tools-current-line-indentation () 25 | "Return a string containing the spaces that make up the current line's indentation." 26 | (save-excursion 27 | (re-search-backward "^\\(\s*\\)" (line-beginning-position)) 28 | (buffer-substring-no-properties (match-beginning 1) (match-end 1)))) 29 | 30 | (defun indent-tools-on-blank-line-p () 31 | "Return true if we are on a blank line." 32 | (equal (line-beginning-position) (line-end-position))) 33 | 34 | (defun indent-tools-end-of-tree-point () 35 | "Get the point of the end of the indentend tree." 36 | (save-excursion 37 | (indent-tools-goto-end-of-tree) 38 | (point))) 39 | 40 | (defun indent-tools--on-last-line () 41 | "Return true if we are on the buffer's last line." 42 | (equal (line-number-at-pos) (count-lines (point-min) (point-max)))) 43 | 44 | (defun indent-tools-goto-end-of-tree () 45 | "Go to the end of the indented tree." 46 | (interactive) 47 | (let ((goal-column (length (indent-tools-current-line-indentation))) ;; see next-line doc 48 | (last-line-reached nil)) 49 | (beginning-of-line-text) 50 | (forward-line) 51 | (while (and (not last-line-reached) 52 | (or 53 | (indent-tools-on-blank-line-p) 54 | (string-equal (char-to-string (following-char)) " "))) 55 | (if (indent-tools--on-last-line) 56 | (setq last-line-reached t) 57 | (next-line))) 58 | (unless last-line-reached (forward-line -1)) 59 | (end-of-line) 60 | )) 61 | 62 | (defun indent-tools-goto-parent () 63 | "Go to this node's parent, one indentation level up." 64 | (interactive) 65 | (beginning-of-line-text) 66 | (if (not (s-blank? (indent-tools-current-line-indentation))) 67 | (progn 68 | (if (search-backward-regexp (concat "^" 69 | (s-left (- (length (indent-tools-current-line-indentation)) 70 | (indent-tools-indentation-of-current-mode)) 71 | (indent-tools-current-line-indentation)) 72 | indent-tools-node-regexp) 73 | nil t) 74 | (beginning-of-line-text) 75 | (message "you don't have more parents"))) 76 | (message "you don't have more parents"))) 77 | 78 | (defun indent-tools-goto-child () 79 | "Go down to the first child (line with greater indentation)." 80 | (interactive) 81 | (beginning-of-line-text) 82 | (unless (search-forward-regexp (concat "^" 83 | (indent-tools-current-line-indentation) 84 | (s-repeat (indent-tools-indentation-of-current-mode) " ") 85 | indent-tools-node-regexp) 86 | nil 87 | t) 88 | (message "you don't have more children.")) 89 | (beginning-of-line-text)) 90 | 91 | (defun indent-tools-select-end-of-tree () 92 | "Activate the mark until the end of the indentation tree." 93 | (interactive) 94 | (let ((beg (line-beginning-position)) 95 | (end (save-excursion 96 | (indent-tools-goto-end-of-tree) 97 | (point)))) 98 | (goto-char beg) 99 | (push-mark) 100 | (activate-mark) 101 | (goto-char end) 102 | )) 103 | 104 | (defun indent-tools-end-of-level () ;; OK needs more tests MORE TESTS PLZ 105 | "Go to the end of this indentation level." 106 | (interactive) 107 | (let* ((indentation (indent-tools-current-line-indentation)) 108 | (last-line-reached nil)) 109 | (beginning-of-line-text) 110 | (forward-line) 111 | (while (not last-line-reached) 112 | (if (indent-tools-on-blank-line-p) 113 | (forward-line)) 114 | (if (< (length (indent-tools-current-line-indentation)) 115 | (length indentation)) 116 | (setq last-line-reached t) 117 | (forward-line))) 118 | 119 | (beginning-of-line-text))) 120 | 121 | (defun indent-tools-end-of-level-point () 122 | "Get the point of the end of this indentation block." 123 | (save-excursion 124 | (indent-tools-end-of-level) 125 | (forward-line -1) 126 | (point))) 127 | 128 | (defun indent-tools-indent-end-of-level () 129 | "Indent until the end of this indentation level." 130 | (interactive) 131 | (let ((beg (line-beginning-position)) 132 | (end (indent-tools-end-of-level-point)) 133 | (offset (indent-tools-indentation-of-current-mode))) 134 | (indent-rigidly beg end offset))) 135 | 136 | (defun indent-tools-select () 137 | "Select the tree (useful to visualize). Also useful: highlight-indentation-current-column-mode." 138 | ; use a red hydra to cancel effects instead ? 139 | (interactive) 140 | (let ((beg (save-excursion 141 | (beginning-of-line-text) (point))) 142 | (end (indent-tools-end-of-tree-point))) 143 | (goto-char beg) 144 | (push-mark) 145 | (activate-mark) 146 | (goto-char end) 147 | )) 148 | 149 | (defun indent-tools-indent (&optional select) 150 | "Indent the current tree. 151 | 152 | The point can be anywhere on the line, it goes to the first 153 | non-blank character before action. 154 | 155 | SELECT: boolean (deprecated) in favor of hydra's feature." 156 | (interactive) 157 | (beginning-of-line-text) 158 | (let ((beg (save-excursion 159 | (beginning-of-line) (point))) 160 | (end (indent-tools-end-of-tree-point)) 161 | (indentation-level (indent-tools-indentation-of-current-mode))) 162 | (if select 163 | (call-interactively 'indent-rigidly t (vector beg end)) ;; hey… hydras do the job of repetition ! 164 | (indent-rigidly beg end indentation-level)))) 165 | 166 | (defun indent-tools-indent-paragraph () 167 | "Indent the current paragraph, for exple the block of text until a new line. The paragraph is the one you would jump with `forward-paragraph'." 168 | (interactive) 169 | (let ((beg (line-beginning-position)) 170 | (end (save-excursion 171 | (forward-paragraph) 172 | (point)))) 173 | (indent-rigidly beg end (indent-tools-indentation-of-current-mode)))) 174 | 175 | (defun indent-tools-indent-end-of-defun () 176 | "Indent until the end of the current function." 177 | (interactive) 178 | (let ((beg (line-beginning-position)) 179 | (end (save-excursion 180 | (end-of-defun) 181 | (point))) 182 | (indentation-level (indent-tools-indentation-of-current-mode))) 183 | (if (equal beg end) 184 | ;; case we're at the last defun or in __main__, not a defun. 185 | (setq end (point-max))) 186 | (indent-rigidly beg end indentation-level) 187 | )) 188 | 189 | (defun indent-tools-indent-space () 190 | "Indent with only a space (specially useful in jade-mode)." 191 | (interactive) 192 | (let ((beg (line-beginning-position)) 193 | (end (indent-tools-end-of-tree-point)) 194 | (indentation-level (indent-tools-indentation-of-current-mode))) 195 | (save-excursion 196 | (replace-regexp "^" " " nil beg end)))) 197 | 198 | (defun indent-tools-demote () 199 | "De-indent the current indented tree." 200 | ;; todo: factorize 201 | (interactive) 202 | (let ((beg (save-excursion 203 | (beginning-of-line) (point))) 204 | (end (indent-tools-end-of-tree-point)) 205 | (indentation-level (- (indent-tools-indentation-of-current-mode)))) 206 | (indent-rigidly beg end indentation-level))) 207 | 208 | (defun indent-tools-comment () 209 | "Comment the current indentation block." 210 | (interactive) 211 | (beginning-of-line-text) 212 | (let ((beg (line-beginning-position)) 213 | (end (indent-tools-end-of-tree-point))) 214 | (comment-region beg end))) 215 | 216 | (defun indent-tools-uncomment () 217 | "Uncomment the paragraph and go to its end, in case we want to carry on un-commeting. 218 | Simple stuff, since the comments hide us the indentation levels." 219 | (interactive) 220 | (let ((beg (line-beginning-position)) 221 | (end (save-excursion 222 | (forward-paragraph) 223 | (point)))) 224 | (uncomment-region beg end) 225 | (goto-char end))) 226 | 227 | (defun indent-tools-goto-next-sibling () 228 | "Go to the next element of the same level." 229 | (interactive) 230 | (end-of-line) 231 | (let ((yaml-regexp indent-tools-node-regexp)) 232 | ;; (setq yaml-element-regexp ".*") ;; should not start by a comment 233 | (or (search-forward-regexp (concat "^" 234 | (indent-tools-current-line-indentation) 235 | ;; "[^\s-]" ;; exclude following whitespaces 236 | yaml-regexp) 237 | nil ; don't bound the search 238 | t ; if search fails just return nil, no error 239 | ) 240 | (message "We didn't find a next sibling.")) 241 | (beginning-of-line-text))) 242 | 243 | (defun indent-tools-goto-previous-sibling () 244 | "Go to previous sibling." 245 | (interactive) 246 | (beginning-of-line-text) 247 | (let ((current-line-indentation (indent-tools-current-line-indentation))) 248 | (beginning-of-line) 249 | (or (search-backward-regexp (concat "^" 250 | current-line-indentation 251 | ;; "[^\s-]" 252 | indent-tools-node-regexp) 253 | nil 254 | t) 255 | (message "We didn't find a previous sibling.")) 256 | (beginning-of-line-text))) 257 | 258 | ;;;;;;; copy 259 | (defun indent-tools-copy (what) 260 | "Copy some text in the kill ring. 261 | 262 | WHAT: str of 'paragraph', 'tree' or 'level'." 263 | (let ((beg (line-beginning-position)) 264 | (end (cond 265 | ((equal what "paragraph") (save-excursion 266 | (forward-paragraph) 267 | (point))) 268 | ((equal what "tree") (indent-tools-end-of-tree-point)) 269 | ((equal what "level") (indent-tools-end-of-level-point)) 270 | ))) 271 | (kill-ring-save beg end) 272 | (message (format "Copied %s" what)))) 273 | 274 | (defhydra indent-tools-copy-hydra (:color blue :after-exit (indent-tools-hydra/body)) 275 | "Mini Hydra with the copy options. Calls the big hydra on exit." 276 | (">" (indent-tools-copy "tree") "this indented tree") 277 | ("l" (indent-tools-copy "level") "all level") 278 | ("p" (indent-tools-copy "paragraph") "paragraph")) 279 | 280 | ;;;;;;; kill 281 | (defun indent-tools-kill-tree () 282 | "Delete the indentatation block at point." 283 | (interactive) 284 | (beginning-of-line-text) 285 | (let ((beg (save-excursion 286 | (beginning-of-line-text) 287 | (point))) 288 | (end (indent-tools-end-of-tree-point))) 289 | (kill-region beg end))) 290 | 291 | 292 | ;;;;;; General hydra 293 | (defhydra indent-tools-hydra (:color red :hint nil) 294 | " 295 | ^Indent^ | ^Navigation^ | ^Actions^ 296 | ------------------+---------------------+----------- 297 | _>_ indent | _j_ v | _K_ kill 298 | _<_ de-indent | _k_ ʌ | _i_ imenu 299 | _l_ end of level | _n_ next sibling | _C_ Copy… 300 | _E_ end of fn | _p_ previous sibling| _c_ comment 301 | _P_ paragraph | _u_ up parent | _U_ uncomment (paragraph) 302 | _SPC_ space | _d_ down child | _f_ fold 303 | ___ undo | _e_ end of tree | _q_ quit 304 | " 305 | 306 | (">" indent-tools-indent) 307 | ("<" indent-tools-demote) 308 | ("E" indent-tools-indent-end-of-defun) 309 | ("c" indent-tools-comment) 310 | ("U" indent-tools-uncomment) 311 | ("P" indent-tools-indent-paragraph) 312 | ("l" indent-tools-indent-end-of-level) 313 | ("K" indent-tools-kill-tree) 314 | ("C" indent-tools-copy-hydra/body :color blue) 315 | ("s" indent-tools-select) 316 | ("e" indent-tools-goto-end-of-tree) 317 | ("u" indent-tools-goto-parent) 318 | ("d" indent-tools-goto-child) 319 | ("S" indent-tools-select-end-of-tree) 320 | ("n" indent-tools-goto-next-sibling) 321 | ("p" indent-tools-goto-previous-sibling) 322 | ("i" helm-imenu) 323 | ("j" forward-line) 324 | ("k" previous-line) 325 | ("SPC" indent-tools-indent-space) 326 | ("_" undo-tree-undo) 327 | ("L" recenter-top-bottom) 328 | ("f" yafolding-toggle-element) 329 | ("q" nil) 330 | ) 331 | (defalias 'hydra-indent-tools 'indent-tools-hydra) 332 | 333 | (defcustom indent-tools-keymap-prefix (kbd "C-c >") 334 | "Indent tools keymap prefix." 335 | :group 'indent-tools 336 | :type 'string) 337 | 338 | ;;; Minor mode 339 | (defvar indent-tools-command-map 340 | (let ((map (make-sparse-keymap))) 341 | (define-key map (kbd ">") #'indent-tools-indent) 342 | (define-key map (kbd "<") #'indent-tools-demote) 343 | (define-key map (kbd "n") #'indent-tools-goto-next-sibling) 344 | (define-key map (kbd "p") #'indent-tools-goto-previous-sibling) 345 | (define-key map (kbd "u") #'indent-tools-goto-parent) 346 | (define-key map (kbd "d") #'indent-tools-goto-child) 347 | (define-key map (kbd "e") #'indent-tools-goto-end-of-tree) 348 | (define-key map (kbd "l") #'indent-tools-end-of-level) 349 | map) 350 | "Keymap for indent-tools commands.") 351 | (fset 'indent-tools-command-map indent-tools-command-map) 352 | 353 | (defvar indent-tools-mode-map 354 | (let ((map (make-sparse-keymap))) 355 | (define-key map indent-tools-keymap-prefix 'indent-tools-command-map) 356 | map) 357 | "Keymap for indent tools mode.") 358 | 359 | ;;;###Autoload 360 | (define-minor-mode indent-tools-minor-mode 361 | "Navigate, indent and act on blocks delemited by their indentation level. 362 | 363 | \\{indent-tools-mode-map}" 364 | :global nil 365 | :keymap indent-tools-mode-map 366 | :group 'indent-tools 367 | :require 'indent-tools 368 | ) 369 | 370 | (provide 'indent-tools) 371 | ;;; indent-tools.el ends here 372 | --------------------------------------------------------------------------------