gyp.el 12 KB

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