dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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