| ;;; gyp.el - font-lock-mode support for gyp files. |
| |
| ;; Copyright (c) 2012 Google Inc. All rights reserved. |
| ;; Use of this source code is governed by a BSD-style license that can be |
| ;; found in the LICENSE file. |
| |
| ;; Put this somewhere in your load-path and |
| ;; (require 'gyp) |
| |
| (require 'python) |
| (require 'cl) |
| |
| (when (string-match "python-mode.el" (symbol-file 'python-mode 'defun)) |
| (error (concat "python-mode must be loaded from python.el (bundled with " |
| "recent emacsen), not from the older and less maintained " |
| "python-mode.el"))) |
| |
| (defadvice python-calculate-indentation (after ami-outdent-closing-parens |
| activate) |
| "De-indent closing parens, braces, and brackets in gyp-mode." |
| (if (and (eq major-mode 'gyp-mode) |
| (string-match "^ *[])}][],)}]* *$" |
| (buffer-substring-no-properties |
| (line-beginning-position) (line-end-position)))) |
| (setq ad-return-value (- ad-return-value 2)))) |
| |
| (define-derived-mode gyp-mode python-mode "Gyp" |
| "Major mode for editing .gyp files. See http://code.google.com/p/gyp/" |
| ;; gyp-parse-history is a stack of (POSITION . PARSE-STATE) tuples, |
| ;; with greater positions at the top of the stack. PARSE-STATE |
| ;; is a list of section symbols (see gyp-section-name and gyp-parse-to) |
| ;; with most nested section symbol at the front of the list. |
| (set (make-local-variable 'gyp-parse-history) '((1 . (list)))) |
| (gyp-add-font-lock-keywords)) |
| |
| (defun gyp-set-indentation () |
| "Hook function to configure python indentation to suit gyp mode." |
| (setq python-continuation-offset 2 |
| python-indent 2 |
| python-guess-indent nil)) |
| |
| (add-hook 'gyp-mode-hook 'gyp-set-indentation) |
| |
| (add-to-list 'auto-mode-alist '("\\.gyp\\'" . gyp-mode)) |
| (add-to-list 'auto-mode-alist '("\\.gypi\\'" . gyp-mode)) |
| |
| ;;; Font-lock support |
| |
| (defconst gyp-dependencies-regexp |
| (regexp-opt (list "dependencies" "export_dependent_settings")) |
| "Regular expression to introduce 'dependencies' section") |
| |
| (defconst gyp-sources-regexp |
| (regexp-opt (list "action" "files" "include_dirs" "includes" "inputs" |
| "libraries" "outputs" "sources")) |
| "Regular expression to introduce 'sources' sections") |
| |
| (defconst gyp-conditions-regexp |
| (regexp-opt (list "conditions" "target_conditions")) |
| "Regular expression to introduce conditions sections") |
| |
| (defconst gyp-variables-regexp |
| "^variables" |
| "Regular expression to introduce variables sections") |
| |
| (defconst gyp-defines-regexp |
| "^defines" |
| "Regular expression to introduce 'defines' sections") |
| |
| (defconst gyp-targets-regexp |
| "^targets" |
| "Regular expression to introduce 'targets' sections") |
| |
| (defun gyp-section-name (section) |
| "Map the sections we are interested in from SECTION to symbol. |
| |
| SECTION is a string from the buffer that introduces a section. The result is |
| a symbol representing the kind of section. |
| |
| This allows us to treat (for the purposes of font-lock) several different |
| section names as the same kind of section. For example, a 'sources section |
| can be introduced by the 'sources', 'inputs', 'outputs' keyword. |
| |
| 'other is the default section kind when a more specific match is not made." |
| (cond ((string-match-p gyp-dependencies-regexp section) 'dependencies) |
| ((string-match-p gyp-sources-regexp section) 'sources) |
| ((string-match-p gyp-variables-regexp section) 'variables) |
| ((string-match-p gyp-conditions-regexp section) 'conditions) |
| ((string-match-p gyp-targets-regexp section) 'targets) |
| ((string-match-p gyp-defines-regexp section) 'defines) |
| (t 'other))) |
| |
| (defun gyp-invalidate-parse-states-after (target-point) |
| "Erase any parse information after target-point." |
| (while (> (caar gyp-parse-history) target-point) |
| (setq gyp-parse-history (cdr gyp-parse-history)))) |
| |
| (defun gyp-parse-point () |
| "The point of the last parse state added by gyp-parse-to." |
| (caar gyp-parse-history)) |
| |
| (defun gyp-parse-sections () |
| "A list of section symbols holding at the last parse state point." |
| (cdar gyp-parse-history)) |
| |
| (defun gyp-inside-dictionary-p () |
| "Predicate returning true if the parser is inside a dictionary." |
| (not (eq (cadar gyp-parse-history) 'list))) |
| |
| (defun gyp-add-parse-history (point sections) |
| "Add parse state SECTIONS to the parse history at POINT so that parsing can be |
| resumed instantly." |
| (while (>= (caar gyp-parse-history) point) |
| (setq gyp-parse-history (cdr gyp-parse-history))) |
| (setq gyp-parse-history (cons (cons point sections) gyp-parse-history))) |
| |
| (defun gyp-parse-to (target-point) |
| "Parses from (point) to TARGET-POINT adding the parse state information to |
| gyp-parse-state-history. Parsing stops if TARGET-POINT is reached or if a |
| string literal has been parsed. Returns nil if no further parsing can be |
| done, otherwise returns the position of the start of a parsed string, leaving |
| the point at the end of the string." |
| (let ((parsing t) |
| string-start) |
| (while parsing |
| (setq string-start nil) |
| ;; Parse up to a character that starts a sexp, or if the nesting |
| ;; level decreases. |
| (let ((state (parse-partial-sexp (gyp-parse-point) |
| target-point |
| -1 |
| t)) |
| (sections (gyp-parse-sections))) |
| (if (= (nth 0 state) -1) |
| (setq sections (cdr sections)) ; pop out a level |
| (cond ((looking-at-p "['\"]") ; a string |
| (setq string-start (point)) |
| (forward-sexp 1) |
| (if (gyp-inside-dictionary-p) |
| ;; Look for sections inside a dictionary |
| (let ((section (gyp-section-name |
| (buffer-substring-no-properties |
| (+ 1 string-start) |
| (- (point) 1))))) |
| (setq sections (cons section (cdr sections))))) |
| ;; Stop after the string so it can be fontified. |
| (setq target-point (point))) |
| ((looking-at-p "{") |
| ;; Inside a dictionary. Increase nesting. |
| (forward-char 1) |
| (setq sections (cons 'unknown sections))) |
| ((looking-at-p "\\[") |
| ;; Inside a list. Increase nesting |
| (forward-char 1) |
| (setq sections (cons 'list sections))) |
| ((not (eobp)) |
| ;; other |
| (forward-char 1)))) |
| (gyp-add-parse-history (point) sections) |
| (setq parsing (< (point) target-point)))) |
| string-start)) |
| |
| (defun gyp-section-at-point () |
| "Transform the last parse state, which is a list of nested sections and return |
| the section symbol that should be used to determine font-lock information for |
| the string. Can return nil indicating the string should not have any attached |
| section." |
| (let ((sections (gyp-parse-sections))) |
| (cond |
| ((eq (car sections) 'conditions) |
| ;; conditions can occur in a variables section, but we still want to |
| ;; highlight it as a keyword. |
| nil) |
| ((and (eq (car sections) 'list) |
| (eq (cadr sections) 'list)) |
| ;; conditions and sources can have items in [[ ]] |
| (caddr sections)) |
| (t (cadr sections))))) |
| |
| (defun gyp-section-match (limit) |
| "Parse from (point) to LIMIT returning by means of match data what was |
| matched. The group of the match indicates what style font-lock should apply. |
| See also `gyp-add-font-lock-keywords'." |
| (gyp-invalidate-parse-states-after (point)) |
| (let ((group nil) |
| (string-start t)) |
| (while (and (< (point) limit) |
| (not group) |
| string-start) |
| (setq string-start (gyp-parse-to limit)) |
| (if string-start |
| (setq group (case (gyp-section-at-point) |
| ('dependencies 1) |
| ('variables 2) |
| ('conditions 2) |
| ('sources 3) |
| ('defines 4) |
| (nil nil))))) |
| (if group |
| (progn |
| ;; Set the match data to indicate to the font-lock mechanism the |
| ;; highlighting to be performed. |
| (set-match-data (append (list string-start (point)) |
| (make-list (* (1- group) 2) nil) |
| (list (1+ string-start) (1- (point))))) |
| t)))) |
| |
| ;;; Please see http://code.google.com/p/gyp/wiki/GypLanguageSpecification for |
| ;;; canonical list of keywords. |
| (defun gyp-add-font-lock-keywords () |
| "Add gyp-mode keywords to font-lock mechanism." |
| ;; TODO(jknotten): Move all the keyword highlighting into gyp-section-match |
| ;; so that we can do the font-locking in a single font-lock pass. |
| (font-lock-add-keywords |
| nil |
| (list |
| ;; Top-level keywords |
| (list (concat "['\"]\\(" |
| (regexp-opt (list "action" "action_name" "actions" "cflags" |
| "conditions" "configurations" "copies" "defines" |
| "dependencies" "destination" |
| "direct_dependent_settings" |
| "export_dependent_settings" "extension" "files" |
| "include_dirs" "includes" "inputs" "libraries" |
| "link_settings" "mac_bundle" "message" |
| "msvs_external_rule" "outputs" "product_name" |
| "process_outputs_as_sources" "rules" "rule_name" |
| "sources" "suppress_wildcard" |
| "target_conditions" "target_defaults" |
| "target_defines" "target_name" "toolsets" |
| "targets" "type" "variables" "xcode_settings")) |
| "[!/+=]?\\)") 1 'font-lock-keyword-face t) |
| ;; Type of target |
| (list (concat "['\"]\\(" |
| (regexp-opt (list "loadable_module" "static_library" |
| "shared_library" "executable" "none")) |
| "\\)") 1 'font-lock-type-face t) |
| (list "\\(?:target\\|action\\)_name['\"]\\s-*:\\s-*['\"]\\([^ '\"]*\\)" 1 |
| 'font-lock-function-name-face t) |
| (list 'gyp-section-match |
| (list 1 'font-lock-function-name-face t t) ; dependencies |
| (list 2 'font-lock-variable-name-face t t) ; variables, conditions |
| (list 3 'font-lock-constant-face t t) ; sources |
| (list 4 'font-lock-preprocessor-face t t)) ; preprocessor |
| ;; Variable expansion |
| (list "<@?(\\([^\n )]+\\))" 1 'font-lock-variable-name-face t) |
| ;; Command expansion |
| (list "<!@?(\\([^\n )]+\\))" 1 'font-lock-variable-name-face t) |
| ))) |
| |
| (provide 'gyp) |