| ;;; gn-mode.el - A major mode for editing gn files. |
| |
| ;; Copyright 2015 The Chromium Authors. All rights reserved. |
| ;; Use of this source code is governed by a BSD-style license that can be |
| ;; found in the LICENSE file. |
| |
| ;; Author: Elliot Glaysher <erg@chromium.org> |
| ;; Created: April 03, 2015 |
| ;; Keywords: tools, gn, ninja, chromium |
| |
| ;; This file is not part of GNU Emacs. |
| |
| ;;; Commentary: |
| |
| ;; A major mode for editing GN files. GN stands for Generate Ninja. GN is the |
| ;; meta build system used in Chromium. For more information on GN, see the GN |
| ;; manual: <https://chromium.googlesource.com/chromium/src/+/master/tools/gn/README.md> |
| |
| ;;; To Do: |
| |
| ;; - We syntax highlight builtin actions, but don't highlight instantiations of |
| ;; templates. Should we? |
| |
| |
| |
| (require 'smie) |
| |
| (defgroup gn nil |
| "Major mode for editing Generate Ninja files." |
| :prefix "gn-" |
| :group 'languages) |
| |
| (defcustom gn-indent-basic 2 |
| "The number of spaces to indent a new scope." |
| :group 'gn |
| :type 'integer) |
| |
| (defcustom gn-format-command "gn format --stdin" |
| "The command to run to format gn files in place." |
| :group 'gn |
| :type 'string) |
| |
| (defgroup gn-faces nil |
| "Faces used in Generate Ninja mode." |
| :group 'gn |
| :group 'faces) |
| |
| (defface gn-embedded-variable |
| '((t :inherit font-lock-variable-name-face)) |
| "Font lock face used to highlight variable names in strings." |
| :group 'gn-faces) |
| |
| (defface gn-embedded-variable-boundary |
| '((t :bold t |
| :inherit gn-embedded-variable)) |
| "Font lock face used to highlight the '$' that starts a |
| variable name or the '{{' and '}}' which surround it." |
| :group 'gn-faces) |
| |
| (defvar gn-font-lock-reserved-keywords |
| '("true" "false" "if" "else")) |
| |
| (defvar gn-font-lock-target-declaration-keywords |
| '("action" "action_foreach" "bundle_data" "copy" "create_bundle" "executable" |
| "group" "loadable_module" "shared_library" "source_set" "static_library" |
| "generated_file" "target")) |
| |
| ;; pool() is handled specially since it's also a variable name |
| (defvar gn-font-lock-buildfile-fun-keywords |
| '("assert" "config" "declare_args" "defined" "exec_script" "foreach" |
| "forward_variables_from" "get_label_info" "get_path_info" |
| "get_target_outputs" "getenv" "import" "not_needed" "print" |
| "process_file_template" "read_file" "rebase_path" "set_default_toolchain" |
| "set_defaults" "set_sources_assignment_filter" "split_list" "template" |
| "tool" "toolchain" "propagates_configs" "write_file")) |
| |
| (defvar gn-font-lock-predefined-var-keywords |
| '("current_cpu" "current_os" "current_toolchain" "default_toolchain" |
| "host_cpu" "host_os" "invoker" "python_path" "root_build_dir" "root_gen_dir" |
| "root_out_dir" "target_cpu" "target_gen_dir" "target_name" "target_os" |
| "target_out_dir")) |
| |
| (defvar gn-font-lock-var-keywords |
| '("all_dependent_configs" "allow_circular_includes_from" "arflags" "args" |
| "asmflags" "assert_no_deps" "bundle_deps_filter" "bundle_executable_dir" |
| "bundle_resources_dir" "bundle_root_dir" "cflags" "cflags_c" "cflags_cc" |
| "cflags_objc" "cflags_objcc" "check_includes" "code_signing_args" |
| "code_signing_outputs" "code_signing_script" "code_signing_sources" |
| "complete_static_lib" "configs" "data" "data_deps" "defines" "depfile" |
| "deps" "include_dirs" "inputs" "ldflags" "lib_dirs" "libs" "output_dir" |
| "output_extension" "output_name" "output_prefix_override" "outputs" "pool" |
| "precompiled_header" "precompiled_header_type" "precompiled_source" |
| "product_type" "public" "public_configs" "public_deps" |
| "response_file_contents" "script" "sources" "testonly" "visibility" |
| "write_runtime_deps" "bundle_contents_dir" "contents" "output_conversion" |
| "rebase" "data_keys" "walk_keys")) |
| |
| (defconst gn-font-lock-keywords |
| `((,(regexp-opt gn-font-lock-reserved-keywords 'words) . |
| font-lock-keyword-face) |
| (,(regexp-opt gn-font-lock-target-declaration-keywords 'words) . |
| font-lock-type-face) |
| (,(regexp-opt gn-font-lock-buildfile-fun-keywords 'words) . |
| font-lock-function-name-face) |
| ;; pool() as a function |
| ("\\<\\(pool\\)\\s-*(" |
| (1 font-lock-function-name-face)) |
| (,(regexp-opt gn-font-lock-predefined-var-keywords 'words) . |
| font-lock-constant-face) |
| (,(regexp-opt gn-font-lock-var-keywords 'words) . |
| font-lock-variable-name-face) |
| ;; $variables_like_this |
| ("\\(\\$\\)\\([a-zA-Z0-9_]+\\)" |
| (1 'gn-embedded-variable-boundary t) |
| (2 'gn-embedded-variable t)) |
| ;; ${variables_like_this} |
| ("\\(\\${\\)\\([^\n }]+\\)\\(}\\)" |
| (1 'gn-embedded-variable-boundary t) |
| (2 'gn-embedded-variable t) |
| (3 'gn-embedded-variable-boundary t)) |
| ;; {{placeholders}} (see substitute_type.h) |
| ("\\({{\\)\\([^\n }]+\\)\\(}}\\)" |
| (1 'gn-embedded-variable-boundary t) |
| (2 'gn-embedded-variable t) |
| (3 'gn-embedded-variable-boundary t)))) |
| |
| (defun gn-smie-rules (kind token) |
| "These are slightly modified indentation rules from the SMIE |
| Indentation Example info page. This changes the :before rule |
| and adds a :list-intro to handle our x = [ ] syntax." |
| (pcase (cons kind token) |
| (`(:elem . basic) gn-indent-basic) |
| (`(,_ . ",") (smie-rule-separator kind)) |
| (`(:list-intro . "") gn-indent-basic) |
| (`(:before . ,(or `"[" `"(" `"{")) |
| (if (smie-rule-hanging-p) (smie-rule-parent))) |
| (`(:before . "if") |
| (and (not (smie-rule-bolp)) (smie-rule-prev-p "else") |
| (smie-rule-parent))))) |
| |
| (defun gn-fill-paragraph (&optional justify) |
| "We only fill inside of comments in GN mode." |
| (interactive "P") |
| (or (fill-comment-paragraph justify) |
| ;; Never return nil; `fill-paragraph' will perform its default behavior |
| ;; if we do. |
| t)) |
| |
| (defun gn-run-format () |
| "Run 'gn format' on the buffer in place." |
| (interactive) |
| ;; We can't `save-excursion' here; that will put us at the beginning of the |
| ;; shell output, aka the beginning of the document. |
| (let ((my-start-line (line-number-at-pos))) |
| (shell-command-on-region (point-min) (point-max) gn-format-command nil t) |
| (goto-line my-start-line))) |
| |
| (defvar gn-mode-map |
| (let ((map (make-sparse-keymap))) |
| (define-key map "\C-c\C-f" 'gn-run-format) |
| map)) |
| |
| ;;;###autoload |
| (define-derived-mode gn-mode prog-mode "GN" |
| "Major mode for editing gn (Generate Ninja)." |
| :group 'gn |
| |
| (setq-local comment-use-syntax t) |
| (setq-local comment-start "#") |
| (setq-local comment-end "") |
| (setq-local indent-tabs-mode nil) |
| |
| (setq-local fill-paragraph-function 'gn-fill-paragraph) |
| |
| (setq-local font-lock-defaults '(gn-font-lock-keywords)) |
| |
| ;; For every 'rule("name") {', adds "name" to the imenu for quick navigation. |
| (setq-local imenu-generic-expression |
| '((nil "^\s*[a-zA-Z0-9_]+(\"\\([a-zA-Z0-9_]+\\)\")\s*{" 1))) |
| |
| (smie-setup nil #'gn-smie-rules) |
| (setq-local smie-indent-basic gn-indent-basic) |
| |
| ;; python style comment: “# …” |
| (modify-syntax-entry ?# "< b" gn-mode-syntax-table) |
| (modify-syntax-entry ?\n "> b" gn-mode-syntax-table) |
| (modify-syntax-entry ?_ "w" gn-mode-syntax-table)) |
| |
| ;;;###autoload |
| (add-to-list 'auto-mode-alist '("\\.gni?\\'" . gn-mode)) |
| |
| (provide 'gn-mode) |