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