json-mode.el (9546B)
1 ;;; json-mode.el --- Major mode for editing JSON files -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2015-2020 Free Software Foundation, Inc. 4 5 ;; Author: Simen Heggestøyl <simenheg@gmail.com> 6 ;; Maintainer: Simen Heggestøyl <simenheg@gmail.com> 7 ;; Version: 0.2 8 ;; Package-Requires: ((emacs "25.1")) 9 ;; Keywords: data 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; Major mode for editing JavaScript Object Notation (JSON) data files. 27 ;; Read more about JSON at http://json.org/. 28 29 ;; It provides support for indentation and syntax highlighting. 30 31 ;; Thanks to Josh Johnston for writing the original JSON mode! 32 33 ;;; Code: 34 35 (require 'json) 36 (require 'smie) 37 38 (defgroup json-mode nil 39 "JavaScript Object Notation (JSON) editing mode." 40 :tag "JSON Mode" 41 :group 'data) 42 43 (defcustom json-mode-indent-level 2 44 "Basic size of one indentation step." 45 :type 'integer) 46 47 (defface json-mode-object-name-face 48 '((t :inherit font-lock-variable-name-face)) 49 "Face to use for JSON object names.") 50 51 (defvar json-mode-map 52 (let ((map (make-sparse-keymap "JSON"))) 53 (define-key map "\C-c\C-f" #'json-mode-pretty-print-dwim) 54 (define-key map "\C-c\C-p" #'json-mode-show-path) 55 (easy-menu-define json-menu map "JSON mode menu" 56 `("JSON" 57 :help "JSON-specific features" 58 ["Pretty-print region" json-mode-pretty-print-dwim 59 :visible (region-active-p)] 60 ["Pretty-print buffer" json-mode-pretty-print-dwim 61 :visible (not (region-active-p))] 62 ["Show path" json-mode-show-path])) 63 map) 64 "Keymap used in JSON mode.") 65 66 (defvar json-mode-syntax-table 67 (let ((st (make-syntax-table))) 68 ;; Objects 69 (modify-syntax-entry ?\{ "(}" st) 70 (modify-syntax-entry ?\} "){" st) 71 ;; Arrays 72 (modify-syntax-entry ?\[ "(]" st) 73 (modify-syntax-entry ?\] ")[" st) 74 ;; Strings 75 (modify-syntax-entry ?\" "\"" st) 76 ;; Comments 77 (modify-syntax-entry ?/ ". 12" st) 78 (modify-syntax-entry ?\n ">" st) 79 st)) 80 81 (defconst json-mode--keywords '("true" "false" "null") 82 "List of JSON keywords.") 83 84 (defvar json-mode-font-lock-keywords 85 `(;; Constants 86 (,(concat "\\<" (regexp-opt json-mode--keywords) "\\>") 87 (0 font-lock-constant-face)))) 88 89 (defun json-mode--string-is-object-name-p (startpos) 90 "Return t if STARTPOS is at the beginning of an object name." 91 (save-excursion 92 (goto-char startpos) 93 (and (eq (char-after) ?\") 94 (condition-case nil 95 (progn (forward-sexp 1) t) 96 (scan-error nil)) 97 (looking-at "[[:blank:]]*:")))) 98 99 (defun json-font-lock-syntactic-face-function (state) 100 "Determine which face to use for strings and comments. 101 Object names receive the face `json-mode-object-name-face' to 102 distinguish them from other strings." 103 (cond 104 ((nth 4 state) font-lock-comment-face) 105 ((and (nth 3 state) 106 (json-mode--string-is-object-name-p (nth 8 state))) 107 'json-mode-object-name-face) 108 (t font-lock-string-face))) 109 110 (defconst json-mode--smie-grammar 111 (smie-prec2->grammar 112 (smie-precs->prec2 '((assoc ",") (left ":"))))) 113 114 (defun json-mode--smie-rules (method arg) 115 "Provide indentation rules for METHOD given ARG. 116 See the documentation of `smie-rules-function' for further 117 information." 118 (pcase (cons method arg) 119 (`(:elem . basic) json-mode-indent-level))) 120 121 (defun json-mode-pretty-print-dwim (&optional alphabetical) 122 "Pretty print region if active, else pretty print the buffer. 123 `json-mode-indent-level' will be used as indentation offset. If 124 ALPHABETICAL is non-nil (interactively, with a prefix argument), 125 JSON object members will be sorted alphabetically by their keys." 126 (interactive "P") 127 (let ((json-encoding-default-indentation 128 (make-string json-mode-indent-level ?\s))) 129 (if (use-region-p) 130 (funcall 131 (if alphabetical 132 #'json-pretty-print-ordered 133 #'json-pretty-print) 134 (region-beginning) (region-end)) 135 (funcall 136 (if alphabetical 137 #'json-pretty-print-buffer-ordered 138 #'json-pretty-print-buffer))))) 139 140 (defun json-mode-show-path () 141 "Show the path to the JSON value under point. 142 The value is also copied to the kill ring." 143 (interactive) 144 (let ((path (json-path-to-position (point)))) 145 (if path 146 (let ((formatted-path 147 (json-mode--format-path (plist-get path :path)))) 148 (when (fboundp 'pulse-momentary-highlight-region) 149 (pulse-momentary-highlight-region 150 (plist-get path :match-start) 151 (plist-get path :match-end))) 152 (message formatted-path) 153 (kill-new formatted-path)) 154 (message "Not a JSON value")))) 155 156 (defun json--which-func () 157 (let ((path (plist-get (json-path-to-position (point)) :path))) 158 (when path 159 ;; There's not much space in the modeline, so this needs 160 ;; to be more compact than what `json-mode--format-path' produces. 161 ;; FIXME: Even in this more compact form it can easily get too long 162 ;; for comfort. Add some way to shorten it. 163 (mapconcat (lambda (key) (format "%s" key)) path ";")))) 164 165 (defun json-mode--format-path (path) 166 "Return PATH formatted as a JSON data selector. 167 PATH should be a list of keys, which can be either strings or 168 integers." 169 (mapconcat (lambda (key) (format "[%S]" key)) path "")) 170 171 ;;;###autoload 172 (add-to-list 'auto-mode-alist '("\\.json\\'" . json-mode)) 173 174 ;;;###autoload 175 (define-derived-mode json-mode prog-mode "JSON" 176 "Major mode for editing JavaScript Object Notation (JSON) data files." 177 (setq-local 178 font-lock-defaults 179 '(json-mode-font-lock-keywords 180 nil nil nil nil 181 (font-lock-syntactic-face-function 182 . json-font-lock-syntactic-face-function))) 183 ;; JSON has no comment syntax, but we set this to keep SMIE happy. 184 ;; Also, some JSON extensions allow comments. 185 (add-hook 'which-func-functions #'json--which-func nil t) 186 (setq-local comment-start "// ") 187 (setq-local comment-end "") 188 (smie-setup json-mode--smie-grammar #'json-mode--smie-rules)) 189 190 (defun json--jit-wrap (beg end) 191 (save-excursion 192 (remove-overlays beg end 'json-jit-wrap t) 193 (goto-char beg) 194 (let ((ppss (syntax-ppss beg)) 195 (last beg)) 196 (while (re-search-forward "[][{},]+" end t) 197 (let* ((pos (match-beginning 0)) 198 (closer (memq (char-after pos) '(?\] ?\})))) 199 (setq ppss (parse-partial-sexp last (point) nil nil ppss) 200 last (point)) 201 (unless (nth 8 ppss) 202 (let ((s (concat "\n" (make-string 203 (* json-mode-indent-level (nth 0 ppss)) 204 ?\s))) 205 (ol (make-overlay (1- (point)) (point)))) 206 (overlay-put ol 'json-jit-wrap t) 207 (overlay-put ol 'after-string s) 208 (when closer 209 (let ((ol (make-overlay pos (1+ pos)))) 210 (overlay-put ol 'json-jit-wrap t) 211 (overlay-put ol 'before-string s)))))))))) 212 213 ;; FIXME: On a small json file, this seems to work OK, but 214 ;; line-based movement is already occasionally very slow. 215 ;; I haven't dared to try it on a largish json file. 216 (define-minor-mode json-jit-wrap-mode 217 "Add virtual newlines." 218 :global nil 219 (if json-jit-wrap-mode 220 (progn 221 ;; Note: this jitter works (almost) character-by-character 222 ;; so doesn't need to round up to while lines (which is great!), 223 ;; but of course, if font-lock is enabled, jit-lock will 224 ;; still end up calling us one whole line at a time :-( 225 (jit-lock-register #'json--jit-wrap) 226 (add-hook 'change-major-mode-hook 227 (lambda () (json-jit-wrap-mode -1)) nil t)) 228 (jit-lock-unregister #'json--jit-wrap) 229 (remove-overlays (point-min) (point-max) 'json-jit-wrap t))) 230 231 ;;;; ChangeLog: 232 233 ;; 2020-11-09 Simen Heggestøyl <simenheg@runbox.com> 234 ;; 235 ;; Bump json-mode version to 0.2 236 ;; 237 ;; 2020-07-22 Stefan Monnier <monnier@iro.umontreal.ca> 238 ;; 239 ;; * json-mode/json-mode.el (json-jit-wrap-mode): New minor mode 240 ;; 241 ;; (json--jit-wrap): New function. 242 ;; 243 ;; 2020-07-22 Stefan Monnier <monnier@iro.umontreal.ca> 244 ;; 245 ;; * json-mode/json-mode.el: Add which-func support 246 ;; 247 ;; (json--which-func): New function. 248 ;; (json-mode): Use it. 249 ;; (json-mode--keywords): New var. 250 ;; (json-mode-font-lock-keywords): Use it instead of the obsolete 251 ;; `json-keywords`. 252 ;; 253 ;; 2016-12-18 Simen Heggestøyl <simenheg@gmail.com> 254 ;; 255 ;; Use syntactic fontification for JSON object names 256 ;; 257 ;; * packages/json-mode/json-mode.el (json-mode-font-lock-keywords): Don't 258 ;; match object names and strings. 259 ;; (json-mode--string-is-object-name-p): New function determining whether a 260 ;; position is at the beginning of an object name. 261 ;; (json-font-lock-syntactic-face-function): Fontify object names. 262 ;; 263 ;; 2016-12-11 Simen Heggestøyl <simenheg@gmail.com> 264 ;; 265 ;; New package: json-mode 266 ;; 267 268 269 (provide 'json-mode) 270 271 ;;; json-mode.el ends here