jEdit Notes

Usage, macros, developer documentation

Last changed: April 2 2006

Warning: Information contained on this page comes mostly from my experience and studies of jEdit source code and thus it can be sometimes inaccurate.

Developer documentation: How does it work?

See jEdit Source Code Intro at jEdit wiki.

jEdit: View x Buffer x TextArea x EditPane explained

From jEdit wiki:

Each window in jEdit is an instance of the View class. Each text area you see in a View is an instance of JEditTextArea, each of which is contained in its own EditPane. Files are represented by the Buffer class.

jEdit Class Diagram - relations of the main classes
jEdit Class Structure
NOTES: Only methods important from the point of view of mutual relations are shown.
In words: Every view has 1 jEditTextArea and many EditPanes, each with 1 buffer.
Warning: this diagram is my understanding of jEdit's structure and may be inaccurate.

KeyEvent processing in jEdit

KeyEvents in jEdit are processed by View.processEvent and are treated differently with respect to the source (text area or another part of the view?) and w.r.t. the type of the event (key pressed/typed/released). The method uses View.inputHandler and its handleKey to handle key bindings - to recognize them, invoke the appropriate action and to discard the input (consuming the event(s)) so that it's not processed further. If an event is consumed KeyEventListeners will never by notified of it (note: KEY_RELEASED events are nearly not filtered at all so even if key pressed/typed event has been discarded you can receive key released). Below I give an approximate diagram of how KEY_PRESSED events originated from a text area are treated.

KeyEvent processing by View.processEvent for a KEY_PRESSED event originated in a text area

KeyEvent processing diagram

LEGEND: evt = the KeyEvent object

Actions: Event -> action name -> code execution

An Action object executes its code when it is invoked. It has assigned a name, which is a String such as "next-page" or "backspace" by which it can be reffered to to invoke it. A user invokes the action via its name and jEdit looks up the Action object and executes it. The action name can be associated to a particular key stroke or menu item and when the user types the key stroke or clicks the menu the action is looked up and performed.

Mapping key events to code that is performed
keyStroke/keyBinding -> action name -> Action.actionPerformed
input

map
1:N

action

map
N:1

Ex: Ctrl-n "new-file" jEdit.newFile(view);

Discovering a word has been typed

TASK: Perform an action when a word has been typed in a given buffer.

Task analysis:

  1. We need to get notified whenever a printable character is inserted into the given buffer and decide whether the character belongs to the word being typed (e.g. 'a') and shall be recorded or not (e.g. ' ','$'). 2
  2. Detect special characters such as '\t' (Tab) and '\n' (Enter) that end the word being typed or modify it (backspace) and react appropritely.
  3. Detect events that discard the current word being recorded such as moving the caret at another place (Page Up, arrows, ...).
  4. Detect actions that insert more-character string at once.

Printable characters can be observed by a KeyListener. We can only add it to a jEditTextArea or a View who have inherited the method addKeyListener from java.awt.Component. But since we're interested in a file == in a buffer this is not a good way.

Buffer is not a graphical (AWT) class but it offers the method addBufferChangeListener. The following methods of BufferListener would interest us: contentInserted, contentRemoved, transactionComplete (undo/redo/compound edit). Note: According to the doc you shouldn't implement it but rather subclass BufferChangeAdapter.

To detect an event that discards the word being recorded we may listen to caret changes via a CaretListener (jEditTextArea.addCaretListener) and check whether its movement corresponds to the character entered/deleted by backspace or not (=> home, page up etc.). I believe that the parameter offset of contentInserted/Removed should be the same as offset of the caret in the active view. Note: the listener's caretUpdate is called even when the caret hasn't changed (such as pressing End already at the end of a line or clicking to the caret position).

A hypothesis: The buffer may be displayed in edit panes having a different caret & caret position in each => check the pair edit pane + buffer. } => We have to decide whether to observer only one buffer + edit pane or whether we observe all edit panes of the given buffer and discard the recorded word if the user starts editing it in another edit pane.

