├── Makefile ├── .travis.yml ├── README.md ├── test └── unit-test.el └── emacs-surround.el /Makefile: -------------------------------------------------------------------------------- 1 | EMACS ?= emacs 2 | 3 | test: 4 | ${EMACS} -Q -batch -L . -l test/unit-test.el -f ert-run-tests-batch-and-exit 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | env: 3 | matrix: 4 | - EMACS=emacs24 5 | - EMACS=emacs-snapshot 6 | before_install: 7 | - sudo add-apt-repository -y ppa:cassou/emacs 8 | - sudo add-apt-repository -y ppa:ubuntu-elisp/ppa 9 | - sudo apt-get update -qq 10 | - sudo apt-get install -qq $EMACS 11 | - if [ "$EMACS" = 'emacs-snapshot' ]; then 12 | sudo apt-get install -qq emacs-snapshot-el emacs-snapshot-nox; 13 | fi 14 | script: 15 | make test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emacs-surround 2 | 3 | [![Build Status](https://travis-ci.org/ganmacs/emacs-surround.svg?branch=master)](https://travis-ci.org/ganmacs/emacs-surround) 4 | 5 | emacs-surround is like [vim.surround](https://github.com/tpope/vim-surround). 6 | This plugins provides easily change, delete and insert such surrounding in pairs. 7 | 8 | ## Example 9 | 10 | ### change 11 | 12 | Press `C-q "'` 13 | 14 | ```rb 15 | "Hello| world!" 16 | ``` 17 | 18 | Change it to 19 | 20 | ```rb 21 | 'Hello world!' 22 | ``` 23 | 24 | ### insert 25 | 26 | Press `C-q i"` or `C-q " ` 27 | 28 | ```rb 29 | Hel|lo 30 | ``` 31 | 32 | Change it to 33 | 34 | ```rb 35 | "Hello" 36 | ``` 37 | 38 | ### delete 39 | 40 | Press `C-q d"` 41 | 42 | ```rb 43 | "He|llo \"world\"" 44 | ``` 45 | 46 | Change it to 47 | 48 | ```rb 49 | Hello \"world\" 50 | ``` 51 | 52 | ## Installation and Settings 53 | 54 | Use `cask` or `el-get` or `clone` form `https://github.com/ganmacs/emacs-surround.git` 55 | 56 | 57 | And add this script your `.init.el` 58 | 59 | ```lisp 60 | (require 'emacs-surround) 61 | (global-set-key (kbd "C-q") 'emacs-surround) 62 | ``` 63 | 64 | ## Customize 65 | 66 | You can use custimze pair. 67 | 68 | For Example Add this line to your `.init.el` 69 | 70 | ```lisp 71 | (add-to-list 'emacs-surround-alist '("}" . ("{ " . " }"))) 72 | ``` 73 | 74 | Now press `C-q {}` 75 | 76 | ```rb 77 | [1, 2, 3].each {p |1+i} 78 | ``` 79 | 80 | Change it to 81 | 82 | ```rb 83 | [1, 2, 3].each { p 1+i } 84 | ``` 85 | -------------------------------------------------------------------------------- /test/unit-test.el: -------------------------------------------------------------------------------- 1 | ;;; test/unit-test -- some unit test -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015 ganmacs 4 | 5 | ;; This file is part of GNU Emacs. 6 | 7 | ;; GNU Emacs is free software: you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; GNU Emacs is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with GNU Emacs. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;; execute 23 | 24 | ;; emacs --directory . -batch -l test/unit-test.el -f ert-run-tests-batch-and-exit 25 | 26 | ;;; Code: 27 | 28 | (require 'ert) 29 | (require 'emacs-surround) 30 | 31 | (defmacro test-emacs-surround (fn src expect) 32 | (let ((got (cl-gensym))) 33 | `(let ((got (with-temp-buffer 34 | (insert ,src) 35 | (goto-char (+ 1 (point-min))) 36 | ,fn 37 | (buffer-string)))) 38 | (should (string= got ,expect))))) 39 | 40 | ;; insert 41 | 42 | (ert-deftest test-eamcs-surround-insert () 43 | "emacs-surround-insert WITHOUT region" 44 | (test-emacs-surround (emacs-surround-insert "'") 45 | "Hello World" "'Hello' World")) 46 | 47 | (ert-deftest test-eamcs-surround-insert-with-region () 48 | "emacs-surround-insert WITH region" 49 | (let* ((input "Hello World") 50 | (got (with-temp-buffer 51 | (insert input) 52 | (goto-char (point-min)) 53 | (set-mark (point)) 54 | (goto-char (point-max)) 55 | (activate-mark) 56 | (emacs-surround-insert "'") 57 | (buffer-string)))) 58 | (should (string= got "'Hello World'")))) 59 | 60 | ;; line 61 | 62 | (ert-deftest test-eamcs-surround-line () 63 | "emacs-surround-line" 64 | (test-emacs-surround (emacs-surround-line "(") 65 | " Hello World" " (Hello World)")) 66 | 67 | ;; delete 68 | 69 | (ert-deftest test-eamcs-surround-delete () 70 | "emacs-surround-delete WITHOUT region" 71 | (test-emacs-surround (emacs-surround-delete "(") 72 | "(Hello World)" "Hello World")) 73 | 74 | (ert-deftest test-eamcs-surround-delete-with-multiple-parens () 75 | "emacs-surround-delete WITH multiple parens" 76 | (test-emacs-surround (emacs-surround-delete "(") 77 | "(Hello (World) !)" "Hello (World) !")) 78 | 79 | (ert-deftest test-eamcs-surround-delete-with-multiple-quote () 80 | "emacs-surround-delete WITH multiple parens" 81 | (test-emacs-surround (emacs-surround-delete "\"") 82 | "\"Hello \\\"World\\\"!\"" "Hello \\\"World\\\"!")) 83 | 84 | (ert-deftest test-eamcs-surround-delete-with-multiple-line () 85 | "emacs-surround-delete WITH multiple parens" 86 | (test-emacs-surround (emacs-surround-delete "(") 87 | "(Hello [W\norld] !)" "Hello [W\norld] !")) 88 | 89 | ;; change 90 | 91 | (ert-deftest test-eamcs-surround-change () 92 | "emacs-surround-change" 93 | (test-emacs-surround (emacs-surround-change "'" "[") 94 | "'Hello World'" "[Hello World]")) 95 | 96 | (ert-deftest test-eamcs-surround-change-with-multiple-parens () 97 | "emacs-surround-change WITH multiple parens" 98 | (test-emacs-surround (emacs-surround-change "(" "'") 99 | "(Hello (World) !)" "'Hello (World) !'")) 100 | 101 | (ert-deftest test-eamcs-surround-change-with-multiple-parens2 () 102 | "emacs-surround-change WITH multiple parens" 103 | (let* ((input "(Hello (World) aaaa!)") 104 | (got (with-temp-buffer 105 | (insert input) 106 | (goto-char (- (point-max) 3)) 107 | (emacs-surround-change "(" "'") 108 | (buffer-string)))) 109 | (should (string= got "'Hello (World) aaaa!'")))) 110 | 111 | (ert-deftest test-eamcs-surround-change-with-multiple-lines () 112 | "emacs-surround-change WITH multiple lines" 113 | (test-emacs-surround (emacs-surround-change "(" "[") 114 | "(Hello \nWorld)" "[Hello \nWorld]")) 115 | 116 | (ert-deftest test-eamcs-surround-change-with-multiple-quote () 117 | "emacs-surround-change WITH multiple parens" 118 | (test-emacs-surround (emacs-surround-change "\"" "(") 119 | "\"Hello \\\"World\\\"!\"" "(Hello \\\"World\\\"!)")) 120 | 121 | ;; customize 122 | 123 | (ert-deftest test-eamcs-surround-change-for-customize () 124 | "emacs-surround-change for customize" 125 | (let ((emacs-surround-alist (cons '("b" . ("{ " . " }")) emacs-surround-alist))) 126 | (test-emacs-surround (emacs-surround-change "{" "b") 127 | "{Hello world}" "{ Hello world }"))) 128 | 129 | ;;; tests/unit-test.el ends here 130 | -------------------------------------------------------------------------------- /emacs-surround.el: -------------------------------------------------------------------------------- 1 | ;;; emacs-surround.el --- surround symbol or change surrouning char -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015 ganmacs 4 | 5 | ;; Author: ganmacs 6 | ;; Maintainer: ganmacs 7 | ;; URL: https://github.com/ganmacs/emacs-surround 8 | ;; Version: 0.0.1 9 | 10 | ;; This file is NOT part of GNU Emacs. 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;;; Code: 28 | 29 | (defgroup emacs-surround nil 30 | "surround.vim for Emacs" 31 | :group 'surround) 32 | 33 | (defcustom emacs-surround-alist 34 | '(("" . ("" . "")) 35 | ("'" . ("'" . "'")) 36 | ("(" . ("(" . ")")) 37 | ("{" . ("{" . "}")) 38 | ("[" . ("[" . "]")) 39 | ("/" . ("/" . "/")) 40 | ("\"" . ("\"" . "\""))) 41 | "Surround key list." 42 | :group 'surround) 43 | 44 | (defvar emacs-surround-separator "^\s()[]:;,=.\n{}") 45 | 46 | (defun emacs-surround-mark-region-line () 47 | "Return list which is begening point of line and end point of line." 48 | (let ((start (progn (back-to-indentation) (point))) 49 | (end (progn (goto-char (line-end-position)) (point)))) 50 | (list start end))) 51 | 52 | ;; (push-mark (point)) 53 | 54 | (defun emacs-surround-mark-region-sep () 55 | "Return list which is start of symbol and end of symbol." 56 | (let ((start (progn (skip-chars-backward emacs-surround-separator) (point))) 57 | (end (progn (skip-chars-forward emacs-surround-separator) (point)))) 58 | (list start end))) 59 | 60 | (defun emacs-surround-same-count-p (str a b) 61 | "Check that A and B appearing number in STR are same or not." 62 | (cl-flet ((count-match-str (regex) 63 | (with-temp-buffer 64 | (insert str) 65 | (goto-char (point-min)) 66 | (count-matches regex)))) 67 | (not (= (count-match-str a) 68 | (count-match-str b))))) 69 | 70 | (defun emacs-surround-quote-p (type) 71 | "Return quote string or not. 72 | TYPE is `forward` or `backward`." 73 | (let ((min (point-at-bol)) 74 | (max (point-at-eol)) 75 | (ppoint (if (eq type 'backward) (point) (- (point) 1)))) 76 | (defun iter (i p) 77 | (if (and (<= min p) (<= p max)) 78 | (if (= (char-before p) 92) ; backquote 79 | (iter (+ i 1) (- p 1)) 80 | (= (mod i 2) 1)))) 81 | (iter 0 ppoint))) 82 | 83 | (defun emacs-surround-mark-between (prefix suffix) 84 | "Return list whch in PREFIX point and SUFFIX point." 85 | (cl-flet ((search-prefix () (search-backward prefix (point-min) nil 1)) 86 | (search-suffix () (search-forward suffix (point-max) nil 1)) 87 | (same-p (s e) (emacs-surround-same-count-p 88 | (buffer-substring s e) prefix suffix))) 89 | (let* ((origin (point)) 90 | (same-surrounds-p (string= prefix suffix))) 91 | (defun search-prefix-to-suffix () 92 | (goto-char origin) 93 | (let* ((start (progn 94 | (search-prefix) 95 | (while (unless suffix (emacs-surround-quote-p 'backward)) (search-prefix)) 96 | (point))) 97 | (end (progn 98 | (forward-char) 99 | (search-suffix) 100 | (while (if same-surrounds-p 101 | (emacs-surround-quote-p 'forward) 102 | (same-p start (point))) 103 | (search-suffix)) 104 | (point)))) 105 | (list start end))) 106 | (defun search-suffix-to-prefix () 107 | (goto-char origin) 108 | (let* ((end (progn 109 | (search-suffix) 110 | (while (unless suffix (emacs-surround-quote-p 'forward)) (search-suffix)) 111 | (point))) 112 | (start (progn 113 | (backward-char) 114 | (search-prefix) 115 | (while (if same-surrounds-p 116 | (emacs-surround-quote-p 'forward) 117 | (same-p (point) end)) 118 | (search-prefix)) 119 | (point)))) 120 | (list start end))) 121 | (let* ((l1 (search-suffix-to-prefix)) 122 | (l2 (search-prefix-to-suffix)) 123 | (start (min (car l1) (car l2))) 124 | (end (max (cadr l1) (cadr l2)))) 125 | (list start end))))) 126 | 127 | (defun emacs-surround-get-alist (key) 128 | "Get list by emacs-surround-alit with KEY." 129 | (cdr (or (assoc key emacs-surround-alist) 130 | (assoc "" emacs-surround-alist)))) 131 | 132 | (defun emacs-surround-wrap (str prefix &optional suffix) 133 | "Wrap STR with PREFIX and SUFFIX(if suffix exists)." 134 | (concat prefix str (or suffix prefix))) 135 | 136 | (defun emacs-surround-replace (str from to) 137 | "Replace FROM to TO in STR. 138 | \\(FROM\\)STR\\(FROM\\) -> \\(TO\\)STR\\(TO\\)." 139 | (let* ((f-prefix (car from)) (f-suffix (cdr from)) 140 | (t-prefix (car to)) (t-suffix (cdr to)) 141 | (regx (format "^%s\\(\\(.\\|\n\\)*\\)%s$" f-prefix f-suffix))) 142 | (if (string-match regx str) 143 | (let ((match (match-string 1 str))) 144 | (emacs-surround-wrap match t-prefix t-suffix))))) 145 | 146 | (defun emacs-surround-cut-region (region) 147 | "Cut region REGION car to REGION cdar." 148 | (apply 'buffer-substring region)) 149 | 150 | (defun emacs-surround-region-list (fn) 151 | "If 'mark-active then region list else call FN and return list." 152 | (if (use-region-p) 153 | (list (region-beginning) (region-end)) 154 | (funcall fn))) 155 | 156 | (defun emacs-surround-helper (mark-fn prefix suffix) 157 | "Helper function emacs-surround (inset|delte|line|change). 158 | MARK-FN is regioning function. 159 | PREFIX and SUFFIX are replace string." 160 | (let ((now (point))) 161 | (let* ((region (emacs-surround-region-list mark-fn)) 162 | (target-str (emacs-surround-cut-region region)) 163 | (replaced-str (emacs-surround-replace 164 | target-str 165 | (emacs-surround-get-alist prefix) 166 | (emacs-surround-get-alist suffix)))) 167 | (if replaced-str 168 | (progn 169 | (apply 'delete-region region) 170 | (insert replaced-str) 171 | (goto-char now)) 172 | (message "not found prefix and suffix"))))) 173 | 174 | (defun emacs-surround-insert (str) 175 | "Insert surround string, STR." 176 | (emacs-surround-helper 'emacs-surround-mark-region-sep "" str)) 177 | 178 | (defun emacs-surround-delete (str) 179 | "Delete surround string, STR." 180 | (let ((s (emacs-surround-get-alist str))) 181 | (emacs-surround-helper (lambda () (emacs-surround-mark-between (car s) (cdr s))) 182 | str ""))) 183 | 184 | (defun emacs-surround-line (str) 185 | "Wrap line with STR." 186 | (emacs-surround-helper 'emacs-surround-mark-region-line "" str)) 187 | 188 | (defun emacs-surround-change (to end) 189 | "Change surround string TO into END." 190 | (let ((s (emacs-surround-get-alist to))) 191 | (emacs-surround-helper (lambda () (emacs-surround-mark-between (car s) (cdr s))) 192 | to end))) 193 | 194 | ;;;###autoload 195 | (defun emacs-surround () 196 | "Surround or Delete symbol etc. 197 | if cmd1 is i(insert), surround at-point-symbol. 198 | if cmd1 is d(delete), delete surround cmd2 char. 199 | if cmd1 is l(line), surround line which point is here. 200 | else change surround cmd1 to cmd2" 201 | (interactive) 202 | (let* ((cmd1 (char-to-string (read-char))) 203 | (_cmd2 (read-char)) 204 | (cmd2 (char-to-string _cmd2))) 205 | (cond ((string= cmd1 "i") (emacs-surround-insert cmd2)) 206 | ((string= cmd1 "d") (emacs-surround-delete cmd2)) 207 | ((string= cmd1 "l") (emacs-surround-line cmd2)) 208 | (t (if (= 13 _cmd2) ;return 209 | (emacs-surround-insert cmd1) 210 | (emacs-surround-change cmd1 cmd2)))))) 211 | 212 | (provide 'emacs-surround) 213 | 214 | ;;; emacs-surround.el ends here 215 | --------------------------------------------------------------------------------