summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2017-01-29 07:22:33 -0700
committerSean Whitton <spwhitton@spwhitton.name>2017-01-29 07:22:33 -0700
commita73b9498d420461b4a94cd5c8057665f163a29fd (patch)
treebb9a4f86406994c3039cb5ee111404f2459193af
New upstream version 0.1
-rw-r--r--ChangeLog4
-rw-r--r--cycle-quotes-pkg.el2
-rw-r--r--cycle-quotes-test.el83
-rw-r--r--cycle-quotes.el145
4 files changed, 234 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..90db773
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4 @@
+2016-06-05 Simen Heggestøyl <simen@e5r.no>
+
+ New package: cycle-quotes
+
diff --git a/cycle-quotes-pkg.el b/cycle-quotes-pkg.el
new file mode 100644
index 0000000..a5052c1
--- /dev/null
+++ b/cycle-quotes-pkg.el
@@ -0,0 +1,2 @@
+;; Generated package description from cycle-quotes.el
+(define-package "cycle-quotes" "0.1" "Cycle between quote styles" 'nil :url "http://elpa.gnu.org/packages/cycle-quotes.html" :keywords '("convenience"))
diff --git a/cycle-quotes-test.el b/cycle-quotes-test.el
new file mode 100644
index 0000000..777bd82
--- /dev/null
+++ b/cycle-quotes-test.el
@@ -0,0 +1,83 @@
+;;; cycle-quotes-test.el --- Tests for cycle-quotes.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+;; Author: Simen Heggestøyl <simenheg@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cycle-quotes)
+(require 'ert)
+;; For testing triple quotes
+(require 'python)
+
+(ert-deftest test-cycle-quotes--set-quote-chars ()
+ (with-temp-buffer
+ (let ((st (make-syntax-table)))
+ (set-syntax-table st)
+ (modify-syntax-entry ?a "\"")
+ (modify-syntax-entry ?b "\"")
+ (cycle-quotes--set-quote-chars)
+ (should (= (length cycle-quotes--quote-chars) 3))
+ (should (memq ?a cycle-quotes--quote-chars))
+ (should (memq ?b cycle-quotes--quote-chars))
+ (should (memq ?\" cycle-quotes--quote-chars)))))
+
+(ert-deftest test-cycle-quotes--next-quote-char ()
+ (let ((cycle-quotes--quote-chars '(?a)))
+ (should (= (cycle-quotes--next-quote-char ?a) ?a)))
+ (let ((cycle-quotes--quote-chars '(?a ?b)))
+ (should (= (cycle-quotes--next-quote-char ?a) ?b)))
+ (let ((cycle-quotes--quote-chars '(?a ?b ?c)))
+ (should (= (cycle-quotes--next-quote-char ?c) ?a))))
+
+(ert-deftest test-cycle-quotes--fix-escapes ()
+ (with-temp-buffer
+ (insert "b\\baabc\\b")
+ (cycle-quotes--fix-escapes (point-min) (point-max) ?a ?b)
+ (should (equal (buffer-string) "bb\\a\\abcb"))))
+
+(ert-deftest test-cycle-quotes ()
+ (with-temp-buffer
+ (let ((st (make-syntax-table)))
+ (set-syntax-table st)
+ (modify-syntax-entry ?' "\"")
+ (modify-syntax-entry ?` "\"")
+ (insert "\"Hi, it's me!\"")
+ (goto-char 5)
+ (cycle-quotes)
+ (should (equal (buffer-string) "`Hi, it's me!`"))
+ (cycle-quotes)
+ (should (equal (buffer-string) "'Hi, it\\'s me!'"))
+ (cycle-quotes)
+ (should (equal (buffer-string) "\"Hi, it's me!\"")))))
+
+(ert-deftest test-cycle-quotes-triple-quotes ()
+ (with-temp-buffer
+ (python-mode)
+ (insert "'''Triple quotes, as found in Python.'''")
+ (goto-char 5)
+ (cycle-quotes)
+ (should (equal (buffer-string)
+ "\"\"\"Triple quotes, as found in Python.\"\"\""))
+ (cycle-quotes)
+ (should (equal (buffer-string)
+ "'''Triple quotes, as found in Python.'''"))))
+
+(provide 'cycle-quotes-test)
+;;; cycle-quotes-test.el ends here
diff --git a/cycle-quotes.el b/cycle-quotes.el
new file mode 100644
index 0000000..faac0cb
--- /dev/null
+++ b/cycle-quotes.el
@@ -0,0 +1,145 @@
+;;; cycle-quotes.el --- Cycle between quote styles -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+;; Author: Simen Heggestøyl <simenheg@gmail.com>
+;; Keywords: convenience
+;; Version: 0.1
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides the `cycle-quotes' command to cycle between
+;; different string quote styles. For instance, in JavaScript, there's
+;; three string quote characters: ", ` and '. In a JavaScript buffer,
+;; with point located someplace within the string, `cycle-quotes' will
+;; cycle between the following quote styles each time it's called:
+;;
+;; --> "Hi, it's me!" --> `Hi, it's me!` --> 'Hi, it\'s me!' --
+;; | |
+;; ------------------------------------------------------------
+;;
+;; As seen in the above example, `cycle-quotes' tries to escape and
+;; unescape quote characters intelligently.
+
+;;; Code:
+
+(defvar-local cycle-quotes--quote-chars '()
+ "A list of string quote characters for the current mode.
+Set the first time `cycle-quotes' is called in a buffer.")
+
+(defvar-local cycle-quotes--quote-chars-mode nil
+ "The latest mode that the quote char list was last computed for.
+If this is different from the current mode, the quote chars need
+to be recomputed.")
+
+(defun cycle-quotes--set-quote-chars ()
+ "Set the quote chars for the current syntax table."
+ (let ((syntax-table (syntax-table)))
+ (while syntax-table
+ (map-char-table
+ (lambda (char-code-or-range syntax)
+ (when (equal (syntax-class syntax) 7)
+ (if (consp char-code-or-range)
+ (let ((from (car char-code-or-range))
+ (to (cdr char-code-or-range)))
+ (dolist (char-code (number-sequence from to))
+ (add-to-list 'cycle-quotes--quote-chars char-code)))
+ (add-to-list
+ 'cycle-quotes--quote-chars char-code-or-range))))
+ syntax-table)
+ (setq syntax-table (char-table-parent syntax-table)))
+ (setq-local cycle-quotes--quote-chars-mode major-mode)))
+
+(defun cycle-quotes--next-quote-char (char)
+ "Return quote char after CHAR."
+ (let ((list-from-char (member char cycle-quotes--quote-chars)))
+ (when list-from-char
+ (if (= (length list-from-char) 1)
+ (car cycle-quotes--quote-chars)
+ (cadr list-from-char)))))
+
+(defun cycle-quotes--fix-escapes (beg end escape-char unescape-char)
+ "Fix character escapes between BEG and END.
+Instances of ESCAPE-CHAR will be escaped by `\', while instances
+where UNESCAPE-CHAR are escaped by `\' will have their escape
+character removed."
+ (let ((escape-string (string escape-char))
+ (unescape-string (string unescape-char)))
+ (save-excursion
+ (goto-char end)
+ (while (search-backward (concat "\\" unescape-string) beg t)
+ (replace-match unescape-string nil t)))
+ (save-excursion
+ (goto-char end)
+ (while (search-backward escape-string beg t)
+ (replace-match (concat "\\" escape-string) nil t)
+ (forward-char -1)))))
+
+;;;###autoload
+(defun cycle-quotes ()
+ "Cycle between string quote styles."
+ (interactive)
+ (unless (eq major-mode cycle-quotes--quote-chars-mode)
+ (cycle-quotes--set-quote-chars))
+ (if (< (length cycle-quotes--quote-chars) 2)
+ (message "The current mode has no alternative quote syntax")
+ (let ((quote-char (nth 3 (syntax-ppss))))
+ (if (not quote-char)
+ (message "Not inside a string")
+ (let ((inside-generic-string (eq quote-char t))
+ ;; Can't use `save-excursion', because the marker will get
+ ;; deleted if point is at the beginning of the string.
+ (start-pos (point)))
+ (when inside-generic-string
+ (skip-syntax-backward "^|")
+ (forward-char -1)
+ (setq quote-char (char-after)))
+ (let ((new-quote-char
+ (cycle-quotes--next-quote-char
+ (if inside-generic-string
+ (char-after)
+ quote-char))))
+ (unless inside-generic-string
+ (search-backward-regexp
+ (concat "\\([^\\]" (string quote-char) "\\)\\|"
+ "^" (string quote-char)))
+ (when (match-beginning 1)
+ (forward-char)))
+ (let ((repeat
+ ;; Handle multiple quotes, such as Python's triple
+ ;; quotes.
+ (save-excursion
+ (search-forward-regexp
+ (format "%c+" quote-char))
+ (- (match-end 0) (match-beginning 0)))))
+ (save-excursion
+ (let ((beg (point)))
+ (forward-sexp)
+ ;; `forward-sexp' fails to jump to the matching quote
+ ;; in some modes, for instance `js2-mode'.
+ (skip-syntax-backward "^\"|")
+ (cycle-quotes--fix-escapes
+ (+ beg 1) (+ (point) 1) new-quote-char quote-char))
+ (delete-char (- repeat))
+ (dotimes (_ repeat)
+ (insert new-quote-char)))
+ (delete-char repeat)
+ (dotimes (_ repeat)
+ (insert new-quote-char))))
+ (goto-char start-pos))))))
+
+(provide 'cycle-quotes)
+;;; cycle-quotes.el ends here