[6a3a178] | 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)
|
---|