1;; Copyright (c) 2008 Paulo Cesar Pereira de Andrade
2;;
3;; Permission is hereby granted, free of charge, to any person obtaining a
4;; copy of this software and associated documentation files (the "Software"),
5;; to deal in the Software without restriction, including without limitation
6;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
7;; and/or sell copies of the Software, and to permit persons to whom the
8;; Software is furnished to do so, subject to the following conditions:
9;;
10;; The above copyright notice and this permission notice (including the next
11;; paragraph) shall be included in all copies or substantial portions of the
12;; Software.
13;;
14;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20;; DEALINGS IN THE SOFTWARE.
21;;
22;; Author: Paulo Cesar Pereira de Andrade
23;;
24
25(require "syntax")
26(require "indent")
27(in-package "XEDIT")
28
29;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
30(defsynprop *prop-indent*
31  "indent"
32  :font		"*courier-medium-r*-12-*"
33  :background	"Gray92")
34
35;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
36(defsynoptions *python-DEFAULT-options*
37  ;; Positive number. Basic indentation
38  (:indentation		.	4)
39
40  ;; Boolean. Move cursor to the indent column after pressing <Enter>?
41  (:newline-indent	.	t)
42
43  ;; Boolean. Set to T if tabs shouldn't be used to fill indentation.
44  (:emulate-tabs	.	t)
45
46  ;; Boolean. Only calculate indentation after pressing <Enter>?
47  ;;		This may be useful if the parser does not always
48  ;;		do what the user expects...
49  (:only-newline-indent	.	nil)
50
51  ;; Boolean. Remove extra spaces from previous line.
52  ;;		This should default to T when newline-indent is not NIL.
53  (:trim-blank-lines	.	nil)
54
55  ;; Boolean. If this hash-table entry is set, no indentation is done.
56  ;;		Useful to temporarily disable indentation.
57  (:disable-indent	.	nil))
58
59
60;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
61;;   Not doing "special" indentation of multiline ( because it is attempting
62;; to do a "smart" indentation and usually don't read more then one line
63;; back to resolve indentation.
64;;   Code for multiline { and [, usually declaring vector/hash like variables
65;; should be working properly.
66;;   Note that the indent lisp hook is only run on character additions, so
67;; it doesn't do a "smart" tabbing when pressing backspace, but it will
68;; properly align to the "closest tab stop" when typping a character.
69(defindent *python-mode-indent* :main
70  ;; this must be the first token
71  (indtoken "^\\s*" :indent
72    :code (or *offset* (setq *offset* (+ *ind-offset* *ind-length*))))
73
74  ;; ignore comments
75  (indtoken "#.*$" nil)
76
77  (indtoken ":" :collon :nospec t)
78
79  ;; don't directly match {}, [], () strings, and :
80  (indtoken "[a-zA-Z0-9+*/%^&<>=.,|!~-]+" :expression)
81
82  ;; if in the same line, reduce now, as delimiters are identical
83  (indtoken "'([^\\']|\\\\.)*'" :expression)
84  (indtoken "\"([^\\\"]|\\\\.)*\"" :expression)
85  ;; otherwise, use a table
86  (indtoken "\"" :cstring :nospec t :begin :string)
87  (indtoken "'" :cconstant :nospec t :begin :constant)
88  (indtoken "\"\"\"" :cstring3 :nospec t :begin :string3)
89  (indtoken "'''" :cconstant :nospec t :begin :constant3)
90
91  (indinit (braces 0))
92  (indtoken "}" :cbrace :nospec t :code (incf braces))
93  (indtoken "{" :obrace	:nospec t :code (decf braces))
94  (indtoken ")" :cparen :nospec t :code (incf braces))
95  (indtoken "(" :oparen	:nospec t :code (decf braces))
96  (indtoken "]" :cbrack	:nospec t :code (incf braces))
97  (indtoken "[" :obrack	:nospec t :code (decf braces))
98
99  ;; This must be the last token
100  (indtoken "$"		:eol)
101
102  (indtable :string
103    ;; Ignore escaped characters
104    (indtoken "\\." nil)
105    ;; Return to the toplevel when the start of the string is found
106    (indtoken "\"" :ostring :nospec t :switch -1))
107  (indtable :constant
108    (indtoken "\\." nil)
109    (indtoken "'" :oconstant :nospec t :switch -1))
110
111  (indtable :string3
112    (indtoken "\"\"\"" :ostring3 :nospec t :switch -1))
113  (indtable :constant3
114    (indtoken "'''" :oconstant3 :nospec t :switch -1))
115
116  ;; Reduce what isn't reduced in regex pattern match
117  (indreduce :expression
118    t
119    ((:expression :expression)
120      ;; multiline strings
121      (:ostring (not :ostring) :cstring)
122      (:oconstant (not :oconstant) :cconstant)
123      (:ostring3 (not :ostring3) :cstring3)
124      (:oconstant3 (not :oconstant3) :cconstant3)
125      ;; braces, parenthesis and brackets
126      (:obrace (not :obrace) :cbrace)
127      (:oparen (not :oparen) :cparen)
128      (:obrack (not :obrack) :cbrack)))
129
130  ;; This should be the most common exit point;
131  ;; just copy previous line indentation.
132  (indreduce :align
133    (< *ind-offset* *ind-start*)
134    ((:indent :eol)
135      (:indent :expression :eol))
136    (setq *indent* (offset-indentation *offset* :resolve t))
137
138    ;; If cursor is not in an indentation tab, assume user is trying to align
139    ;; to another block, and just use the resolve code to round it down
140    (unless (/= (mod *indent* *base-indent*) 0)
141      ;; else use "previous-line" indentation.
142      (setq *indent* (offset-indentation *ind-offset* :resolve t)))
143    (indent-macro-reject-left))
144
145  ;; This should be second most common exit point;
146  ;; add one indentation level.
147  (indreduce :align
148    (< *ind-offset* *ind-start*)
149    ((:indent :expression :collon :eol))
150    (setq *indent* (+ *base-indent* (offset-indentation *ind-offset* :resolve t)))
151    (indent-macro-reject-left))
152
153  (indresolve :align
154    (setq *indent* (- *indent* (mod *indent* *base-indent*))))
155
156  ;; Calculate special indentation for [ and {
157  (indresolve (:obrack :obrace)
158    (and
159      (< *ind-offset* *ind-start*)
160      (setq *indent* (+ *base-indent*
161		       (offset-indentation *ind-offset* :resolve t)))))
162  (indresolve (:cbrack :cbrace)
163    (setq *indent* (- (offset-indentation *ind-offset* :resolve t)
164		     (if (>= *ind-offset* *ind-start*)
165		       *base-indent* 0))))
166)
167
168
169;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
170(defun python-offset-indent (&aux char (point (point)))
171  ;; Skip spaces forward
172  (while (member (setq char (char-after point)) indent-spaces)
173    (incf point))
174  point)
175
176(compile 'python-offset-indent)
177
178;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
179(defun python-should-indent (options &aux point start end offset)
180  (when (hash-table-p options)
181    ;; check if previous line has extra spaces
182    (and (gethash :trim-blank-lines options)
183      (indent-clear-empty-line))
184
185    ;; indentation disabled?
186    (and (gethash :disable-indent options)
187      (return-from python-should-indent))
188
189    (setq
190      point	(point)
191      start	(scan point :eol :left)
192      end	(scan point :eol :right))
193
194    ;; if at bol and should indent only when starting a line
195    (and (gethash :only-newline-indent options)
196      (return-from python-should-indent (= point start)))
197
198    ;; at the start of a line
199    (and (= point start)
200      (return-from python-should-indent (gethash :newline-indent options)))
201
202    ;; if first character
203    (and (= point (1+ start))
204      (return-from python-should-indent t))
205
206    (setq offset start)
207    (while (and
208	     (< offset end)
209	     (member (char-after offset) indent-spaces))
210      (incf offset))
211
212    ;; cursor is at first character in line, with possible spaces before it
213    (return-from python-should-indent (or (= offset end) (= offset (1- point))))
214  )
215  ;; Should not indent
216  nil)
217
218(compile 'python-should-indent)
219
220;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
221(defun python-indent (syntax syntable)
222  (let*
223    ((options (syntax-options syntax))
224      *base-indent*)
225
226    (or (python-should-indent options) (return-from python-indent))
227    (setq
228      *base-indent* (gethash :indentation options 4))
229
230    (indent-macro
231      *python-mode-indent*
232      (python-offset-indent)
233      (gethash :emulate-tabs options))))
234
235(compile 'python-indent)
236
237
238;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
239(defvar *python-mode-options* *python-DEFAULT-options*)
240
241
242;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
243(defsyntax *python-mode* :main nil #'python-indent *python-mode-options*
244  ;; keywords
245  (syntoken
246    (string-concat
247      "\\<("
248      "and|break|class|continue|def|del|enumerate|except|False|for|"
249      "elif|else|if|in|is|len|None|not|or|pass|print|raise|range|"
250      "return|self|True|try|type|while|yield"
251      ")\\>")
252    :property *prop-keyword*)
253
254  (syntoken "^\\s+" :property *prop-indent*)
255
256  ;; preprocessor like
257  (syntoken
258    (string-concat
259      "\\<("
260      "from|import"
261      ")\\>")
262    :property *prop-preprocessor*)
263
264  ;; namespaces/accessors
265  (syntoken "(\\w+\\.)+" :property *prop-preprocessor*)
266
267  ;; more preprocessor like
268  (syntoken "\\<__[a-zA-Z0-9]+__\\>" :property *prop-keyword*)
269
270  ;; numbers
271  (syntoken
272    (string-concat
273      "\\<("
274      ;; Integers
275      "(\\d+|0x\\x+)L?|"
276      ;; Floats
277      "\\d+\\.?\\d*(e[+-]?\\d+)?"
278      ")\\>")
279    :icase t
280    :property *prop-number*)
281
282  ;; comments
283  (syntoken "#.*" :property *prop-comment*)
284
285  ;; punctuation
286  (syntoken "[][(){}+*/%^&<>=.,|!~:-]+" :property *prop-punctuation*)
287
288  ;; constant or constant like
289  (syntoken "'" :nospec t :property *prop-constant* :begin :constant)
290  (syntoken "'''" :nospec t :property *prop-constant* :begin :constant3)
291
292  ;; strings
293  (syntoken "\"" :nospec t :property *prop-string* :begin :string)
294  (syntoken "\"\"\"" :nospec t :property *prop-string* :begin :string3)
295
296  (syntable :constant *prop-constant* nil
297    (syntoken "\\\\.")
298    (syntoken "'" :nospec t :switch -1))
299  (syntable :constant3 *prop-constant* nil
300    (syntoken "'''" :nospec t :switch -1))
301  (syntable :string *prop-string* nil
302    (syntoken "\\\\.")
303    (syntoken "\"" :nospec t :switch -1))
304  (syntable :string3 *prop-string* nil
305    (syntoken "\"\"\"" :nospec t :switch -1))
306)
307