PyZine
 


Article Finder
People
Issue 6 - Revision 7  /   October 18, 2004 


 
  Py Links:
Latest Issue
Issue 08
Issue 07
Issue 06
Issue 05
Issue 04
Issue 02
Issue 01
 
 
Downloads
     
  Articles:
Throughout the quarter we cover topics of interest to Python developers.

  Python MIDI

  Python in Bioinformatics

  Getting Twisted

  optparse

  jEdit

  Python RFID

  3d Graphics in Python

 
 
 
     


Customizing jEdit
a Programmer's Text Editor, using Python
- - - - - - - - - - - -

By Ollie Rutherfurd | September 4, 2004

print
Abstract

jEdit (http://www.jedit.org/) is a powerful, flexible, and free programmer's text editor. It features syntax highlighting - 130 languages, including Python, Pyrex, Mod_python's Python Server Pages, Zope Page Templates, reStructuredText, and MoinMoin wiki pages; folding - the ability to hide portions of a buffer while editing; extensibility - via macros and plugins, which can provide support for Virtual File Systems, Structure Browsers, Fold Handlers, code-completion, and more; auto-indent - when editing Python, lines following a line ending in ":" are indented; and an integrated Python debugger - via the JPyDebug plugin. However, there are many editors, chock-full of features, that make Python coding easier. What separates jEdit from others is that one can customize and extend jEdit using Python. jEdit is written in Java (TM) but with JythonInterpreter, a jEdit plugin that embeds Jython into jEdit, one can write macros, and even plugins for jEdit in Python. In fact, much of the JythonInterpreter plugin itself is written in Python! This article is an introduction to customizing and enhancing jEdit with macros written in Python.

Installation

This article contains a lot of code and is meant to be hands-on. You'll get the most out of it by running the code presented. So, if you don't have jEdit installed, download and install [1] it from http://www.jedit.org. If 4.2 (final) is available, use that, otherwise try the most recent 4.2pre version (4.2pre14 is currently the most recent release). At this point in the development cycle the pre-releases are very stable. Install JythonInterpreter, via Plugins > Plugin Manager. Installing JythonInterpreter will automatically install Console, ErrorListL, and SideKick. You may also wish to install ActionHooks, which is discussed in this article, though it's not required. Other useful plugins are Archive, which enables you to edit files inside archives; FTP, which enables you to edit files over FTP and SFTP; Tags, which provides support for CTags: TextTools, which provides commenting, sorting, and text transposing actions; and JPyDebug, [2] which provides an integrated Python debugger.

Macros Overview

Macros are scripts run by jEdit, or a plugin, that are given access to jEdit's API. However, to understand how powerful macros can be for customizing, it helps to have a high-level understanding of jEdit Actions. In jEdit, all editing functions such as Open File, Go to Next Line, and Delete Previous Character are actions. By default, Open File is bound to CONTROL+o, Go to Next Line is bound to the up arrow key, and Delete Previous Character is bound to backspace. However, these and almost all built-in actions can be bound to macros instead. This flexibility enables one to customize almost all editing functionality. For example, jEdit comes with macros that can be used as replacements for Delete Previous Character (Macros > Editing > Greedy Backspace) and Delete Next Character (Macros > Editing > Greedy Delete) which behave as if tab characters are used when editing files without tabs. This is very handy for Python programmers using spaces for indenting -- if you are using 4-space indents, you can press backspace once instead of 4 times!

In this article, we'll look at four types of macros:

  1. Enhancement macros. Macros that provide some additional functionality. Examples of these are Remove Lines and Find Next Occurrence.
  2. Action replacement macros Macros that can be used instead of built-in actions. Examples of these are Macros > Editing > Greedy Backspace and Previous Line, Keeping Column.
  3. Hook macros Macros run in response to an editor event, such as a buffer being saved. To use hook macros, you'll need the ActionHooks plugin, which enables one to configure macros or actions that should be run in response to editor events. See Hook Macros for an example that converts SQL keywords to uppercase when a buffer is saved.
  4. Startup scripts Macros run when jEdit is starting. They can import classes, provide utility functions, alter settings, or even provide plugin-like functionality.
Built-in Variables

Before we look at any macros, let's look at the variables jEdit exposes to macros. When a macro is run, JythonInterpreter sets the following variables [3] in the init module [4] exposed to the macro:

  • buffer: A Buffer object represents the contents of the currently visible open text file
  • view: A View represents the current top-level editor window, extending Java's JFrame class, which contains the various visible components of the program, including the text area, menu bar, toolbar, and any docked windows. This variable has the same value as the return value of: jEdit.getActiveView().
  • editPane: An EditPane object contains a text area and buffer switcher. A view can be split to display edit panes. Among other things, the EditPane class contains methods for selecting the buffer to edit. Most of the time your macros will manipulate the buffer or the text area. Sometimes you will need to use view as a parameter in a method call. You will probably only need to use editPane if your macros work with split views. This variable has the same value as the return value of: view.getEditPane().
  • textArea: A JEditTextArea is the visible component that displays the current buffer. This variable has the same value as the return value of: editPane.getTextArea().

For a detailed jEdit API reference, see jEdit API Reference in the jEdit Users's Guide (Help > jEdit Help).

Hello World

The obligatory first macro:

if __name__ == '__main__':


   # Macros contains methods to display messages
   # to the user and get input from the user
   from org.gjt.sp.jedit import Macros


   # init.view is the current top-level editor window
   Macros.message(init.view,'Hello World!')

To run this macro, save it as "~/.jedit/macros/Hello_World.py". As jEdit supports macro handlers for different languages, you must include the ".py" extension, because that's how jEdit knows which macro handler is responsible for running a macro.

After you save the macro, you can run it from Macros > Hello World. Note that underscores in the filename are replaced by spaces and the extension is not displayed.

Now that we've run it , let's take a quick look at it before we move on to more interesting things. When JythonInterpreter runs a macro, it sets __name__ to __main__ (though in older versions, it was set to main). There's nothing that requires you to use if __name__ == '__main__', but it's a good practice. What's really interesting is the line from org.gjt.sp.jedit import Macros -- that's not importing Python code, but directly accessing jEdit's API, which is written in Java. Thanks to the magic of Jython, the interaction is seamless. Macros contains methods for interactions -- both for displaying messages, errors, etc. and for getting input. Also, take note of init.view. The init module contains references to jEdit variables, view, buffer, textArea, and editPane. These variables are also available as locals in the macro. We'll be seeing a lot of the first three variables -- we'll use them in most macros.

Enhancment Macros - Underline Line

Now that we've figured out how to get jEdit to say hello, let's get it to do something a little more useful. The following is handy for people writing reST (reStructuredText) documents, where section titles are indicated by a line consisting of a given character repeated often enough so that the line of characters is longer than or equal in length to the preceding line which contains the title. For example:

A Section Title
===============

Here's a macro that will underline the current line:

def underline_line(view,char):
   # get local reference to textArea
   textArea = view.getTextArea()
   # current line (0-based)
   lineno = textArea.getCaretLine()
   # get length of the current line
   linelen = textArea.getLineLength(lineno)
   # underline is the length of the current line
   underline = '\n' + char * linelen
   # find offset of the end of the current line
   lineend = view.getBuffer().getLineEndOffset(lineno)
   # insert underline at end of current line
   # NOTE: line is pre-pended with '\n', as the last line
   # of the buffer may not have a '\n'
   view.getBuffer().insert(lineend-1,underline)

if __name__ == '__main__':
  from java.awt import Toolkit # Toolkit provides "beep"
  from org.gjt.sp.jedit import Macros
  # shouldn't do anything if the buffer is read-only
  if init.buffer.isReadOnly():
      Toolkit.getDefaultToolit().beep()
   else:
      c = Macros.input(init.view,'Underline Char','=')
      if c:
         underline_line(init.view,c[0])

Underline_Line.py first checks if the buffer is read-only. If not, it prompts for an underline character, providing "=" as a default. As a general practice, I usually pass view as a parameter to all macro functions. As view contains everything else, this is easier than passing buffer and textArea -- and if you need to display a window which needs a view, you already have it. This macro demonstrates:

  • determining the current line number
  • finding the length of the current line
  • finding the position of the end of the current line
  • inserting text into a buffer

Remove LInes

This macro removes all lines from the current buffer which contain a given regex pattern. First, we'll prompt the user for a regex pattern, then remove all lines from the current buffer where the regex returns a match. As with Hello World, we'll use Macros, but this time we'll use it to get the regex. Once the user enters a regex, we'll compile it, extract all the text from the current text area, split it into lines, remove lines where the given pattern is found, and then reset the text to the remaining lines.


def remove_lines(view,regex):
  import re

  # try to compile the re -- if there's a syntax
  # error, inform user and return
  try:
    r = re.compile(regex)
  except Exception,e:
    Macros.error(view,'Error with `%s`: %s' % (regex,e))
    return

  # will return the line if it doesn't match the regex
    def not_found(line,r=r):
    if not r.search(line):
      return line

  # split text into lines -- note that jEdit always
  # uses newline (\n) as the line separator internally
  # regardless of how the file is saved on disk.
  lines = view.getTextArea().getText().split('\n')

  # set text to all lines that don't match the given re
  view.getTextArea().text = '\n'.join(filter(not_found,lines))

if __name__ == '__main__':   # using `Macros.input
  from org.gjt.sp.jedit import Macros

  # prompt for regex -- if user selects 'Cancel',
  # regex will be None, if nothing entered, then ''.
  regex = Macros.input(init.view,'Match Pattern (regex):')

  # only call if user entered something
  if regex:
  remove_lines(init.view,regex)

A Jython trick is demonstrated in this script, in the following line: view.getTextArea.text = '\n'.join(filter(fn,lines))

JEditTextArea doesn't actually have a public text attribute. It has a public void setText(String text) method. Jython is aware of this and when this line is executed, it calls setText('\n'.join(filter(fn,lines))). Generally, I don't use this shortcut, but it's handy and can make macros shorter.


Find Next Occurence

This macro is similar to Vim's * key-binding. When invoked, it uses SearchAndReplace, the main class for jEdit's search and replace API, to find and select the next occurrence of the word at the caret -- or the current selection, if there is one.

from java.awt import Toolkit
from org.gjt.sp.jedit.search import SearchAndReplace, CurrentBufferSet


def find_next_occurance(view):


   # returns an array of Selections -- as jEdit supports
   # selecting multiple non-adjacent blocks of text
   selection = view.getTextArea().getSelection()

   # select current word if nothing selected
   #
   # NOTE: Java arrays have a "length" property
   # which returns the length of the array.
   # However, Jython wraps Java arrays, so
   # one can use "len" to determine the length
   # of the array -- selection.length will
   # actually raise an attribute error, as
   # the array wrapper doesn't have a "length"
   # attribute.
   if len(selection) == 0:
      view.getTextArea().selectWord()

   # only if one or less selections -- doesn't work
   # with multiple selections
   if len(selection) <= 1:
     word = view.getTextArea().getSelectedText()
     SearchAndReplace.setSearchString(word)
     # use 0 or 1 in place of Java's "true" or "false".
     SearchAndReplace.setAutoWrapAround(1)
     SearchAndReplace.setReverseSearch(0)
     SearchAndReplace.setIgnoreCase(0)
     SearchAndReplace.setRegexp(0)
     # only search in the current buffer -- different
     # buffer sets allow for different search scopes,
     # such as all buffers, by file extension, files
     # in a directory, etc...
     SearchAndReplace.setSearchFileSet(CurrentBufferSet())
     SearchAndReplace.find(view)
   else:
     Toolkit.getDefaultToolkit().beep()

if __name__ == '__main__':
   find_next_occurance(init.view)

Action Replacement Macros - Previous Line, Keeping Column

This macro moves the caret to the previous line, but keeps the caret in the same column. If necessary, it pads the end of the previous line with whitespace. This mimics the behavior found in editors such as Borland's Delphi IDE. Personally, it would drive me nuts, but jEdit users have asked how to achieve this, and the point is that it's possible.

# Toolkit provides beeping
from java.awt import Toolkit
# MiscUtilities can create WS padding
from org.gjt.sp.jedit import MiscUtilities

def prev_line_keeping_column(textArea):

   # get the current line-number (zero-based)
   lineno = textArea.getCaretLine()

   # if on the first line, beep and return
   if lineno == 0:
      Toolkit.getDefaultToolkit().beep()
      return

   # figure out the column position on the current line
   col = textArea.getCaretPosition() - textArea.getLineStartOffset(lineno)

   # get previous line text, and the end offset for the previous line
   prev_line = textArea.getLineText(lineno-1)
   prev_line_end = textArea.getLineEndOffset(lineno-1)-1 # -1 for \n

   # if the previous line is shorter than the current one, we'll
   # need to add whitespace to pad it out to the current column    if len(prev_line) < col:

     # get current buffer
     buffer = textArea.getBuffer()
     # if not using tabs, we need to use '0' for tabSize
     # when creating padding whitespace
     if buffer.getBooleanProperty('noTabs'):
       tab_size = 0
     else:
       tab_size = buffer.getProperty('tabSize')

     # create needed whitespace
     padding = MiscUtilities.createWhiteSpace(col - len(prev_line), tab_size)

     # insert whitespace at the end of the previous line
     buffer.insert(prev_line_end,padding)

     # move the caret to the end of the previous line
     textArea.setCaretPosition(prev_line_end+len(padding))

     else:
     # fall back to default -- no padding needed
     textArea.goToPrevLine(0) # don't select it

if __name__ == '__main__':
   # if the buffer is read-only, we can't insert WS
   # so fall back on the built-in action
   if init.buffer.isReadOnly():
     init.textArea.goToPrevLine(0) # 0 == don't select
     else:
     prev_line_keeping_column(init.textArea)

Although it may not be obvious, this macro demonstrates a caveat of overriding built-in actions with macros. If you look at Utilities > Global Options > Shortcuts, you'll see that Go to Previous Line and Select Previous Line are different actions with different bindings. Though this makes sense, as they're different actions, it means you'd need to write two macros if you wanted this behavior when going to the previous line and selecting the previous line.

Insert Newline with Prefix

This macro inserts a new line, prefixing it with a continuation of whatever the current line started with. For example, if the current line starts with a comment character, the next line will also start with one. If the current line starts with "1.", the next line will start with "2." This macro could be used instead of Insert Newline and Indent, which is the default action binding for ENTER, though in practice you probably wouldn't want to do so, as it might be more of a nuisance to remove unwanted prefixes than a benefit to have them. Instead of binding this to ENTER, I have it bound to S+ENTER (Shift+Enter), so it's close at hand, but not always in effect.

def newline_with_prefix(view):
   import re
   textArea = view.getTextArea()
   line = textArea.getLineText(textArea.getCaretLine())

   # RE's split the prefix up into whitespace, body, and
   # whitespace to make it easy to increment numbers and
   # letters designating lists

   # match numbered lists
   num_list_re = re.compile(r'^(\s*)([0-9]+[.)])(\s+)')
   # match alpha lists
   alpha_list_re = re.compile(r'^(\s*)([A-z][.)])(\s+)')
   # match bullet lists
   bullet_list_re = re.compile(r'^(\s*)([>=*+-])(\s+)')
   # match common comment characters
   comment_re = re.compile(r"^(\s*)([*;#']|--|//)(\s+|$)")
   # match anything that doesn't look like a word
   misc_re = re.compile(r'^(\s*)((?:[^A-z"\'+-]|`)+)(\s+)')

   # functions to extract (and manipulate) body portion of prefix
   # increment number
   num_fn = lambda body: str(int(body[:-1]) + 1) + body[-1]
   # next character
   alpha_fn = lambda body: chr(ord(body[0])+1) + body[1]
   # return what was passed (when body doesn't need to be modified)
   body_fn = lambda body: body

   patterns = (
     ('Numbered List', num_list_re, num_fn),
     ('Alpha List', alpha_list_re, alpha_fn),
     ('Comment Prefix', comment_re, body_fn),
     ('Bullet List', bullet_list_re, body_fn),
     # uncomment for ascii art, etc...
     ('Misc.', misc_re, body_fn),
   )


   for (name,pattern,fn) in patterns:
     m = pattern.search(line)
     if m:
       # display what was matched in the View's
       # status bar -- makes debugging and modifying
       # the patterns much easier by showing what
       # was matched.
       view.getStatus().setMessageAndClear('Recognized as %s (%s)' % (name,str(m.groups())))


       pre,body,post = m.groups()
       body = fn(body)
       prefix = ''.join(['\n',pre,body,post])


       # remove selected text
       textArea.setSelectedText('')
       # save caret -- since insertion may move it
       caret = textArea.getCaretPosition()


       # insert prefix at the caret
       textArea.getBuffer().insert(caret,prefix)


       # move the caret to just after the prefix
       textArea.setCaretPosition(caret+len(prefix))
       break
   else:
     # no prefix matched, fall back on default behavior
     textArea.insertEnterAndIndent()

if __name__ == '__main__':
   if init.buffer.isReadOnly():
     from java.awt import Toolkit
     toolkit.getDefaulToolkit().beep()
   else:
     newline_with_prefix(init.view)

Hook Macros

Hook macros are macros that are invoked in response to editor events such as opening, closing, or saving a buffer. To use hook macros, you'll need to install the ActionHooks plugin. ActionHooks watches jEdit's internal message bus (the EditBus) and executes macros or actions when events occur. As an example, we'll write a pair of macros to automatically uppercase SQL keywords whenever an SQL file is saved.

Here's a macro that uppercases all keywords in a buffer:

# need Token so we can identify syntax token types
from org.gjt.sp.jedit.syntax import Token


def ucase_keywords(view):


   # binding to locals for performance
   KEYWORD1 = Token.KEYWORD1
   KEYWORD4 = Token.KEYWORD4
   textArea = view.getTextArea()
   b = view.getBuffer() # feeling terse
   token = None

   try:
     # use a compound edit, so the whole
     # thing can be undone with one 'undo'
     b.beginCompoundEdit()

     # get selections, so we can restore it later
     selection = textArea.getSelection()
     pos,lineStart = 0,0


     # one line at a time
     for line in range(textArea.getLineCount()):

       # Get the first syntax token on the line.
       # token is the head of a linked list of
       # tokens for the line.
       token = b.markTokens(line).firstToken
       lineStart = buffer.getLineStartOffset(line)
       pos = lineStart
       # we need to "manually" track the position in the line

       # token will be None when we get to the end of the line
       while token != None:

         # only care about keyword tokens
         if KEYWORD1 <= token.id <= KEYWORD4:
           word = b.getText(pos,token.length)
           # remove and re-insert uppercased keywords
           b.remove(pos,token.length)
           b.insert(pos,word.upper())

         # move pos to start of next token
         pos += token.length
         # move on to the next token
         token = token.next

     # restore selection
     textArea.selection = selection

   # make sure to always end a compound edit!
   # if you don't, jEdit can get confused and
   # you may see error messages about unbalanced
   # edits
   finally:
     b.endCompoundEdit()

   # setMessageAndClear will clear the message after a few seconds
   view.getStatus().setMessageAndClear('Converted Keywords to Uppercase')

if __name__ == '__main__':
   # just beep if buffer is read-only
   if init.buffer.isReadOnly():
     from java.awt import Toolkit
     toolkit.getDefaulToolkit().beep()
   else:
     ucase_keywords(init.view)

To invoke Uppercase_Keywords.py whenever an SQL file is saved, we'll write a wrapper macro and bind it to BufferUpdate.SAVING. Using a wrapper enables one control which modes (file types) to invoke the macro for. Without this wrapper, we'd either have all keywords uppercased when any buffer is saved, or we'd have to hard-code that logic in Uppercase_Keywords.py. To differentiate these wrapper macros from others, I keep them in a "Hook" folder.

if __name__ == '__main__':
   view = init.view
   # only uppercase if using one of the SQL modes.
   if view.getBuffer().getMode().getName().find('sql') > -1:
     # Python scripts end with '.py', and the action we
     # want lives in ~/.jedit/macros/Py_jEdit/
   view.getInputHandler().invokeAction('Py_jEdit/Uppercase_Keywords.py')

Note in the comment for this macro, which demonstrates invoking another macro, it says we're invoking an action not a macro. Internally, macros are actions. If you want to invoke a built-in action, you'd just supply its name instead.

To bind Uppercase_Keywords_on_Save.py to BufferUpdate.SAVING,. go to Plugins > Plugin Options > ActionHooks.

Startup Scripts

Startup scripts are macros executed when jEdit is starting. They're a good place to import commonly used classes or define utility functions. Startup scripts are located in ~/.jedit/startup/. As startup scripts are executed while jEdit is starting, but before the first view has been created, view, buffer, textArea, and editPane will exist, but all be set to None.

Since we have repeatedly imported org.gjt.sp.jedit.Macros, let's import it in a startup script.

Save the following in ~/.jedit/startup/example.py:

from org.gjt.sp.jedit import Macros

As another example, we imported java.awt.Toolkit a number of times, so let's add a function for that:

def beep():
   from java.awt import Toolkit
   Toolkit.getDefaultToolit().beep()

Next time you start jEdit, Macros will be imported and beep() will be available. If you'd like to run your startup script without re-starting jEdit, go to Macros > Run Other Macro... and browse for "~/.jedit/startup/example.py".

Jython Interpreter's Console

Writing macros is often an iterative process -- finding the APIs you're interested in and seeing how they work. JythonInterpreter's Console (Plugins > JythonInterpreter > JythonInterpreter) provides a nice environment for iteractively controlling jEdit, exploring jEdit's API, and testing macro code.

As an example, let's double-space the contents of the current text area:

Jython 2.1 on java1.4.2_03 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>> ta = jEdit.getActiveView().getTextArea()
>>> ta.selectAll()
>>> ta.text = ta.getText().replace('\n','\n\n')

You can also interactively explore jEdit's API:

Jython 2.1 on java1.4.2_03 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>> dir(jEdit)
['_closeBuffer', '_getBuffer', 'addActionSet', 'addMode',
'addPluginJAR', 'backupSettingsFile', 'checkBufferStatus',
'closeAllBuffers', 'closeBuffer', 'closeView',
'commitTemporary', 'exit', 'getAction', 'getActionContext',
'getActionNames',...]

One nuance of inspecting Java classes is that you may get different output from dir(instance) as opposed to dir(instance.__class__). As a result, the following is a handy helper method:

Jython 2.1 on java1.4.2_03 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>> def ls(instance, pattern=None):
... import re
... atts = dir(instance)
... cls = getattr(instance,'__class__',None)
... if cls: atts.extend(dir(cls))
... atts.sort()
... if pattern: atts = filter(re.compile(pattern).search,atts)
... return atts
...
>>> ls(jEdit,'Buffer')
['_closeBuffer', '_getBuffer', 'checkBufferStatus',
'closeAllBuffers', 'closeBuffer', 'getBuffer',
'getBufferCount', 'getBuffers', 'getFirstBuffer',
'getLastBuffer', 'reloadAllBuffers', 'saveAllBuffers']

If you add this to the startup script from earlier ("~/.jedit/startup/example.py"), it will be available when you re-start jEdit.

Resources for Writing Macros

Probably the most difficult part of writing macros for jEdit is learning the API. Even though jEdit's API is very well documented, there are lots of classes and methods, so getting started can be a little overwhelming. Here are some resources to get you started.

  • Read existing macros. jEdit comes with over 60 macros written in BeanShell. BeanShell is a Java-like scripting language that jEdit uses internally. Even though the syntax is different from Python, they're still a great reference. My collection of Python macros is available at http://www.rutherfurd.net/jEdit/macros/. However, I've since re-written many of them in BeanShell for inclusion with jEdit 4.2. As a result, they may not all be up-to-date with the API changes in 4.2.
  • Record macros. By default, jEdit has support for executing and recording macros in BeanShell. Though there is no support for recording macros in Python, recording and reading the generated BeanShell code is a good way to see what's going on behind the scenes. To record a temporary macro:
    1. Macros > Record Temporary Macro
    2. Do whatever (note: mouse activities aren't recorded)
    3. Macros > Stop Recording
    4. View "Temporary_Macro.bsh"
  • jEdit only keeps one temporary macro, so any existing temporary macro will be over-written when you record a new one.
  • Part III of jEdit's User's Guide is on writing macros. It assumes one is writing macros in BeanShell, but the concepts are generally applicable.
  • The jEdit API Reference in jEdit's User's Guide provides details on jEdit's API. •Subscribe to users@jedit.org or stop by #jedit on irc.freenode.net.
References

[1] As jEdit is written in Java, you will need a 1.3 or greater JDK or JRE installed on your system. See http://java.sun.com for downloads.

[2] JPyDebug also provides a class browser, but JythonInterpreter's is used because of the loading order. To display JPyDebug, go to Utilities > Global Options > Docking and change the Docking Position of "Python Environment" to something other than "floating".

[3] jEdit User's Guide, Chapter 12, Predefined Variables in BeanShell, Slava Pestov et al.

[4] The variables are also set as locals, but examples will use the init module, as it makes them easier to spot.


Ollie Rutherfurd

shim
shim

 Py is committed to bringing you great Python Articles.

shim
shim


Home   Subscribe   Migration FAQ   Contact PyZine   Write for PyZine   ZopeMag   opensourcexperts.com  

Reproduction of material from any of PyZine's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 PyZine Zope/Plone hosting by Nidelven IT