jEditTextArea.getCaretPosition()

Developing Plugins

My Plugins

So far I've created the plugin TextAutocomplete and contributed to LaTeX Tools.

Create a new plugin in CVS

Let's see how the SourceForge's user malyvelky creates the plugin named TextAutocomplete

bash$ cvs -z3 -d :ext:malyvelky@cvs.sourceforge.net:/cvsroot/jedit co -l plugins
bash$ cd plugins/
bash$ mkdir TextAutocomplete
bash$ cvs add TextAutocomplete

Now you leave the plugins/ directory and somewhere else execute:

bash$ cvs -z3 -d :ext:malyvelky@cvs.sourceforge.net:/cvsroot/jedit co plugins/TextAutocomplete
malyvelky@cvs.sourceforge.net's password:
cvs checkout: Updating plugins/TextAutocompleted

This will create the nearly empty directory plugins/TextAutocomplete/ that will contain the directory CVS/ and that shall become the root directory of your plugin.

How are plugins loaded?

The following methods are interesting:

Misc

Execute BeanShell code
Do you want to execute a snippet of BeanShell code in your plugin? See the class org.gjt.sp.jedit.BeanShell and its methods cacheBlock and runCachedBlock. See BeanShellAction for an example.

Implement completion with SideKick

If you want to add completion (e.g. command completion) to buffers handeled by your plugin, you need to several thing. I will demonstrate that using the SideKickParser uk.co.antroy.latextools.parsers.LaTeXParser with the latex_parser and label LaTeX - thanks to Anthony Roy who wrote it.

First you should read the jEdit Help for SideKick.

Create and setup a SideKick parser

  1. Set the following properties, e.g. in <my plugin>.props:
    plugin.<your plugin name>.depend.0=plugin sidekick.SideKickPlugin <version>
    mode.<a mode name>.sidekick.parser=latex_parser
    # For example: mode.tex.sidekick.parser=latex_parser
    sidekick.parser.latex_parser.label=LaTeX
  2. Create a subclass of SideKickParser - let's call it LaTeXParser - and override the method parse.
  3. Create services.xml that maps the parser's name (latex_parser) to its class (LaTeXParser):
    <?xml version="1.0"?>
    <!DOCTYPE SERVICES SYSTEM "services.dtd">
    <SERVICES>
        <SERVICE CLASS="sidekick.SideKickParser" NAME="latex_parser">
            new uk.co.antroy.latextools.parsers.LaTeXParser("latex_parser");
        </SERVICE>
    </SERVICES>
  4. Assert that the parser is used where you want it - in Plugins > Plugin Options... > SideKick > Parsers find the buffer mode you're interested in and set the parser to "LaTeX".

Create the support for completion

  1. Subclass SideKickCompletion and add the possible completions of the text typed so far (the parameter 'text') to the list in its constructor (items.add("completion1")). You may but don't need to override any method.
    SideKickCompletion represents the possible completions to display in the pop-up window and is created in and returned from your subclass of SideKickParser. Interesting methods:
  2. In your SideKickParser subclass override the method supportsCompletion to return true and implement the method complete(EditPane editPane, int caret) - if the word typed so far (the 'text' passed to the constructor of the SideKickCompletion) is important to you, you need to get it - for example as follows:
        Buffer buffer = editPane.getBuffer();
        JEditTextArea textArea = editPane.getTextArea();
        int caretLine = textArea.getCaretLine();
        int caretInLine = caret - buffer.getLineStartOffset(caretLine);
        if (caretInLine == 0) return null;
        String line = buffer.getLineText(caretLine);
        int wordStart = TextUtilities.findWordStart(line, caretInLine - 1, "");
        String currentWord = line.substring(wordStart, caretInLine);

    The completion is triggered (and the completions are offered) either manually, after a certain delay (if canCompleteAnywhere is true) or immediately when particular character has been typed - see the SideKickParser's methods