1;;; gn-mode.el - A major mode for editing gn files. 2 3;; Copyright 2015 The Chromium Authors. 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;; Author: Elliot Glaysher <erg@chromium.org> 8;; Created: April 03, 2015 9;; Keywords: tools, gn, ninja, chromium 10 11;; This file is not part of GNU Emacs. 12 13;;; Commentary: 14 15;; A major mode for editing GN files. GN stands for Generate Ninja. GN is the 16;; meta build system used in Chromium. For more information on GN, see the GN 17;; manual: <https://gn.googlesource.com/gn/+/refs/heads/master/README.md> 18 19;;; To Do: 20 21;; - We syntax highlight builtin actions, but don't highlight instantiations of 22;; templates. Should we? 23 24 25 26(require 'smie) 27 28(defgroup gn nil 29 "Major mode for editing Generate Ninja files." 30 :prefix "gn-" 31 :group 'languages) 32 33(defcustom gn-indent-basic 2 34 "The number of spaces to indent a new scope." 35 :group 'gn 36 :type 'integer) 37 38(defcustom gn-format-command "gn format --stdin" 39 "The command to run to format gn files in place." 40 :group 'gn 41 :type 'string) 42 43(defgroup gn-faces nil 44 "Faces used in Generate Ninja mode." 45 :group 'gn 46 :group 'faces) 47 48(defface gn-embedded-variable 49 '((t :inherit font-lock-variable-name-face)) 50 "Font lock face used to highlight variable names in strings." 51 :group 'gn-faces) 52 53(defface gn-embedded-variable-boundary 54 '((t :bold t 55 :inherit gn-embedded-variable)) 56 "Font lock face used to highlight the '$' that starts a 57variable name or the '{{' and '}}' which surround it." 58 :group 'gn-faces) 59 60(defvar gn-font-lock-reserved-keywords 61 '("true" "false" "if" "else")) 62 63(defvar gn-font-lock-target-declaration-keywords 64 '("action" "action_foreach" "bundle_data" "copy" "create_bundle" "executable" 65 "group" "loadable_module" "shared_library" "source_set" "static_library" 66 "generated_file" "target" "rust_library" "rust_proc_macro")) 67 68;; pool() is handled specially since it's also a variable name 69(defvar gn-font-lock-buildfile-fun-keywords 70 '("assert" "config" "declare_args" "defined" "exec_script" "foreach" 71 "forward_variables_from" "get_label_info" "get_path_info" 72 "get_target_outputs" "getenv" "import" "not_needed" "print" 73 "process_file_template" "read_file" "rebase_path" "set_default_toolchain" 74 "set_defaults" "set_sources_assignment_filter" "split_list" "string_join" 75 "string_split" "template" "tool" "toolchain" "propagates_configs" 76 "write_file")) 77 78(defvar gn-font-lock-predefined-var-keywords 79 '("current_cpu" "current_os" "current_toolchain" "default_toolchain" 80 "host_cpu" "host_os" "invoker" "python_path" "root_build_dir" "root_gen_dir" 81 "root_out_dir" "target_cpu" "target_gen_dir" "target_name" "target_os" 82 "target_out_dir")) 83 84(defvar gn-font-lock-var-keywords 85 '("all_dependent_configs" "allow_circular_includes_from" "arflags" "args" 86 "asmflags" "assert_no_deps" "bundle_deps_filter" "bundle_executable_dir" 87 "bundle_resources_dir" "bundle_root_dir" "cflags" "cflags_c" "cflags_cc" 88 "cflags_objc" "cflags_objcc" "check_includes" "code_signing_args" 89 "code_signing_outputs" "code_signing_script" "code_signing_sources" 90 "complete_static_lib" "configs" "data" "data_deps" "defines" "depfile" 91 "deps" "include_dirs" "inputs" "ldflags" "lib_dirs" "libs" "output_dir" 92 "output_extension" "output_name" "output_prefix_override" "outputs" "pool" 93 "precompiled_header" "precompiled_header_type" "precompiled_source" 94 "product_type" "public" "public_configs" "public_deps" 95 "response_file_contents" "script" "sources" "testonly" "visibility" 96 "write_runtime_deps" "bundle_contents_dir" "contents" "output_conversion" 97 "rebase" "data_keys" "walk_keys")) 98 99(defconst gn-font-lock-keywords 100 `((,(regexp-opt gn-font-lock-reserved-keywords 'words) . 101 font-lock-keyword-face) 102 (,(regexp-opt gn-font-lock-target-declaration-keywords 'words) . 103 font-lock-type-face) 104 (,(regexp-opt gn-font-lock-buildfile-fun-keywords 'words) . 105 font-lock-function-name-face) 106 ;; pool() as a function 107 ("\\<\\(pool\\)\\s-*(" 108 (1 font-lock-function-name-face)) 109 (,(regexp-opt gn-font-lock-predefined-var-keywords 'words) . 110 font-lock-constant-face) 111 (,(regexp-opt gn-font-lock-var-keywords 'words) . 112 font-lock-variable-name-face) 113 ;; $variables_like_this 114 ("\\(\\$\\)\\([a-zA-Z0-9_]+\\)" 115 (1 'gn-embedded-variable-boundary t) 116 (2 'gn-embedded-variable t)) 117 ;; ${variables_like_this} 118 ("\\(\\${\\)\\([^\n }]+\\)\\(}\\)" 119 (1 'gn-embedded-variable-boundary t) 120 (2 'gn-embedded-variable t) 121 (3 'gn-embedded-variable-boundary t)) 122 ;; {{placeholders}} (see substitute_type.h) 123 ("\\({{\\)\\([^\n }]+\\)\\(}}\\)" 124 (1 'gn-embedded-variable-boundary t) 125 (2 'gn-embedded-variable t) 126 (3 'gn-embedded-variable-boundary t)))) 127 128(defun gn-smie-rules (kind token) 129 "These are slightly modified indentation rules from the SMIE 130 Indentation Example info page. This changes the :before rule 131 and adds a :list-intro to handle our x = [ ] syntax." 132 (pcase (cons kind token) 133 (`(:elem . basic) gn-indent-basic) 134 (`(,_ . ",") (smie-rule-separator kind)) 135 (`(:list-intro . "") gn-indent-basic) 136 (`(:before . ,(or `"[" `"(" `"{")) 137 (if (smie-rule-hanging-p) (smie-rule-parent))) 138 (`(:before . "if") 139 (and (not (smie-rule-bolp)) (smie-rule-prev-p "else") 140 (smie-rule-parent))))) 141 142(defun gn-fill-paragraph (&optional justify) 143 "We only fill inside of comments in GN mode." 144 (interactive "P") 145 (or (fill-comment-paragraph justify) 146 ;; Never return nil; `fill-paragraph' will perform its default behavior 147 ;; if we do. 148 t)) 149 150(defun gn-run-format () 151 "Run 'gn format' on the buffer in place." 152 (interactive) 153 ;; We can't `save-excursion' here; that will put us at the beginning of the 154 ;; shell output, aka the beginning of the document. 155 (let ((my-start-line (line-number-at-pos))) 156 (shell-command-on-region (point-min) (point-max) gn-format-command nil t) 157 (goto-line my-start-line))) 158 159(defvar gn-mode-map 160 (let ((map (make-sparse-keymap))) 161 (define-key map "\C-c\C-f" 'gn-run-format) 162 map)) 163 164;;;###autoload 165(define-derived-mode gn-mode prog-mode "GN" 166 "Major mode for editing gn (Generate Ninja)." 167 :group 'gn 168 169 (setq-local comment-use-syntax t) 170 (setq-local comment-start "#") 171 (setq-local comment-end "") 172 (setq-local indent-tabs-mode nil) 173 174 (setq-local fill-paragraph-function 'gn-fill-paragraph) 175 176 (setq-local font-lock-defaults '(gn-font-lock-keywords)) 177 178 ;; For every 'rule("name") {', adds "name" to the imenu for quick navigation. 179 (setq-local imenu-generic-expression 180 '((nil "^\s*[a-zA-Z0-9_]+(\"\\([a-zA-Z0-9_]+\\)\")\s*{" 1))) 181 182 (smie-setup nil #'gn-smie-rules) 183 (setq-local smie-indent-basic gn-indent-basic) 184 185 ;; python style comment: “# …” 186 (modify-syntax-entry ?# "< b" gn-mode-syntax-table) 187 (modify-syntax-entry ?\n "> b" gn-mode-syntax-table) 188 (modify-syntax-entry ?_ "w" gn-mode-syntax-table)) 189 190;;;###autoload 191(add-to-list 'auto-mode-alist '("\\.gni?\\'" . gn-mode)) 192 193(provide 'gn-mode) 194