From 0b8304c49ab2958bc2b4ef7286467a71faa38f41 Mon Sep 17 00:00:00 2001 From: Paulo Cesar Pereira de Andrade Date: Sat, 12 Apr 2008 17:43:04 -0300 Subject: Add python mode. This mode adds syntax highlight and automatic indentation. Unlike most other modes with automatic indentation, this mode most only reads one line back to figure the proper indentation. Some features include: o When the first character in a line is typped, it automatically moves it to the proper tab stop. o Increments one indentation level if line ends in ':'. o Properly handle vector/hash table declarations. --- Makefile.am | 1 + lisp/modules/progmodes/python.lsp | 306 ++++++++++++++++++++++++++++++++++++++ lisp/modules/xedit.lsp | 2 + 3 files changed, 309 insertions(+) create mode 100644 lisp/modules/progmodes/python.lsp diff --git a/Makefile.am b/Makefile.am index 39d8b0b..7d70784 100644 --- a/Makefile.am +++ b/Makefile.am @@ -201,6 +201,7 @@ dist_progmodes_DATA = \ ${srcdir}/lisp/modules/progmodes/man.lsp \ ${srcdir}/lisp/modules/progmodes/patch.lsp \ ${srcdir}/lisp/modules/progmodes/perl.lsp \ + ${srcdir}/lisp/modules/progmodes/python.lsp \ ${srcdir}/lisp/modules/progmodes/rpm.lsp \ ${srcdir}/lisp/modules/progmodes/sgml.lsp \ ${srcdir}/lisp/modules/progmodes/sh.lsp \ diff --git a/lisp/modules/progmodes/python.lsp b/lisp/modules/progmodes/python.lsp new file mode 100644 index 0000000..ff70856 --- /dev/null +++ b/lisp/modules/progmodes/python.lsp @@ -0,0 +1,306 @@ +;; Copyright (c) 2008 Paulo Cesar Pereira de Andrade +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice (including the next +;; paragraph) shall be included in all copies or substantial portions of the +;; Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;; Author: Paulo Cesar Pereira de Andrade +;; + +(require "syntax") +(require "indent") +(in-package "XEDIT") + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defsynprop *prop-indent* + "indent" + :font "*courier-medium-r*-12-*" + :background "Gray92") + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defsynoptions *python-DEFAULT-options* + ;; Positive number. Basic indentation + (:indentation . 4) + + ;; Boolean. Move cursor to the indent column after pressing ? + (:newline-indent . t) + + ;; Boolean. Set to T if tabs shouldn't be used to fill indentation. + (:emulate-tabs . t) + + ;; Boolean. Only calculate indentation after pressing ? + ;; This may be useful if the parser does not always + ;; do what the user expects... + (:only-newline-indent . nil) + + ;; Boolean. Remove extra spaces from previous line. + ;; This should default to T when newline-indent is not NIL. + (:trim-blank-lines . nil) + + ;; Boolean. If this hash-table entry is set, no indentation is done. + ;; Useful to temporarily disable indentation. + (:disable-indent . nil)) + + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +;; Not doing "special" indentation of multiline ( because it is attempting +;; to do a "smart" indentation and usually don't read more then one line +;; back to resolve indentation. +;; Code for multiline { and [, usually declaring vector/hash like variables +;; should be working properly. +;; Note that the indent lisp hook is only run on character additions, so +;; it doesn't do a "smart" tabbing when pressing backspace, but it will +;; properly align to the "closest tab stop" when typping a character. +(defindent *python-mode-indent* :main + ;; this must be the first token + (indtoken "^\\s*" :indent + :code (or *offset* (setq *offset* (+ *ind-offset* *ind-length*)))) + + ;; ignore comments + (indtoken "#.*$" nil) + + (indtoken ":" :collon :nospec t) + + ;; don't directly match {}, [], () strings, and : + (indtoken "[a-zA-Z0-9+*/%^&<>=.,|!~-]+" :expression) + + ;; if in the same line, reduce now, as delimiters are identical + (indtoken "'([^\\']|\\\\.)*'" :expression) + (indtoken "\"([^\\\"]|\\\\.)*\"" :expression) + ;; otherwise, use a table + (indtoken "\"" :cstring :nospec t :begin :string) + (indtoken "'" :cconstant :nospec t :begin :constant) + (indtoken "\"\"\"" :cstring3 :nospec t :begin :string3) + (indtoken "'''" :cconstant :nospec t :begin :constant3) + + (indinit (braces 0)) + (indtoken "}" :cbrace :nospec t :code (incf braces)) + (indtoken "{" :obrace :nospec t :code (decf braces)) + (indtoken ")" :cparen :nospec t :code (incf braces)) + (indtoken "(" :oparen :nospec t :code (decf braces)) + (indtoken "]" :cbrack :nospec t :code (incf braces)) + (indtoken "[" :obrack :nospec t :code (decf braces)) + + ;; This must be the last token + (indtoken "$" :eol) + + (indtable :string + ;; Ignore escaped characters + (indtoken "\\." nil) + ;; Return to the toplevel when the start of the string is found + (indtoken "\"" :ostring :nospec t :switch -1)) + (indtable :constant + (indtoken "\\." nil) + (indtoken "'" :oconstant :nospec t :switch -1)) + + (indtable :string3 + (indtoken "\"\"\"" :ostring3 :nospec t :switch -1)) + (indtable :constant3 + (indtoken "'''" :oconstant3 :nospec t :switch -1)) + + ;; Reduce what isn't reduced in regex pattern match + (indreduce :expression + t + ((:expression :expression) + ;; multiline strings + (:ostring (not :ostring) :cstring) + (:oconstant (not :oconstant) :cconstant) + (:ostring3 (not :ostring3) :cstring3) + (:oconstant3 (not :oconstant3) :cconstant3) + ;; braces, parenthesis and brackets + (:obrace (not :obrace) :cbrace) + (:oparen (not :oparen) :cparen) + (:obrack (not :obrack) :cbrack))) + + ;; This should be the most common exit point; + ;; just copy previous line indentation. + (indreduce :align + (< *ind-offset* *ind-start*) + ((:indent :eol) + (:indent :expression :eol)) + (setq *indent* (offset-indentation *offset* :resolve t)) + + ;; If cursor is not in an indentation tab, assume user is trying to align + ;; to another block, and just use the resolve code to round it down + (unless (/= (mod *indent* *base-indent*) 0) + ;; else use "previous-line" indentation. + (setq *indent* (offset-indentation *ind-offset* :resolve t))) + (indent-macro-reject-left)) + + ;; This should be second most common exit point; + ;; add one indentation level. + (indreduce :align + (< *ind-offset* *ind-start*) + ((:indent :expression :collon :eol)) + (setq *indent* (+ *base-indent* (offset-indentation *ind-offset* :resolve t))) + (indent-macro-reject-left)) + + (indresolve :align + (setq *indent* (- *indent* (mod *indent* *base-indent*)))) + + ;; Calculate special indentation for [ and { + (indresolve (:obrack :obrace) + (and + (< *ind-offset* *ind-start*) + (setq *indent* (+ *base-indent* + (offset-indentation *ind-offset* :resolve t))))) + (indresolve (:cbrack :cbrace) + (setq *indent* (- (offset-indentation *ind-offset* :resolve t) + (if (>= *ind-offset* *ind-start*) + *base-indent* 0)))) +) + + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defun python-offset-indent (&aux char (point (point))) + ;; Skip spaces forward + (while (member (setq char (char-after point)) indent-spaces) + (incf point)) + point) + +(compile 'python-offset-indent) + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defun python-should-indent (options &aux point start end offset) + (when (hash-table-p options) + ;; check if previous line has extra spaces + (and (gethash :trim-blank-lines options) + (indent-clear-empty-line)) + + ;; indentation disabled? + (and (gethash :disable-indent options) + (return-from python-should-indent)) + + (setq + point (point) + start (scan point :eol :left) + end (scan point :eol :right)) + + ;; if at bol and should indent only when starting a line + (and (gethash :only-newline-indent options) + (return-from python-should-indent (= point start))) + + ;; at the start of a line + (and (= point start) + (return-from python-should-indent (gethash :newline-indent options))) + + ;; if first character + (and (= point (1+ start)) + (return-from python-should-indent t)) + + (setq offset start) + (while (and + (< offset end) + (member (char-after offset) indent-spaces)) + (incf offset)) + + ;; cursor is at first character in line, with possible spaces before it + (return-from python-should-indent (or (= offset end) (= offset (1- point)))) + ) + ;; Should not indent + nil) + +(compile 'python-should-indent) + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defun python-indent (syntax syntable) + (let* + ((options (syntax-options syntax)) + *base-indent*) + + (or (python-should-indent options) (return-from python-indent)) + (setq + *base-indent* (gethash :indentation options 4)) + + (indent-macro + *python-mode-indent* + (python-offset-indent) + (gethash :emulate-tabs options)))) + +(compile 'python-indent) + + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defvar *python-mode-options* *python-DEFAULT-options*) + + +;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +(defsyntax *python-mode* :main nil #'python-indent *python-mode-options* + ;; keywords + (syntoken + (string-concat + "\\<(" + "and|break|class|continue|def|del|enumerate|except|False|for|" + "elif|else|if|in|is|len|None|not|or|pass|print|raise|range|" + "return|self|True|try|type|while|yield" + ")\\>") + :property *prop-keyword*) + + (syntoken "^\\s+" :property *prop-indent*) + + ;; preprocessor like + (syntoken + (string-concat + "\\<(" + "from|import" + ")\\>") + :property *prop-preprocessor*) + + ;; namespaces/accessors + (syntoken "(\\w+\\.)+" :property *prop-preprocessor*) + + ;; more preprocessor like + (syntoken "\\<__[a-zA-Z0-9]+__\\>" :property *prop-keyword*) + + ;; numbers + (syntoken + (string-concat + "\\<(" + ;; Integers + "(\\d+|0x\\x+)L?|" + ;; Floats + "\\d+\\.?\\d*(e[+-]?\\d+)?" + ")\\>") + :icase t + :property *prop-number*) + + ;; comments + (syntoken "#.*" :property *prop-comment*) + + ;; punctuation + (syntoken "[][(){}+*/%^&<>=.,|!~:-]+" :property *prop-punctuation*) + + ;; constant or constant like + (syntoken "'" :nospec t :property *prop-constant* :begin :constant) + (syntoken "'''" :nospec t :property *prop-constant* :begin :constant3) + + ;; strings + (syntoken "\"" :nospec t :property *prop-string* :begin :string) + (syntoken "\"\"\"" :nospec t :property *prop-string* :begin :string3) + + (syntable :constant *prop-constant* nil + (syntoken "\\\\.") + (syntoken "'" :nospec t :switch -1)) + (syntable :constant3 *prop-constant* nil + (syntoken "'''" :nospec t :switch -1)) + (syntable :string *prop-string* nil + (syntoken "\\\\.") + (syntoken "\"" :nospec t :switch -1)) + (syntable :string3 *prop-string* nil + (syntoken "\"\"\"" :nospec t :switch -1)) +) diff --git a/lisp/modules/xedit.lsp b/lisp/modules/xedit.lsp index d3e2a02..2b76970 100644 --- a/lisp/modules/xedit.lsp +++ b/lisp/modules/xedit.lsp @@ -70,6 +70,8 @@ "RPM spec" "rpm" . *rpm-mode*) ("\\.(pl|pm|ph)$" "Perl" "perl" . *perl-mode*) + ("\\.(py)$" + "Python" "python". *python-mode*) ("\\.(sgml?|dtd)$" "SGML" "sgml" . *sgml-mode*) ("\\.html?$" -- cgit v1.2.3