This document describes aspects of jedit relevant to Tk/Tcl programmers and advanced jedit users, including how to write new editing modes (or modify existing ones) and how to incorporate jedit windows in your own applications.
(This section isn't quite finished, but it should have enough to get you started, if you also look at the code for existing editing modes, especially hook-mode, which is a demonstration of the various facilities available.)
You create a new editing mode by creating a file called mode-mode.tcl, where mode is the name of the new mode. This file can live in any of the directories ~/.tk/jeditmodes, $tk_library/jedit/jeditmodes, or $tk_library/jeditmodes. (The directories are searched in that order.)
The bare minimum a new mode file should have in it is a procedure mode:mode:init (where mode is the name of the mode) which contains code like the following, to set modespecific preferences:
proc mode:vmail:init { t } {
global MODEPREFS
j:read_prefs -array MODEPREFS -prefix vmail \
-directory ~/.tk/jeditmodes -file vmail-defaults {
{textfont default}
{textwidth 80}
{textheight 24}
{textwrap char}
{sabbrev 0}
{dabbrev 0}
{autobreak 1}
{autoindent 0}
{savestate 0}
{buttonbar 1}
{buttons {
jedit:cmd:done
jedit:cmd:save
}}
{menu,editor 1}
{menu,file 1}
{menu,edit 1}
{menu,prefs 0}
{menu,abbrev 1}
{menu,filter 1}
{menu,format 0}
{menu,display 0}
{menu,mode1 1}
{menu,mode2 1}
{menu,user 1}
}
}
(Actually, you can leave out the preferences you don't want to override in your mode in the list you give to j:read_prefs.) This code fragment sets the defaults for the various modespecific preferences for your new mode, although a user can still override them (for this particular mode) on the ModeSpecific Preferences panel.
Note that the mode:mode:init procedure gets an argument t. That's the name of the text widget whose mode is being set, so you can do things like configure tags or add bindings if you need to.
If you need to refer to the widget's toplevel (for instance, to add new userinterface components), you can do that with the command `jedit:text_to_top $t'. If you do anything in mode:mode:init that shouldn't persist if the user changes modes, you should also provide a procedure called mode:mode:cleanup (which will also be called with the text widget as its sole argument) to undo your changes. For instance, if you pack an entry for the user to type a cryptographic key into the toplevel window in mode:crypt:init, you should create a mode:crypt:cleanup procedure to remove it. (Both procedures would need to use jedit:text_to_top to get the name of the toplevel window.)
If your mode defines any new strings to display to the user, you should define them at the top of the mode file with j:ldb:set_defaults. (At present there's no support for a modespecific naturallanguage database file separate from the jedit database, so your translations will need to be specified explicitly in the mode file, merged into the jedit database file, or stored in your users' personal database files. I hope to support modespecific databases in the future.)
You can specify the buttons that are to appear on the buttonbar for your mode by providing them as the `buttons' default when you read in your modespecific preferences with j:read_prefs in your initialisation procedure. Currently there's no user interface to allow your users to override this default; this, too, is on my agenda for a future version.
Alternatively, to create a custom buttonbar, you can define a procedure called mode:mode:mkbuttons with code similar to the following:
proc mode:vmail:mkbuttons { w t } {
j:buttonbar $w -pady 2 -buttons [format {
{send { } {jedit:cmd:done %s}}
{sign { } {mode:vmail:insert_sig %s}}
} $t $t]
$w.b configure -bitmap @/some/path/send.xbm
$w.b configure -bitmap @/some/path/sign.xbm
return $w
}
This procedure gets both the name of the toplevel widget and the name of the text widget. It should create the buttonbar as a child of the toplevel and return its path, but not pack it itself. This method lets you create buttons that don't correspond to registered commands, or buttons that don't look like normal jedit buttons.
I recommend that the names of any procedures you define in your mode file start with `mode:mode:', where mode is the name of the mode. At present, nothing enforces this, but it will help ensure that your mode doesn't conflict with other modes. (Remember, the user can have more than one jedit window open at a time, and they can be in different modes). Future versions of jedit may require this naming convention.
You can further customise the behaviour of the editor in your new mode by creating any of the following procedures:
mode:mode:pre_read_hook
filename
widget
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
mode:
If a mode:mode:pre_..._hook or mode:mode:post_..._hook procedure is defined, it will be called before or after the relevant action. If one of the procedures without `_hook' is defined, it will be called instead of the default action.
Note that the behaviour of the `Close' command and it's corresponding modespecific procedures is linked to that of the `Quit' command and it's corresponding modespecific procedures. If there is only one window open, choosing `Close' is exactly like choosing `Quit', and will trigger mode:mode:pre_quit_hook and mode:mode:quit if they exist rather than the corresponding `Close' hooks. The mode:mode:pre_close_hook and mode:mode:close procedures are called only when there's more than one window open. This means that if you define them, you usually want to take similar actions in the corresponding `Quit' hooks.
If you define new commands in your mode, you should register them using the j:command:register procedure. This allows you to use the commands in menus created with j:menu:commands.
If you want to create a special menu for your jedit mode, you can create a procedure called mode:mode:mkmenu1, where mode is the name of your mode. It will receive two arguments: the name of a menubutton to create, and the name of the jedit text widget this menu is attached to. As an example, here's the procedure that define's the vmail mode's `Mail' menu:
##################################################
# define the Vmail menu:
##################################################
proc mode:vmail:mkmenu1 { menu t } {
j:menubutton $menu $menu.m {Vmail}
j:menu:commands $menu $t {
mode:vmail:insert_sig
jedit:cmd:done
}
}
(In this example, mode:vmail:insert_sig is a procedure defined elsewhere in the mail-mode.tcl file.) It's easiest to create menus with the procedures in the jmenu.tcl library, but you can create them however you like.
Note that the mode:mode:mkmenu1 procedure has to create the menubutton itself (but not pack it), and it has no control over the name of the menubutton widget.
If you want to create two modespecific menus, you can also define a mode:mode:mkmenu2 procedure; the requirements are the same as for mode:mode:mkmenu1.
I don't yet have a mechanism for automatically applying modespecific command accelerators using the information provided by j:ldb:binding. In fact, if you provide bindings in the string database for modespecific commands, they may be applied to other windows, in different modes. For now, I recommend using explicit Tk bind(n) commands in mode:mode:init to set up modespecific command accelerators. I also recommend using Meta-1 through Meta-9 for modespecific command accelerators if possible; they're reserved for that purpose, so they won't conflict with other bindings, and they're automatically cleared when the user switches modes.
Almost all the functionality of jedit is in libraries, so if you write your own Tkbased applications, you can easily incorporate jedit into them. You need to include a little bit of code at the beginning of your application to allow it to use the jstools libraries; this is described in the Usage section of their documentation. Essentially, you need to make sure the auto_path Tcl variable is set properly to find the libraries. Then you can say
jedit:jedit [-window w] [-mode mode] [-line line] [-file filename]
to edit a file (or open a new empty document window, if you don't specify `-file filename').
If the window w doesn't already exist, it will be created as a new toplevel window. (If you don't specify it, an arbitrary window name will be chosen.) It's fine for w to exist already, though; that way you can include an entire jedit window inside another window.
If you specify mode, the window will be in that mode; otherwise jedit will guess the mode based on the filename.
If you specify mode and also define all the necessary procedures for that mode in your code before calling jedit:jedit (such as mode:mode:init), you can tightly constrain the behaviour of the resulting jedit window, essentially creating a custom mode for your application without having to provide a separate mode file to define it.
If you specify line (and it's a valid number), then the insert point will be positioned at the beginning of the lineth line of text after the file is loaded; this is used to implement the vistyle +linenumber commandline syntax that lots of applications expect in an editor (e.g. to position the cursor at the beginning of the body, rather than the beginning of the header, of a mail or news message.)