Tuesday, September 9, 2008

emacs python mode from scratch: stage 5 - more movement methods

So let's continue with the seemingly modest goal of getting tabbing to work

Presumably my current target is to get python-indent-line working

From some quick browsing this function has the following important dependencies:

db-python-indent-line-1 ;; which sets global: db-python-indent-list-length
db-python-calculate-indentation (104 lines)

And then looking at python-calculate-indentation we find it requires

- db-python-indent-string-contents # global var
- db-python-indentation-levels # 59 lines

So I guess this time our target is to get the support functions in place for python-indentation-levels.

- python-indent
- python-block-pairs

and the function(s)

- python-first-word
- python-initial-text
- python-beginning-of-block
- python-end-of-block

  • python-indent:

    This is pretty straight forward, set a customizable variable for what the default number of columns for indentation will be. Just to be thorough we need to look up what safe-local-variable means.

    From poking around in files.el

    ;; Safe local variables:
    ;; For variables defined by major modes, the safety declarations can go into
    ;; the major mode's file, since that will be loaded before file variables are
    ;; processed.
    ;; For variables defined by minor modes, put the safety declarations in the
    ;; file defining the minor mode after the defcustom/defvar using an autoload
    ;; cookie, e.g.:
    ;; ;;;###autoload(put 'variable 'safe-local-variable 'stringp)
    ;; Otherwise, when Emacs visits a file specifying that local variable, the
    ;; minor mode file may not be loaded yet.
    ;; For variables defined in the C source code the declaration should go here:

    So basically it's a way to do some simple type checking on a variable.

  • python-block-pairs

    This is an alist of python keywords and the keywords that they are the "closers" for.

  • python-first-word

    A simple function that returns to the beginning of code for a line and calls current-word

  • python-initial-text

    A function to grab the non-comment code on a line. Interestingly this function doesn't seem to work as advertised. Both in my hand copied subset of code and in the full working original both seem to keep the comments at the end of the line.

    I will have to keep this in mind when looking at how it is used to see if this will affect the functionality.

  • python-beginning-of-block

    Move point to beginning of containing block. It starts by moving past any blank space and/or comments and then going to start of current statement.

    Then while point is not on the 0th column and/or the arg passed in is not 0 continue recursively until one of the target conditions is true

    The logic here is a bit of a challenge (for me) to follow. Its a twisty maze of whiles, whens, ands, recursion and throw/catches.

    It seems to be doing something like:

    Move up a line and continue doing it until either we can't go up any more or we hit the condition where we generate a 'done (ie the conditions match a new outer block have been hit).

    This is probably the most "lispy" code so far and seemed excessively weird when I first tried to wrap my brain around it. But it seems halfway sensible to me now

  • python-end-of-block

    For some reason I was expecting this function to be a simple variation of python-beginning-of-block, but the logic is fairly different. It *does* use many of the same function but the way they are combined is not a simple reverse of the above logic.

    This function and the above are definitely a bit more sophisticated in their logic and the code is more strange (to me). As an example:

    within a let* expression there is the following "variable assignent" which is really not a variable assignment.

    (_ (if (db-python-comment-line-p)
    (db-python-skip-comments/blanks t)))

    At this point I'm not sure whether to think this is an ugly hack or just to try and get used to this as idomatic emacslisp. Seems like it would be cleaner just to do this test and function call before the let*, or within a nested let. Hmmm... Can't do it before because we need the value of point before moving and having a nested let makes the code more complicated. I guess I'm already starting to see this more as a clever hack than first blush.

    More strangely, python-beginning-of-block is called recursively and this function is an iteration. I wonder if these were done by different people or in different moods. Or perhaps there is some compelling reason not to do it recursively. Not obvious to me in any case why the difference in style.

Anyway, we are now closer to being able to implement some tab bevhavior. With any luck next phase will have us implementing python-indentation-levels

By lines of code I'm about 20% of the way through.

No comments: