1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
|
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="plugin-implement">
<!-- jEdit buffer-local properties: -->
<!-- :xml.root=users-guide.xml: -->
<!-- :maxLineLen=80:wrap=soft: -->
<title>Implementing a Simple Plugin</title>
<!-- jEdit 4 Plugin Guide, (C) 2001, 2002 John Gellene -->
<!-- jEdit 4.5 Plugin Guide, (C) 2005, 2011 Alan Ezust -->
<para>There are many applications for the leading operating systems that
provide a <quote>scratch-pad</quote> or <quote>sticky note</quote> facility
for the desktop display. A similar type of facility operating within the
jEdit display would be a convenience. The use of dockable windows would
allow the notepad to be displayed or hidden with a single mouse click or
keypress (if a keyboard shortcut were defined). The contents of the notepad
could be saved at program exit (or, if earlier, deactivation of the plugin)
and retrieved at program startup or plugin activation.</para>
<para>We will keep the capabilities of this plugin modest, but a few other
features would be worthwhile. The user should be able to write the contents
of the notepad to storage on demand. It should also be possible to choose
the name and location of the file that will be used to hold the notepad
text. This would allow the user to load other files into the notepad
display. The path of the notepad file should be displayed in the plugin
window, but will give the user the option to hide the file name. Finally,
there should be an action by which a single click or keypress would cause
the contents of the notepad to be written to the new text buffer for further
processing.</para>
<para>The full source code for QuickNotepad is contained in jEdit's source
code distribution. We will provide excerpts in this discussion where it is
helpful to illustrate specific points. You are invited to obtain the source
code for further study or to use as a starting point for your own
plugin.</para>
<section id="plugin-load">
<title><indexterm>
<primary>Plugin API</primary>
<secondary>loading at startup</secondary>
</indexterm> How Plugins are Loaded</title>
<para>We will discuss the implementation of the
<application>QuickNotepad</application> plugin, along with the jEdit
APIs it makes use of. But first, we describe how plugins are
loaded.</para>
<para>As part of its startup routine, jEdit's <function>main</function>
method calls various methods to load and initialize plugins.</para>
<para>Additionally, plugins using the jEdit 4.2 plugin API can be loaded
and unloaded at any time. This is a great help when developing your own
plugins -- there is no need to restart the editor after making changes
(see <xref linkend="plugin-implement-reloading" /> ).</para>
<para>Plugins are loaded from files with the <filename>.jar</filename>
filename extension located in the <filename>jars</filename>
subdirectories of the jEdit installation and user settings directories
(see <xref linkend="settings-directory" />).</para>
<para>For each JAR archive file it finds, jEdit scans its entries and
performs the following tasks:</para>
<itemizedlist>
<listitem>
<para>Adds to a collection maintained by jEdit a new object of
type <ulink url="../api/org/gjt/sp/jedit/PluginJAR.html">
<classname>PluginJAR</classname></ulink>. This is a data
structure holding the name of the JAR archive file, a reference
to the <ulink url="../api/org/gjt/sp/jedit/JARClassLoader.html">
<classname>JARClassLoader</classname></ulink>, and a collection
of plugins found in the archive file.</para>
</listitem>
<listitem>
<para>Loads any properties defined in files ending with the
extension <filename>.props</filename> that are contained in the
archive. See <xref
linkend="plugin-implement-properties" />.</para>
</listitem>
<listitem>
<para>Reads action definitions from any file named
<filename>actions.xml</filename> in the archive (the file need
not be at the top level). See <xref
linkend="plugin-implement-actions" />.</para>
</listitem>
<listitem>
<para>Parses and loads the contents of any file named
<filename>dockables.xml</filename> in the archive (the file need
not be at the top level). This file contains BeanShell code for
creating docking or floating windows that will contain the
visible components of the plugin. Not all plugins define
dockable windows, but those that do need a
<filename>dockables.xml</filename> file. See <xref
linkend="plugin-implement-dockables" />.</para>
</listitem>
<listitem>
<para>Checks for a class name with a name ending with
<filename>Plugin.class</filename>.</para>
<para>Such a class is known as a <firstterm>plugin core
class</firstterm> and must extend jEdit's abstract <ulink
url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink> class.</para>
<para>The initialization routine checks the plugin's properties
to see if it is subject to any dependencies. For example, a
plugin may require that the version of the Java runtime
environment or of jEdit itself be equal to or above some
threshold version. A plugin can also require the presence of
another plugin.</para>
<para>If any dependency is not satisfied, the loader marks the
plugin as <quote>broken</quote> and logs an error
message.</para>
</listitem>
</itemizedlist>
<para>After scanning the plugin JAR file and loading any resources, a
new instance of the plugin core class is created and added to the
collection maintained by the appropriate <ulink
url="../api/org/gjt/sp/jedit/PluginJAR.html">
<classname>PluginJAR</classname></ulink>. jEdit then calls the
<function>start()</function> method of the plugin core class. The
<function>start()</function> method can perform initialization of the
object's data members. Because this method is defined as an empty
<quote>no-op</quote> in the <ulink
url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink> abstract class, a plugin need
not provide an implementation if no unique initialization is
required.</para>
</section>
<section id="plugin-implement-quicknotepadplugin">
<title>The QuickNotepadPlugin Class</title>
<para>The major issues encountered when writing a plugin core class
arise from the developer's decisions on what features the plugin will
make available. These issues have implications for other plugin elements
as well.</para>
<itemizedlist>
<listitem>
<para>Will the plugin provide for actions that the user can
trigger using jEdit's menu items, toolbar buttons and keyboard
shortcuts?</para>
</listitem>
<listitem>
<para>Will the plugin have its own visible interface?</para>
</listitem>
<listitem>
<para>Will the plugin have settings that the user can
configure?</para>
</listitem>
<listitem>
<para>Will the plugin respond to any messages reflecting changes
in the host application's state?</para>
</listitem>
<listitem>
<para>Should the plugin do something special when it gets
focus?</para>
</listitem>
</itemizedlist>
<para>Recall that the plugin core class must extend <ulink
url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink>. In QuickNotepad's plugin core
class, there are no special initialization or shutdown chores to
perform, so we will not need a <function>start()</function> or
<function>stop()</function> method.</para>
<para>The resulting plugin core class is lightweight and straightforward
to implement:</para>
<itemizedlist>
<listitem>
<informalexample>
<programlisting>public class QuickNotepadPlugin extends EditPlugin {
public static final String NAME = "quicknotepad";
public static final String OPTION_PREFIX = "options.quicknotepad.";
}
</programlisting>
</informalexample>
<para>The class has been simplified since 4.1, and all we
defined here were a couple of <classname>String</classname> data
members to enforce consistent syntax for the name of properties
we will use throughout the plugin.</para>
</listitem>
<listitem>
<para>These names are used in <filename>actions.xml</filename>
for each of the menu choices. This file is discussed in more
detail in <xref linkend="plugin-implement-actions" />. Each
action is a beanshell script.</para>
<informalexample>
<programlisting>
<!DOCTYPE ACTIONS SYSTEM "actions.dtd">
<ACTIONS>
<ACTION NAME="quicknotepad.choose-file">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).chooseFile();
</CODE>
</ACTION>
<ACTION NAME="quicknotepad.save-file">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).saveFile();
</CODE>
</ACTION>
<ACTION NAME="quicknotepad.copy-to-buffer">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).copyToBuffer();
</CODE>
</ACTION>
</ACTIONS>
</programlisting>
</informalexample>
</listitem>
<listitem>
<para>The names also come up in the properties file,
<filename>QuickNotePad.props</filename> file. The properties
define option panes and strings used by the plugin. It is
explained in more detail in <xref
linkend="plugin-implement-properties" /> and the <ulink
url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink> API docs.</para>
<informalexample>
<programlisting>
# jEdit only needs to load the plugin the first time the user accesses it
# the presence of this property also tells jEdit the plugin is using the new API
plugin.QuickNotepadPlugin.activate=defer
# These two properties are required for all plugins
plugin.QuickNotepadPlugin.name=QuickNotepad
plugin.QuickNotepadPlugin.author=John Gellene
# version number == jEdit version number
plugin.QuickNotepadPlugin.version=4.4
# online help
plugin.QuickNotepadPlugin.docs=index.html
# we only have one dependency, jEdit 4.4.1
plugin.QuickNotepadPlugin.depend.0=jedit 04.04.99.01
# plugin menu
plugin.QuickNotepadPlugin.menu=quicknotepad \
- \
quicknotepad.choose-file \
quicknotepad.save-file \
quicknotepad.copy-to-buffer
# action labels for actions supplied by dockables.xml
quicknotepad.label=QuickNotepad
# action labels for actions supplied by actions.xml
quicknotepad.choose-file.label=Choose notepad file
quicknotepad.save-file.label=Save notepad file
quicknotepad.copy-to-buffer.label=Copy notepad to buffer
# plugin option pane
plugin.QuickNotepadPlugin.option-pane=quicknotepad
# Option pane activation BeanShell snippet
options.quicknotepad.code=new QuickNotepadOptionPane();
# Option pane labels
options.quicknotepad.label=QuickNotepad
options.quicknotepad.file=File:
options.quicknotepad.choose-file=Choose
options.quicknotepad.choose-file.title=Choose a notepad file
options.quicknotepad.choose-font=Font:
options.quicknotepad.show-filepath.title=Display notepad file path
# window title
quicknotepad.title=QuickNotepad
# window toolbar buttons
quicknotepad.choose-file.icon=Open.png
quicknotepad.save-file.icon=Save.png
quicknotepad.copy-to-buffer.icon=CopyToBuffer.png
# default settings
options.quicknotepad.show-filepath=true
options.quicknotepad.font=Monospaced
options.quicknotepad.fontstyle=0
options.quicknotepad.fontsize=14
# Setting not defined but supplied for completeness
options.quicknotepad.filepath=
</programlisting>
</informalexample>
</listitem>
</itemizedlist>
</section>
<section id="plugin-implement-properties">
<title>The Property File</title>
<para>jEdit maintains a list of <quote>properties</quote>, which are
name/value pairs used to store human-readable strings, user settings,
and various other forms of meta-data. During startup, jEdit loads the
default set of properties, followed by plugin properties stored in
plugin JAR files, finally followed by user properties.</para>
<para>Some properties are used by the plugin API itself. Others are
accessed by the plugin using methods in the <ulink
url="../api/org/gjt/sp/jedit/jEdit.html">
<classname>jEdit</classname></ulink> class. Others are accessed by the
scripts used by plugin packagers <footnote>
<para>See the <guimenuitem>Macros/Properties/Create Plugin
Announcement</guimenuitem> macro for an example.</para>
</footnote>.</para>
<para>Property files contained in plugin JARs must end with the filename
extension <filename>.props</filename>, and have a very simple syntax,
which the following example illustrates:</para>
<informalexample>
<programlisting># Lines starting with '#' are ignored.
name=value
another.name=another value
long.property=Long property value, split over \
several lines
escape.property=Newlines and tabs can be inserted \
using the \t and \n escapes
backslash.property=A backslash can be inserted by writing \\.</programlisting>
</informalexample>
<para>Now we look at a fragment from the
<filename>QuickNotepad.props</filename> file <footnote>
<para>Examine the actual file for a more complete example</para>
</footnote> which contains properties for the QuickNotepad plugin.
The first type of property data is information about the plugin itself;
these are the only properties that must be specified in order for the
plugin to load:</para>
<informalexample>
<programlisting># general plugin information
plugin.QuickNotepadPlugin.activate=defer
plugin.QuickNotepadPlugin.name=QuickNotepad
plugin.QuickNotepadPlugin.author=John Gellene
plugin.QuickNotepadPlugin.version=4.03
plugin.QuickNotepadPlugin.docs=QuickNotepad.html
# depends on jEdit 4.4.1
plugin.QuickNotepadPlugin.depend.0=jedit 04.04.99.01
plugin.QuickNotepadPlugin.description=A demo jEdit plugin that provides a notepad dockable.
plugin.QuickNotepadPlugin.longdescription=description.html
</programlisting>
</informalexample>
<para>These properties are each described in detail in the documentation
for the <ulink url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink> class and do not require
further discussion here.</para>
<para>Next in the file comes a property that sets the title of the
plugin's dockable window. Dockable windows are discussed in detail in
<xref linkend="plugin-implement-dockables" />.</para>
<informalexample>
<programlisting># dockable window name
quicknotepad.title=QuickNotepad</programlisting>
</informalexample>
<para>Next, we see menu item labels for the plugin's actions. All of
these but the first are defined in <literal>actions.xml</literal> file,
and that is because the dockable itself has its own actions. Actions are
discussed further in <xref linkend="plugin-implement-actions" />.</para>
<informalexample>
<programlisting># action labels
# Dockable label
quicknotepad.label=QuickNotepad
# Additional strings extracted from the plugin java source
quicknotepad.choose-file.label=Choose notepad file
quicknotepad.save-file.label=Save notepad file
quicknotepad.copy-to-buffer.label=Copy notepad to buffer
</programlisting>
</informalexample>
<para>Next, the plugin's menu is defined. See <xref
linkend="plugin-implement-quicknotepadplugin" />.</para>
<informalexample>
<programlisting># application menu items
quicknotepad.menu.label=QuickNotepad
quicknotepad.menu=quicknotepad - quicknotepad.choose-file \
quicknotepad.save-file quicknotepad.copy-to-buffer</programlisting>
</informalexample>
<para>We have created a small toolbar as a component of QuickNotepad, so
file names for the button icons follow:</para>
<informalexample>
<programlisting># plugin toolbar buttons
quicknotepad.choose-file.icon=Open.png
quicknotepad.save-file.icon=Save.png
quicknotepad.copy-to-buffer.icon=Edit.png</programlisting>
</informalexample>
<para>The menu item labels corresponding to these icons will also serve
as tooltip text.</para>
<para>Finally, the properties file set forth the labels and settings
used by the option pane:</para>
<informalexample>
<programlisting># Option pane labels
options.quicknotepad.label=QuickNotepad
options.quicknotepad.file=File:
options.quicknotepad.choose-file=Choose
options.quicknotepad.choose-file.title=Choose a notepad file
options.quicknotepad.choose-font=Font:
options.quicknotepad.show-filepath.title=Display notepad file path
# Initial default font settings
options.quicknotepad.show-filepath=true
options.quicknotepad.font=Monospaced
options.quicknotepad.fontstyle=0
options.quicknotepad.fontsize=14
# Setting not defined but supplied for completeness
options.quicknotepad.filepath=</programlisting>
</informalexample>
<tip>
<title>PropertySideKick</title>
<para>There is a SideKick for Property files, provided in the
JavaSideKick plugin. This gives you a compact and sorted tree view
of property files.</para>
</tip>
</section>
<section id="plugin-implement-editbus">
<title>The EditBus</title>
<para>jEdit (and some plugins) generate several kinds of messages to
alert plugins and other components of jedit-specific events. The message
classes, all derived from <ulink
url="../api/org/gjt/sp/jedit/EBMessage.html">
<classname>EBMessage</classname></ulink> cover the opening and closing
of the application, changes in the status of buffers and views, changes
in user settings, as well as changes in the state of other program
features. A full list of messages can be found in the <ulink
url="../api/org/gjt/sp/jedit/msg/package-summary.html">org.gjt.sp.jedit.msg</ulink>
package.</para>
<para>For example, the ViewUpdate messages are all related to the jEdit
View, or the top-level window. If the user creates multiple Views, a
plugin may need to know when they are created or destroyed, so it would
monitor ViewUpdate messages.</para>
<para>BufferUpdate messages are all related to jEdit buffers. They let
plugins know when a buffer has become dirty, when it is about to be
closed, after it is closed, created, loaded, or saved. Each of these
messages are described in further detail in the API docs.</para>
<para>As another example, The Navigator plugin monitors a newly added
(to jEdit 4.3) EBMessage of the kind <ulink
url="../api/org/gjt/sp/jedit/BufferChanging.html">BufferChanging</ulink>.
The BufferChanging event provides Navigator enough advance notice to
save the TextArea's caret just before the current EditPane changes its
active Buffer. The <literal>BufferChanged</literal> event, another
<literal>EditPaneUpdate</literal> message, is thrown shortly afterward.
This is not used by Navigator, but it is used by SideKick to determine
when it is time to reparse the buffer.</para>
<para>Plugins register <ulink
url="../api/org/gjt/sp/jedit/EBComponent.html">
<function>EBComponent</function></ulink> instances with the <ulink
url="../api/org/gjt/sp/jedit/EditBus.html">
<classname>EditBus</classname></ulink> to receive messages reflecting
changes in jEdit's state.</para>
<para><ulink url="../api/org/gjt/sp/jedit/EBComponent.html">
<function>EBComponent</function></ulink>s are added and removed with the
<ulink
url="../api/org/gjt/sp/jedit/EditBus.html#addToBus(org.gjt.sp.jedit.EBComponent)">
<function>EditBus.addToBus()</function></ulink> and <ulink
url="../api/org/gjt/sp/jedit/EditBus.html#removeFromBus(org.gjt.sp.jedit.EBComponent)">
<function>EditBus.removeFromBus()</function></ulink> methods.</para>
<para>Typically, the <ulink
url="../api/org/gjt/sp/jedit/EBComponent.html#handleMessage(org.gjt.sp.jedit.EBMessage)">
<function>EBComponent.handleMessage()</function></ulink> method is
implemented with one or more <function>if</function> blocks that test
whether the message is an instance of a derived message class in which
the component has an interest.</para>
<programlisting>if(msg instanceof BufferUpdate) {
// a buffer's state has changed!
}
else if(msg instanceof ViewUpdate) {
// a view's state has changed!
}
// ... and so on</programlisting>
<para>If a plugin core class will respond to EditBus messages, it can be
derived from <ulink url="../api/org/gjt/sp/jedit/EBPlugin.html">
<classname>EBPlugin</classname></ulink>, in which case no explicit
<function>addToBus()</function> call is necessary. Otherwise, <ulink
url="../api/org/gjt/sp/jedit/EditPlugin.html">
<classname>EditPlugin</classname></ulink> will suffice as a plugin base
class. Note that QuickNotepad uses the latter.</para>
<tip>
<title>Using the Activity Log to see the EditBus</title>
<para> To determine precisely which EditBus messages are being sent by
jEdit or the plugins, start up jEdit with an additional argument,
<literal>-log=5</literal>. You can set an even lower log level to see
further details (the default is 7). With a log level of 5 or lower, the
Activity Log will include [notice]s, which will show us exactly which
EditBus message is sent and when. See <xref linkend="activity-log" />
for more details. </para> </tip>
</section>
<section id="plugin-implement-actions">
<title>The Actions Catalog</title>
<para>Actions define procedures that can be bound to a menu item, a
toolbar button or a keyboard shortcut. Most plugin Actions <footnote>
<para>Some plugins, such as Sidekick, Console, and
ProjectViewer, create pure Java EditAction-derived Actions,
based which services are available, or which files are found in
a certain path. However, this is an advanced topic you can
explore further in the source and API docs of those
plugins.</para>
</footnote> are short scripts written in BeanShell, jEdit's macro
scripting language. These scripts either direct the action themselves,
delegate to a method in one of the plugin's classes that encapsulates
the action, or do a little of both. The scripts are usually short;
elaborate action protocols are usually contained in compiled code,
rather than an interpreted macro script, to speed execution.</para>
<para>Actions are defined by creating an XML file entitled
<filename>actions.xml</filename> and placing it in the plugin JAR
file.</para>
<para>The <filename>actions.xml</filename> file from the
<application>QuickNotepad</application> plugin looks as follows:</para>
<informalexample>
<programlisting><ACTIONS>
<ACTION NAME="quicknotepad.choose-file">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).chooseFile();
</CODE>
</ACTION>
<ACTION NAME="quicknotepad.save-file">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).saveFile();
</CODE>
</ACTION>
<ACTION NAME="quicknotepad.copy-to-buffer">
<CODE>
wm.addDockableWindow(QuickNotepadPlugin.NAME);
wm.getDockableWindow(QuickNotepadPlugin.NAME).copyToBuffer();
</CODE>
</ACTION>
</ACTIONS></programlisting>
</informalexample>
<para>This file defines three actions. They each use a built-in variable
<literal>wm</literal>, which refers to the current view's <ulink
url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
<classname>DockableWindowManager</classname></ulink>. Whenever you need
to obtain a reference to the current dockable, or create a new one, this
is the class to use. We use the method <filename>addDockable() followed
by getDockable()</filename> to create if necessary, and then bring up
the QuickNotepad plugin dockable. This will be docked or floating,
depending on how it was last used.</para>
<para>When an action is invoked, the BeanShell scripts address the
plugin through static methods, or if instance data is needed, the
current <ulink url="../api/org/gjt/sp/jedit/View.html">
<classname>View</classname></ulink>, its <ulink
url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
<classname>DockableWindowManager</classname></ulink>, and the plugin
object return by the <filename>getDockable()</filename> method.</para>
<para>If you are unfamiliar with BeanShell code, you may nevertheless
notice that the code statements bear a strong resemblance to Java code,
with one exception: the variable <varname>view</varname> is never
assigned any value.</para>
<para>For complete answers to this and other BeanShell mysteries, see
<xref linkend="writing-macros-part" />; two observations will suffice
here. First, the variable <varname>view</varname> is predefined by
jEdit's implementation of BeanShell to refer to the current
<classname>View</classname> object. Second, the BeanShell scripting
language is based upon Java syntax, but allows variables to be typed at
run time, so explicit types for variables need not be declared.</para>
<para>A formal description of each element of the
<filename>actions.xml</filename> file can be found in the documentation
of the <ulink url="../api/org/gjt/sp/jedit/ActionSet.html">
<classname>ActionSet</classname></ulink> class.</para>
</section>
<section id="plugin-implement-dockables">
<title>The dockables.xml Window Catalog</title>
<para>A Dockable is a window that can float like a dialog, or dock into
jEdit's docking area. Each dockable needs a label (for display in menus,
and on small buttons) and a title (for display in the floating window's
title bar).</para>
<para>The jEdit plugin API uses BeanShell to create the top-level
visible container of a plugin's interface. The BeanShell code is
contained in a file named <filename>dockables.xml</filename>. It usually
is quite short, providing only a single BeanShell expression used to
create a visible plugin window.</para>
<para>The following example from the QuickNotepad plugin illustrates the
requirements of the data file:</para>
<informalexample>
<programlisting><?xml version="1.0"?>
<!DOCTYPE DOCKABLES SYSTEM "dockables.dtd">
<DOCKABLES>
<DOCKABLE NAME="quicknotepad">
new QuickNotepad(view, position);
</DOCKABLE>
</DOCKABLES></programlisting>
</informalexample>
<para>In this example, the <classname><DOCKABLE></classname>
element has a single attribute, the dockable window's identifier. This
attribute is used to key a property where the window title is stored;
see <xref linkend="plugin-implement-properties" />.</para>
<para>For each dockable, jedit defines an action with the same name.
This means you do not need to define an explicit action to create your
dockable - in fact, jEdit defines three actions: "toggle", "get" and
"new floating instance" for each.</para>
<para>The contents of the <classname><DOCKABLE></classname>
element itself is a BeanShell expression that constructs a new
<classname>QuickNotepad</classname> object. The <varname>view</varname>
and <varname>position</varname> are predefined by the plugin API as the
view in which the plugin window will reside, and the docking position of
the plugin. You can use <varname>position</varname> to customize the
layout of your plugin depending on whether it appears on the sides, or
the top/bottom, or as a floating dockable.</para>
<para>A formal description of each element of the
<filename>dockables.xml</filename> file can be found in the
documentation of the <ulink
url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
<classname>DockableWindowManager</classname></ulink> class. This class
also contains the public interface you should use for getting, showing,
hiding, and other interactions with the plugin's top-level
windows.</para>
</section>
<section id="plugin-implement-services">
<title>The services.xml file</title>
<para>A "service" is a mechanism by which one plugin can work with other
plugins and avoid a bidirectional build-dependency. For example, the XML
plugin "depends" on Sidekick, but in fact, it is SideKick which creates
and operates on an object (a <literal>SideKickParser</literal>, in fact)
defined in the XML plugin. In a way, the dependency is
bidirectional.</para>
<para>Similarly, the AntFarm plugin defines but does not instantiate a
<literal>Shell</literal> object. It is the Console plugin which creates
a specific shell for each available service. SideKick and Console use
the ServiceManager to search for services offered by other
plugins.</para>
<para>Here is an example of a service from the XML plugin, which extends
Sidekick:</para>
<informalexample>
<programlisting>
<!DOCTYPE SERVICES SYSTEM "services.dtd">
<SERVICES>
<SERVICE CLASS="sidekick.SideKickParser" NAME="xml">
new xml.parser.SAXParserImpl();
</SERVICE>
</SERVICES>
</programlisting>
</informalexample>
<para>The object it returns tells Sidekick how it can parse files of a
specific type. The API docs for SideKickParser indicate exactly which
methods must be implemented in a plugin which offers this service. It
should be enough information to let Sidekick, which has its own
dockable, display the tree information in its own view.</para>
<para>For more information about services, refer to the <ulink
url="../api/org/gjt/sp/jedit/ServiceManager.html">ServiceManager</ulink>
class API documentation. There, you can find out what the tags and
attributes mean, as well as how to register and use services.</para>
</section>
<section id="plugin-implement-quicknotepad">
<title>The QuickNotepad Class</title>
<para>Here is where most of the features of the plugin will be
implemented. To work with the dockable window API, the top level window
will be a <classname>JPanel</classname>. The visible components reflect
a simple layout. Inside the top-level panel we will place a scroll pane
with a text area. Above the scroll pane we will place a panel containing
a small tool bar and a label displaying the path of the current notepad
file.</para>
<para>We have identified three user actions that need implementation
here: <function>chooseFile()</function>,
<function>saveFile()</function>, and
<function>copyToBuffer()</function>. As noted earlier, we also want the
text area to change its appearance in immediate response to a change in
user options settings. In order to do that, the window class must
respond to a <classname>PropertiesChanged</classname> message from the
EditBus.</para>
<!-- <para>
We could have the plugin core class receive and delegate
<classname>PropertiesChanged</classname> messages to the window class.
However, this would require the plugin core class to hold a reference
to either the plugin window class or the visible window class and to
update that reference when the user activates or deactivates the
plugin. It is simpler to have the plugin window class subscribe to the
EditBus directly; many plugins take this approach. This means that
<classname>QuickNotepad</classname> must implement the
<classname>EBComponent</classname> interface.
</para> -->
<para>Unlike the <classname>EBPlugin</classname> class, the
<classname>EBComponent</classname> interface does not deal with the
component's actual subscribing and unsubscribing to the EditBus. To
accomplish this, we use a pair of methods inherited from the Java
platform's <classname>JComponent</classname> class that are called when
the window is made visible, and when it is hidden. These two methods,
<function>addNotify()</function> and
<function>removeNotify()</function>, are overridden to add and remove
the visible window from the list of EditBus subscribers.</para>
<para>We will provide for two minor features when the notepad is
displayed in the floating window. First, when a floating plugin window
is created, we will give the notepad text area input focus. Second, when
the notepad if floating and has input focus, we will have the
<keycap>Escape</keycap> key dismiss the notepad window. An
<classname>AncestorListener</classname> and a
<classname>KeyListener</classname> will implement these details.</para>
<para>Here is the listing for the data members, the constructor, and the
implementation of the <classname>EBComponent</classname>
interface:</para>
<informalexample>
<programlisting>public class QuickNotepad extends JPanel
implements EBComponent
{
private String filename;
private String defaultFilename;
private View view;
private boolean floating;
private QuickNotepadTextArea textArea;
private QuickNotepadToolPanel toolPanel;
//
// Constructor
//
public QuickNotepad(View view, String position)
{
super(new BorderLayout());
this.view = view;
this.floating = position.equals(
DockableWindowManager.FLOATING);
this.filename = jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX
+ "filepath");
if(this.filename == null || this.filename.length() == 0)
{
this.filename = new String(jEdit.getSettingsDirectory()
+ File.separator + "qn.txt");
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "filepath",this.filename);
}
this.defaultFilename = new String(this.filename);
this.toolPanel = new QuickNotepadToolPanel(this);
add(BorderLayout.NORTH, this.toolPanel);
if(floating)
this.setPreferredSize(new Dimension(500, 250));
textArea = new QuickNotepadTextArea();
textArea.setFont(QuickNotepadOptionPane.makeFont());
textArea.addKeyListener(new KeyHandler());
textArea.addAncestorListener(new AncestorHandler());
JScrollPane pane = new JScrollPane(textArea);
add(BorderLayout.CENTER, pane);
readFile();
}
//
// Attribute methods
//
// for toolBar display
public String getFilename()
{
return filename;
}
//
// EBComponent implementation
//
public void handleMessage(EBMessage message)
{
if (message instanceof PropertiesChanged)
{
propertiesChanged();
}
}
private void propertiesChanged()
{
String propertyFilename = jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX + "filepath");
if(!defaultFilename.equals(propertyFilename))
{
saveFile();
toolPanel.propertiesChanged();
defaultFilename = propertyFilename.clone();
filename = defaultFilename.clone();
readFile();
}
Font newFont = QuickNotepadOptionPane.makeFont();
if(!newFont.equals(textArea.getFont()))
{
textArea.setFont(newFont);
textArea.invalidate();
}
}
// These JComponent methods provide the appropriate points
// to subscribe and unsubscribe this object to the EditBus
public void addNotify()
{
super.addNotify();
EditBus.addToBus(this);
}
public void removeNotify()
{
saveFile();
super.removeNotify();
EditBus.removeFromBus(this);
}
...
}</programlisting>
</informalexample>
<para>This listing refers to a
<classname>QuickNotebookTextArea</classname> object. It is currently
implemented as a <classname>JTextArea</classname> with word wrap and tab
sizes hard-coded. Placing the object in a separate class will simply
future modifications.</para>
</section>
<section id="plugin-implement-quicknotepadtoolbar">
<title>The QuickNotepadToolBar Class</title>
<para>There is nothing remarkable about the toolbar panel that is placed
inside the <classname>QuickNotepad</classname> object. The constructor
shows the continued use of items from the plugin's properties
file.</para>
<informalexample>
<programlisting>public class QuickNotepadToolPanel extends JPanel
{
private QuickNotepad pad;
private JLabel label;
public QuickNotepadToolPanel(QuickNotepad qnpad)
{
pad = qnpad;
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.add(makeCustomButton("quicknotepad.choose-file",
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
QuickNotepadToolPanel.this.pad.chooseFile();
}
}));
toolBar.add(makeCustomButton("quicknotepad.save-file",
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
QuickNotepadToolPanel.this.pad.saveFile();
}
}));
toolBar.add(makeCustomButton("quicknotepad.copy-to-buffer",
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
QuickNotepadToolPanel.this.pad.copyToBuffer();
}
}));
label = new JLabel(pad.getFilename(),
SwingConstants.RIGHT);
label.setForeground(Color.black);
label.setVisible(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX
+ "show-filepath").equals("true"));
this.setLayout(new BorderLayout(10, 0));
this.add(BorderLayout.WEST, toolBar);
this.add(BorderLayout.CENTER, label);
this.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 10));
}
...
}</programlisting>
</informalexample>
<para>The method <classname>makeCustomButton()</classname> provides
uniform attributes for the three toolbar buttons corresponding to three
of the plugin's use actions. The menu titles for the user actions serve
double duty as tooltip text for the buttons. There is also a
<function>propertiesChanged()</function> method for the toolbar that
sets the text and visibility of the label containing the notepad file
path.</para>
</section>
<section id="plugin-implement-options">
<title>The QuickNotepadOptionPane Class</title>
<para>Using the default implementation provided by
<classname>AbstractOptionPane</classname> reduces the preparation of an
option pane to two principal tasks: writing a
<function>_init()</function> method to layout and initialize the pane,
and writing a <function>_save()</function> method to commit any settings
changed by user input. If a button on the option pane should trigger
another dialog, such as a <classname>JFileChooser</classname> or jEdit's
own enhanced <classname>VFSFileChooserDialog</classname>, the option
pane will also have to implement the
<classname>ActionListener</classname> interface to display additional
components.</para>
<para>The QuickNotepad plugin has only three options to set: the path
name of the file that will store the notepad text, the visibility of the
path name on the tool bar, and the notepad's display font. Using the
shortcut methods of the plugin API, the implementation of
<function>_init()</function> looks like this:</para>
<informalexample>
<programlisting>public class QuickNotepadOptionPane extends AbstractOptionPane
implements ActionListener
{
private JTextField pathName;
private JButton pickPath;
private FontSelector font;
...
public void _init()
{
showPath = new JCheckBox(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX
+ "show-filepath.title"),
jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX + "show-filepath")
.equals("true"));
addComponent(showPath);
pathName = new JTextField(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX
+ "filepath"));
JButton pickPath = new JButton(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX
+ "choose-file"));
pickPath.addActionListener(this);
JPanel pathPanel = new JPanel(new BorderLayout(0, 0));
pathPanel.add(pathName, BorderLayout.CENTER);
pathPanel.add(pickPath, BorderLayout.EAST);
addComponent(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX + "file"),
pathPanel);
font = new FontSelector(makeFont());
addComponent(jEdit.getProperty(
QuickNotepadPlugin.OPTION_PREFIX + "choose-font"),
font);
}
...
}</programlisting>
</informalexample>
<para>Here we adopt the vertical arrangement offered by use of the
<function>addComponent()</function> method with one embellishment. We
want the first <quote>row</quote> of the option pane to contain a text
field with the current notepad file path and a button that will trigger
a file chooser dialog when pressed. To place both of them on the same
line (along with an identifying label for the file option), we create a
<classname>JPanel</classname> to contain both components and pass the
configured panel to <function>addComponent()</function>.</para>
<para>The <function>_init()</function> method uses properties from the
plugin's property file to provide the names of label for the components
placed in the option pane. It also uses a property whose name begins
with <function>PROPERTY_PREFIX</function> as a persistent data item -
the path of the current notepad file. The elements of the notepad's font
are also extracted from properties using a static method of the option
pane class.</para>
<para>The <function>_save()</function> method extracts data from the
user input components and assigns them to the plugin's properties. The
implementation is straightforward:</para>
<informalexample>
<programlisting>public void _save()
{
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "filepath", pathName.getText());
Font _font = font.getFont();
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "font", _font.getFamily());
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "fontsize", String.valueOf(_font.getSize()));
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "fontstyle", String.valueOf(_font.getStyle()));
jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
+ "show-filepath", String.valueOf(showPath.isSelected()));
}</programlisting>
</informalexample>
<para>The class has only two other methods, one to display a file
chooser dialog in response to user action, and the other to construct a
<classname>Font</classname> object from the plugin's font properties.
They do not require discussion here.</para>
</section>
<section id="plugin-implement-docs">
<title>Plugin Documentation</title>
<para>While not required by the plugin API, a help file is an essential
element of any plugin written for public release. A single web page is
often all that is required. There are no specific requirements on
layout, but because of the design of jEdit's help viewer, the use of
frames should be avoided. Topics that would be useful include the
following:</para>
<itemizedlist>
<listitem>
<para>a description of the purpose of the plugin;</para>
</listitem>
<listitem>
<para>an explanation of the type of input the user can supply
through its visible interface (such as mouse action or text
entry in controls);</para>
</listitem>
<listitem>
<para>a listing of available user actions that can be taken when
the plugin does not have input focus;</para>
</listitem>
<listitem>
<para>a summary of configuration options;</para>
</listitem>
<listitem>
<para>information on development of the plugin (such as a change
log, a list of <quote>to do</quote> items, and contact
information for the plugin's author); and</para>
</listitem>
<listitem>
<para>licensing information, including acknowledgments for any
library software used by the plugin.</para>
</listitem>
</itemizedlist>
<para>The location of the plugin's help file is stored in the
<literal>plugin.QuickNotepad.docs</literal> property; see <xref
linkend="plugin-implement-properties" />.</para>
</section>
<section id="plugin-implement-building">
<title>The build.xml Ant build file</title>
<para>We have already outlined the contents of the user action catalog,
the properties file and the documentation file in our earlier
discussion. The final step is to compile the source file and build the
archive file that will hold the class files and the plugin's other
resources.</para>
<para>Publicly released plugins include with their source a makefile in
XML format for the <application>Ant</application> utility. The format
for this file requires few changes from plugin to plugin. Here is a
version of <filename>build.xml</filename> that could be used by
QuickNotepad:</para>
<informalexample>
<programlisting>
<project name="QuickNotepad"
default="build">
<description>
This is an ant build.xml file for building the QuickNotepad plugin for jEdit.
</description>
<property name="user-doc.xml"
value="users-guide.xml" />
<property file="build.properties" />
<property file="../build.properties" />
<property name="build.support"
value="../../../build-support" />
<import file="${build.support}/plugin-build.xml" />
</project>
</programlisting>
</informalexample>
<para>This build file imports another modular build file,
<literal>plugin-build.xml</literal> from the <literal>build-support</literal>
project. It is available as a package you can check out from subversion, or found online in the <ulink
url="https://jedit.svn.sourceforge.net/svnroot/jedit/build-support/trunk/">jEdit's
SVN repository</ulink>. It contains the common build steps used to build the core jEdit plugins, and some example <literal>build.properties.sample</literal> files which you can adapt for use with your development environment.</para>
<para>Customizing this build file for a different plugin will likely
only require three changes to build.xml file:</para>
<itemizedlist>
<listitem>
<para>the name of the project</para>
</listitem>
<listitem>
<para>the dependencies of the plugin</para>
</listitem>
<listitem>
<para>The extra files that need to be copied into the
jar.</para>
</listitem>
</itemizedlist>
<para>Because this build file and the one of most plugins import a
<literal>build.properties</literal> file from the current and the parent
directories, it is possible to build most of jEdit's plugins in a
uniform way by setting the common properties in a single
<literal>build.properties</literal> file, placed in the plugin source's
parent directory. </para>
<tip> <para>For a full discussion of the <filename>Ant</filename> file
format and command syntax, you should consult the <ulink
url="http://jakarta.apache.org/ant/manual/index.html">Ant
documentation</ulink>, also available through jEdit's help system if you
installed the Ant Plugin. When editing Ant build files, the XML plugin
gives you completion tips for both elements <emphasis>and</emphasis>
attributes. The Console plugin provides you with an ANT button which you
can bind to keyboard actions. In addition, there are the AntFarm and
Antelope plugins which also proivde you with alternate means to execute
Ant targets through the Console.</para> </tip> </section>
<section id="plugin-implement-reloading">
<title>Reloading the Plugin</title>
<para>Once you have compiled your plugin, you will need to test its
behavior when it is reloaded. Follow these steps to reload your plugin
without restarting jEdit:</para>
<itemizedlist>
<listitem>
<para>From the Plugins menu open the Plugin Manager.</para>
</listitem>
<listitem>
<para>On the Manage tab uncheck Hide libraries. This will allow
you to see plugins that are not loaded.</para>
</listitem>
<listitem>
<para>Recheck the plugin to reload it.</para>
</listitem>
</itemizedlist>
<tip>
<para>The Activator plugin provides a very convenient (dockable) way
to test the activating and reloading behavior of your plugin. Be
sure to test your plugin's reloading behavior with both the
Activator and the Reloader tabs.</para>
</tip>
<para>If you have reached this point in the text, you are probably
serious about writing a plugin for jEdit. Good luck with your efforts,
and thank you for contributing to the jEdit project.</para>
</section>
<section id="plugin-debugging" >
<title> Tips for debugging plugins </title>
<bridgehead> BeanShell </bridgehead>
<para> jEdit includes a Beanshell interface into its currently running JVM at all times. You can access it a variety of ways, but one way is from <literal>Plugins - Console - Shells - BeanShell</literal>. From here, you can interactively inspect the values of any object in memory, call any of its member functions, or create new instances of any class that is currently loaded by jEdit or any of its plugins. All this, without setting any breakpoints!
</para>
<para> If you're too lazy to type each Beanshell statement interactively, you can also create debugging code snippets as macros and invoke them from <literal>utilities - beanshell - evaluate selection</literal>, or <literal>Macros - Misc - Evaluate Buffer in Beanshell</literal>, or place the file in your own macros directory and bind it to its own keyboard shortcut. </para>
<bridgehead> Other useful tips </bridgehead>
<para> This section is new but will be expanded shortly. Please post suggestions to the <literal>jedit-devel</literal> mailing list. </para>
</section>
</chapter>
|