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