Wednesday, October 1, 2008

emacs python mode from scratch: stage 7 - python-calculate-indentation

We continue with our task of trying to get tabbing working by copying over python-calculate-indentation and examining how it works.

First thing that is apparent is that we also need these variables to be defined and set:

  • python-indent-string-contents
    • if true, then for doc strings do alignment

  • python-continuation-offset
    • how many spaces to add if indenting within a line following a continuation (ie "\")

  • python-honour-comment-indentation
    • go ahead and align a comment with a previous comment even if it's not itself in a "normal" position

So on a first glance python-calculate-indentation doesn't seem to work as i would have expected. I just get one position back and the value doesn't change on successive runs even if there are various logical candidates. So my guess is that this function returns the best first guess and then a higher level function calling this will changes some global variable so that this function will return the next best and so on until it cycles through.

So let's see what is really going and and how good my guess is.

This function (similarly to python-indentation-levels) is just one big "cond". The various sections are:

  • multi-line string
  • after a backslash or in brackets (ie continuation context)
  • beginning of buffer
  • non-indentable comment
  • the rest

The number of new cases here is a bit of a surprise since I had half expected that the previous function we looked at (python-indentation-levels) already handled the main cases. But I guess not.

  • multi-line string

    If we are currently in a string then do one of two things. If we have set our preferences so that we don't want to indent string contents, then just take the current indentation.

    Otherwise if we are on a line that starts the string, then just keep the current indentation. The actual work horse of this section is to keep walking backward a line at a time until we find a non-blank line and then use that as the indentation level.

  • after backslash or in brackets

    The logic here might be best summarized with pseudo code


    if in bracketed expression
    find first item in list and align with that
    otherwise align with the start statement (plus a "step" per bracketing level)
    else # must be backslash continuation line
    if this is not the first continuation line
    take indent of previous line
    else
    indent one "step" (plus even one more if previous line
    is a opening block)

    Fortunately this function is pretty well documented or it would be a bear to get through. Well, it's still a bear, but it's less of a bear. I wonder if instead of good documentation this would be a good place to create well named smaller functions. My personal style is to aim for functions that are the width of my hand (or two). Not being able to see the function in its entirety on one screen is pretty obnoxious to me.

    Note: needed to remember that "if" has one clause for the then statement (hence we find a progn) but the else can be any number of clauses. Strange asymmetry.

  • beginning of buffer

    No choice here. It's gotta be 0.

  • non-indentable comment

    There is a note here that basically says that they copied this behavior from python-mode.el but that it's not necessarily a good idea to have done so.

    A non-indentable comment seems to be a comment line that has no space-like characters between the comment starter ("#") and the text


    e.g.
    # hello
    #there

    That seems weird. But now I know to look for this behavior when editing.

  • the rest.

    So this is weird. This is where the last function we looked at (python-indentation-levels) will get called. but at this point I'm confused about how how they interact. Any way let's wade through this and see what we can figure out

    There are four main items here


    • move backward over white space if need to
    • call our friend python-indentation-levels
    • prefer indenting of comments with a following statement
    • take the caar (car of the car) of the last thing in python-indent-list


I have to admit at this point that I can mostly grok the code but the actual details are quite daunting. I would be hard pressed to recreate this logic from scratch. Also I'm still completely nonplussed as to how the magic cycling of tabbing options occurs.

Peeking around the original file I see a reference to cycling so I hope things get more clear when I get to that. But, once again we've been through a bunch of code and we still don't have a working tab cycler yet. But I think we are within spitting distance now.

For what it's worth, I'm now about 30% of the way through the code.

No comments: