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