dotemacs

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

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