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