1
;; processing-mode.el
2
3
;; Processing.org language based on Java mode. Adds keyword
4
;; highlighting for all recognized Processing language functions.
5
;; Allows compilation of buffers and "sketches" from within Emacs but
6
;; only for more recent versions of Processing.
7
8
;; Copyright (C) 2008, 2009 Rudolf Olah <omouse@gmail.com>
9
10
;; This program is free software: you can redistribute it and/or modify
11
;; it under the terms of the GNU General Public License as published by
12
;; the Free Software Foundation, either version 3 of the License, or
13
;; (at your option) any later version.
14
15
;; This program is distributed in the hope that it will be useful,
16
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
;; GNU General Public License for more details.
19
20
;; You should have received a copy of the GNU General Public License
21
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23
(eval-when-compile
24
  (require 'compile)
25
  (require 'cl))
26
27
(define-derived-mode processing-mode
28
  java-mode "Processing"
29
  "Major mode for Processing.
30
\\{java-mode-map}")
31
32
(defvar processing-location nil
33
  "The directory where Processing can be found. Assumes you have
34
downloaded the standalone package.")
35
36
(defconst processing-platform
37
  (cond ((string= system-type "gnu/linux")
38
	 "linux")
39
	((or (string= system-type "darwin") (string= system-type "macos"))
40
	 "macosx")
41
	((or (string= system-type "ms-dos") (string= system-type "windows-nt")
42
	     (string= system-type "cygwin"))
43
	 "windows"))
44
  "The platform that Processing is running on. It can be `linux', `macosx' or `windows'.")
45
46
;; Functions
47
(defun make-java-classpath (&rest args)
48
  "Returns a string that is a Java CLASSPATH. Each arg is a
49
folder containing .class files or a .jar. The delimiter is based
50
on the platform, with MS Windows using \";\", and other platforms
51
using \":\"."
52
  (reduce (lambda (x y) (concat x (if (string= processing-platform "windows") ";" ":")
53
				y))
54
	  args))
55
56
(defvar processing-import-libraries
57
  '((minim "jl1.0" "mp3spi1.9.4" "tritonus_share" "tritonus_aos"
58
	   "minim-spi" "minim" "jsminim"))
59
  "An alist of library names and the JAR (Java ARchive) files
60
required for their use. Each element looks like (library-name
61
&REST jar-files) where library-name is a SYMBOL and jar-files are
62
strings.")
63
64
(defun processing-import-library (library-name)
65
  "Generates a STRING that is a Java classpath. The paths are
66
constructed from the REST of the library found in the alist
67
``processing-import-libraries''. The suffix \".jar\" is added to
68
each string."
69
  (make-java-classpath (mapcar (lambda (x) (expand-file-name (concat processing-location
70
								     "libraries/"
71
								     (symbol-name library-name)
72
								     "/library/" x ".jar")))
73
			       (rest (assoc library-name processing-import-libraries)))))
74
75
(defun processing-read-libraries (sketch-dir)
76
  "Returns a LIST of SYMBOLS that are the names of Processing
77
libraries. This list if stored in the \"libraries_required.txt\"
78
file found in the Processing sketch directory ``sketch-dir''.
79
80
If the file does not exist, it is assumed that there are no
81
libraries required and NIL is returned.
82
83
A general error is signalled if the library is not supported.
84
Check the variable ``processing-import-libraries'' to see which
85
libraries are supported."
86
  (let ((file-name (expand-file-name (concat sketch-dir "libraries_required.txt"))))
87
    (if (and (file-exists-p file-name) (file-readable-p file-name))
88
	(let ((temp-buf (generate-new-buffer "libraries-required-by-sketch")))
89
	  (set-buffer temp-buf)
90
	  (insert-file-contents file-name)
91
	  (unwind-protect
92
	      (loop while (< (point) (buffer-size))
93
		    for library-name = (read temp-buf) then (read-temp-buf)
94
		    if (assoc library-name processing-import-libraries)
95
		    collect library-name
96
		    else do (error "Processing-mode does not know how to handle the library %s"
97
				   library-name)
98
		    do (forward-line))
99
	    (kill-buffer temp-buf)))
100
      nil)))
101
102
(defun processing-make-compile-command (sketch-dir output-dir cmd &optional platform)
103
  "Returns a string which is the compile-command for Processing
104
sketches, targetting the sketch files found in ``sketch-dir'',
105
with the output being stored in ``output-dir''. The command flag
106
that is executed on the sketch depends on the type of ``cmd''.
107
108
Valid types of commands are:
109
110
  - \"preprocess\"
111
  - \"build\"
112
  - \"run\"
113
  - \"present\"
114
  - \"export-applet\"
115
  - \"export-application\"
116
117
When ``cmd'' is set to \"export-application\", the ``platform''
118
must be set to one of \"windows\", \"macosx\", or \"linux\". If
119
no platform is selected, the default platform that Emacs is
120
running on will be selected."
121
  (concat (file-name-as-directory processing-location)
122
	  "java/bin/java -classpath \""
123
	  (apply 'make-java-classpath
124
		 (mapcar (lambda (x) (expand-file-name (concat processing-location x)))
125
			 '("java/lib/rt.jar"
126
			   "java/lib/tools.jar"
127
			   "lib/antlr.jar" "lib/core.jar"
128
			   "lib/ecj.jar" "lib/jna.jar"
129
			   "lib/pde.jar")))
130
	  "\" processing.app.Commander"
131
	  " --sketch=\"" (expand-file-name sketch-dir)
132
	  "\" --output=\"" (expand-file-name output-dir)
133
	  ;; Remove this comment when Processing implements the --preferences=??? command-line option.
134
	  ;;"\" --preferences=\"" (expand-file-name "~/.processing/preferences.txt")
135
	  "\" --" cmd
136
	  (if (string= cmd "export-application")
137
	      (concat " --platform="
138
		      (if platform platform (processing-platform))))))
139
140
(defun processing-commander (sketch-dir output-dir cmd &optional platform)
141
  "Runs the Processing compiler, using a compile-command
142
constructed using the ``processing-make-compile-command''
143
function."
144
  (let ((compilation-error-regexp-alist '(processing)))
145
    (compile (processing-make-compile-command sketch-dir output-dir cmd platform))))
146
147
(defun processing-sketch-compile (&optional cmd)
148
  "Runs the Processing Commander application with the current
149
buffer. The output directory is the sub-directory ``output''
150
which will be found in the parent directory of the buffer file."
151
  (interactive)
152
  ;; TODO: Add support for temporary sketches
153
  (let ((sketch-dir (file-name-directory buffer-file-name)))
154
    (processing-commander sketch-dir (concat sketch-dir "output") (if cmd cmd "run"))))
155
156
(defun processing-sketch-present ()
157
  (interactive)
158
  (processing-sketch-compile "present"))
159
160
(defun processing-sketch-build ()
161
  "Runs the build command for a Processing sketch. Processing
162
will process the sketch into .java files and then compile them
163
into .class files."
164
  (interactive)
165
  (processing-sketch-compile "build"))
166
167
(defun processing-export-application ()
168
  "Turns the Processing sketch into a Java application. Assumes
169
that the platform target is whatever platform Emacs is running
170
on."
171
  t)
172
173
;; Add hook so that when processing-mode is loaded, the local variable
174
;; 'compile-command is set.
175
(add-hook 'processing-mode-hook
176
	  (lambda ()
177
	    (let ((sketch-dir (file-name-directory buffer-file-name)))
178
	      (set (make-local-variable 'compile-command)
179
		   (processing-make-compile-command sketch-dir
180
						    (concat sketch-dir "output")
181
						    "build")))))
182
183
;; Key bindings
184
(define-key processing-mode-map "\C-c\C-r" 'processing-sketch-compile)
185
(define-key processing-mode-map "\C-c\C-p" 'processing-sketch-present)
186
(define-key processing-mode-map "\C-c\C-b" 'processing-sketch-build)
187
188
;; Regular expressions
189
;; Compilation
190
(pushnew
191
 ;; Mode name, REGEXP FILE LINE COLUMN TYPE HYPERLINK HIGHLIGHT
192
 '(processing "^\\([[:alnum:]]+.pde\\):\\([0-9]+\\):\\([0-9]+\\):.*$"
193
	      1 2 3)
194
 compilation-error-regexp-alist-alist)
195
196
;; Font-lock, keywords
197
(defconst processing-font-lock-keywords-1
198
  (eval-when-compile
199
    `( ;; Shape functions
200
      (,(concat
201
	 (regexp-opt '("triangle" "line" "arc" "point" "quad" "ellipse"
202
		       "rect" "curve" "bezier")
203
		     t)
204
	 "(") 1 font-lock-function-name-face t)
205
      (,(concat
206
	 (regexp-opt '("strokeWeight" "smooth" "strokeJoin" "noSmooth"
207
		       "ellipseMode" "rectMode" "background" "stroke")
208
		     t)
209
	 "(") 1 font-lock-doc-face t)
210
      (,(regexp-opt '("width" "height" "frameRate" "frameCount" "noCursor()" "cursor()")
211
		    t)
212
       . font-lock-constant-face)
213
      (,(concat "screen." (regexp-opt '("width" "height") t))
214
       1 font-lock-constant-face t)
215
      ))
216
  "Subdued level highlighting for Processing mode.")
217
218
;;(defconst processing-font-lock-keywords-2
219
;;  (append processing-font-lock-keywords-1
220
;;	  (eval-when-compile
221
;;	    `(	    
222
223
(defvar processing-font-lock-keywords processing-font-lock-keywords-1
224
  "Default expressions to highlight in Processing mode.")
225
226
;; YASnippets
227
(if (fboundp 'yas/minor-mode)
228
    (progn
229
      (require 'yasnippet)
230
      (message "processing-mode: defining YASnippets")
231
      (yas/define-snippets
232
       'processing-mode
233
       '(
234
	 ;; (key template name condition)
235
	 ("tri" "triangle(${x1}, ${y1}, ${x2}, ${y2}, ${x3}, ${y3});"
236
	  "triangle" nil)
237
	 ("l(" "line(${x1}, ${y1}, ${x2}, ${y2});" "line 2d" nil)
238
	 ("l(.3d" "line(${x1}, ${y1}, ${z1}, ${x2}, ${y2}, ${z2});" "line 3d" nil)
239
	 ("arc" "arc(${x}, ${y}, ${width}, ${height}, ${start}, ${stop});" "arc" nil)
240
	 ("p(" "point(${x}, ${y});" "point 2d" nil)
241
	 ("p(.3d" "point(${x}, ${y}, ${z});" "point 3d" nil)
242
	 ("quad" "quad(${x1}, ${y1}, ${x2}, ${y2}, ${x3}, ${y3}, ${x4}, ${y4});"
243
	  "quad" nil)
244
	 ("ell" "ellipse(${x}, ${y}, ${width}, ${height});" "ellipse" nil)
245
	 ("rect" "rect(${x}, ${y}, ${width}, ${height});" "rect" nil)
246
	 
247
	 ;; Color Setting
248
	 ("background" "background(${gray_or_color_or_hex});" "background .." nil)
249
	 ("background.ca" "background(${gray_or_color_or_hex}, ${alpha});"
250
	  "background .. alpha" nil)
251
	 ("background.rgb" "background(${red}, ${green}, ${blue});" "background RGB" nil)
252
	 ("background.rgba" "background(${red}, ${green}, ${blue}, ${alpha});"
253
	  "background RGBA" nil)
254
	 ("colorm" "colorMode(${RGB_or_HSV});" "color mode" nil)
255
	 ("colorm.r" "colorMode(${RGB_or_HSV}, ${range});" "color mode range" nil)
256
	 ("colorm.rgb" "colorMode(${RGB_or_HSV}, ${range1}, ${range2}, ${range3});"
257
	  "color mode RGB/HSV range" nil)
258
	 ("colorm.rgba" "colorMode(${RGB_or_HSV}, ${range1}, ${range2}, ${range3}, ${range4});"
259
	  "color mode RGB/HSV, A range" nil)
260
	 ("stroke" "stroke(${gray_or_color_or_hex});" "stroke .." nil)
261
	 ("stroke.ca" "stroke(${gray_or_color_or_hex}, ${alpha});" "stroke .. alpha" nil)
262
	 ("stroke.rgb" "stroke(${red}, ${green}, ${blue});" "stroke RGB" nil)
263
	 ("stroke.rgba" "stroke(${red}, ${green}, ${blue}, ${alpha});" "stroke RGBA" nil)
264
	 ("fill" "fill(${gray_or_color_or_hex});" "fill .." nil)
265
	 ("fill.ca" "fill(${gray_or_color_or_hex}, ${alpha});" "fill .. alpha" nil)
266
	 ("fill.rgb" "fill(${red}, ${green}, ${blue});" "fill RGB" nil)
267
	 ("fill.rgba" "fill(${red}, ${green}, ${blue}, ${alpha});" "fill RGBA" nil)
268
	 )
269
       'java-mode)
270
      t)
271
  (progn
272
    (message "processing-mode: YASnippets not installed. Not defining any snippets.")
273
    nil))
274
275
(provide 'processing-mode)