-
Notifications
You must be signed in to change notification settings - Fork 46
/
nim-compile.el
198 lines (171 loc) · 7.33 KB
/
nim-compile.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
;;; nim-compile.el --- A support package of compilation for Nim -*- lexical-binding: t -*-
;;; Commentary:
;; TODO: compilation command is way too long. Maybe we should shorten
;; or hide some default options like excessiveStackTrace etc.
;;; Code:
(require 'cl-lib)
(require 'rx)
(require 'nim-vars)
(defvar nim-compile-command-checker-functions
'(nim-compile--project)
"Checker functions to decide build command.
Functions (hooks) take one argument as file file string and
return build command like ‘nim c -r FILE.nim’")
(defvar nim-compile-default-command
'("c" "-r" "--verbosity:0" "--hint[Processing]:off" "--excessiveStackTrace:on"))
(defvar-local nim-compile--current-command nil)
;; MEMO:
;; Implemented based on compiler document:
;; http://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files
;; (5) for PROJECT/PROJECT.(nim.cfg/.nims)
(defun nim-get-project-file (candidates &optional dir)
(let* ((projdir (file-name-base
(directory-file-name (or dir default-directory))))
(projfile
(directory-file-name (mapconcat 'file-name-as-directory
`(,default-directory ,projdir)
""))))
(cl-loop for ext in candidates
for file = (format "%s%s" projfile ext)
if (file-exists-p file)
do (cl-return file))))
;; (3,4) (parentDir or projectDir)/nim.cfg
(defconst nim-config-regex
(rx (group (or (group (or "nimcfg" "nim.cfg"))
(group (? (and (0+ any) ".")) "nim.cfg"))
line-end)))
(defun nim-find-config-file ()
"Get the config file from current directory hierarchy.
The config file would one of those: config.nims, PROJECT.nim.cfg, or nim.cfg."
(nim-find-file-in-heirarchy
(file-name-directory (buffer-file-name))
nim-config-regex))
(defun nim-find-file-in-heirarchy (current-dir pattern)
"Search starting from CURRENT-DIR for a file matching PATTERN upwards through the directory hierarchy."
(catch 'found
(locate-dominating-file
current-dir
(lambda (dir)
(let ((pfile (nim-get-project-file '(".nims" ".nim.cfg") dir)))
(when pfile (throw 'found pfile)))
(let ((file (cl-first (directory-files dir t pattern nil))))
(when file (throw 'found file)))))))
(defcustom nim-project-root-regex "\\(\.git\\|\.nim\.cfg\\|\.nimble\\)$"
"Regex to find project root directory."
:type 'string
:group 'nim)
(defun nim-get-project-root ()
"Return project directory."
(file-name-directory
(nim-find-file-in-heirarchy
(file-name-directory (buffer-file-name)) nim-project-root-regex)))
;; Compile command support
(require 'compile)
(defun nim-compile--project (file)
"Return ‘nim build FILE’ if there is PROJECT.nims."
(let ((proj (nim-get-project-file '(".nims" ".nim.cfg"))))
(when (and proj (nim-nims-file-p proj)
(eq major-mode 'nim-mode))
(nim--fmt '("build") file))))
(defun nim-nims-file-p (file)
"Test if FILE is a nim script file."
(equal "nims" (file-name-extension file)))
(defun nim-nimble-file-p (file)
"Test if FILE is a nimble file."
(equal "nimble" (file-name-extension file)))
(defun nim-compile--get-compile-command ()
"Return Nim's compile command or use previous command if it exists."
(if nim-compile--current-command
nim-compile--current-command
(setq nim-compile--current-command
(let ((file (when buffer-file-name
buffer-file-name)))
(when file
(cond
;; WIP
((eq 'nimscript-mode major-mode)
(let ((pfile (nim-get-project-file '(".nims" ".nimble"))))
(cond
;; as build tool
((nim-nimble-file-p file)
(let ((nim-compile-command "nimble"))
(nim--fmt '("build") "")))
((and (nim-nims-file-p pfile)
(equal pfile buffer-file-name))
(nim--fmt '("build") pfile))
(t
;; as script file
(nim--fmt '("e") file)))))
(t
(let ((cmd (run-hook-with-args-until-success
'nim-compile-command-checker-functions file)))
(or cmd (nim--fmt nim-compile-default-command file))))))))))
(defun nim--fmt (args file)
"Format ARGS and FILE for the nim command into a shell compatible string."
(mapconcat
'shell-quote-argument
(delq nil `(,nim-compile-command ,@args ,@nim-compile-user-args ,file))
" "))
(define-compilation-mode nim-compile-mode "nim-compile"
"major-mode for *nim-compile* buffer."
;; keep `nim--colorize-compilation-buffer' for `recompile' function (g key)
(if (eq major-mode 'nim-compile-mode)
(add-hook 'compilation-filter-hook #'nim--colorize-compilation-buffer t)
(remove-hook 'compilation-filter-hook #'nim--colorize-compilation-buffer t)))
(defun nim-compile--assert (command)
"Check COMMAND.
Basically copied from `compile-command's document."
(and (stringp command)
(or (not (boundp (quote compilation-read-command))) compilation-read-command)))
;;;###autoload
(defun nim-compile (&optional command)
"Compile and execute the current buffer as a nim file.
All output is written into the *nim-compile* buffer.
If you put COMMAND argument, you can specify the compilation command."
(interactive)
(when (derived-mode-p 'nim-mode)
(setq-local compile-command
(or command
(if (or compilation-read-command current-prefix-arg)
(compilation-read-command (nim-compile--get-compile-command))
(nim-compile--get-compile-command))))
;; keep users' previous command if they changed
(setq-local nim-compile--current-command compile-command)
(if (nim-compile--assert compile-command)
(funcall 'compile compile-command 'nim-compile-mode)
(error "Something goes wrong"))))
;; enable the regular for nim error messages in compilation buffers
(add-to-list 'compilation-error-regexp-alist 'nim)
;; Define a regex to parse Nim's compilation message to jump over
;; error or warning points.
(add-to-list
'compilation-error-regexp-alist-alist
`(nim ,(rx line-start
(or
;; Info
(group-n 5 (or "Hint: " "template/generic instantiation from here"))
;; Warning or Error
(group-n 7
;; File name
(group-n 1 (1+ (in alnum "\\" "/" "_" "." "-") "") ".nim" (? "s"))
"("
;; Line
(group-n 2 (1+ digit))
;; Column -- this parameter is optional when it
;; comes from stacktrace (see #171)
(? ", " (group-n 3 (1+ digit)))
")"
;; Type -- also this parameter doesn't show up when it
;; come from stacktrace
" " (? (group-n 4 (or "Warning" "Error") ": "))))
;; Capture rest of message
(0+ any) line-end)
;; See `compilation-error-regexp-alist's document for the detail
1 2 3 (4 . 5)))
(require 'ansi-color)
(defun nim--colorize-compilation-buffer ()
"Colorize compilation buffer."
(let ((inhibit-read-only t))
(ansi-color-apply-on-region compilation-filter-start (point-max))))
(provide 'nim-compile)
;;; nim-compile.el ends here