emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master fd2bf1f: New package: cycle-quotes


From: Simen Heggestøyl
Subject: [elpa] master fd2bf1f: New package: cycle-quotes
Date: Sun, 5 Jun 2016 10:46:32 +0000 (UTC)

branch: master
commit fd2bf1fbfc8ae6d47000199d020fefe516328358
Author: Simen Heggestøyl <address@hidden>
Commit: Simen Heggestøyl <address@hidden>

    New package: cycle-quotes
---
 packages/cycle-quotes/cycle-quotes-test.el |   83 ++++++++++++++++
 packages/cycle-quotes/cycle-quotes.el      |  145 ++++++++++++++++++++++++++++
 2 files changed, 228 insertions(+)

diff --git a/packages/cycle-quotes/cycle-quotes-test.el 
b/packages/cycle-quotes/cycle-quotes-test.el
new file mode 100644
index 0000000..777bd82
--- /dev/null
+++ b/packages/cycle-quotes/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 <address@hidden>
+
+;; 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/packages/cycle-quotes/cycle-quotes.el 
b/packages/cycle-quotes/cycle-quotes.el
new file mode 100644
index 0000000..faac0cb
--- /dev/null
+++ b/packages/cycle-quotes/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 <address@hidden>
+;; 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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]