Extending Alpha
Alpha |
Version: | 9.2.3 - "Suhail" |
Last update: | 2021-05-21 14:43:29 |
Abstract
This file is a manual about extending the capacities of Alpha
programmatically. If you need information about Alpha's interface and
functionalities, please refer to the Alpha Manual.
This document serves three classes of readers: first of all it contains
information for POWER USERS wanting to extend their Alpha by adding
a few new keyboard shortcuts, menu items, and procedures they have written
for personal use. This is covered in PART 1: TWEAKING ALPHA.
Gradually such users become PACKAGE DEVELOPERS contributing
their advances to the Alpha Community. As explained in PART 2: WRITING PACKAGES, this means bundling the new functionality
into packages (Features, Menus, Modes, etc.), and learning how
these interact with AlphaTcl as a whole.
From here the step to becoming a CORE ALPHATCL DEVELOPER is
not big: soon you will be interested in understanding Alpha's startup
sequence, learn how to take advantage of event hooks and traced variables,
minor modes, window attributes, and so on -- which is the subject matter of
PART 3: ADVANCED TOPICS.
Other files that document technical details of AlphaTcl:
INTRODUCTION
This document explains how to write or modify code to add functionality to
Alpha. At a first level, this may amount to defining some new keyboard
shortcuts to existing functions, or to write some simple Tcl procedures to
meet personal needs. Next, one may be interested in making such an added
function available as a menu item. It also happens that one would like to
modify one of Alpha's standard procedures to suit one's needs. All this is
possible, and not very complicated, thanks to the fact that most of Alpha's
functionality is implemented in Tcl scripts, stored as plain text files
inside the application bundle, easily accessible, and which can be modified
on the fly.
The library of these scripts, altogether called AlphaTcl,
also provides a consistent API by which you can add new features to Alpha
by bundling your scripts into so-called packages. This does not
imply that your scripts cease to be plain text files or that they are
frozen or stowed away: the wrapping merely consists in declaring the
scripts as a unit or a module which can be referenced, turned on and off as
a whole, and which links the script into global functionalities such as the
preferences system, help facilities, dependency handling and so on.
Furthermore, such a package comes with an installation mechanism so that it
is easy to make it avaible to other users.
Examples of packages are new modes or menus, adding an item to an
existing mode or menu, or simply adding some new functionality to existing
actions.
This document also tells you how to make your package interact
effectively with a few other commonly used packages which users might
already be using, and some internal technologies in AlphaTcl which you
don't have to reinvent. Finally, Part 3 covers additional technical documentation of AlphaTcl, mostly
advanced topics, such as event hooks and window attributes.
Alpha and AlphaTcl
Under the hood, the application Alpha has three components, and you can
actually see them if you open the application bundle by control-clicking on
the Alpha icon in the Finder, and then select Show Package Contents:
- One component is a compiled binary executable, written in Objective C,
often referred to as the core. (It is found in the subfolder called MacOS inside the Contents folder.) This part, which should be
regarded as a black box once it is compiled, defines all the low-level
functionality of the program, such as showing the content of a file in a
window, inserting characters in that window as you type them on the
keyboard, drawing the menus on the screen, performing the corresponding
action when you select an item in a menu, saving the changes to the
harddisk, etc. The core also runs a Tcl interpreter, and makes many of its
functions avaible to this interpreter as special Tcl commands: for example
there is a command [getPos] which returns the position
(in the active window) of the insertion point, a command [goto] which moves the insertion point, a command [text get] which acquires and returns the text found between two given
positions, and so on.
- The second component is Tcl, embedded into the application as a
framework. That Alpha runs a Tcl interpreter means that the interesting
part of the state of the program can be manipulated with Tcl commands in
this interpreter. (The non-interesting part refers to things like memory
allocation, event loop, having the cursor following the mouse movements,
and all such stuff we are lucky not to have to bother about.) The benefit
of running the interpreter is that the core can delegate many tasks to Tcl.
For example, tasks related to string manipulation, one of the forces of
Tcl, can often be accomplished more elegantly in Tcl (e.g. without worrying
about memory addresses and pointers and all such). Tcl accomplishes
those tasks by evaluating scripts. The collection of those scripts is...
- The third component is the collection of those scripts, collectively
called AlphaTcl. This library is huge: thousands of lines of code in over
five hundred Tcl files. You can see these files in the folder Contents/Resources/Scripts/AlphaTcl inside the application bundle. You
can also access all these files easily from within Alpha, as explained in a
moment, and edit and evaluate them (i.e load them into the interpreter) and
observe how Alpha's behaviour changes.
The script files in AlphaTcl are written in Tcl, but with additional
commands provided by the core, as mentioned above. Of course, the scripts
themselves define new commands (aka as procs), so in conclusion
there are three kinds of commands available:
- Alpha core commands
- functions defined in the core, but made available to the Tcl interpreter.
These are for example, position commands, commands for getting text in and
out of a window, window and menu handling commands, but only low level
such. The set of all core commands is documented in the help file Alpha Commands.
- Tcl core commands
- the standard commands of the scripting language Tcl. This includes
fundamentals like [if] and [while] for conditionals,
and [proc] for
defining new procedures. See the documentation for the
Core Tcl Commands.
- AlphaTcl commands
- the procedures defined in the library using the Tcl command [proc]. Almost all the functionality available in Alpha's graphical
user interface, for example the commands corresponding to all the menu
items, as well as definition of keyboard shortcuts and menus, is
implemented in such procs. These procs are documented in comments inside
the Tcl files, alongside with the proc definitions. Since there are
thousands of commands, and since this library is a rather dynamical thing,
it is not practical to have a man page describing them all. The Tcl files
are really meant to be read also by humans.
Since the core is a black box, and Tcl is a scripting language which for
our purposes can be considered immutable, what this Extending Alpha
file is about is mainly AlphaTcl.
The Tcl Menu, the Tcl Shell, and the Alpha Developer Menu
Whenever you open a Tcl file in Alpha, Alpha enters Tcl Mode, and
in particular activates the Tcl Menu. The Tcl Menu contains
facilities for evalutating Tcl scripts in the Tcl interpreter. You should
in particular remember the ⌘L
keyboard shortcut for
evaluating the whole file (or the current selection is there is one), and
⌃⌘L
for evaluating only the proc surrounding the
insertion point.
More direct and interactive access to the Tcl interpreter is to be
had in the Alpha Tcl Shell, opened via Tools ↣ Tcl Shell (or ⌘Y
): here you can type any command, and
evaluate it by hitting Return
. For example, you can inspect the
value of some global variable:
«» set tabSize
«» set fillColumn
«» set errorInfo
or evaluate a command, like
«» listpick [list a b c]
Evaluating the commands in a Tcl file line by line with ⌘L
, combined with inspecting variable values in the Tcl Shell is an
invaluable tool in developing Tcl scripts, to run them and to debug them.
Tcl Mode is designed to work with Tcl projects in general, i.e.
whatever you might be programming in Tcl. In the present manual we are
concerned with AlphaTcl scripts. For this purpose, there is a second menu
available, the Alpha Developer Menu, with tools more explicitly geared
towards AlphaTcl development, like for example
- debugging
- rebuilding Tcl and package indices
- creation of new modes/menu/features/extensions
- quick links to AlphaTcl related web sites.
- inspection of internal variables
Unlike the Tcl Menu which is automatically activated whenever a Tcl window
is frontmost, the Alpha Developer Menu must be turned on
explicitly. It can be turned on either globally, in the Alpha ↣ Global Setup ↣ Menus dialog, or for Tcl mode only,
via Alpha ↣ Tcl Mode Setup ↣ Menus.
One of the first things you may wish to do in the Alpha
Developer Menu is to build the standard filesets for all the Tcl files
in AlphaTcl. Select Alpha Developer Menu ↣ Create AlphaTcl Filesets. This provides really convenient access to the
AlphaTcl files from the Fileset Menu.
For more information about debugging, see the help files Bug Reporting and Debugging Help,
available from the Help Menu.
Editing Tcl files
We assume you have built the AlphaTcl filesets, as just decribed. Then all
Alpha's Tcl files are easily accessible from the Filesets Menu, and
you can browse around in them, experiment with them, learn from them, and
copy code to base your own procs upon. Alpha automatically enters Tcl mode
when editing a Tcl file, and then the Evaluate commands are
available: ⌘L
to evaluate the whole file or the
selection if there is one, and ⌃⌘L
for evaluating
the proc surrounding the insertion point.
If you don't know where to look for a specific procedure, there are
two options: one is to use the Tcl Menu ↣ Tcl Procedures ↣ Find Proc Definition menu item. If the proc is defined
in a namespace (i.e. has :: inside its name, as in pos::lineStart),
it is necessary, in the listpick, first to select the namespace ::pos (notice that there is a leading :: here) and then in the subsequent
listpick find the proc lineStart. A somewhat easier method, if you
know the exact name of the proc, is to type it in the
Tcl Shell (without
evaluating with Return
), highlight its name, and then do Shift-Command-Double-Click (F6): this will open the Tcl file containing the
definition of the proc, and move to it. This works in all Tcl files: while
reading a proc foo which calls another proc bar, you can
jump directly to the definition of bar with Shift-Command-Double-Click.
If you want to write a new proc based on a given one, it is helpful
to experiment with it by editing it, to observe the effects of a
modification. This is easy, just remember not to save. Simply evaluate
without saving, discard changes when closing the window, and restart Alpha
to bring it back to its normal state. However, to avoid saving by accident,
and to allow you to save your experiments somewhere else, it is recommended
to edit only a copy, instead of experimenting directly in the Tcl file
inside the application. You can of course copy the file as you please, but
copy it to a location outside Alpha. You can also get a temporary debugging window (not associated to a file on disk) by highlighting the
name of the proc and selecting the Tcl Menu ↣ Tcl Procs ↣ Debug Proc menu item.
To understand a proc, it is helpful to experiment with it for example by
sprinkling some alertnote commands all over it: if you are curious to know
what is the value of the variable $pos at some places in the proc, insert
alertnote commands like:
alertnote "first value: $pos"
... # more code
alertnote "second value: $pos"
...
inside the body of the proc. After evaluation, this will display the
values next time the proc is invoked, and you can follow how the variable
changes value.
PART 1: TWEAKING ALPHA
This first part is for Alpha users who wish to modify or extend some part
of the AlphaTcl code, mainly for personal use, without necessarily creating
new packages.
Basics
An illustrative example
Suppose you want to write a proc that strips the trailing space of the
current line. Such a proc would have the shape
proc stripTrailingSpaceOfCurrentLine {} {
# body of proc
}
It takes no arguments, because the proc itself will determine the position
and the text to operate on. So the body of the proc should implement the
algorithm: (1) determine the positions; (2) acquire the text; (3) manipulate
the text; (4) put the manipulated text back in. (In fact, this rough
description applies to a great many procs in AlphaTcl!) Here goes:
proc stripTrailingSpaceOfCurrentLine {} {
# Determine positions:
set pos [getPos]
set lineStartPos [linePos start $pos]
set lineEndPos [linePos end $pos]
# Acquire the text:
set txt [text get $lineStartPos $lineEndPos]
# Manipulate the text, if required:
if { [regsub {\s+$} $txt "" txt] } {
# Put the manipulated text back in, at the correct positions:
text replace $lineStartPos $lineEndPos $txt
}
}
binding create {z 'p'} stripTrailingSpaceOfCurrentLine
Now you can perform this operation with the keyboard shortcut ⌃P
(this is what the last instruction means with the
[binding create] command).
Perhaps you'll find it easy to read this proc and understand what it
does, but how were you supposed to guess all those position procs, and how
to come up with that cryptic [regsub] ? Well, you must
start somewhere, and the good news is that you can get quite far just by
looking at other code snippets, and that you don't have to read thick
manuals. But of course there are places to look up things: all commands
that relate to elementary operations in a window, like getting positions or
reading and writing text, are typically Alpha core commands, and you can
easily find them by perusing Alpha Commands, in which
the commands are listed by subject. On the other hand, once you have a text
string stored in a variable, and want to manipulate it, that's typically
plain Tcl, in this case the command [regsub]. To read
about those, look in the documentation for the
Core Tcl Commands.
Once you feel comfortable with this proc, you may want more: why not a
proc that strips the trailing space of every line in the entire document?
proc stripTrailingSpaceOfDocument {} {
goto [minPos]
while { [pos::compare [getPos] < [maxPos]] } {
stripTrailingSpaceOfCurrentLine
goto [linePos next]
}
}
(Watch out! is this proc correct or will it go into an infinite loop if the
document ends with a line without a linebreak? This depends on how [linePos next] is defined. One could fear that when it
comes to the last line, since there is no next line it would go instead to
the start of the current line. Well it doesn't. Either you could figure
this out with a few experiments (carried out before writing the while loop), or you could look up the exact specifications in Alpha Commands. It turns out that the specifications are not clear
on this point :-( So look up the definition itself, by Shift-Command-Double-Clicking on the name of the proc. This will take you to
the file positions.tcl, where you can read some
more info, and if needed, scrutinise the body of the proc itself.)
Now let's bind that to ⌃⇧P
:
binding create {zs 'p'} stripTrailingSpaceOfDocument
Bad surprise: the binding doesn't work :-( In Tcl mode at least, ⌃⇧P
is instead bound to Tcl::previousProc, or something
like that. This is a typical Alpha frustration -- the mode-specific binding
takes precedence over the global binding just introduced. A first solution is
to remove the mode-specific binding with
binding delete -tag Tcl {zs 'p'}
This will reveal that your binding was in fact correctly registered, but
that it was just overruled by the mode-specific binding. Alternatively you
could choose another key combination not already in use, e.g. ⌃⌥⇧P
:
binding create {zos 'p'} stripTrailingSpaceOfDocument
Finally, suppose we want to create a new menu item at the bottom of the
Text menu, after a separator line: this is accomplished by
menuItem append $menu::core(Text) -
menuItem append $menu::core(Text) "Strip Trailing Space Of Current Line"
In this case, that's all there is to it. Since the name of the menu item
is matched by the name of the proc (modulo an obvious conversion), the menu
itself figures out how to invoke this proc when the menu item is selected.
However, a much better way of obtaining the link between the menu item and
the proc is to supply it explicitly:
menuItem append $menu::core(Text) "Strip All Trailing Spaces" \
-command stripTrailingSpaceOfDocument
Notice in the menu how this way has the additional benefit of automatically
displaying the keyboard shortcut with the menu item!
All this is explained in more detail in the sections below.
Where to put new code? -- prefs files and smarter source
Where do you put your personal Tcl code so that Alpha can find it? Alpha
has a set of preferences files for this purpose (see the section User defined preference files in the
file Alpha Preferences). There is one global preferences file which
is loaded when Alpha is launched. In this file you should put code which
should be loaded regardless of which mode you're working in. This prefs.tcl file is opened / created by selecting the
menu item Alpha ↣ Preferences ↣ Edit Prefs File.
Then there is one preferences file for each mode. The file for a mode is
loaded as the last code to be loaded when a mode is used the first time.
This preferences file can be opened / created using the menu item
Alpha ↣ Mode Setup ↣ Edit Prefs File.
If you write many procs, and you prefs files start to become bloated, you
may consider putting the procs in separate Tcl files, say
myFootballProcs.tcl. You can put this file any safe place you like, but
it is not recommended to put it inside the Alpha application bundle. In
order for Alpha to load this file, add the following line to your
prefs.tcl file:
source /complete/path/to/the/file/myFootbalProcs.tcl
You can also choose to use the User folders: one is the User Packages folder, which can be opened via Alpha ↣ Preferences ↣ Show User Packages, the other is the
Application Support Folder, accessed via Alpha ↣ Preferences ↣ Show Support Folder. See the help file Application Support Help for more information.
If your tweaking of Alpha is concerned with modification of existing AlphaTcl
procs rather than new procs, clearly you have to ensure that the tweaked
version is sourced rather than the original. Modifying the original AlphaTcl
file is an obvious way to ensure this, but it is not recommended. For one
thing, it makes it difficult to revert to the original proc if required, for
example if the development of the replacement proc goes astray. Second, if
you update AlphaTcl at some point, either you risk that your precious
modifications be overwritten by an update, or if you simply install a new
version of Alpha, you'll have to dig out all your modifications from the old
application.
The correct thing to do is to make a copy of the original file and
place it in the Application Support Folder at the appropriate place (i.e inside the subfolder Modes if the original file was in the so-named folder. This is to be
thought of as a user tree: whenever a file is found in this tree,
it will be sourced instead of the one found in the application tree. So do all your editing in this copy. There are commands for making it
easier to create and manage such smarter source files in the
submenu Smarter Source of the Alpha Developer Menu. You should activate the package Smarter Source. See the help file Smarter Source Help for further information.
Finally, as your additions begin to take the form of some coherent
unit, you should consider bundling them into a package. This is what Part 2 of this document
will explain in great detail.
The User Menu
We shall shortly discuss how to define new menus and how to insert new menu
items into existing menus. If we are talking merely about some personal
procs that you would like to make available through a menu, for your own
use, a very convenient method is to turn on the User Menu package,
which you do in Alpha ↣ Global Setup ↣ Menus.
This inserts a new menu called User (represented by an icon
showing an open menu and a user's head) whose main item is New item. Upon selecting this item, a dialog will ask the name of the item you
want to add and the name of the procedure to be triggered by this item. You
can also assign a keyboard shortcut. In this way, all the technical
complication of menu creation is bypassed. For instance, you could make a
menu item for the proc stripTrailingSpaceOfCurrentLine seen in the
An illustrative example section. You could name the
item Strip Trailing Space (or whatever) and the associated proc would be
stripTrailingSpaceOfCurrentLine. Very handy.
See User Menu Help for more information.
Some background, general advice and tips
Tcl files and Tcl indices
The AlphaTcl library is too big for it to be practical to load entirely
into the Tcl interpreter: a file is only loaded (sourced) when some
procedure defined in it is needed for some operation. For this to be
possible, AlphaTcl (like most Tcl programs) keeps a tclIndex file
in each folder: this file lists all the procedures defined in the files
present in the folder, and says which file they belong to. If you modify
the files by adding or renaming procs, it is necessary to rebuild these
index files. Typically, you have defined a new proc [myproc] in
some Tcl file, and although it has been declared and loaded and tested, the
next time you start Alpha you get instead the error message
invalid command name myproc
because the Tcl interpreter is not aware of where this proc is to be found.
It may also happen that the file in which it is defined actually has
been sourced for some other reason, (i.e. another proc in it which is
requested and which is in the old tclIndex file). When one proc is
requested, naturally the whole file and all procs defined in it are loaded
at the same time. But even if this happens, one cannot rely on this
happening every time Alpha starts up, so it is important to rebuild the
indices anyway.
It is easy to rebuild Tcl indices: select the Rebuild AlphaTcl
Indices command in the Alpha Developer Menu, or for more fine-grained control, the commands in the
submenu Tcl Menu ↣ Tcl Indices. After
rebuilding indices, quit and restart Alpha. It is also possible to rebuild
the indices manually just in one particular folder, by cd'ing to
that directory in the AlphaTcl Shell (⌘Y
), and issuing the command
auto_mkindex
The Tcl indices should not be confused with the package info files (kept in
the bundle's Cache folder)
which store information about package definitions. Although a
feature/mode/menu may be defined in a Tcl file that is properly indexed,
this does not mean that it is sourced, because Alpha caches package
information to avoid having to scan hundreds of files on startup: the
definitions are cached in the package info files. So after a modification
to a package definition, it is further necessary to rebuild the package
info files. See the section on Package Indices,
further below.
Note that AlphaTcl will always check whether the number of files/folders have
changed between editing sessions and will perform an automatic rebuild of all
indices when necessary.
Compatibility and portability issues
This section concerns respect for the past and protection against the future!
Currently Alpha is the only application making use of the AlphaTcl library.
In the past, the library has been shared by other members of the Alpha
family, and this could be the case again in the future. For this reason it
is important that the procs that make up AlphaTcl do not make too many
assumptions about the outer circumstances, and that they make themselves
robust by abstraction layers and by introspection. This section also has
some information about how older code (created for AlphaTcl v 7.x or 8.0)
should be adjusted in order to work with the current library.
Abstraction layers
Currently, the only use of the AlphaTcl library is in Alpha, the only member
of the Alpha family currently being maintained and developed. Nevertheless,
the AlphaTcl library continues to develop in such a way that other Alphas,
old or new, could use it. The upshot is that the code, as much as possible
should abstract from any particular features of the binary Alpha or of the
circumstance that Alpha runs in Mac OS X.
Important path variables
The location of the prefs folder (or other folders that Alpha needs to
reference) are never given explicitly as ~/Library/Preferences/Alpha-v9.0/
, even if this would be the correct
place in OS X. It is always referenced through the variable $PREFS
which early, when Alpha starts up, is initialised to the above path. This is
not only a question of consideration for future Alpha family members or
ports to other platforms, but as much a very useful abstraction: if it is
later on decided that the prefs should be kept in a different place, then
this change can be made in one single line in Alpha, instead of making the
change throughout the code base. Similarly there are variables holding the
value of other paths that Alpha needs (like $HOME for the AlphaTcl library
and $APPLICATION for the application bundle).
Directory delimiters
Although paths in unix and OS X both use a slash as directory separator,
the slash should never be used literally in paths in AlphaTcl. Instead, the
path should be built up or decomposed using the Tcl commands
[file join] and [file split].
respectively so that you don't assume any particular delimiter, and your
code works fine on all platforms.
If you really need the file separator, use [file separator] to get the correct value. In general, excessive manual
tweaking of file separators is probably a sign your code isn't designed in
the simplest way -- please ask on the Alpha's Mailing Lists if
you run into problems like this.
Position procs
Alpha uses natural numbers to indicate positions in a window by counting
from the beginning of the window. However, you should not rely on natural
number arithmetic to perform operations on positions. Instead, AlphaTcl
provides the following 5 functions, which provide all the functionality you
need:
See Alpha Commands for details, and the positions.tcl file for more position query procs that are
available in AlphaTcl.
Environment introspection
Whenever an AlphaTcl script assumes some Alpha or OS X specific feature,
such as AppleScript or a folder called /Applications, or
whatever, the script ought to be wrapped in a conditional, testing whether
we are really in OS X! This was daily practice in AlphaTcl development up
until around 2005, but since then, the tendency has been to relax a little
bit more, since currently the only actively maintained version of Alpha is
the Alpha which runs only on OS X. However, it is still very much
recommended for serious development to have an eye on this issue.
There are many other environment characteristics that code may need
to test for: version of Alpha, AlphaTcl or Tcl, version of the operating
system, processor type, etc. Although OS X is based on unix, it should not
be assumed that all standard unix tools are available, because some might
be installed only if the user installs Developer Tools, and it is also
possible that the PATH
has been tampered, so that even the most basic
tools may not be readily available.
The basic advice is to try to avoid any dependence! For example,
many unix tools for access to the file system have Tcl equivalents which
should always be preferred. As a rule of thumb, all calls to [exec] should be wrapped in suitable exception handling.
The most important tests are probably against versions of Alpha and
AlphaTcl: the present lines are written for Alpha 9.0, and at the point of
its release, all the involved components are in perfect harmony. But soon
there will be some beta version of 9.1 out, with marvelous new scripts
depending on fancy new functionalities in the core. Any script making use
of such new stuff should guard against suddenly being executed in an old
binary -- it all too easily happens that someone updates from svn and gets
new AlphaTcl scripts into his good old Alpha 9.0 ...
Of course it is not practical to wrap hundreds of lines of code in
conditionals, and one of the big advantages of bundling code into packages is
that the package can make the checks once and for all during its
initialisation, so that all the scripts in the package don't have to bother
about this.
For package requirement scripts, three of the most basic calls are
something like:
alpha::package require Alpha 9.0
alpha::package require AlphaTcl 9.0
package require Tcl 8.6
See the section Package Requirements for more details.
Other useful variables, and the meaning of their possible values are the
following:
Variable | Value | Implication |
$tcl_platform(platform) | macintosh | Classic MacOS, but NOT Mac OS X. |
$tcl_platform(platform) | unix | Unix, for example Mac OS X (hence command-line tools should be available) |
$tcl_platform(platform) | windows | Microsoft Windows OS (any) -- also with command-line tools. |
${alpha::macos} | 0 | Windows or Unix, but NOT Mac OS X. |
${alpha::macos} | 1 | Classic MacOS (now obsolete) |
${alpha::macos} | 2 | Mac OS X |
${alpha::platform} | alpha | Alpha 7,8,X |
${alpha::platform} | tk | Alphatk (on any platform) |
[info tclversion] | 8.x | Tcl 8.x. |
$alpha::windowingsystem | alpha | Alpha on Mac OS X |
$alpha::windowingsystem | aqua | Alphatk on Aqua-Tk (Mac OS X) |
$alpha::windowingsystem | windows | Alphatk on Windows |
$alpha::windowingsystem | classic | Alphatk on MacOS classic |
$alpha::windowingsystem | x11 | Alphatk on Unix with X-windows (could be Mac OS X with DarwinX) |
Some further calls that may be needed in particular circumstances (listed
with their current value as of this release, as illustration):
Variable | Current Value | Implication |
[exec sw_vers -productName] | Mac OS X | system name |
[exec sw_vers -productVersion] | 10.13.6 | system version |
[exec arch] | i386 | CPU architecture |
[exec uname -s] | Darwin | operating system name |
[exec uname -r] | 17.7.0 | operating system release |
msgcat::mclocale | c | current locale |
Line endings
Plain text files on OS X (and in unix) use \n
for line endings (LF).
Your code should never assume this, however, because there are still many
Mac files around with line ending \r
(CR), and on Windows the old DOS
convention of \r\n
(CRLF) is still used.
Whenever your code tests for a line ending, it should test for both
\r
and \n
.
Error handling
Errors which are not caught in your code with a [catch]
are bugs, unless they start with the string cancel (case doesn't
matter here), when they are considered to be the result of the user
cancelling an operation. This can make your code simpler, by allowing you
to simply throw a cancel error, as in
error "cancel"
error "Cancelled -- this is not a valid option."
If the string cancel is present as in these two examples, then the
error message will just be displayed in the status bar.
Any other error which is not caught is a bug, and may be shown to
the user, depending on their error reporting settings (see the Errors Preferences panel).
Escaping infinite loops
Even the best programmers get themselves into infinite loops while
experimenting with new code, and typically just when there are many open
windows with unsaved changes! The following trick allows you, in many cases,
to escape an infinite loop.
Step 0: write the following two lines in your prefs.tcl file:
package require Tclx
signal -restart error SIGINT
Do this now -- it has to be done beforehand! This will be done once for
all (and this is why it's called Step 0!).
Step 1: in the advent of an infinite loop (or any task that takes too long
to wait for), switch to the application Terminal, and from there send an
interrupt signal to Alpha. To do this you first have to figure out the
process identification number of Alpha, which you can do with the command
ps aux | grep Alpha
This will list two processes -- one is Alpha and the other is the grep
process itself.
Step 2: supposing Alpha has process identifier 4547, the interrupt
signal is sent with the command
kill -SIGINT 4547
This will provoke an error in Alpha, and in particular escape the loop.
Then save all your work (and restart Alpha).
Defining keyboard shortcuts
Any command can be bound to any key combination, or to a menu item. If one
command is bound to both a keyboard combination and a menu item, then the
symbol for the key combination will automatically be displayed in
the menu item. It is important to note that this displayed symbol is only a
visual aid for the user to remember the shortcut; there is no direct link
between the menu item and the keyboard shortcut. This means that anybody
who wishes to change the shortcut (a developer, a user, or a script which
dynamically assigns keyboard shortcuts), does not have to bother about
menus (which could have been modified, turned off, or depend on modes).
Conversely, anybody modifying the menus, doesn't have to bother about
bindings. If there is one available, it will automatically be displayed.
Keyboard shortcuts
The main info concerning bindings is found in the help file Keyboard Shortcuts and the reference for the [binding] command.
Here is a minimal example to start with:
binding create {z 'p'} {alertnote hello}
You can evaluate this in the AlphaTcl Shell (⌘Y
) to test it.
This will create a binding from the key combination ⌃P
to the command alertnote hello. The z indicates that the
Ctrl modifier key has to be pressed simultaneously with the P. The
available modifier keys, which can be combined in any order, are
c | Command (⌘ ) |
o | Option or Alt (⌥ ) |
z | Control (⌃ ) |
s | Shift (⇧ ) |
More complex key combinations are possible, as explained further below.
To elaborate further on the example, consider
binding create -creator PAUL -tag TeX {z 'p'} {alertnote hello}
The TeX tag means that the binding is only available in TeX mode. The tag
doesn't have to be the name of a mode, it can be any string, and then the
binding only takes effect in windows whose bindtag attribute list contains
this string as an element (cf.
Defining minor modes and modifying window behaviours below). This
allows bindings to be activated even on a window-by-window basis.
The creator tag (which has to be a four-letter creator code) does not have
any effect on the binding per se, but it is highly recommended to put it in
all binding declarations, so as to keep track of who defined which bindings.
For example you can obtain a list of the bindings with a given creator:
binding list -creator PAUL
For the format of the return value of this command, see [binding list].
Typically a package will define all its bindings with some creator code, say
SODA, so that the deactivation script of the package can turn them all off at
once by doing
binding::deleteBindingsForCreator SODA
To turn off an individual binding, do
binding delete -tag TeX {z 'p'}
Note that a binding can be turned off without knowing what command it was
bound to. If you want to know, do
binding info command -tag TeX {z 'p'}
Note that in the last two examples, the tag is important, since otherwise in
the delete example other bindings might be deleted as well, and in the info
example, the command of a different binding (a global one) might be returned.
Special keys
To bind to special keys, there are many possibilities, but the easiest is
perhaps via virtual keycodes. For example, to bind to shift-return, do
binding create {vs Return} {alertnote hello}
Here the v indicates that the second element is a virtual keycode.
For a list of all virtual keycodes, see the
Symbolic key names section.
Composite bindings
Since there is a high risk of conflicts between competing bindings, it is
often practical, and highly recommended, that a package -- or any user who
is setting up his personal bindings -- defines all bindings as two-strokes
bindings with the same first part, like for example ⌃P X
, ⌃P Q
, etc. In this way, from the
global viewpoint only one binding is occupied, namely ⌃P
, while inside this binding the package has complete freedom to choose
the second letters (which themselves can be combinations), and can much
more easily find nice mnemonic shortcuts. As an example of a package taking
this principle rather far, see the package Embrace.
In a two-stroke key combination, the first stroke is called the
prefix. The prefix must be declared separately. For instance:
binding create {z 'p'} {prefixBinding}
This will work as a composite binding precisely because the prefix is
bound to the [prefixBinding] command.
Once this is in place, you can define all sorts of bindings for the second
part of the composite binding, by doing for instance
binding create -prefix {z 'p'} {"" 'h'} {alertnote howdy}
This binds the combination ⌃P H
. The command
[prefixBinding] admits an optional argument to specify a
prompt. For example one could do
binding create {z 'p'} {prefixBinding -prompt "Type letter of desired alert: "}
binding create -prefix {z 'p'} {"" 'h'} {alertnote howdy}
binding create -prefix {z 'p'} {"" 'b'} {alertnote bye}
The binding command allows several further variations, and support for
character codes, glyph codes, and symbolic names. The definitive reference
is the [i] command help page.
Creating new menus and menu items
Alpha's menu commands allow the user to build custom menus of the same
sophistication as Alpha's standard menus, or to insert menu items into any
existing menu. This includes nested submenus, dynamical menu items,
automatically disabling menu items if the corresponding command is not
applicable in the current situation, decorating the menu items with
tickmarks, bullets or indeed any unicode character. It is also possible to
create a menu which is rebuilt on the fly at the moment the user opens it,
so as to reflect the current state of the application. For example, the Windows Menu contains a list of all open windows. This list is compiled
on the fly when the user opens the menu.
Adding menu items (for example to Alpha's standard menus)
The main info concerning the menu handling commands is found in the reference
pages of the [menuRef] and [menuItem] core commands.
There are two aspects of this: one is to insert the menu item into
some existing menu. The second is to link this new menu item to the
envisaged command.
The main difficulty of the first aspect is to correctly specify the
place for the new menu item. Since in principle various menus can have the
same name (for example, there are many menus called Help), every
menu has a reference token. This token is a unique string which is returned
when the menu is declared, via the command [menuRef].
For example, the Search Menu might have reference something like
menu18. But even if this token is known, one should never refer to
it explicitly, because it may vary from session to session (depending for
example on the order in which some modes are sourced during the session),
so it is necessary either to store the token in some global variable where
it can always be looked up, or to search through all the defined menus to
find the reference.
In the case of the Search Menu, as an example, the menu is
actually declared in the file alphaMenus.tcl,
inside the proc [menu::buildCoreMenus]. Here you see
the declaration, inside a loop where at some point the variable $menuName has value Search:
set token [menuRef create -title $menuName -name $menuName]
set menu::core($menuName) $token
The first line creates the menu, and the second stores the token for later
use. You can peruse the whole array menu::core to see which tokens are
assigned to which menus, but the idea is never to use the tokens directly,
but rather always refer to this array.
Other menus than the core menus are defined elsewhere, and you'll have to
look around in the tcl files to find the global variables that store their
identifier token. They are usually called menuToken but may be a simple
variable or an array variable.
Alternatively, there is of course a way of querying. The command
menuRef list
will return a list of all existing menu tokens. For each item $token in this
list you can ask for the name of the menu:
menuRef set $token -name
or perhaps better
menuRef set $token -title
Although there may be duplicate titles, most likely you can find the desired
menu uniquely in this way.
Although we were really only interested in inserting an item into an
existing menu, we see that it is necessary to know a little bit about how
the whole menu was created! There is further info about this in the next
section. You can also ask on the mailing lists, of course!
We assume now that we have got hold of the reference to the menu,
and that it is stored as the value of the variable $token. Now we can
insert a new menu item into this menu by the command
menuItem insert $token $index $text
here the variable $text contains the text the item will appear as.
The variable $index is a natural number indicating the position
within the menu. The first item has index 0, and the last can be referenced
using the list idiom end, the second-to-last by end-1, etc.
Note that these indices might not necessarily be the positions you see
graphically in the menu, because it may have disabled or dynamical items
which also count! So to figure out the correct index, you really need to
look up the definition of the menu and see which items are there. Note also
that if you wish to put the menu item last, the correct index is
menuItem insert $token end+1 $text
(because end refers to the last already existing item). There is an
alternative, much simpler, syntax for this, namely
menuItem append $token $text
There are many options for configuring menu items. See section
Further menu item possibilitites below.
Now the item has been inserted into the menu. But selecting it will
only produce an error, because there is not yet any command associated to
this new item. This should be done by giving a -command $cmd argument to
the call to [menuItem]. As an example, we refine the above example to
menuItem insert $token $index -command $cmd
where $cmd is the desired command. As a concrete example, try
menuItem insert $menu::core(Search) 12 "Hi" -command {alertnote hello}
Go to the Search Menu and try it out. Luckily, the item will be gone
next time you restart Alpha. If you can't wait that long, do
menuItem delete $menu::core(Search) 12
Creating new menus
Main info: [menuRef] command.
Creating a new menu involves three steps: first the menu is declared,
then it is filled with menu items using the command [menuItem], and finally
the menu is inserted somewhere, typically in the menu bar or as a submenu,
but it could also be as a popup menu or as a contextual menu.
To create a new menu, do
set myMenuToken [menuRef create -title $name]
and refer to $myMenuToken in all future correspondence with the menu.
Several variations and options are possible.
Icons
One possible option is to supply a
-icon $iconToken
argument instead of a -title argument, in order to have the menu displayed
with the icon $iconToken instead of a name. The icon token is
typically obtained with the command [imageRef create].
As an illustration:
set iconToken [imageRef create -file "/path/to/some/myIcon.icns"]
set myMenuToken [menuRef create -icon $iconToken]
Note that AlphaTcl provides convenience procs in the file icons.tcl. The proc [icon::createMenuIcon] will
find an icns by name without specifying the complete path of the file: it
looks in the appropriate folders. For instance, here is how the Filesets
Menu package creates its main menu:
icon::createMenuIcon fileset
set menuToken(main) [menuRef create -icon [icon::getMenuIcon fileset] \
-name $filesetMenu -command $menuProc]
Menu procs
Another possible option is the -command argument, like in
set myMenuToken [menuRef create -title $name -command "myMenuProc"]
Here myMenuProc is a callback proc which takes two arguments: the reference to
the menu (i-e the token), and the name of a menu item. This proc is then required
to take the appropriate action depending on the menu selection. So it
will typically look something like this:
proc myMenuProc {menuToken itemName} {
switch $itemName {
"Hi" {
alertnote "Hello"
}
...
default {
error "Not implemented yet"
}
}
}
It should be noted that instead of supplying such a menu proc for the whole
menu, it is also possible to supply a command for each menu item
individually. This second method has some conceptual advantages, in that
each menu item becomes more of an independent unit, a name associated with
a command, without depending on features of a parent menu and its attendant
menu proc. For example, a menu item with its own command can be moved to
another menu without further administration overhead.
Dynamically built menus
Finally, an important possible option to the [menuRef]
command is the -updateProc argument
which lets you specify a Tcl procedure to evaluate before a menu is
opened, typically to rebuild the whole menu in accordance with the
situation. This is useful for menus whose content varies with the context.
When the procedure declared by this option is invoked, the token of the
menu is appended to its list of arguments. An example of a menu using this
is the Windows Menu, the exact list of menu items is only
calculated on demand at the moment the user opens the menu.
Inserting the new menu
Once the menu has been constructed, it has to be inserted somewhere.
To insert it in the main menu bar, do
menuRef insert $token -before $otherToken
Here $token is the menu we are inserting, and $otherToken is the reference
to another menu in the menu bar, before which we wish to insert the new
menu. If unspecified, the menu is appended at the right of the menu bar
(yet before the Help menu).
Nested submenus
To insert the new menu $myMenuToken as a submenu of another menu $parentMenu,
say as the second item, do
menuItem set $parentMenu 1 -submenu $myMenuToken
Note that a same submenu can not be inserted in two different parents.
Further menu item possibilitites
The main info about menu items is found in the reference of the [menuItem] command.
Separators and static text
Separators are menu items on their own, and count in the index count.
A separator may be defined in two different ways:
Note that the name can contain other stuff than the dash. This can be
useful for later use, if for example a futher item is to be inserted, then
one can navigate by searching for the exact string naming that separator.
Another solution is to set a tag (which is an integer value) on the item
with the -tag option and then find it
using the [menuItem index] command.
Headers or other static text can be indented with the -indent option:
menuItem append token "Section header" -header 1
There are many facilities for changing the layout of the text of an item,
for example with the extra flags like -style
-style 2 -icon [icon::getNamed lightBlueDot]
which will set the item in italic and insert a light blue dot. See the [menuItem] reference page for all the options.
Dynamical menu items
Here is a typical example, taken from the Edit Menu:
menuItem insert 17 $menu::core(Edit) "Shift Left" -dynamic 1
menuItem insert 18 $menu::core(Edit) "Shift Left Space" -dynamic 1 -alternate 1 -modifiers "o"
Both menu items have the -dynamic flag,
which means that their display depends on which modifier keys are pressed.
The second item has the option -alternate 1, which means that it is an alternate item.The second item has also
the -modifiers "o" option which means that it will only be
displayed when the option key (⌥
) is pressed.
It is important that the dynamic pair are adjacent in the order of the menu
items. Otherwise both items will be shown.
Note that the modifier specified to trigger the alternate item is
supposed to match the keyboard shortcut specified elsewhere for the command
attached to the item. That is, if the keyboard shortcut defined with [binding create] is ⌘[
for Shift
Left, then it should be ⌥⌘[
for Shift Left Space.
Disabling menu items
At any time, one can enable or disable a menu item with the command
menu::enableItem $token "Name Of The Item" 0
This is a convenience proc based on the -enabled option.
If the test to perform for deciding whether a menu item should be
enabled or disabled is whether there is one (or two, three, etc) open
window(s) which can be edited or not, then the enabling and disabling can
be automatised by registering a dimThreshold hook for the menu
item. If for example a menu item Copy in menu $token needs
at least one open window to make sense, the enabling and disabling can be
achieved by registering a hook in this way:
hook::register dimThreshold [list $token "Copy"] 1
See the section on Event Hooks for more information about hooks.
PART 2: WRITING PACKAGES
Alpha provides nice facilities for bundling your scripts into packages, be
it extensions, features or modes; this helps them take advantage of global
facilities such as preferences and help text, makes it easier to do
dependency checking, and last but not least, makes it easy to distribute
and install. The most advanced type of package, that of a Mode, provides
functionalities like syntax colouring, comment handling, and function
marking, which cannot be achieved without the alpha::mode
declaration that defines a mode. This Part 2 describes those facilities for
writing packages.
We strongly recommend that you look at some of the existing packages
and base your code upon them. There are at least a dozen quite small,
simple packages in AlphaTcl which would make good templates. Here's a
partial list:
These files are all found in either the
CorePackages
or
Packages
folders.
Package basics
There are two basic types of package which Alpha uses: modes and features.
A mode helps with editing a file for a particular purpose:
web pages use HTML mode, C++ code uses C++ mode, LaTeX
documents use TeX mode,... There are about 70 such modes currently
available.
Features are of three types: menus, extensions and ordinary features. A feature adds functionality to Alpha
either globally (a global feature) or just for particular modes (a
mode feature). Menus are one type of feature used to extend Alpha,
and aren't really much different from features which don't add menus: the
only distinguishing difference is a couple of lines of code which do some
menu creation/deletion.
For examples of existing menus and features which may be activated
in Alpha, see the Menus Preferences dialog and the Features Preferences dialog.
All packages will contain a package identification and initialization
command, called as appropriate. (One of the purposes of this part is to
describe the minor, but important differences between them).
The following procs can be used to declare a new package:
NOTE: Technically, menus, features and extensions are all
treated in the same way by Alpha. However each will have different
associated information which will determine whether/in what section it
appears in a dialog box. All this information is stored in the
index::feature array.
It is not actually necessary to place modes in Tcl/Modes,
menus in Tcl/Menus or packages in Tcl/Packages inside the
AlphaTcl library: they could go anywhere on the auto_path variable.
The chosen layout has proved convenient though.
Examples
Here are some examples:
alpha::library identities 0.5 { ...
alpha::feature recentFiles 0.5.1 "global-only" { ...
The package Identities is an extension which allows
you to create multiple identities when using Alpha. It installs an Alpha ↣ Preferences ↣ Identities submenu for the
commands which allow you to create new and edit current identities. It is
designed to be turned on globally, and there is no action to be taken if it
is turned off. The number is the version of this package; in this case,
version 0.5.
The package Recent Files, which creates a menu
of recent files accessible from Alpha's File menu, is an global-only feature that is defined in the file recentFiles.tcl. Like extensions, global-only features are only
designed to be turned on and off globally (so you can't arrange to use the
recent files menu when in C++ mode but not when in HTML mode), which makes
sense for this package, since a user will either want to have access to the
menu all the time, or not at all. It is a feature so that we can
also create a deactivation script to remove the menu when it is turned off.
Notice this is declared as an feature, not as a menu.
The [alpha::menu] proc is reserved for menus
which appear in Alpha's menubar, not submenus such as this.
In fact the Alpha ↣ Mode Setup ↣ Features dialog lets expert users turn such global extensions off for particular
modes, but this might cause unforeseen problems.
See the Text Mode Features Preferences.
Another example:
alpha::feature latexAccents 1.0.5 {TeX Bib} ...
Some users of LaTeX find it easier to type accented characters 'éåü…' directly into their documents rather than somewhat esoteric control
codes like \'{e}
. The package Latex Accents
makes that task a lot easier. It is only useful for LaTeX and BibTeX
documents, so the author specifies this information in an extra argument to
[alpha::feature] which is not allowed (or relevant) for
[alpha::library].
Most modes add a menu which is automatically activated in that mode.
Other menus are useful globally. It's up to the user to decide whether each
feature/menu should be activated globally or only on a mode-by-mode basis,
but each feature can specify its default. Mode authors can of
course set the defaults for their mode. Examples of globally useful menus
are the
Filesets Menu,
the Filters Menu
and the Electric Menu
Finally, an extension is a simple form of feature which is either globally
active or off (it either doesn't make sense or isn't particularly useful to
turn extensions on and off in a mode-dependent way). Examples of extensions
are the printer choices sub-menu, or the bib-engine (used to interact with
BibTeX).
Note: a menu is something which sits in Alpha's
menubar, at the top level. A feature or extension can create submenus which
sit inside top level menus, but these are not menus in the same
sense. The main distinction is that menus must be registered with the procs
[alpha::menu] or [addMenu], whereas
submenus need no special registration.
Quickstart
For the impatient reader: here's how to write a very simple feature which
contains one new procedure and one new key-binding (to that procedure). Just
create a file which looks like this:
alpha::feature myPackage 0.1 {C C++} {
# no global initialization required
} {
# activation script
# bind Ctrl-P to my procedure, in those two modes:
binding create -tag C {z 'p'} myProcedure
binding create -tag C++ {z 'p'} myProcedure
} {
# deactivation script
binding delete -tag C {z 'p'}
binding delete -tag C++ {z 'p'}
} requirements {
# if your package has any particular requirements (such as
# particular versions of Alpha or it only runs on unix),
# place code to test that here. Otherwise omit this
# section completely.
if {[info tclversion] < 8.0} {
error "My package requires Tcl 8"
}
} uninstall {
this-file
} maintainer {
"My Name" my@email http://webpage..
} help {
Binds the blah-key to 'myProcedure' which carries out...
This package is only designed to do something useful in
C and C++ modes.
}
proc myProcedure {} {
# do some cool stuff
alertnote "It works"
}
Save this file in the Packages subfolder of Alpha's Application Support Folder.
The name of the file does not matter: it could be myPackage.tcl or
anything else. Then quit Alpha: when you relaunch it, it detects the
presence of the new file and automatically rebuilds the necessary indices
in order to register the identity and characteristics of the new package
myPackage.
By default this package declares it is useful for C and C++ modes, although
the user could choose to activate the package globally or individually for
any set of modes. You will find it in the Mode Features sub panel of the
Alpha ↣ Global Setup ↣ Features dialog.
Package indices
There is one subtle and important point about packages: the commands [alpha::feature], [alpha::mode], etc.
listed above, contrary to all other commands, are not meant for execution
at runtime, and if you try to invoke one of them nothing will
happen. They are not executed at startup either.
THE PACKAGE DECLARATION COMMANDS ARE ONLY EXECUTED WHEN ALPHA REBUILDS PACKAGE INDICES. |
So while developing a package or modifying an existing one, any
changes to the declaration will not take effect by evaluating, nor by
restarting. It is necessary to request a Rebuild AlphaTcl Indices
from the Alpha Developer Menu.
What happens is that Alpha, maintains a Cache of all package
information, in order to avoid sourcing all the files defining packages,
while still having all meta-data about them available. Avoiding to source
all the files is simply for economy of memory, and to speed up the startup.
The necessity of having the meta-data available is for example to display
it in the prefs panes, and to know which other packages should be turned on
first, if one package is requested.
While this system is practical in normal usage (and, for most users,
it is never necessary to rebuild indices, and when it is, typically Alpha
figures it out by itself), it is often a source of confusion for the
developer and for package writers in particular. For example, it often
happens in the development phase that you change the name of some variable,
and next time Alpha starts up you get a start-up error complaining about an
unknown variable. It is then because the old variable is still present
(uninitialised) in the cached package script. Rebuilding indices then cures
the problem. Or worse: your recently modified package continues to work
fine, but when shipped to other users, unknown variable errors
occur. It may then be that some component in the package is referencing an
old variable that is still defined on your machine because it was defined
in the old init script which is in the cache, but that on the foreign
machine, a fresher cache will not have the old initialisation, hence
causing the error. The upshot is to remember to rebuild package indices
whenever you make changes to a package declaration.
Packages are rebuilt automatically for example whenever Alpha
detects a new file in the AlphaTcl directory hierarchy, so when a user
installs a package for the first time in Alpha's Application Support Folder.
Declaring your package to Alpha
A package must contain, preferably as its first non-comment line (this is
important), a statement like this:
alpha::mode NAME VERSION ...
alpha::menu NAME VERSION ...
alpha::feature NAME VERSION ...
alpha::extension NAME VERSION ...
(The other parameters to these commands are explained below). The name will
identify your package, and for modes must be at most 4 characters long. It
should not contain any spaces (this limitation may be lifted in a future
version of Alpha). The version is a string of the form 1.0.1, or 2.3b1 or
1.4.530.1.3a5. Modes, menus and extensions take different arguments for the
remainder of the alpha:: declaration line, but each ends in a
script which Alpha scans and stores for you (Alpha scans all installed
files for package declaration lines and caches this information so that at
startup, no files need be read). For modes and menus, this script is
executed automatically at startup. For features, there are initialization
and activation/deactivation scripts. An extension is a simpler form of a
feature which only has a single initialization script (used the first time
it is activated). Package initialization occurs in the order: modes, menus
and finally extensions.
Important: The declaration command must not be wrapped in
any [catch] statements. This is necessary to allow Alpha
to rebuild package indices rapidly (note that it is no longer required to
be at the beginning of the line). If you wish to write code which is able
to run both inside AlphaTcl and with other Tcl interpreters, try something
like:
if {[info commands alpha::extension] ne ""} {
alpha::extension ...
} else {
# initialize in some other way for other generic Tcl interpreters
}
Your package will not function properly if you don't obey the above
guidelines.
Alpha itself is considered a package, with a version number, so that
your code can request a particular version of Alpha. Alpha's version number
also has a patchlevel which will be updated with each Tcl-only patch
release. Hence you can write:
alpha::package require Alpha 9.0
You can similarly require particular versions of other packages. You should
require as old a version as possible, so that you don't force users
to upgrade unnecessarily.
Package requirements
A package can specify a separate script for testing whether the current
environment is suitable for the package. This is called a requirements section, as in the following example:
alpha::extension iAppleX-tools 1.1 {
# declaration script
} requirements {
# This package only runs on OS X on Intel, and needs AlphaTcl 8.1
if {$tcl_platform(platform) != "unix" ||
$alpha::macos != 2 || ![regexp "86" [exec arch]] } {
error "The iAppleX-tools package requires OS X/Intel"
}
alpha::package require AlphaTcl 8.1
}
Packages may be dependent on particular operating systems or even hardware
(see the Environment introspection section for more
details), or depend on particular versions of Alpha or on other packages
(e.g. filesets 2.0). Note that Alpha and AlphaTcl themselves are considered
packages.
The main proc for testing whether dependencies are met is the
proc [alpha::package] which is similar to the Tcl command
[package] but
differs in a few respects. As in the example above, you can use
[alpha::package] to check/request the presence of other packages. The
syntax is:
alpha::package require NAME ?VERSION?
Other sub-commands are exists names versions vcompare vsatisfies
forget uninstall and mode, menu and package. These last three
mimic the usual [alpha::mode] [alpha::menu] and [alpha::package] commands.
- alpha::package require ?-extension -mode -menu? name version
- alpha::package exists ?-extension -mode -menu? name version
- alpha::package names
- alpha::package uninstall name version [this-file|this-directory|script]
- alpha::package vcompare v1 v2
- alpha::package vsatisfies v1 v2
- alpha::package versions ?-extension -mode -menu? name
- alpha::package type name
- alpha::package info name
- alpha::package maintainer name version {name email web-page}
- alpha::package help name version [file 'name'|text]
Equivalent to alpha::mode, alpha::menu and alpha::extension
- alpha::package mode ...
- alpha::package menu ...
- alpha::package extension ...
For extensions only:
- alpha::package forget name version
Packages whose requirements fail cannot be activated in any of the
preferences dialogs, and are listed separately in the "Installed Packages"
help file, together with the reason that their requirements failed. The
user can then perhaps see that, say, the package would work if they
upgraded some component of Alpha, and may decide to do so.
Writing new features or extensions
An extension is a package which can be turned on once and then left alone.
Something which requires turning on/off for different modes is a feature. In fact an extension is just implemented as a simple form of
feature. A new extension must provide at the very least the following
formal line, preferably as the first non-comment line of one of its files:
alpha::extension 'NAME' 'VERSION'
Normally it is useful for the user to be able to choose whether to activate
an extension or not. In this case you must also provide a script for Alpha
to evaluate if the user chooses to activate the extension. Such a script is
given by a line of the form:
alpha::extension 'NAME' 'VERSION' 'SCRIPT'
If no such script argument is given, the extension is called auto-loading
and really just provides some new procedures which can be used by other code.
A feature is more sophisticated and takes arguments of the following form:
alpha::feature 'NAME' 'VERSION' 'LIST OF MODES/GLOBAL' \
'INIT SCRIPT' 'ACTIVATE SCRIPT' 'DEACTIVATE SCRIPT'
Here is an example of extension declaration from the package Bibtex Log Helper:
alpha::library marks 1.1 {
marks::initializePackage
}
Here we didn't bother to turn the feature on and off, since its initialization
was so trivial, and it won't interfere with other modes at all. Here's a more
complex example of a feature declaration:
alpha::feature latexMathbb 1.4 {TeX Bib} {
# Initialization script.
namespace eval TeX {}
# Make sure TeX mode preferences are available.
catch {latex.tcl}
set TeX::UseMathbb 0
newPref var blackboardBoldSymbols "NZQRCH" TeX {TeX::Mathbb::adjustBindings}
} {
# Activation script.
TeX::Mathbb::turnOnOff 1
} {
# Deactivation script.
TeX::Mathbb::turnOnOff 0
}
We didn't bother with activation / deactivation, since the definitions
don't take effect in other modes. The simple extension and feature commands make it very, very easy to extend Alpha's
functionality without messing with the user's preferences file, without
creating any '...+.tcl' extension files and without a complex installation
process. Alpha simply maintains a database of all extension
scripts, and evaluates at startup all scripts for extensions which the user
has activated.
Adding items to global menus
Packages can add menu items or submenus to global menus without further
bureaucracy. The techniques were described above in Section Adding menu items. This code should typically be placed in
the init script of the package declaration.
Writing new extensions (keyboard caveats)
Writers of any package for Alpha should pay some attention to the problems
which can arise with international keyboards. Some bindings are simply not
available on some keyboards. For instance, on some keyboards, you need to
use shift to get the key '\'
(unlike American keyboards
where it is a single keypress). On such a keyboard there is no distinction
between '⌘
' and '⇧⌘
'. There
is no simple workaround for this problem.
Possibilities are: (i) check the current keyboard definition and
adjust bindings appropriately (based upon user feedback, presumably). (ii)
let the bindings be user-definable either by using newPref binding
to define things, or by using a menu-scheme such as is used by HTML mode.
See more information on keyboard settings in the International Menus package.
[alpha::library] packages
Packages that are declared using [alpha::library]
provide extra functionality to AlphaTcl without requiring the user to turn
on/off a menu or feature. These packages are essentially early and
always-on features, so their activation scripts will be evaluated
when Alpha is first started, before the user's global features are
activated and before mode scripts are sourced.
This is most useful for packages which either (1) add different
preferences (or additional options for current preferences) that can be
used in other code, or (2) simply provide a library of Tcl code
which requires a minimal script to properly enable the user-interface.
The basic formal syntax is
alpha::library 'NAME' 'VERSION' 'SCRIPT' args
Additional arguments can include the standard help, maintainer, uninstall, and requirements. If the requirements script throws an error then the activation script will not be sourced
on startup. These packages should not include any preinit script.
For example, the various Version Control packages (such as
Svn, Git, Bazaar, Mercurial, CVS, etc.) add different options that the user
can set, so if the code is available and relevant to the user's platform, we
simply evaluate the package's script when Alpha is first started. Thus we
find this in vcMercurial.tcl :
# Feature declaration
alpha::library vcMercurial 0.1 {
hook::register vcsSupportPackages {vcs::mercurialSupport}
vcs::register Mercurial
;proc vcs::mercurialSupport {args} {
hook::register preOpeningHook mercurial::preOpeningHook
hook::register activateHook mercurial::activateHook
hook::register closeHook mercurial::properClose mercurial
}
# Create a "minor mode" for the windows under mercurial control
alpha::minormode "mercurial" +bindtags "mercurial" +hookmodes "mercurial" +featuremodes "mercurial"
} uninstall {
this-file
} maintainer {
...
The result here is that a new Version Control option named Mercurial is added to the VCS popup menu presented in the editAFileset dialog.
Important: the [alpha::library] package
declaration should _not_ be used to add new menu items or keyboard
shortcuts. It can create new preferences or define new support procedures,
but there should be _no_ impact on the user with regard to default
behaviors, editing or otherwise.
Note that the runAlphaTcl.tcl file declares
other packages in AlphaTcl to be early and/or always-on.
These lists are hard-wired, reserved for those packages deemed universally
useful, and only adjusted after discussion on one of the Alpha mailing lists.
Writing new menus
You may of course write your own menu for personal use.
In this section we use the word menu as meaning "package
whose added functionality is a menu". In the AlphaTcl library, these are
placed in the folder $HOME/Tcl/Menus.
The menu declaration contains a start-up section of much the same form as a
mode or feature:
alpha::menu ftpMenu 1.2 global Ftp {
# One-time initialization script
# here we do nothing
} {
# Activation script
# here we do the standard thing of calling the menu proc
ftpMenu
} {
# Deactivation script
}
# proc ftpMenu to auto-load
proc ftpMenu {} {}
The global parameter tells Alpha that this menu isn't associated
with any particular mode (otherwise you can replace global by a
list of modes possibly including the global keyword, e.g. {global WWW
HTML}).
Older versions of Alpha used to call a procedure with the same name
as the menu (here ftpMenu) automatically whenever the menu was to
be inserted. The newer setup is a bit more verbose, but puts more control
in your hands.
Note: if all you want to do is add a submenu to an already
existing menu, go to the section Adding items to global menus: you don't need the alpha::menu statement, but actually
need to write a feature using [alpha::feature].
A menu-package is a set of code which builds and handles a
standalone menu which the user may choose as a global menu. Examples are
the Ftp Menu, Fileset Menu, Mac Menu, User Menu, Color Menu and Mail Menu (in fact this last item,
since it has a mode associated with it, could in fact be rewritten as a
mode with attached menu).
Writing new modes
The mode is the basic editing environment which affects all users. Modes
do not require any activation on the part of the user. As long as they are
installed in AlphaTcl, they are always available.
Topic covered include:
Standard mode files
To add a mode to Alpha, a file (usually ending with Mode.tcl) must
be created and placed in in the $HOME/Tcl/Modes directory or in the
Modes subfolder of Alpha's Application Support Folder.
The Alpha ↣ Global Setup ↣ Create New Mode assistant provides interactive help to set up the basics of your mode.
It is highly recommended to use this assistant, but in most cases you'll
need to elaborate on the output, and you'll need to know how a mode
definition works, as described in this section.
The mode-definition file should begin with a call to the proc [alpha::mode]. This command has the following formal syntax:
alpha::mode mode version startupScript suffixes modeFeatures script
This proc is only designed to be called during package-indices rebuilding,
and its effect is to store the script and the parameters in Cache/index/mode. If called in any other circumstances the proc simply
returns. The reason for storing the script and the parameters is to make
the characteristics of the mode available to Alpha without having to source
the whole mode-definition file. This is important since a typical user will
not use more than a few out of Alpha's 70+ modes.
The arguments to [alpha::mode] are:
- mode
- a four-letter signature of the mode.
- version
- the version number of the mode.
- startupScript
- a script executed the first time Alpha switches to the mode
Typically this script will just be a dummy proc defined in the
mode-definition file, and the effect is simply to source the file.
- suffixes
- a list of file extensions that will trigger the mode.
- modeFeatures
- a list of features that will be activated by the mode.
- script
- a script that Alpha will execute at start-up. This part is very important.
It must contain everything that Alpha needs to know about the mode in
advance, i.e. before the mode is used the first time and the
mode-definition file is sourced. This script is stored in Cache/index/mode. Typically it contains declaration of a mode-specific
menu, and definition of interface name.
Here is a simple example from Pascal mode:
alpha::mode Pasc 1.1 source {*.p} {} {
# Script to execute at Alpha startup
set unixMode(pascal) {Pasc}
set ::mode::interfaceNames(Pasc) "Pascal"
}|))
The first time Alpha enters Pascal mode, AlphaTcl will create a ((i Pasc
i)) namespace and then the ((i startupScript i)) will be evaluated, in this
case the keyword ((i source i)) has the effect of evaluating the ((e
ah::link file pascalMode.tcl e)) file. Subsequent switches to Pascal mode
will not need to source the file and so the startup script is only ever
evaluated once. As a simpler alternative, you can use an empty dummy
procedure as a way of ensuring the file has been sourced once and once
only.
((nl)) The ((i suffixes i)) allow Alpha to automatically determine the
correct mode of a newly opened file. In this example, every file with file
extension ((i .p i)) will open in Pascal mode. There is another mechanism
for putting windows into the right mode, namely for so-called unix files,
for which the first line of the file may determine the mode. In this
example, the mode declaration says that every file with the string ((i
pascal i)) in its first line will open in Pascal mode. Note that this
declaration is in the ((i script i)) part: it has to be in this part
because clearly Alpha needs to know this rule before entering Pascal mode
for the first time!
((nl)) The ((i modeFeatures i)) argument does not involve any surprises in
this case: it is a list containing two menus to be activated whenever we
enter Pascal mode.
((nl)) In the ((i script i)) part, the second line is also worth an explanation: the line
((|
set ::mode::interfaceNames(Pasc) "Pascal"
tells Alpha to use the string Pascal instead of Pasc in the user
interface, for example in the modes popup menu. Again, it is clear that
this instruction must be placed in the script part so that Alpha can know
this in advance and display the mode correctly.
Important: Perhaps the MOST important part of the above code
is the existence of the startupScript. When this script is called,
the result must be that all of the mode's preferences are declared. In
other words, the startupScript should ensure that the Tcl code containing
all the newPref declarations is evaluated. This is very, very
important!
The normal way to do this is to have the startupScripts be a dummy
procedure: e.g. proc dummyPascal {} {} as above. Tcl's autoloading mechanism
will then source the file containing that procedure. Hence the dummy
procedure should normally be in the same file as the mode's newPref
declarations. This is important because almost directly after that call,
Alpha expects all of the mode's preferences to be stored in the
${mode}modeVars array, which will only be true if all of the newPref commands
have been evaluated.
Important: the result of calling dummyProc/startupScript must indeed be that all your newPref declarations are executed. As a
result of this, the preferences will be stored in the <mode>modeVars array, but will not yet be copied into the global scope (i.e. the <mode>modeVars(myPref) array element will exist, but the global var myPref will not yet exist). Once your dummyProc/startupScript returns
(which generally means your mode's initialization and sourcing of files is
complete), only then are the array entries copied into the global scope (in
the latter half of the changeMode proc).
Here is another simple example, from Perl mode:
alpha::mode Perl 3.7 source {*.pl *.ph *.pm} {
perlMenu
} {
# Script to execute at Alpha startup
addMenu perlMenu Perl
set unixMode(perl) Perl
# Make sure that we have a 'Perl::PrevScript' variable.
ensureset Perl::PrevScript {*startup*}
# Make sure that we have a 'PerlSearchPath' variable.
ensureset PerlSearchPath ""
}
In this case the script contains the command addMenu:
addMenu mname ?name? ?<pertains to modes>?
which defines a new menu, with name the visible name of the menu.
This menu can be used in any mode, although by default, it is only attached
to Perl mode. mname is actually a variable which contains (will
contain) the real menu name. The third argument usually contains a single
mode with which this menu is distributed. Its use is mainly so that Alpha
knows that this menu belongs primarily to this mode, so that if the user
asks for information on the menu, Alpha knows to respond with information
on the mode instead (curiously Alpha wouldn't otherwise know).
Here is an example from Diff mode:
alpha::mode Diff 1.0 diffMenu {*.diff} {diffMenu} {
addMenu diffMenu diff Diff
} uninstall {
file delete "$pkg_file"
file delete [file join ${HOME} Tools "GNU Diff"]
} maintainer { "John Doe" john@doe.example }
The uninstall and maintainer sections are optional, and explained later.
Here is a more complex example for Python mode:
# minimalist mode set-up #
alpha::mode Pyth 0.2 source {*.py *.pyc *.pyi} PythonMenu {
addMenu PythonMenu
#To set the mode from a unix-like "#!python" first line
set unixMode(python) {Pyth}
}
# dummy proc to load this mode.
proc dummyPython {} {}
# dummy proc to load the code to make the PythonMenu
proc PythonMenu {} {}
# rest of mode's code follows...
# Lets the automatic comment insertion/continuation
# routines function with this mode.
set Pyth::commentCharacters(General) "\#"
set Pyth::commentCharacters(Paragraph) [list "## " " ##" " # "]
set Pyth::commentCharacters(Box) [list "#" 1 "#" 1 "#" 3]
The package declaration should contain all code which is necessary to
recognize a given file as belonging to that mode (hence the use of unixMode for python), which will then make Alpha call the dummyProc
which will auto-load the entire file. Other information, such as the commentCharacters entries above should not go in the package
declaration.
Notice that there are two types of dummy proc; each menu
Alpha uses should have a proc of the same name associated with it. This
proc is called by Alpha _each_ time Alpha tries to insert the menu into the
menubar. The proc can be empty (as above), or could actually do something
if desired. The second kind of dummy proc is the mode dummy proc,
given in the [alpha::mode] command. Here it is called
dummyPython. Alpha calls this proc the first time it switches to
Pyth mode. Again the proc can do something if desired, but will usually be
empty. If both procs are empty, as above, one can of course just use one
proc (called PythonMenu in this case), and replace the [alpha::mode] line by:
alpha::mode Pyth 0.2 source {*.py *.pyc *.pyi} PythonMenu
The only advantage of this approach is that it saves a small amount of
memory (you can delete the dummyPython proc from the file). Note
that this only holds true for modes whose Tcl code is in one file.
Multi-file modes
Modes that consist of more than a single file should no longer use a source
statement that assumes that the other files for the mode will be in the $HOME/Tcl/Modes directory or in the Modes subfolder of Alpha's
Application Support Folder.
The best solution is to use Alpha's standard auto-loading capability
which will source a file when it needs a procedure which is contained in
that file. If you must use source manually, you can use 'file
dirname [procs::find someProcInThisFile]' to get the current directory.
Your other files should also be there.
A convenient way of implementing your multi-file loading is to
create procs with the same name as the file at the beginning of each file.
Then to load the file you just do catch "filename". For example if
there is a proc defined proc perlEngine.tcl {} {} at the start of the
file perlEngine.tcl, then I can auto-load that file by
having the following code in perlMode.tcl (note:
actual code differs slightly):
if {[catch perlEngine.tcl]} {
alertnote "Problem loading 'perlEngine.tcl'"
}
Remember, you don't necessarily need to source all your mode/pkg's files in
one go. Tcl is designed to source files for you when they are needed (when
a procedure contained in one of them is called). Hence you only need to
source files which are required immediately (to set up some data,
variables, menus etc.) and not everything else. It is usually best to have
a single file which contains all the initialization code, and let any other
files be auto-loaded as necessary.
Mode procs
The following procs are either required or desired for a mode to be fully
functional within Alpha.
Have a look at a standard mode like Tcl or C++ to see what some of these
should do. (In fact if you are writing your own mode, it is always helpful to
examine other modes and borrow from them.)
Marking procs
<mode>::MarkFile -w win
<mode>::parseFuncs
These provide indexes into code via the Parse Functions
and Marks
popups.
See the sections
below about The Marks Menu and
The Parse Functions Popup Menu). Note that,
for future compatibility if your mode makes use of the auto mark on
open flag, the MarkFile procedure should be able to mark windows which are
not the frontmost window.
Info providers (shift-command-double-click)
The following procedure may be used to provide access to source or file
related info:
<mode>::DblClick
It is invoked when the user shift-command-double-clicks inside the text
area. The proc takes two arguments representing the start and end positions
of the relevant region in the text pane. See examples in the AlphaTcl
library, for instance the proc [Tcl::DblClick].
Electric behaviour
These procs assist formatting and save on keystrokes.
<mode>::carriageReturn
This proc, supported by the following two, helps to keep indentation standard
(indirectly called by a carriage return).
<mode>::indentLine
This proc indents a line by inserting spaces/tabs as appropriate. If a 'correct
indentation' proc is given, then this procedure is not necessary.
<mode>::correctIndentation -w win pos {next ""}
This proc allows smart-paste package to function. This procedure must never throw an
error.
<mode>::electricLeft
<mode>::electricRight
<mode>::electricSemi
These procs provide electric behaviour for '{', '}', and ';' respectively.
Their use is primarily for languages that use '{' and '}' for code blocks,
and ';' as the line terminator. They are indirectly called by the key they
correspond to, and then, only if a corresponding mode preference flag has
been defined and set to 1. See the Electric Braces And Semicolon section below.
Smart first line
If your mode will want to be able to use the first line of a file to determine
what mode a file should be opened up in, you need to tell Alpha what word in
the first line should trigger that mode:
set unixMode(python) {Pyth}
A good place to do this is in the body of the your mode's package declaration
alpha::mode … {… statement (see example for Python above). Note that the
presence of the word itself is not sufficient; it must be of the form
#! /usr/bin/python as is common on Unix (where it tells the shell with what
application to run the script)
Note that there is already built in support for opening a file in a
given mode if the first line contains:
-*-<mode_label>-*-
e.g.:
# -*-Tcl-*-
See also the Smart first line section in
the Alpha Manual for more information about the editing settings that can
be specified in this magic first line.
unixMode array
When you open unix files where the first non-empty line reads
#!/dir/subdir/command ...
then Alpha tries to find a mode corresponding to command. With the
unixMode array, you can tell Alpha which mode to choose when
opening such a file. You may add to this array with lines like:
set unixMode(command) mode
where command is the lowercase version of command in the line
#! /dir/subdir/command ...
and mode is the mode which you want Alpha set for the window. For
example, to make Alpha open files with a line #! /usr/bin/perl ... in Perl mode use the line:
set unixMode(perl) Perl
modeCreator array
The modeCreator array allows you to tell Alpha which mode to choose
when you open a window depending on which application has created the file.
Add to this array with lines like:
set modeCreator(4_char_creator_code) mode_label
The 4_char_creator_code is the signature of the application which
has created the file and mode is the mode which you want Alpha set
for the window. For instance:
set modeCreator(ToyS) Scrp
set modeCreator(MOZB) HTML
Tip: To find the signature of an application, launch it, open the
Tcl Shell (⌘Y
) and
type ps at the prompt. This will list all running applications; the
second column lists the signatures. Click here to do this now.
A good place to declare a modeCreator entry is in the body
of the your mode's init script in the alpha::mode … {… statement.
Note: identifying applications by their 4-char creator code is
deprecated and this array is not of great use in AlphaTcl which now makes
use of bundle IDs.
Comment characters
If your mode will want to use the standard Alpha comment/uncomment block
procedures, file headers, ... you need to tell Alpha what characters are used
for comments. You should just define the following variables:
<mode>::commentCharacters(General) |
<mode>::commentCharacters(Paragraph) |
<mode>::commentCharacters(Box) |
For instance, here are the values for C++ mode:
set C++::commentCharacters(General) [list "*" "//"]
set C++::commentCharacters(Paragraph) [list "/* " " */" " * "]
set C++::commentCharacters(Box) [list "/*" 2 "*/" 2 "*" 3]
If you do this then there is no need to mess with the commentCharacters
procedure. (In general it is best if your mode does not need to redefine
procedures in Alpha's core).
If you want to over-ride the standard proc [::CommentLine] , then you can include a mode proc, as in the proc [C::CommentLine].
There are currently three kinds of comments: General, Paragraph,
and Box. Other kinds could also be added in the future, though as of this
writing these are the only three that are widely defined by modes in
AlphaTcl. They are not required. Some modes do not define Box
comments.
General purpose comment characters are used to check if we're in a comment
block, but are not used in the default Line/Paragraph/Box routines.
This General category could be a list, such as
set C++::commentCharacters(General) [list "*" "//"]
if there are several different characters that could indicate a comment.
Paragraph comment characters should be a list with three items: the beginning,
the end, and any character that should be used in between them. This is
most useful for modes that only use bracketed comments, but allows for
other modes to create Paragraph style comments like this block in
Tcl mode:
##
# some comment here
# continued...
##
obtained with the following setting:
set Tcl::commentCharacters(Paragraph) [list "## " " ##" " # "]
Box comment characters should be a list with six to eight items:
- Beginning Comment String
- Beginning Comment Length
- Ending Comment String
- Ending Commment Length
- Fill Character
- Space Offset
- String for Vertical Edges (optional)
- String for Top-Right and Bottom-Left Corners (optional)
Here's a typical example for a mode with a single comment character:
set Tcl::commentCharacters(Box) [list "#" 1 "#" 1 "#" 3]
Here's an example for a mode using bracketed characters:
set yacc::commentCharacters(Box) [list "/*" 2 "*/" 2 "*" 3]
If item (7) is not specified, it is equal to (5). Here are examples with 7
items:
set HTML::commentCharacters(Box) [list "<!--" 4 "-->" 3 "-" 3 "|"]
set Aida::commentCharacters(Box) {"!!" 2 "!!" 2 "!" 3 "!!"}
If item (8) is not specified, it is equal to (7). Here is an example
with 8 items:
set Text::commentCharacters(Box) {"+" 1 "+" 1 "-" 3 "|" "+"}
Experiment with the Text ↣ Comment Box menu
item to determine correct values, and in particular how different Space
Offset values change the construction of the box. The menu item Text ↣ Uncomment Box (obtained by pressing the
Option key while the Text menu is opened) lets you remove a
box.
Note: and yes, it seems that the Comment Length
values could be pretty easily computed using [string length], but there are tricky exceptions. For instance, in Lex/Flex mode we have:
set lex::commentCharacters(Box) [list "%{\n/*" 2 "*/\n%}" 2 "*" 3]
with Comment Length equal to 2 and not 5.
Paragraph definitions
To customize your mode's paragraph filling, you can set the following
variables:
<mode>::startPara |
<mode>::endPara |
For instance:
set Tcl::startPara {^(.*\{)?[ \t]*$}
set Tcl::endPara {^(.*\})?[ \t]*$}
The above example's regular expressions (third word) are for Tcl code.
Alpha uses these to determine what it should act on when it is
requested to re-wrap, make a selection or navigate with respect to
paragraphs.
Note that currently the wrapping routines take no notice of code
formatting rules and are limited utility outside of the Text mode.
The only proc's that use these are found in textFill.tcl.
Indent On Return
The Indent On Return mode preference allows pressing Return
to indent
correctly for the following line so you may begin typing immediately. To use
this, simply include this preference as a default mode preference using
something like
newPref flag indentOnReturn 1 modeName
but do not bind to the Return
key.
If you want to modify the behavior of Indent On Return for
your mode, then define a <mode>::carriageReturn proc, as in the
proc [Tcl::carriageReturn].
Automatic indentation
Two variables are associated with a window's indentation scheme: indentationAmount and the window's tab-size (which can be read with the
commmand [getWinInfo] or the proc [text::getTabSize]). If you are writing a custom indentation routine, the
proc [indent::setup] will be useful to handle all these
choices for you. Look at the relevant section of globals.tcl to see what that procedure sets up for you.
Most people use either
tab-size = 4, indentation-amount = 4 spaces
OR
tab-size = 8, indentation-amount = 4 spaces
These cases are quite different, and it's nice if your mode allows the user to
work with their preferred setup.
Electrics
Throughout the documentation and in actual proc names, you will see the use
of the word electric, so a note on its usage might be helpful. Electric
is used in the sense of automatic, power-assisted behaviour, it is intended
to save time, keystrokes, and brainpower. Such behaviour is usually invoked
by certain keystrokes (determined by various preference settings).
See the Electrics Preferences panel.
Electric code templates
If your mode wants to insert text into the window which contains template stops
(usually bullets '•' in Alpha), so that the user can move from one to the next
using the standard Alpha template packages (Alpha comes with a basic one, and
more sophisticated ones build upon the same infrastructure), then you should
insert template text with:
elec::Insertion "blah blah •• blah blah"
This is a simple example with a single template stop. Template stops are
noted programmatically with a pair of bullets (even though only one
appears in the text). You can place between the pair of bullets some more
information about the template stop, for instance:
elec::Insertion "while \{•condition•\} \{\r\t•while body•\r\}\r••"
would be useful to insert a typical Tcl [while] loop.
The template packages can prompt the user with the explanatory text making
code entry a little bit easier.
The elec::Insertion routine works just like [text insert] except it treats any item •PROMPT• as a template
stop called PROMPT. This procedure takes a variable number of
arguments, just like [text insert]. It has one further
side-effect. If there are any stops in the block, then the cursor is
positioned at the first such stop. Hence you don't need to do this: set
p [getPos] ; text insert "blah..." ; goto $p ;
elec::nextStop. Instead you
just do 'elec::Insert "blah..."'.
Use elec::nextStop, elec::prevStop etc. to move
amongst tab stops. The basic Alpha distribution setup includes only basic
template support. Activate the package Better Templates
to extend this support to persistent stops, with user-prompting in the text
or status bar, as well as other enhancements. You don't have to change your
code to take advantage of the features of Better Templates. It
comes for free if you use elec::Insertion etc.
Electric braces and semicolon
If your mode wants to use electric '{', '}', ';' (i.e. characters that end the
current line and indent the next one automatically), you need to define a few
procedures named
${mode}::electricLeft |
${mode}::electricRight |
${mode}::electricSemi |
which will be called automatically. You do NOT need to bind anything to the
keys. Alpha will automatically call your mode's procedures if (1) the
preferences are in place, (2) they remain turned on by the user, and (3) the
mode procs are named correctly. If you do not define these procedures, (but
conditions (1) and (2) are met), then Alpha will use a default electric
procedure which works pretty well for C, Perl and Java code:
Completions
If your mode is to use a variety of completion routines, define an array entry
like this:
set completions(${mode}) \
{completion::cmd completion::electric completion::word}
For the meaning of the list items, look at elecCompletion.tcl. If all you need is the basic Command, Electric and Word completion routines, the above list will do
the trick. You will then need to define a variable ${mode}cmds like
the following one (which is in C mode):
set Ccmds { #elseif #endif #include class default enum for register return
struct switch typedef volatile while }
It MUST be in alphabetical order. For electric template insertions,
you need to create an array with entries like these (again taken from C
mode):
set Celectrics(for) " (•init•;•test•;•increment•)\{\n\t•loop body•\n\}\n••"
set Celectrics(while) " (•test•)\{\n\t•loop body•\n\}\n••"
set Celectrics(switch) " (•value•)\{\n…case •item•:\n\t•case body•\n…default:\n\t•default body•\n\}\n••"
set Celectrics(case) " •item•:\n…•case body•\ncase"
Mode-specific completions
If your mode has its own completion routines, they must be named ${mode}::Completion::Type, where Type is an entry in the above
list. You'll have to know a reasonable bit of Tcl to write your own
routines like that. Look at the proc [C::Completion::Class] for a relatively simple example, or see the BibCompletions.tcl file.
<mode>Completions.tcl
Each mode can have a completions file full of defined electrics that will
be used by the package Elec Completions. To use this,
place the appropriate definitions in a file called <mode>Completions.tcl. Such files are located in the $HOME/Tcl/Completions directory or in the Completions subfolder
of Alpha's Application Support Folder and they will also be sourced
automatically the first time a file opens in your mode. There is therefore
no need for you to source the file yourself.
Electric Menu templates
The variable ${mode}Templates is a list of names which are added to
the electric menu's Templates sub-menu. The real procs should be
called file::${name}.
For instance, C modes defines:
lunion "CTemplates" createNewClass newFunction
which make use of the procs [file::newFunction]
and [file::createNewClass].
The Marks popup menu
Each mode has a procedure <mode>::MarkFile which is called to
create the contents of the Marks popup menu. Just what text-patterns are used to
trigger the formation of a namedMark, its name, text position and
extent, and the order in which they are present in the menu, is all
determined by the <mode>::MarkFile your procedure.
See the proc [Igor::MarkFile] for an example.
For computer language editing modes, the common convention was to
create an index by routine names for each routine defined in the file, and
to present it in alphabetical order. The more current convention is to
either hardwire, or present the user with the option of listing the routine
names in the order in which they were defined in the file, indented under
the name of the code section in which it was defined.
The Tcl mode proc [Tcl::MarkFile] is a good
example of the above -- by default the defined Tcl proc's are presented in
alphabetical order. However if you check the Stuctural Marks flag
in the Editing panel of the Tcl Mode Preferences
preferences, you get an index with the above format (after regenerating the
index via the Mark Window menu item). If you organize your Tcl code
into sections of logically or functionally related proc's, and then give
them a short header by using the Insert Divider option under the
Tcl Editing submenu, you have an index that can be used to quickly
get to a procedure when remember its position or functionality more than
you do its exact name.
Of course, it is still often the case that you remember the name and
just want to get to it quickly via an alphabetical index, so modes that use
the above scheme usually provide an alphabetical listing via the Parse
Functions popup, which is located right beside the marks popup (see
next topic).
The Parse Functions popup menu
The Parse Functions
popup menu can be put to whatever use the mode author wants. If your are
using the scheme mentioned in the above section, it is good to use this
popup to present an alphabetical listing of the routines. Some modes add
extras such as indicating the number of arguments a routine expects (see
Tcl mode), whether an argument is a reference or a value
pass (see the M2, i.e. modula mode) or anything else that might be useful.
Languages that use multi-part (qualified) identifiers may name the first
part of a group of identifiers and indent the rest of the identifiers that
share that first part under it.
There is an automated mechanism to generate the contents of this
popup menu. For this, Alpha requires two regular expressions, one which
will match a pattern containing something which needs marking, and the
other which will take the matched text and extract, or parse, as the first
bracketed '()' subpattern, the name of the item (i.e. the section or
function name). If you need help with this, please ask on the Alpha's Mailing Lists.
These regular expressions are specified as mode preferences named
respectively funcExpr and parseExpr. For example, man mode uses ^\.de [^\r\n\t]+
and \.de ([^\r\n\t]+)
,
where the first pattern matches a block of text starting with '.de '
and continuing up to the next new-line or tab character (this is what a
function definition looks like in that mode), and the second pattern
extracts from that block of text, as the first bracketed expression, the
actual sub-string to use as the function name, in this case everything
after the '.de '
.
Power user tip: the key combo ⌥⌘K
will put up a listpick dialog of the indexes under the '{}' popup, as this
is usually alphabetical you can type the starting letters of the index you
want to go to. (note: see Emacs Help for some other
tips to navigate any scrolling list dialog box.)
Package preferences
AlphaTcl includes hundreds of different preferences that allow the user to
modify the program's behavior without first being an AlphaTcl programmer.
While preference bloat is always a problem, a well-defined preference
provides a balance between flexibility and the learning curve, and sets a
default value that is at least intuitive but not intrusive.
Topics covered include:
Preference types
Alpha stores preferences in three different places:
- Global preferences are set in the Alpha ↣ Preferences menus, and are
for variables/flags which maintain a value at the global scope.
- Mode prefs are set in the Alpha ↣ Mode Setup ↣ Mode Preferences items, and
are for variables/flags which are stored in a mode array, but are
transferred into global scope when that mode is active (and hence
temporarily override any global preferences with the same names)
- Packages may add to the global/mode preferences as they desire. They may
also store preferences in their package array ${pkg}modeVars(…). Such
variables/flags are never transferred into the global scope. Menu items to
edit a package's preferences should be placed in the global menu, unless
they are global/mode prefs which should be added to Alpha's default
routines for use by the standard Alpha dialogs.
There is a standard proc
package::addPrefsDialog which you can use to add a panel to the
standard dialog to edit the contents of your ${pkg}modeVars(…)
array.
Any editing of preferences using the standard dialogs code in AlphaTcl
will automatically save any changes when Alpha quits and they will be
reloaded the next time Alpha starts up. If you need to make manual changes
to preferences (or, indeed, any Tcl variable or array), pass the changed
variable name to prefs::modified, for example:
prefs::modified myPrefName
prefs::modified myPrefArray
prefs::modified myPrefArray(justThisElement)
You can pass a variable name, an array name, or just one element of an
array. This registers the corresponding variables for saving: actual
saving occurs when the application quits. To save them immediately, you
may use the prefs::saveModified proc.
Adding to the core prefs dialogs
If you wish to add items to any of the core preferences pages (Backups,
Electric, Miscellaneous,...), you can do that like this:
lunion varPrefs(Electric) var1 var2
lunion flagPrefs(Electric) flag1 flag2
All non-registered global preferences are added to the Other Packages page, so there is no need to do that automatically. Make sure you don't
add too much to any of these pages, because they will become too large to
display correctly!
You can also add new core preferences pages. All you have to do is
create a new flagPrefs entry (Alpha uses the command array
names flagPrefs to list the different pages):
lunion flagPrefs(NewPage) flag1
Only add such pages if your package really does merit it; otherwise you're
better off just add a new global preferences dialog in the global menu.
Defining a package's flags and variables
Preferences for a mode or package are defined formally as follows:
# description of the preference
newPref type name {val 0} {pkg "global"} {pname ""} \
{options ""} {subopt ""}
The meaning of the arguments is as follows:
- type designates the kind of preference. The possible values
are explained in the table below.
- name is the variable's name, i-e the name of the variable
containing the preference
- val is its default value (which will be ignored if the variable
already has a value)
- pkg is either global to mean a global preference, or the name
of the mode or package (no spaces) for which this is a preference.
- pname is a callback procedure to invoke if this preference is
changed by the user (no need to setup a trace). This proc is only called
for changes made through prefs dialogs or prefs menus created by Alpha's
core procs. Other changes are not traced.
The main possible types of preferences are
app | an application's ID (declared with [app::registerID]) |
binding | key-combo |
color | a color name |
file | input only |
flag | on/off only |
folder | a folder path |
font | a font specification |
geometry | a list of form {x y w h} |
io-file | either input or output |
menubinding | key-combo which works in a menu |
rgb | a color triplet {R G B} |
searchpath | list of paths |
url | an URL string |
variable | anything |
This list is not exhaustive, new types may be introduced in the future.
There are also three special types:
- preferences whose name start with link (like linkflag,
linkfont,
linkrgb,
linkvariable) designate variables
which are coupled with some C variable in the core of the application. Any
time the value of this preference is changed in AlphaTcl, the
corresponding C variable is changed accordingly, and vice versa. This kind
of variables are known as linkvars.
- preferences whose name start with core (like corevariable,
coreflag) designate preferences
which are handled by the core as user defaults. Their value is stored or
read by the core itself. They also are linkvars.
- preferences whose name start with early (like earlyfolder ) designate preferences
whose value has been set earlier during the startup process and must be
preserved.
Variables whose name ends in
App |
Color |
FilePaths |
Folder |
Font |
Mode |
Path |
SearchPath |
URL |
(case matters here) are treated differently by the GUI, but are still
considered of type variable. The proc newPref
automatically does the mapping for you. The declared type is the same as
the suffix but in lower case. For instance:
newPref color functionColor magenta C
Depending on the previous values, there are two optional arguments
(called options and subopt) with the
following uses (depending on the type of the preference):
- variable:
- options is a list of items from which this preference takes a
single item. subopt is any of item, index, varitem or varindex or array, where item
indicates the pref is simply an item from the given list of items, index indicates it is an index into that list, and var*
indicates items is in fact the name of a global variable which
contains the list. array means take one of the values from an
array. If no value is given, item is the default
- binding:
- options is the name of a proc to which this item should be bound. If
options = '1', then we bind to the proc with the same name as this variable.
Otherwise we do not perform automatic bindings.
subopt indicates whether the binding is mode-specific or global. It should
either be global or the name of a mode. If not given, it defaults to
global for all non-modes, and to mode-specific for all packages. (Alpha
tests if something is a mode by the existence of modeMenus($mode))
- menubinding:
- menubindings are like bindings, but they don't have any automatic binding
capabilities, and are restricted to key-sequences which the MacOS allows in
menus.
Declaring help text
Alpha has the ability to extract descriptive text for your preference items
automatically, provided they are declared using newPref, and that you follow
these guidelines.
If there is a comment (a line starting with '#') on the line/lines
preceding the newPref command, Alpha will (when it rebuilds the
package indices) store the text in that line/lines and use it to display
helpful information for that preference. For example if you hit the help button in a dialog, Alpha will display this information. Note that
if the preference is simply overriding one of the built-in global values
(lineWrap, commentContinuation, etc), then there is no need
to provide a comment. AlphaTcl's core already contains good descriptions
which will be used automatically (but if your mode does provide a comment
it will over-ride the core's description).
This information is also used for tip windows in the prefs dialog
related to your package/mode, provided you use the standard mechanisms for
declaring your mode/menu/feature/extension and you use the standard
preference mechanism supplied. The format of the comment lines is simple
for all except basic flags (newPref flag ...). These will display a
different help text in tips depending on their state. There are four
possible states, although Alpha only really uses the first and third such
states at present. The first state is the unchecked state, and the
third the checked state. You declare separate help text for the
four possible states like this:
# it is unchecked|it is dimmed|it is checked|it is checked and dimmed
newPref flag myFlag ...
In fact all help items have four possible states, although you will usually
not notice the other possibilities. As you can see, Alpha uses '|' to
separate the different pieces of text. Currently a typical help text for a
checkbox item should probably just look like this:
# To use a solid rectangular cursor, click this box||To use a thin
# vertical cursor, click this box.
newPref flag blockCursor 0
Notice the syntax of the two messages. Apple's interface guidelines give
some advice for tips which you should follow for two reasons:
first, it's good advice for writing tips, and second, Alpha assumes
your messages are of the above form to use the text effectively both for
tips, and for descriptive text. Alpha will automatically convert the
above to:
Block Cursor: To use a solid rectangular cursor, turn this item on. To
use a thin vertical cursor, turn this item off.
which is used in the descriptive dialogs Alpha sometimes provides. This
advice is of greatest importance for flag preference items, since
they require two separate on/off tip help texts. Other items currently just
expect one piece of text. Each text item should be no longer than 255
characters. The simplest tip methods impose this constraint.
A similar mechanism is also available for menus and packages, using
a help argument in the package declaration. If this help argument
looks like this:
alpha::menu filtersMenu 3.1 global Filters {
addMode Fltr filtersMenuTcl {*.flt} {}
package::addPrefsDialog filtersMenu
} {
# Activation script.
...
} {
# Deactivation script.
...
} help {
file "Filters Help"
}
then the proc [package::helpWindow] will send the name of the specified file to
the proc [help::openFile] where it will be dealt with appropriately.
See the Doc Files Help file for more information on Alpha's Help file
colorizing/marking/etc syntax that is used to create files like this one.
If a full help text or help file is not provided, otherwise, a tip
will be created using the || etc. syntax described above, as in
alpha::feature colorPrefs 0.2 "global-only" {
# Initialization script.
...
} {
menu::insertSubmenu ...
} {
menu::removeItems ...
} help {
This package provides support for coloring of text in Alpha windows||
This package provides support for coloring of text in Alpha windows||
Alpha supports automatic coloring of the text. The way Alpha colors the
text depends on the current mode.
In this case the fifth || clause is used by the proc [package::helpWindow]
while the first four are used for tip help.
See the Menus Preferences dialog and the Features Preferences dialog for examples of tip windows (just hover over items with the
mouse).
Installation and package requirements
For your private use, most likely you have already found a good place to keep
your Tcl files so that both Alpha and yourself can find them.
In order to distribute your package to other users, the easiest is to take
advantage of the installation facility provided, as described in this
section. There are also a couple of words about licensing issues and coding
standards, which should be considered if your code is going to be set free on
the internet.
tclIndex files
Standard Tcl uses tclIndex files to find procedures which are
called but not yet defined. Your installation directories may contain index
files if you desire, but they are only installed if no current index file
exists in the installation location. You cannot override this behaviour.
Uninstalling packages
Each package should provide an uninstall argument in its package
declaration, as in
alpha::extension developerUtilities 1.1 {
# declaration script
} uninstall {
# uninstall script
...
}
For a single file package, the following is normal:
} uninstall {
this-file
}
If the uninstall argument is this-directory, then that
entire file's directory is removed. Make sure you don't use uninstall
this-directory for a single-file package, or you'll wipe out the entire
package hierarchy. All alpha::<packageDeclaration> procs (like [alpha::mode], [alpha::menu] and
friends) may contain an optional uninstall script like the above.
Disabling packages
A feature can add a script to be evaluated when the user disables
the package. This is the 6th argument for the proc [alpha::feature]. Note that extensions do NOT have deactivation
scripts, since these types of packages are intended to be turned on
globally and add some extra functionality. If the extension adds a submenu
or performs some other operation that needs to be undone when it is
turned off, then make it a global-only feature instead. See the smarterSource.tcl file for an example.
Licensing issues
Obviously if you want to allow your scripts to be distributed with Alpha,
you need to license it under a compatible license agreement -- one that
allows free redistribution of your code. The most obvious choices are a Tcl/Tk/BSD style license or a GNU style license, although other
licenses are possible.
If your Tcl code is of use with Tcl/Tk in general (i.e. not
restricted to being used inside Alpha), then you are strongly urged to use
a Tcl-compatible license, not a GNU license, since that is the norm in the
Tcl community, and is also adopted by most of AlphaTcl.
Coding standards (tab sizes etc)
It is of course appreciated if contributed code follow the coding style of
AlphaTcl, which is rather generic. Rather than being a question of strict
rules, this is mostly a matter of mimicking the style perceived in the
existing files, with the obvious variations.
The most important guidelines are
'DOCUMENT YOUR CODE'
providing exact specifications in comments before each proc definition, as well as
helpful explanations of tricky points inside the body of the proc. It is
preferable if variable names have sufficient human-language (in fact,
English) content, so that any reader can guess their meaning from their name.
Although Tcl is very flexible with variable and command names, it is strongly
encouraged to use only plain ascii letters, numbers, and underscore.
On the other hand, syntactic sugar like the word then in [if] clauses is generally not appreciated as helpful.
The core AlphaTcl library generally uses a tabsize of 8 but a visual
indentation of 4 (this is the default for Alpha, and also the standard for
Emacs and a lot of open-source projects in the unix world). If you are
writing standalone packages for distribution with AlphaTcl there is no need to
abide by that convention, but individual procedures contributed to AlphaTcl
files will be (re)formatted according to that convention before being applied.
Note that AlphaTcl contains facilities for individual files or filesets to
specify their default tabsize, so Alpha can quite happily be made to work
simultaneously on many different projects using different tabsizes.
Further technologies (miscellaneous extras)
This section, perhaps most useful to new modes, explains how to make a
mode/menu/extension aware of and take advantage of certain existing packages
and technologies. We cover the packages
and mention also
- xserv and pipes
- console attributes
- temporary files
- history List
Providing customisable menu shortcuts
This section gives information about user menu shortcuts support in the
AlphaTcl packages. The information below is intended for developers who
write a new menu for Alpha and want to offer the possibility of modifying
the keyboard shortcuts associated with the items contained in this menu or
its submenus.
Starting with version 8.2, Alpha provides a per-package interface to
let users assign new shortcuts to menu items. Each package can decide which
of its menus or submenus are thus configureable, it can install its own
interface or rely on another package's interface, it can restrict the list
of configureable items. All these possibilities are detailed in the
following sections.
The user shortcuts API
Installing support for configureable menu item shortcuts is very simple.
It involves two tasks:
- the package must provide an item to expose this service in one of its
menus or submenus. The item could be named Menu Shortcuts, or
Assign Menu Shortcuts, or whatever is deemed appropriate. This
item should trigger the proc: [prefs::dialogs::menuShortcuts] which is
explained below.
- the package should invoke the proc: [menu::setUserShortcuts] in its activation script in order to
automatically load user defined shortcuts the next time Alpha is launched,
or, on the contrary, to remove the bindings when the package is
deactivated.
This is all there is to it.
The [prefs::dialogs::menuShortcuts] proc expects
two or three arguments:
- an array associating menu names with menu tokens. The menu names are
the names displayed in the interface: they do not have necessarily to be
the real title or name of the menu (some menus anyway do not have a name or
a title, but an icon instead). This is where one can decide which menus or
submenus will be exposed since the package is free to build the array as it
wishes: if you want to exclude a menu or a submenu, just don't put it in
the array.
- a name to identify the package. This does not have necessarily to be
the name used in the declaration of the package. This name is used to store
the user defined shortcuts between sessions and is displayed in the title
bar of the dialog in prettified form.
- additionnally an optional third argument can be passed to specify a
mode: in that case all the bindings defined by the menu shortcuts will be
mode-specific bindings rather than global bindings.
The [menu::setUserShortcuts] proc has a first argument
with value on or off in order to respectively enable or disable the user
defined shortcuts. The remaining arguments are exactly the same as with the
proc [prefs::dialogs::menuShortcuts] the same array,
the same package name and the same optional mode.
There is a handy proc [menu::handleUserShortcuts] which is a wrapper around the previous procs. Its first argument is a
subcommand amongst get, set, install and uninstall. The other arguments are the package name, the name of the
array and optionnally the mode. Most packages make use of this proc to
handle the user menu shortcuts.
There are many examples of this mechanism in the AlphaTcl library
which can serve as sample code. See for example the Filters Menu package.
This package defines a proc called flt::doMenuShortcuts like this:
proc flt::doMenuShortcuts {action} {
variable menuToken
set tmpArray(Main\ menu) $menuToken(main)
set tmpArray(Utils\ submenu) $menuToken(utils)
menu::handleUserShortcuts $action "FiltersMenu" tmpArray
}
Then all it has to do is:
- in its activation script, it calls
flt::doMenuShortcuts install
- in its deactivation script, it calls
flt::doMenuShortcuts uninstall
- in its menu building proc, it defines an item called Menu
Shortcuts which triggers the following command:
flt::doMenuShortcuts set
That's all there is to it.
The menu shortcuts hook
Some packages which bring extra functionalities to Alpha insert a small
submenu in one of the basic submenus. In order not to clutter all the
submenus with spurious commands to set menu shortcuts, it is also possible
for a package to have its user-defined menu shortcuts managed by a parent package. For instance packages such as Window Utilities,
Speech, Spelling, Redefine Colors would have their
shortcuts managed by the AlphaTcl library itself. This means that the
submenus inserted by these packages would be accessed via the same
interface as the other basic menus: from the user's point of view, this
will feel very natural since there is no reason to make a distinction
between submenus.
Similarly one can imagine a package adding extra functionalities to
a major mode menu: such a package would like its submenu to be managed,
regarding shortcuts, by the same interface as the rest of the menu.
In order to achieve this, a package just has to register with the
parent package using the [menu::shortcutsHook] proc.
This proc is invoked during the activation or deactivation of the package
in order to register or unregister respectively. Its arguments specify the
package's name, the parent's name and a callback procedure which will be
invoked by the parent package in order to get the names and the tokens of
the submenus it has to manage. For instance here is how the Spellcheck
package would register:
menu::shortcutsHook register spellcheck AlphaTcl spell::shortcutMenuTokens
To unregister, the instruction is simply
menu::shortcutsHook deregister spellcheck
The registered callback (spell::shortcutMenuTokens in the example
above) is a proc which takes no argument and which returns a list with an
even number of items of the form:
menuName menuToken...
This is a list containing alternately a menu name (which is displayed in
the interface dialog) and the corresponding menu token. This is the format
expected by the [array get] Tcl command in order to
load a list in an array.
Note that there is no obligation to use this hook mechanism: many
packages which insert a submenu in one of the basic menus still use a
standalone interface as described in the previous section. This is the case
of Columns Manipulation, Notes, Favorites, Function Comments, Window Lines, etc. for instance.
Partial menu lists
There is another hook which lets a package expose only certain items of its
menus or submenus to the menu shortcuts interface. This can be useful for
menus which display changing data: this is typically the case of the Open Recent submenu or the Windows menu. There is no point to
attach a menu shortcut to items which are constantly updated and modified.
Only the persistent items in these menus should be exposed.
In order to achieve this, the package must register a proc with the
shortcutMenuItems hook. The hook is registered during activation and
unregistered during deactivation. Here is how the Recent Files package
registers:
hook::register shortcutMenuItems recentmulti::listItemsForShortcuts "Open Recent"
This instruction simply registers a callback (named recentmulti::listItemsForShortcuts in the previous example) for the Open Recent submenu. When the callback is invoked, it receives three
arguments: the menu name, the associated menu token and the package's name.
It should return the list of items it wants to be exposed in the user
shortcuts interface.
Source-Header files
If your mode makes distinctions between Source and Header
files, you should define these two variables sourceSuffices and headerSuffices like in the following example:
newPref var sourceSuffices { .cc .cp .cpp .c .icc } C++
newPref var headerSuffices { .h .hh } C++
They are used by the [file::sourceHeaderToggle] proc
which is bound to ⌘-F2
when the Search Paths package is active.
Contextual Menu help
AlphaTcl implements the contextual menu in the package Contextual Menu. The contextual menu is accessed using the right mouse
button or Ctrl-click. This menu is mode specific, and the context
can also include the location of the current window, any surrounding files,
any text surrounding the cursor position, etc.
What follows below is possibly more information than you want to
know about how the contextual menu is created in Alpha -- see the contextualMenu.tcl file for information about how to
add CM modules for your mode or package.
Alpha's core detects control-clicks in the text area of document
windows (or any text pane if the window is split). It then invokes the
contextualMenuHook passing two arguments: the position in the text
corresponding to the location of the mouse click and the full name of the
window. Alpha expects this hook to return the contextual menu, entirely
rebuilt.
To rebuild the contextual menu, the contextualMenuHook does
hook::callAll to invoke any registered CM hooks. It'll pass, via a
global variable called alpha::CMArgs a list of four values: the
position, the start and end positions of the current selection and the
window. If there is no selection, the three first values are equal.
The contextual menu is a built-in menu created by the core with
token contextual. Using this token, you can use the commands [menuRef] and [menuItem] as you would do
with any other menu in Alpha.
Basically the contextual menu is divided in four sections, each separated by a
divider:
- The first contains window specific modules defined by this package,
such as the current path (similar to the title bar popup), and the current
marks.
- The second is for mode-specific modules. It is not always present,
depending on the availability of modules for the current mode.
- The third includes global contextual menu modules defined by other
packages in AlphaTcl.
- The fourth section includes more window specific modules defined
in this package that probably won't be accessed very often, such as mode,
format, and wrap. The contextual menu Customize item is always
inserted at the end of the menu.
Information on how a package (mode or feature) can create contextual menu
plug-ins is given in the Information for developers section of the Contextual Menu Help file.
Xserv and pipes
If your mode or feature involves communication with some external process
or helper application, you should, when possible, take advantage of Alpha's
external-applications API called xserv. By bundling the interaction
into an xservice, you ensure that it integrates with the Helper
Applications prefs panes, and is made available also for related features,
and that it does not double existing functionality.
Xserv is documented in great detail in Xserv Help and Xserv Guide.
For more fine-grained two-way communication with a unix program, the
xserv interface might not be sufficiently flexible. In this case it
may be necessary to write the interaction from scratch using pipes, and
then declaring the whole interface as an xservice. There are many
predefined procs to help with this, found in appPath.tcl.
For advanced examples in AlphaTcl, see the next sections which
explain the resources available to create consoles communicating with a
unix tool. See also TeX mode which defines its own console in tetexComm.tcl.
Console attributes and 'strict console'
If your package uses console windows to display feedback from the program,
you may take advantage of the functionality in consoleAttributes.tcl: this allows console windows to maintain
their characteristics, such as window geometry, font size, command history,
even when the window is closed, so that the user can adjust to his liking
without having to do this every time the console is opened. It is very
easy: you declare the console once and for all using
console::create "* My Console *" -mode Shel -g 100 100 100 100 \
-font Monaco -fontsize 9
and then invoke it by
console::open "* My Console *"
For details, see the documentation in the file consoleAttributes.tcl.
There is a further package called strictConsole.tcl which attempts to simulate the behaviour found in
many unix shells where there is a prompt, and where only the text situated
after the last prompt can be edited. This package takes care of everything
related to the prompt, including command history.
For an example which illustrates these technologies, see the
implementation of most VCS modules related to the Version Control package. This is used notably by
the Svn, Git, Mercurial, Bazaar, Fossil and Monotone consoles. The corresponding API is defined in vcsConsole.tcl.
Interactive console
The interactive console is a strict console which installs a pipe and the
necessary callbacks so that the user can execute commands at the prompt of
a command line and get the result exactly like in a shell window or any
interactive unix tool, and then execute another command, and so on. It is
used in particuler by the Bc, Coq, Gnuplot, Julia, Maxima, Matlab, Octave and R
consoles. See the file Consoles Help for more
information.
Client packages just have to declare the console with
console::interactive::register and to open it with
console::interactive::open. The important arguments to specify (besides
the usual arguments supported by consoles as explained in
consoleAttributes.tcl) are:
- -prog
- the command line tool you want to communicate with
- -progOpts
- options for the program
- -shelltype
- this argument is required: this is the namespace in which
some callbacks may be defined, like <type>::Prompt
If -prog is specified, it designates the path of the command line
tool that the console wants to establish its connection with. If it is not
specified, the console gets the path by invoking a proc named ${type}::ensureProg where ${type} is the value of the -shelltype option. In that case, the client is responsible for
providing this proc.
If -progOpts is specified, the console inserts these program
options in the opened pipe. For instance, if the value is $opts
, the pipe
will be |prog $opts 2>@1
, instead of just |prog 2>@1
by
default.
Other more advanced (and usually unnecessary) options are explained in
the next paragraphs.
To send a command to the program, the console invokes by default console::interactive::readlineProc defined in this file. A client may
declare, for special purposes, its own readlineProc proc via the
-readlineProc option in console::interactive::register.
Similarly, to receive the data sent back by the program, the console
invokes by default console::interactive::writeResult defined in
this file. A client may declare, for special purposes, its own writeResult proc via the -writeResult option in console::interactive::register.
The readlineProc makes sure that we have a valid pipe connection to the
program. This is accomplished by the proc console::interactive::ensurePipe
defined in this file. A client may declare, for special purposes, its own
ensurePipe proc via the -ensurePipe option in console::interactive::register.
Client packages should define a proc <type>::Prompt (where
<type> is the value declared via the -shelltype argument)
to return the prompt string at the beginning of command lines in the
console. If this proc is undefined, no prompt is written. If the prompt is
never going to change, it is also possible to declare it once for all via
the -prompt argument. In that case the <type>::Prompt proc is not
necessary.
Example:
here is schematically how the bc mode
declares its console to communicate with the bc Unix tool (basic console, the arbitrary precision numeric processing language):
set prog /usr/bin/bc
console::interactive::register "* Bc Console *" -shelltype bc \
-mode bc -hookmodes [list bc Shel] \
-startuptext "Welcome to Alpha's Bc console.\n" \
-prog $prog -progOpts "-l"
Then the console can be opened with
console::interactive::open "* Bc Console *"
and that's all there is to it!
Temporary files
AlphaTcl has some facilities for working with temporary files. This
involves creating unique names and keeping the files in package-specific
subfolders inside the Prefs folder, and
also some facilities for linking positions in a temporary file to the
positions in the original file by specifying some offsets.
As an example of how this works, see the Diff mode. Here
diff-selections work by creating a temporary file for each of the two
selections under comparison, and redirect the various diff and merge
operations from the temporary files to the original files, transparently so
that it is never noticed that temporary files are involved.
See the files temp.tcl and
tempWindows.tcl for instructions.
PART 3: ADVANCED TOPICS
Most of this part will not be relevant to the average developer, but this
stuff needs documenting somewhere!
Topics covered include:
Embedding AlphaTcl
Alpha (like AlphaX and Alphatk in the past) is an application which embeds
the AlphaTcl library and uses it to provide almost all functionality outside
of the basic editing window and core dialogs. This section is here to
document what that actually means, since it isn't documented anywhere else.
Historically, (e.g. up to Alpha 7.x) embedding AlphaTcl has largely
meant setting a HOME variable to the location of the AlphaTcl
library files and sourcing '[file join $HOME Tcl SystemCode
AlphaBits.tcl]'. As the AlphaTcl library has become more complex, this
startup sequence has been retained, but more flexibility has been added.
This is particularly important because parts of the gui presented to the
user by the embedding application may be customizable by the user, yet all
preference handling (which it would be nice to use for that customization)
happens through the AlphaTcl library. Some very early preferences are thus
defined as user defaults which can then be read by the embedding
application to decide on the correct gui to display (e.g. position of the
status bar) before the AlphaTcl library is loaded.
To start the AlphaTcl library, the embedding application must:
- set the HOME variable
- source [file join $HOME Tcl SystemCode Init AlphaInit.tcl]
- call alpha::PreGuiStartup
Now preferences are available, so the GUI can be created. Then:
- call alpha::PostGuiStartup
This two-phase initialization is used in Alpha. AlphaTcl contains a
directory called Init for the files required for the first phase.
Window management
AlphaTcl window API
When windows are created, renamed, or destroyed, various of the hooks
defined later in this document are called (winCreatedHook, etc).
These make use of the AlphaTcl Window API to keep track of window names,
attributes, as follows:
- win::created name
- a window with this name has been created
- win::destroyed name
- a window with this name has been destroyed
- win::nameChanged oldname newname
- a window has been renamed
These three procedures may then be used by any AlphaTcl package to
query information about the current windows:
- win::Exists name
- does a window with this name exist?
- win::CreationOrder
- return list of window names, ordered by creation
- win::StackOrder
- return list of window names, ordered frontmost to backmost
Any AlphaTcl variables (such as $win::Active, $win::attr(),
$win::StackOrder) should be considered private to AlphaTcl's core
and only accessed through the above functions.
Standard window attributes (widgets)
This provides control over all the widgets found outside the editable area
of an Alpha window, e.g. toolbar, scrollbar, buttons and so on.
Any of these can be turned on and off by passing appropriate values via the
-attributes option in the core commands [openWindow] and [openFile].
The value to -attributes is an
integer formed as a sum of squares of 2 depending on which widgets should
be turned off. Possible values are found in the
window attributes table.
For example, to create a window without the VCS
,
Parse Functions
,
and Marks
popup controls, you need 4 + 8 + 16 = 28:
new -n FooWin -attr 28
Defining new window attributes
AlphaTcl provides very nice support for any mode/package/feature to attach,
detach and query an arbitrary set of information associated with any
window. Such information is automatically cleaned up when the window is
closed, and automatically adjusted if the user renames/saves the window.
AlphaTcl developers can use this to make it much easier to provide
window-specific behaviours. This support has a public API defined by the
following functions:
For example, this can be used to make it much easier for, say, Diff mode to run multiple simultaneous diffs, or for Brws mode to have different browser geometries, etc.
So, how is this used? Each window has a set of attributes associated
with it, which will persist for the lifetime of the window, and track the
window as it is saved, renamed, moved, etc. Some of these attributes are
termed core attributes which are used internally by Alpha's editing
engine. An obvious example of this is the font and fontsize of the window.
Other attributes are defined/read/set only in AlphaTcl - an important
example is the mode attribute which defines the editing mode to use
for the window. A more internal and less known one is the Modified
attribute which stores the last known modification time of a window
corresponding to a file on disk. It is used particularly to allow AlphaTcl
to respond to the situation where the file on disk appears to be more
recent than the contents of the editing window.
Here are the current attributes:
Core attributes for window mode-like behaviour:
bindtags |
colortags |
hookmodes |
varmodes |
featuremodes |
Other core attributes:
AlphaTcl (some of these may only be defined if certain packages are
active):
- mode (rather an important attribute, this one!)
- Modified
- indentationAmount
- indentUsingSpacesOnly
When opening a new window, the appropriate value of these attributes must
be calculated and assigned to the window. This is done in a prioritised
fashion, taking account of five different priority levels:
- global
- the global default, which is the lowest priority level.
- mode
- the default mode-specific value (from proc [mode::getVar],
which typically looks in the <mode>modeVars() array).
- fileset
- any fileset specific value, see e.g.
package Fileset Indentation Preference.
- window
- a value which is specific just to this window, e.g. as
read from the window's first line (tabsize:5).
- command
- effectively a forced value, as given, say in a direct command, like edit -tabsize 3 Readme.txt. This is the highest priority level.
The way this works is that any code may, during early window creation (e-g
in the [winCreatedHook] proc), call the proc [win::setInitialConfig] with a window name, an attribute name, a value
and a priority level. For example, one of the fileset packages does this:
proc fileset::checkIndentationPreference {fset name} {
set tab [fileset::getInformation $fset "Tab Size"]
set indent [fileset::getInformation $fset "Indentation Amount"]
set spaces [fileset::getInformation $fset "Indent Using Spaces Only"]
if {[string length $tab]} {
win::setInitialConfig $name tabsize $tab "fileset"
}
if {[string length $indent]} {
win::setInitialConfig $name indentationAmount $indent "fileset"
}
if {[string length $spaces]} {
win::setInitialConfig $name indentUsingSpacesOnly $spaces "fileset"
}
}
which therefore provides up to three different attributes (one for the core
and two for AlphaTcl, although the code doesn't have to distinguish between
them), each of which is given the fileset priority level. This
means it will over-ride any global or mode default, but itself be
over-ridden by any window-specific or direct command options.
The resolution between the different priority levels happens just
once, when proc [win::getAndReleaseInitialInfo] is
called in winCreatedHook. Once that resolution is complete, the
original priority levels of the winning attribute values are forgotten.
Defining minor modes and modifying window behaviours
Each window has five different aspects of behaviour, relating to bindings,
colouring, code hooks, variables and active features. For each such aspect, the window will have zero or more associated tags which
dictate the details of that specific behaviour. Typically, an ordinary
editing window will have just one tag for each behaviour, with each tag
being just $mode, reflecting the editing mode of that window.
However, Alpha and AlphaTcl provide support for modifying or replacing each
behaviour aspect for any window, either directly (with the [win::setInfo] proc and/or the [setWinInfo] core
command) or through the definition and application of minor modes.
We will first explain what the five editing aspects are, and how the
list of associated tags are used, before explaining how to define and make
use of minor modes in AlphaTcl packages.
To start with an example, the colouring aspect of any window is
dictated by a prioritised list of color tags attached to the
window. Here is a somewhat contrived example:
setWinInfo colortags [list "verb" "noun"]
As Alpha then colours the contents of the window, it examines each chunk of
text (typically each word) and asks each colour tag in turn whether that
tag wishes to colour the word. When one colour tag claims ownership of the
word, it is coloured appropriately, and Alpha moves onto the next chunk of
text.
So, for example, if the command colorTagKeywords has been
used as follows:
colorTagKeywords -k green "verb" {be go dog run walk drive fly}
colorTagKeywords -k blue "noun" {person dog cat car}
Then any text in the window would be coloured with the given verbs in green
and the given nouns in blue. Notice that dog would be coloured in
green, since it is the verb tag which is the first to match.
Note |
As of 2017-03-04, colorTags are not fully implemented in AlphaCocoa |
Each of the five aspects of the window controls one behaviour type,
in much the same way as just described: the aspect has an ordered list of
tags, and when deciding what aspect-specific action to take, Alpha checks
each tag in turn until it finds one that applies to the action in question.
If none is found then a global default action would occur (which might be
to do nothing).
So, let's examine a second example, this time with bindtags
aspect used for keyboard bindings/shortcuts. Here a similar approach is
taken: the window has a prioritised list of bind tags attached:
setWinInfo bindtags [list "shell" "Tcl"]
When the user presses a key in the window, Alpha asks each bind tag in
turn whether that tag wishes to claim the key-sequence. If it does, the
appropriate command is triggered. For example:
binding create -tag Tcl {"" '\n'} Tcl::carriageReturn
...
binding create -tag shell {"" '\n'} Shel::carriageReturn
...
Now if the user presses the return key in this window, the
[Shel::carriageReturn] function will be the first to match, and it will
therefore be triggered.
The five behaviour types are summarised as follows:
- colortags
- the list of colouring schemes to use for the window. For each of these
tags, when colouring the window's contents Alpha will examine the colouring
information defined in a call to 'regModeKeywords $tag' or
'colorTagKeywords $tag'. The first tag in the list is allowed to declare
comment definitions and colours, string definitions and colours, etc.,
while all subsequent colouring tags may only declare keywords to colour.
- bindtags
- the list of binding schemes to use for the window. For each of these tags,
when the user presses a certain key-combination, Alpha will examine the set
of bindings defined with binding create -tag <tag>, searching for
any match to the given key-combo.
- hookmodes
- the list of hook schemes to use for the window. For each of these tags,
when executing hooks with hook::callProcForWin (e.g. hooks like
electricLeft, correctIndentation, carriageReturn, etc), AlphaTcl will check
for procs defined in the namespace $tag::. The first such hook
found will be called (or a global hook if none is found).
- varmodes
- the list of variable schemes to use for the window. For each of these tags,
when checking for a window/mode-specific variable value with proc [win::getModeVar] (typical variables are startPara,
tabSize, indentationAmount, etc), AlphaTcl will check for
variables defined in the namespace $tag:: or the array ${tag}modeVars() as appropriate. The first such variable found will be
returned (or a global variable if none is found). Note that the following
list of variables are searched for in the namespace: escapeChar
quotedstringChar lineContinuationChar commentCharacters(General)
commentCharacters(Paragraph) commentCharacters(Box) startPara endPara, and
all others in the array. Note also that the only variables copied from a
<mode>modeVar() array to the global namespace are those associated with the
real mode of the window as given by proc [win::getMode].
- featuremodes
- the list of feature schemes to use for the window. For each of the tags in
the featuremodes list, when activating the window, AlphaTcl will adjust the
set of currently active features according to the information in mode::features($tag). Similarly when deactivating the window. In this
activation/deactivation process, AlphaTcl always compares the set of
features needed for the new window with those for the previously active
window, and makes the appropriate minimal set of on/off calls to have the
appropriate set of features active. Currently, for real modes,
mode::features($mode) can be edited through the mode's preferences dialogs.
There is no current way the user can edit this set for feature tags which
are not modes (they must be hard-coded). This feature activation and
deactivation all takes place through core-private proc [alpha::changeMode].
All of the above behaviour aspects can be set with proc [win::setInfo]. There is also a mechanism, with minor modes by
which a whole set of modifications (for any or all of the five behaviour
aspects) can be created as a set, and applied to any window. This is done
with the proc [alpha::minormode], as follows:
alpha::minormode $name ?(+aspect|-aspect|aspect) value?+
Use '+' to extend, '-' to remove and nothing to set. For example:
alpha::minormode tclshell +bindtags TclShell -hookmodes Tcl varmodes TeX
Then you may use win::setInitialConfig winname minormode name window to apply a minor mode to a window (this only currently works before
that window is created, but this limitation is due to be removed).
Event hooks
The flexibility of Tcl enables one to over-ride or otherwise intercept
almost any operation in Alpha. However, to make it much easier for your
code to take action on common events, a number of hooks are
provided. It is better if you can use these hooks rather than do all the
rename saveHook mySaveHook... stuff. To have a script called when
under a particular circumstance, you just use
hook::register hookName script ?context? ?context...?
Hooks are scripts executed automatically when a specified event occurs.
For example, many modes have a date-stamp pre-save hook, which whenever the
user says Save, updates the date stamp in the header of the file before
actually saving.
When certain events occur, e.g. when opening and closing windows,
Alpha (or AlphaTcl) calls an event hook. Core hooks call specific
procedures defined in alphaHooks.tcl -- these procs
might then in turn call the proc [hook::callAll], and
in this case you can register your own event hooks to add to the default
behavior when the corresponding event occurs. The core will never call
[hook::callAll] directly. Other hooks are called from specific AlphaTcl
procs (not by the core). This section lists all available hooks, and
specifies when they are called and by whom.
The file hook.tcl contains the implementation of
these calling mechanisms (if you need to look further), but it is more
important to know exactly what hooks are available, under exactly what
circumstances they are called, what contexts are available, and, of course,
what arguments will be added to your script before it is evaluated. In
almost all cases it does not really matter whether your script returns
successfully or throws an error, but, some hooks do have a particular
behaviour dependent on the return code, and it is good practise not to
throw errors unnecessarily!
Topics covered in this section include:
Defining hooks
To add your own procedure to be called when the hook in invoked, there are
two things you have to do. First write a proc to be called when some events
occur. This proc must have the parameters shown in the table. Let's say
you want to define a saveHook. Then define some proc
proc mysaveHook {name}{
.
.
}
The next thing you have to do is to register the proc. This is done
with a line like:
hook::register 'hook-name' 'your proc' 'mode' ?... 'mode'?
The optional mode parameters specify in which mode(s) the hook will be called.
If no mode parameters are given, the hook will be called regardless of the
mode. Avoid this unless absolutely necessary.
Let's assume that you want your hook to be called in TeX and Perl
modes. To register it, you would use the line:
hook::register saveHook mysaveHook TeX Perl
Note, however, that a few hooks don't use the mode to determine when to be
called and should be registered slightly differently, see below.
Hooks may be invoked in three different ways, through one of the
following commands:
- [hook::callAll]
- this proc invokes all the procedures registered with the hook. The order in
which they are called is indefinite. Note that [hook::callAll] wraps all called hook procedures in a [catch], so you should not attempt to cancel the action calling the hook
by throwing an error (throwing an error in a hooked procedure is usually
considered a bug in that procedure).
- [hook::callUntil]
- this proc invokes the registered procedures one by one until one of
them claims to be able to handle the request: it does that by taking action
and returning '1'. Procedures which cannot handle the action return '0'. If
a procedure has been executed successfully, the remaining callbacks are not
invoked.
- [hook::callUntilOk]
- this proc invokes the registered procedures one by one until one of
them claims to be able to handle the request, that is to say executes
without raising an error. If a procedure has been executed successfully,
the remaining callbacks are not invoked.
There is also a convenience proc [hook::callForWin]
which is a wrapper around the preceding procs. It has a type argument
(all, until, or untilok) and takes care of getting
the mode(s) of the specified window.
Many of the hooks below are invoked by a procedure with the name of
the hook to which you register your action (indicated below with something
like proc someHookName), others are called during the sourcing of
particular files or by other procedures as indicated.
Note that hooks are always invoked in a stacked fashion. This means
that if one hook (say an openHook) causes the execution of other
hooks (e.g. it calls [killWindow] which therefore causes
the closeHook's to be called), then all the first hooks will be
executed before any of the second hooks (so, in the example given, all openHooks will be executed, some of them possibly with no window in
existence, and only then will all closeHooks be executed. This has
the important benefit that code can always be sure openHook is
called before closeHook, even if some other code has closed the
window).
There are some subtleties associated with these hooks, and the
additional parameters your hook receives are not directly documented
anywhere. We suggest you look at existing code and ask on Alpha's Mailing Lists for further information. Contributed explanations will be added
to this file.
In the next sections, when necessary, we list the parameters with
which your hooked script is called. If your script is in the variable
script, these will be evaluated like this:
eval $script [list $param1 $param2 ...]
Where a parameter is given the name winName, it may include the ' <2>' duplicate window markers. Where it is given as path, it
is the name of a file (which may or may not exist on disk).
Here are some examples of hook registration:
hook::register saveHook modified "C" "C++"
hook::register saveHook modified "Pasc"
hook::register saveHook htmlLastModified HTML
hook::register savePostHook codeWarrior_modified "C++" "C"
hook::register savePostHook ftpPostHook
hook::register saveasHook htmlLastModified HTML
For these hooks, as with many others, the general form is:
hook::register 'hook-name' 'your script' 'mode' ?... 'mode'?
So, the context is the mode for which you would like your script evaluated
(and more than one mode can be provided). If you don't include a mode
argument, then your proc will be called no matter what the current mode is.
Avoid this unless absolutely necessary.
You can get a list of all hooks for which something has already been
registered with the proc [hook::information]. For example:
Welcome to Alpha's Tcl shell.
«» hook::information
activateHook aevtodocHook changeMode closeHook contextualMenuHook
contextualPostBuildHook deactivateHook dimThreshold dirtyHook
fileset-delete fileset-file-opening fileset-new fileset-uncache
fileset-update keyboard lockHook openHook openRecentMenu preCloseHook
preOpeningHook procRenames quitHook removekeyboard resumeHook savePostHook
shortcutMenuItems startupHook unlockHook vcsSupportPackages
winChangedNameHook wwwMenuInit
However, other hooks which do not occur in this list may also be available
(this happens if they are simply not used anywhere). For example, suspendHook is one which is called whenever the user shifts the focus
from Alpha to another application.
While one set of hooked scripts (e.g. all openHooks) is
being evaluated, any action which results in further hooks being called
will be queued, and not take place until the first set of hooks is
complete.
Window hooks
The filePreOpeningHook is called when a
file is going to be opened in Alpha.
It is called by the core inside the [openFile] command
just before the document is created. The first argument is the name of the
file on disk, and the second argument is the name of the window we will use
(e.g. it may have trailing <2>...), but that window does not yet exist at
all.
The idea is that we can use this proc and the registered hooks it calls to
set the window's mode or the window's default attributes, to adjust
tab-size, encoding, etc, before the window is properly created, and before
the contents of the file are read in (this is particularly important if we
want to set a particular encoding to use for that reading!). One must call
the proc win::setInitialConfig with the future name of the window
in order to set any of these details.
The filePreOpeningHook returns a default value for the
interface attributes of the window that will be opened (see the possible
values here).
A mode callback registered for the filePreOpeningHook has the
opportunity to set a suggested value via the 'winAttrs' key in the
initial config array. For instance like this:
win::setInitialConfig $winname winAttrs 1234 "window"
This value (1234 in the previous example) is returned by filePreOpeningHook and retrieved by the core. It is then used as a
default value for the -attr option. Note that
an -attr option (if explicitely specified) has precedence over this
default value.
The activateHook is called when a window is brought to front, including, if it is a normal
(non-minimized, non-hidden) window, when it is first created. The window
should exist (and the command [getWinInfo], the command
[winNames] and the proc [win::Current] should all work and behave as if the window is present). The window's
contents will be available during this hook (even if called during window
opening -- in such a case it is called after any openHooks). The
current mode Alpha is in will have been adjusted just prior to
calling this hook (but the activateHook could if desired change the
mode of the window, although the desire to change the window would best be
handled elsewhere). It is allowed to use the command [killWindow] to remove the window, but in that case the window will not
exist for any other activateHooks which are to be called. Note that
activateHook and deactivateHook events always occur in
pairs. Any window that has the activateHook called on it will
always, at some point in the future, have deactivateHook called
(either when the window is closed or another window is brought to the
front).
The closeHook is called after a window has been closed. When the hook is called, the window
no longer exists (so [getWinInfo] will fail, and the
window should not appear in the [winNames] list, and [win::Current] will certainly not refer to it), but any
extra AlphaTcl-attributes stored with the proc [win::setInfo] will still be available through proc [win::getInfo] (they will be cleaned up directly when this hook returns).
Many uses of closeHook might perhaps be better implemented through
preCloseHook. After all the closeHooks are called, AlphaTcl
will arrange for any relevant requireOpenWindowHook menu dimming to
be carried out to take account of the window's disappearance.
The deactivateHook is called when a window ceases to be the frontmost window, for any reason
(e.g. is sent to back, or the user switches attention to another window, or
it is hidden or minimized, or the window was frontmost but is closed). Note
that activateHook and deactivateHook events always occur in
pairs. Any window that has the activateHook called on it will
always, at some point in the future, have deactivateHook called
(either when the window is closed or another window is brought to the
front).
The alpha::openHook hook is called when a window is opened, once the contents are available, and before
any activateHook is called. The window fully exists and has just
been made visible to the user when this hook is called.
All window operations are available. Older versions of Alpha
prohibited the calling of the command [killWindow] from
this hook, but it should now be allowed in newer versions. Note that, if
one hook has called [killWindow], then the window will
no longer exist in any other hook. Therefore, for full reliability, openHooks should not actually assume the window exists and should use
win::Exists to check if it does. Before any openHooks are
called, AlphaTcl will arrange for any relevant requireOpenWindowHook menu dimming to be carried out to take account of the new window's
existence.
The preCloseHook is called just before a window is closed, but after the user has been asked if
a dirty window should be saved (so we know for certain the window is going
to disappear). This hook should not attempt to change any window contents
or file information. However, all such contents is available when the hook
is called, and the window will still appear in the [winNames] list. If the window was frontmost, then [win::Current] will still refer to it. Any uses of [win::getInfo] or [getWinInfo] will work. Basically
the window exists in all respects. This hook cannot be used to abort the
closing of the window, and any errors thrown by a hook are considered bugs
in the hook.
The saveHook is called when a window is saved, whether that save is through [save] or [saveAs], just before its
contents are written to disk. This is the last chance to modify the
contents of the window (e.g. a modification date) before the contents is
committed to disk. A useful hook to, for example, update a last-modified
date in a file's header.
The savePostHook is called after a window is saved (whether by [save] or
[saveAs]).
The titlebarPathHook is called when the titlebar title is clicked. Hook must build a menu and
return its name.
The winChangedNameHook is called when a window's name has changed (e.g. through Save As), and
after any change in mode which may have resulted from the new name. This
may also result in the mode of the window changing (so changeMode
hook may also be called). Alpha ensures, just before this hook is called
that the new file exists on disk (even if only as a dummy file with size
0). The mode for which this hook is called is the final mode for the window
(where that differs from the original mode).
This hook is also called if a file is moved outside of Alpha (e.g.
it is dragged from one location in the OS to another, or it is renamed in
the OS). In consistency with the usage in the other window hooks, the
first argument is the name of the calling window (i.e. the new name),
whereas the old name is just a secondary information.
The winChangeModeHook is called when a window's mode is changed (e.g. by saveAs, or the
user's direct intervention). This hook is not mode-specific, at least at
present (we have only one user of this hook - the Latex Accents package, so we could consider changing this with more
widespread use).
Alpha hooks
Hook | Parameter(s) | Description |
quitHook | (none) | Called when quitting Alpha |
resumeHook | (none) | Called when switching to Alpha from another application. |
startupHook | (none) | Called at the end of startup. |
suspendHook | (none) | Called when switching to another application. |
themeChangedHook | (none) | Called when switching appearance at System level. |
screenParametersChangedHook | (none) | Called when user changes the screen geometry. |
winChangedScreenHook | (none) | Called when user changes screen in a multi-display setting. |
The startupHook is called by AlphaTcl (not by the core) at the end
of startup (see [alpha::finishLoadingAlphaTcl]). There
is no default proc for this hook. It will execute any proc registered by
some packages in their initialization script. It is called when Alpha
starts up, but after all other initialization has taken place
(before any files are opened though)
The quitHook is called by the core after all the document
windows have been saved and closed. All the preCloseHooks and closeHooks will have already been executed.
The themeChangedHook is called by the core when the user
switches the general appearance between light and
dark modes (on OS X 10.14 or greater) in the
General System Preferences panel.
The screenParametersChangedHook is called by the core when
the user changes the screen geometry, the position of the dock or the size
of the status bar. At this point, the core has already updated the
appropriate variables (screenWidth, screenHeight) and recalculated
proposed values for defOrigX, defOrigY, defWidth, defHeight.
Mode hooks
The changeMode hook is called when the mode is changed, after the new mode has been activated and
all features/menus have been adjusted and the new mode's variables/prefs
are in place.
The changeModeFrom hook is called when the mode is about to change, before the new mode has been
activated; no features/menus have yet been adjusted, and the old mode's
variables/prefs are still in place.
The mode::editPrefsFile hook is called when a mode prefs file is opened for editing.
The mode::init hook is called the first time a mode is loaded. Note that at that time the mode
exists, but its variables have not yet been made global, and its menus have
not yet been inserted into the menu bar.
Keyboard hooks
The keyboard hook is called at startup and when the keyboard preference is changed.
The removekeyboard hook is called when the keyboard preference is changed by the user.
The keyboard hooks use the keyboard name (the ones in the keyboard popup
menu in the International Preferences panel
panel) rather than the mode to determine when to be called. Thus to
register a keyboard or removekeyboard hook, use lines like:
hook::register 'hook-name' 'your proc' 'keyboard name' ?... 'keyboard name'?
Miscellaneous hooks
The launch hook is called when a helper application is launched by calling [app::ensureRunning].
The launch hook uses the application's bundle ID rather than the mode
to determine when to be called. Thus to register a launch hook use a line
like:
hook::register launch 'your proc' bundleID ?... bundleID?
The requireOpenWindowsHook hook is called when opening and closing windows.
This hook is used to en-/disable meaningless menu items which would
require the presence of a certain number of windows to be active. You can
register your own menu items using a line like:
hook::register requireOpenWindowsHook [list ?-m? menu item] N
where N is the number of windows required (1 or 2 usually)
As an example, this is a line from Diff mode
registering the item Compare Windows to require 2 open windows to
be enabled, followed by a line from HTML mode (which shows the use of the
-m menu flag in this hook):
hook::register requireOpenWindowsHook [list compare windows] 2
hook::register requireOpenWindowsHook [list -m Browsers "Send File to Browser"] 1
Credits and Copyright
This document has been placed in the public domain. All AlphaTcl code has
license information specific to the packages/authors, assume that it can be
distributed under a Tcl-style license unless otherwise specified.
Authors: Most of the material of Parts 2 and 3 was written by Vince
Darley around 2004, with contributions from Craig B. Upright, Tom
Fetherston and Pete Keleher. A thorough revision, documentation of many of
the new technologies introduced since version 8.1, and most of the material
in Part 1, is due to Joachim Kock (2011) with many contributions from
Bernard Desgraupes.
Copyright (c) 2021, the Alpha Community.
All rights reserved.
This software is free software and distributed under
the terms of the new BSD license:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the Alpha Community nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ALPHA COMMUNITY BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.