• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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