json-mode.el (9371B)
1 ;;; json-mode.el --- Major mode for editing JSON files. 2 3 ;; Copyright (C) 2011-2014 Josh Johnston 4 5 ;; Author: Josh Johnston 6 ;; URL: https://github.com/joshwnj/json-mode 7 ;; Package-Version: 1.8.0 8 ;; Package-Commit: eedb4560034f795a7950fa07016bd4347c368873 9 ;; Version: 1.6.0 10 ;; Package-Requires: ((json-snatcher "1.0.0") (emacs "24.4")) 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 <http://www.gnu.org/licenses/>. 24 25 ;;; Commentary: 26 27 ;; extend the builtin js-mode's syntax highlighting 28 29 ;;; Code: 30 31 (require 'js) 32 (require 'rx) 33 (require 'json-snatcher) 34 35 (defgroup json-mode '() 36 "Major mode for editing JSON files." 37 :group 'js) 38 39 ;;;###autoload 40 (defconst json-mode-standard-file-ext '(".json" ".jsonld") 41 "List of JSON file extensions.") 42 43 ;; This is to be sure the customization is loaded. Otherwise, 44 ;; autoload discards any defun or defcustom. 45 ;;;###autoload 46 (defsubst json-mode--update-auto-mode (filenames) 47 "Update the `json-mode' entry of `auto-mode-alist'. 48 49 FILENAMES should be a list of file as string. 50 Return the new `auto-mode-alist' entry" 51 (let* ((new-regexp 52 (rx-to-string 53 `(seq (eval 54 (cons 'or 55 (append json-mode-standard-file-ext 56 ',filenames))) eot))) 57 (new-entry (cons new-regexp 'json-mode)) 58 (old-entry (when (boundp 'json-mode--auto-mode-entry) 59 json-mode--auto-mode-entry))) 60 (setq auto-mode-alist (delete old-entry auto-mode-alist)) 61 (add-to-list 'auto-mode-alist new-entry) 62 new-entry)) 63 64 ;;; make byte-compiler happy 65 (defvar json-mode--auto-mode-entry) 66 67 ;;;###autoload 68 (defcustom json-mode-auto-mode-list '( 69 ".babelrc" 70 ".bowerrc" 71 "composer.lock" 72 ) 73 "List of filenames for the JSON entry of `auto-mode-alist'. 74 75 Note however that custom `json-mode' entries in `auto-mode-alist' 76 won’t be affected." 77 :group 'json-mode 78 :type '(repeat string) 79 :set (lambda (symbol value) 80 "Update SYMBOL with a new regexp made from VALUE. 81 82 This function calls `json-mode--update-auto-mode' to change the 83 `json-mode--auto-mode-entry' entry in `auto-mode-alist'." 84 (set-default symbol value) 85 (setq json-mode--auto-mode-entry (json-mode--update-auto-mode value)))) 86 87 ;; Autoload needed to initalize the the `auto-list-mode' entry. 88 ;;;###autoload 89 (defvar json-mode--auto-mode-entry (json-mode--update-auto-mode json-mode-auto-mode-list) 90 "Regexp generated from the `json-mode-auto-mode-list'.") 91 92 (defconst json-mode-quoted-string-re 93 (rx (group (char ?\") 94 (zero-or-more (or (seq ?\\ ?\\) 95 (seq ?\\ ?\") 96 (seq ?\\ (not (any ?\" ?\\))) 97 (not (any ?\" ?\\)))) 98 (char ?\")))) 99 (defconst json-mode-quoted-key-re 100 (rx (group (char ?\") 101 (zero-or-more (or (seq ?\\ ?\\) 102 (seq ?\\ ?\") 103 (seq ?\\ (not (any ?\" ?\\))) 104 (not (any ?\" ?\\)))) 105 (char ?\")) 106 (zero-or-more blank) 107 ?\:)) 108 (defconst json-mode-number-re (rx (group (one-or-more digit) 109 (optional ?\. (one-or-more digit))))) 110 (defconst json-mode-keyword-re (rx (group (or "true" "false" "null")))) 111 112 (defconst json-font-lock-keywords-1 113 (list 114 (list json-mode-keyword-re 1 font-lock-constant-face) 115 (list json-mode-number-re 1 font-lock-constant-face)) 116 "Level one font lock.") 117 118 (defvar json-mode-syntax-table 119 (let ((st (make-syntax-table))) 120 ;; Objects 121 (modify-syntax-entry ?\{ "(}" st) 122 (modify-syntax-entry ?\} "){" st) 123 ;; Arrays 124 (modify-syntax-entry ?\[ "(]" st) 125 (modify-syntax-entry ?\] ")[" st) 126 ;; Strings 127 (modify-syntax-entry ?\" "\"" st) 128 st)) 129 130 (defvar jsonc-mode-syntax-table 131 (let ((st (copy-syntax-table json-mode-syntax-table))) 132 ;; Comments 133 (modify-syntax-entry ?/ ". 124" st) 134 (modify-syntax-entry ?\n ">" st) 135 (modify-syntax-entry ?\^m ">" st) 136 (modify-syntax-entry ?* ". 23bn" st) 137 st)) 138 139 (defun json-mode--syntactic-face (state) 140 "Return syntactic face function for the position represented by STATE. 141 STATE is a `parse-partial-sexp' state, and the returned function is the 142 json font lock syntactic face function." 143 (cond 144 ((nth 3 state) 145 ;; This might be a string or a name 146 (let ((startpos (nth 8 state))) 147 (save-excursion 148 (goto-char startpos) 149 (if (looking-at-p json-mode-quoted-key-re) 150 font-lock-keyword-face 151 font-lock-string-face)))) 152 ((nth 4 state) font-lock-comment-face))) 153 154 ;;;###autoload 155 (define-derived-mode json-mode javascript-mode "JSON" 156 "Major mode for editing JSON files" 157 :syntax-table json-mode-syntax-table 158 (set (make-local-variable 'font-lock-defaults) 159 '(json-font-lock-keywords-1 160 nil nil nil nil 161 (font-lock-syntactic-face-function . json-mode--syntactic-face)))) 162 163 ;;;###autoload 164 (define-derived-mode jsonc-mode json-mode "JSONC" 165 "Major mode for editing JSON files with comments" 166 :syntax-table jsonc-mode-syntax-table) 167 168 ;; Well formatted JSON files almost always begin with “{” or “[”. 169 ;;;###autoload 170 (add-to-list 'magic-fallback-mode-alist '("^[{[]$" . json-mode)) 171 172 ;;;###autoload 173 (defun json-mode-show-path () 174 "Print the path to the node at point to the minibuffer." 175 (interactive) 176 (message (jsons-print-path))) 177 178 (define-key json-mode-map (kbd "C-c C-p") 'json-mode-show-path) 179 180 ;;;###autoload 181 (defun json-mode-kill-path () 182 "Save JSON path to object at point to kill ring." 183 (interactive) 184 (kill-new (jsons-print-path))) 185 186 (define-key json-mode-map (kbd "C-c P") 'json-mode-kill-path) 187 188 ;;;###autoload 189 (defun json-mode-beautify (begin end) 190 "Beautify / pretty-print the active region (or the entire buffer if no active region)." 191 (interactive "r") 192 (unless (use-region-p) 193 (setq begin (point-min) 194 end (point-max))) 195 (json-pretty-print begin end)) 196 197 (define-key json-mode-map (kbd "C-c C-f") 'json-mode-beautify) 198 199 (defun json-toggle-boolean () 200 "If point is on `true' or `false', toggle it." 201 (interactive) 202 (unless (nth 8 (syntax-ppss)) ; inside a keyword, string or comment 203 (let* ((bounds (bounds-of-thing-at-point 'symbol)) 204 (string (and bounds (buffer-substring-no-properties (car bounds) (cdr bounds)))) 205 (pt (point))) 206 (when (and bounds (member string '("true" "false"))) 207 (delete-region (car bounds) (cdr bounds)) 208 (cond 209 ((string= "true" string) 210 (insert "false") 211 (goto-char (if (= pt (cdr bounds)) (1+ pt) pt))) 212 (t 213 (insert "true") 214 (goto-char (if (= pt (cdr bounds)) (1- pt) pt)))))))) 215 216 (define-key json-mode-map (kbd "C-c C-t") 'json-toggle-boolean) 217 218 (defun json-nullify-sexp () 219 "Replace the sexp at point with `null'." 220 (interactive) 221 (let ((syntax (syntax-ppss)) symbol) 222 (cond 223 ((nth 4 syntax) nil) ; inside a comment 224 ((nth 3 syntax) ; inside a string 225 (goto-char (nth 8 syntax)) 226 (when (save-excursion (forward-sexp) (skip-chars-forward "[:space:]") (eq (char-after) ?:)) 227 ;; sexp is an object key, so we nullify the entire object 228 (goto-char (nth 1 syntax))) 229 (kill-sexp) 230 (insert "null")) 231 ((setq symbol (bounds-of-thing-at-point 'symbol)) 232 (cond 233 ((looking-at-p "null")) 234 ((save-excursion (skip-chars-backward "[0-9.]") (looking-at json-mode-number-re)) 235 (kill-region (match-beginning 0) (match-end 0)) 236 (insert "null")) 237 (t (kill-region (car symbol) (cdr symbol)) (insert "null")))) 238 ((< 0 (nth 0 syntax)) 239 (goto-char (nth 1 syntax)) 240 (kill-sexp) 241 (insert "null")) 242 (t nil)))) 243 244 (define-key json-mode-map (kbd "C-c C-k") 'json-nullify-sexp) 245 246 (defun json-increment-number-at-point (&optional delta) 247 "Add DELTA to the number at point; DELTA defaults to 1." 248 (interactive) 249 (when (save-excursion (skip-chars-backward "[0-9.]") (looking-at json-mode-number-re)) 250 (let ((num (+ (or delta 1) 251 (string-to-number (buffer-substring-no-properties (match-beginning 0) (match-end 0))))) 252 (pt (point))) 253 (delete-region (match-beginning 0) (match-end 0)) 254 (insert (number-to-string num)) 255 (goto-char pt)))) 256 257 (define-key json-mode-map (kbd "C-c C-i") 'json-increment-number-at-point) 258 259 (defun json-decrement-number-at-point () 260 "Decrement the number at point." 261 (interactive) 262 (json-increment-number-at-point -1)) 263 264 (define-key json-mode-map (kbd "C-c C-d") 'json-decrement-number-at-point) 265 266 (provide 'json-mode) 267 ;;; json-mode.el ends here