Home | History | Annotate | Download | only in emacs
      1 ;;; gyp.el - font-lock-mode support for gyp files.
      2 
      3 ;; Copyright (c) 2012 Google Inc. All rights reserved.
      4 ;; Use of this source code is governed by a BSD-style license that can be
      5 ;; found in the LICENSE file.
      6 
      7 ;; Put this somewhere in your load-path and
      8 ;; (require 'gyp)
      9 
     10 (require 'python)
     11 (require 'cl)
     12 
     13 (when (string-match "python-mode.el" (symbol-file 'python-mode 'defun))
     14   (error (concat "python-mode must be loaded from python.el (bundled with "
     15                  "recent emacsen), not from the older and less maintained "
     16                  "python-mode.el")))
     17 
     18 (defadvice python-calculate-indentation (after ami-outdent-closing-parens
     19                                                activate)
     20   "De-indent closing parens, braces, and brackets in gyp-mode."
     21   (if (and (eq major-mode 'gyp-mode)
     22            (string-match "^ *[])}][],)}]* *$"
     23                          (buffer-substring-no-properties
     24                           (line-beginning-position) (line-end-position))))
     25       (setq ad-return-value (- ad-return-value 2))))
     26 
     27 (define-derived-mode gyp-mode python-mode "Gyp"
     28   "Major mode for editing .gyp files. See http://code.google.com/p/gyp/"
     29   ;; gyp-parse-history is a stack of (POSITION . PARSE-STATE) tuples,
     30   ;; with greater positions at the top of the stack. PARSE-STATE
     31   ;; is a list of section symbols (see gyp-section-name and gyp-parse-to)
     32   ;; with most nested section symbol at the front of the list.
     33   (set (make-local-variable 'gyp-parse-history) '((1 . (list))))
     34   (gyp-add-font-lock-keywords))
     35 
     36 (defun gyp-set-indentation ()
     37   "Hook function to configure python indentation to suit gyp mode."
     38   (setq python-continuation-offset 2
     39         python-indent 2
     40         python-guess-indent nil))
     41 
     42 (add-hook 'gyp-mode-hook 'gyp-set-indentation)
     43 
     44 (add-to-list 'auto-mode-alist '("\\.gyp\\'" . gyp-mode))
     45 (add-to-list 'auto-mode-alist '("\\.gypi\\'" . gyp-mode))
     46 (add-to-list 'auto-mode-alist '("/\\.gclient\\'" . gyp-mode))
     47 
     48 ;;; Font-lock support
     49 
     50 (defconst gyp-dependencies-regexp
     51   (regexp-opt (list "dependencies" "export_dependent_settings"))
     52   "Regular expression to introduce 'dependencies' section")
     53 
     54 (defconst gyp-sources-regexp
     55   (regexp-opt (list "action" "files" "include_dirs" "includes" "inputs"
     56                     "libraries" "outputs" "sources"))
     57   "Regular expression to introduce 'sources' sections")
     58 
     59 (defconst gyp-conditions-regexp
     60   (regexp-opt (list "conditions" "target_conditions"))
     61   "Regular expression to introduce conditions sections")
     62 
     63 (defconst gyp-variables-regexp
     64   "^variables"
     65   "Regular expression to introduce variables sections")
     66 
     67 (defconst gyp-defines-regexp
     68   "^defines"
     69   "Regular expression to introduce 'defines' sections")
     70 
     71 (defconst gyp-targets-regexp
     72   "^targets"
     73   "Regular expression to introduce 'targets' sections")
     74 
     75 (defun gyp-section-name (section)
     76   "Map the sections we are interested in from SECTION to symbol.
     77 
     78    SECTION is a string from the buffer that introduces a section.  The result is
     79    a symbol representing the kind of section.
     80 
     81    This allows us to treat (for the purposes of font-lock) several different
     82    section names as the same kind of section. For example, a 'sources section
     83    can be introduced by the 'sources', 'inputs', 'outputs' keyword.
     84 
     85    'other is the default section kind when a more specific match is not made."
     86   (cond ((string-match-p gyp-dependencies-regexp section) 'dependencies)
     87         ((string-match-p gyp-sources-regexp section) 'sources)
     88         ((string-match-p gyp-variables-regexp section) 'variables)
     89         ((string-match-p gyp-conditions-regexp section) 'conditions)
     90         ((string-match-p gyp-targets-regexp section) 'targets)
     91         ((string-match-p gyp-defines-regexp section) 'defines)
     92         (t 'other)))
     93 
     94 (defun gyp-invalidate-parse-states-after (target-point)
     95   "Erase any parse information after target-point."
     96   (while (> (caar gyp-parse-history) target-point)
     97     (setq gyp-parse-history (cdr gyp-parse-history))))
     98 
     99 (defun gyp-parse-point ()
    100   "The point of the last parse state added by gyp-parse-to."
    101   (caar gyp-parse-history))
    102 
    103 (defun gyp-parse-sections ()
    104   "A list of section symbols holding at the last parse state point."
    105   (cdar gyp-parse-history))
    106 
    107 (defun gyp-inside-dictionary-p ()
    108   "Predicate returning true if the parser is inside a dictionary."
    109   (not (eq (cadar gyp-parse-history) 'list)))
    110 
    111 (defun gyp-add-parse-history (point sections)
    112   "Add parse state SECTIONS to the parse history at POINT so that parsing can be
    113    resumed instantly."
    114   (while (>= (caar gyp-parse-history) point)
    115     (setq gyp-parse-history (cdr gyp-parse-history)))
    116   (setq gyp-parse-history (cons (cons point sections) gyp-parse-history)))
    117 
    118 (defun gyp-parse-to (target-point)
    119   "Parses from (point) to TARGET-POINT adding the parse state information to
    120    gyp-parse-state-history. Parsing stops if TARGET-POINT is reached or if a
    121    string literal has been parsed. Returns nil if no further parsing can be
    122    done, otherwise returns the position of the start of a parsed string, leaving
    123    the point at the end of the string."
    124   (let ((parsing t)
    125         string-start)
    126     (while parsing
    127       (setq string-start nil)
    128       ;; Parse up to a character that starts a sexp, or if the nesting
    129       ;; level decreases.
    130       (let ((state (parse-partial-sexp (gyp-parse-point)
    131                                        target-point
    132                                        -1
    133                                        t))
    134             (sections (gyp-parse-sections)))
    135         (if (= (nth 0 state) -1)
    136             (setq sections (cdr sections)) ; pop out a level
    137           (cond ((looking-at-p "['\"]") ; a string
    138                  (setq string-start (point))
    139                  (goto-char (scan-sexps (point) 1))
    140                  (if (gyp-inside-dictionary-p)
    141                      ;; Look for sections inside a dictionary
    142                      (let ((section (gyp-section-name
    143                                      (buffer-substring-no-properties
    144                                       (+ 1 string-start)
    145                                       (- (point) 1)))))
    146                        (setq sections (cons section (cdr sections)))))
    147                  ;; Stop after the string so it can be fontified.
    148                  (setq target-point (point)))
    149                 ((looking-at-p "{")
    150                  ;; Inside a dictionary. Increase nesting.
    151                  (forward-char 1)
    152                  (setq sections (cons 'unknown sections)))
    153                 ((looking-at-p "\\[")
    154                  ;; Inside a list. Increase nesting
    155                  (forward-char 1)
    156                  (setq sections (cons 'list sections)))
    157                 ((not (eobp))
    158                  ;; other
    159                  (forward-char 1))))
    160         (gyp-add-parse-history (point) sections)
    161         (setq parsing (< (point) target-point))))
    162     string-start))
    163 
    164 (defun gyp-section-at-point ()
    165   "Transform the last parse state, which is a list of nested sections and return
    166    the section symbol that should be used to determine font-lock information for
    167    the string. Can return nil indicating the string should not have any attached
    168    section."
    169   (let ((sections (gyp-parse-sections)))
    170     (cond
    171      ((eq (car sections) 'conditions)
    172       ;; conditions can occur in a variables section, but we still want to
    173       ;; highlight it as a keyword.
    174       nil)
    175      ((and (eq (car sections) 'list)
    176            (eq (cadr sections) 'list))
    177       ;; conditions and sources can have items in [[ ]]
    178       (caddr sections))
    179      (t (cadr sections)))))
    180 
    181 (defun gyp-section-match (limit)
    182   "Parse from (point) to LIMIT returning by means of match data what was
    183    matched. The group of the match indicates what style font-lock should apply.
    184    See also `gyp-add-font-lock-keywords'."
    185   (gyp-invalidate-parse-states-after (point))
    186   (let ((group nil)
    187         (string-start t))
    188     (while (and (< (point) limit)
    189                 (not group)
    190                 string-start)
    191       (setq string-start (gyp-parse-to limit))
    192       (if string-start
    193           (setq group (case (gyp-section-at-point)
    194                         ('dependencies 1)
    195                         ('variables 2)
    196                         ('conditions 2)
    197                         ('sources 3)
    198                         ('defines 4)
    199                         (nil nil)))))
    200     (if group
    201         (progn
    202           ;; Set the match data to indicate to the font-lock mechanism the
    203           ;; highlighting to be performed.
    204           (set-match-data (append (list string-start (point))
    205                                   (make-list (* (1- group) 2) nil)
    206                                   (list (1+ string-start) (1- (point)))))
    207           t))))
    208 
    209 ;;; Please see http://code.google.com/p/gyp/wiki/GypLanguageSpecification for
    210 ;;; canonical list of keywords.
    211 (defun gyp-add-font-lock-keywords ()
    212   "Add gyp-mode keywords to font-lock mechanism."
    213   ;; TODO(jknotten): Move all the keyword highlighting into gyp-section-match
    214   ;; so that we can do the font-locking in a single font-lock pass.
    215   (font-lock-add-keywords
    216    nil
    217    (list
    218     ;; Top-level keywords
    219     (list (concat "['\"]\\("
    220               (regexp-opt (list "action" "action_name" "actions" "cflags"
    221                                 "conditions" "configurations" "copies" "defines"
    222                                 "dependencies" "destination"
    223                                 "direct_dependent_settings"
    224                                 "export_dependent_settings" "extension" "files"
    225                                 "include_dirs" "includes" "inputs" "libraries"
    226                                 "link_settings" "mac_bundle" "message"
    227                                 "msvs_external_rule" "outputs" "product_name"
    228                                 "process_outputs_as_sources" "rules" "rule_name"
    229                                 "sources" "suppress_wildcard"
    230                                 "target_conditions" "target_defaults"
    231                                 "target_defines" "target_name" "toolsets"
    232                                 "targets" "type" "variables" "xcode_settings"))
    233               "[!/+=]?\\)") 1 'font-lock-keyword-face t)
    234     ;; Type of target
    235     (list (concat "['\"]\\("
    236               (regexp-opt (list "loadable_module" "static_library"
    237                                 "shared_library" "executable" "none"))
    238               "\\)") 1 'font-lock-type-face t)
    239     (list "\\(?:target\\|action\\)_name['\"]\\s-*:\\s-*['\"]\\([^ '\"]*\\)" 1
    240           'font-lock-function-name-face t)
    241     (list 'gyp-section-match
    242           (list 1 'font-lock-function-name-face t t) ; dependencies
    243           (list 2 'font-lock-variable-name-face t t) ; variables, conditions
    244           (list 3 'font-lock-constant-face t t) ; sources
    245           (list 4 'font-lock-preprocessor-face t t)) ; preprocessor
    246     ;; Variable expansion
    247     (list "<@?(\\([^\n )]+\\))" 1 'font-lock-variable-name-face t)
    248     ;; Command expansion
    249     (list "<!@?(\\([^\n )]+\\))" 1 'font-lock-variable-name-face t)
    250     )))
    251 
    252 (provide 'gyp)
    253