foo.sl: How to write a Jed mode

A mode to place "foo" at the beginning of each line and highlight it.[1]

Copyright (c) 2007 GŁnter Milde (milde users.sf.net) Released under the terms of the GNU General Public License (ver. 2 or later)

[1]Actually, this is a manual and template for the creation of Jed extension scripts ("modes"). It assumes you to have basic knowledge of S-Lang and Jed and know how to use its help system (Using the help browser from Jedmodes is recommended.)

Contents

Usage

You can load this mode with require("foo");

provide("foo");

To find out what the foo mode does, try:

M-x foo-mode

and have a look at the Mode menu.

Customisation

You can use the foo_mode_hook to define your own shortcuts, e.g.

%% define foo_mode_hook(mode)
%% {
%%   local_setkey("start_line_with_foo(\"bar \")", "^A");
%% }

Also, have a look at the Custom variables.

Versions

1.0 first draft
1.2 2006-01-10 adapted to SLang 2
1.3 2007-10-21 literate version

Requirements

from Jed's standard library:

require("comments");
require("keydefs"); % symbolic names for keys

from http://jedmodes.sf.net/:

autoload("close_buffer", "bufutils");

Recommendations

(from http://jedmodes.sf.net/) Browse documentation:

#if (expand_jedlib_file("browse_url.sl") != "")
autoload("browse_url", "browse_url");
#endif

Custom variables

Use custom_variable() to provide user-configurable options.

These variables can be defined and initialized by users but will be set to the default value if they do not exist at evaluation time.

%!%+
%\variable{Foo_Insertion}
%\synopsis{String to insert with \var{start_line_with_foo}}
%\usage{String_Type Foo_Insertion = "foo "}
%\description
%  The string that will be inserted by the function
%  \sfun{start_line_with_foo}.
%\seealso{foo_mode, start_line_with_foo}
%!%-
custom_variable("Foo_Insertion", "foo ");

Namespace

The implements() function may be used to name the local namespace. Doing so will enable access to the members of the namespace from outside the unit:

implements("foo");
Attention:

The default scope of definitions changes:

Definition without implements with implements
private define fun private private
static define fun static static
define fun public static
public define fun public public

make_ini can generate autoloads for all functions that are defined with define public. To put a function in the "Global" namespace but prevent generation of an autoload, use e.g. define  public (with 2 spaces).

Static variables

Define quasi-constants or variables for inter-function communication as private if they are only used in this compilation unit (file) and as static if they should be accessible with the namespace->name notation. (Do this after the implements, as otherwise the variables become inaccessible.)

% the name of the mode, keymap, and syntax table
private variable mode = "foo";

static variable foo_word_chars = get_word_chars()+ "_";

Functions

It helps the end-user a lot, if you provide on-line help text with the public functions. The tmtools mode at Jedmodes helps with formatting, the tm and make_ini modes makes it aviable to the Jed help system.

%!%+
%\function{start_line_with_foo}
%\synopsis{Prepend line with the sting in\var{Foo_Insertion}}
%\usage{ start_line_with_foo(insertion=Foo_Insertion)}
%\description
%  Prepend the current line with the string given in the custom
%  variable \var{Foo_Insertion} or in the optional argument \var{insertion}.
%\example
% While
%#v+
%  start_line_with_foo()
%#v-
% inserts by default "foo " at the beginning of the line, you can override
% this by setting \var{Foo_Insertion} or by e.g.
%#v+
%  start_line_with_foo("bar ")
%#v-
%\notes
%  Please document all public functions to the user can get online-help
%  via the Help menu.
%  \var{tm_make_doc} is a nice help in creating in-source documentation
%  in the "tm" format used by Jed
%\seealso{foo_mode, Foo_Insertion}
%!%-
public define start_line_with_foo() % (insertion=Foo_Insertion)
{
   variable insertion;
   if (_NARGS)                  % optional argument present
     insertion = ();
   else
     insertion = read_mini("start every line with:", Foo_Insertion, "");
   push_spot();
   bol;
   insert(insertion);
   pop_spot();
}

Internal functions are best commented with a short abstract line:

% give a message() with help usage.
static define small_help()
{
   message("<RET>:Insert Foo q:Quit foo mode");
}

Static functions that are intended for use from other modules, should have an on-line help text too:

%!%+
%\function{foo->normalize_modename}
%\synopsis{find the normalized form of a mode}
%\usage{foo->normalize_modename(mode)}
%\description
% take a mode name and do some guesses for the associated slang-function
%\example
% To do this for the current mode, use e.g.
%#v+
%  normalize_modename(what_mode, pop)
%#v-
% (the `pop' will pop the second return value of `what_mode').
%\notes
%  A related function is in bufutils.sl.
%\seealso{normalized_modename}
%!%-
static define normalize_modename(mode)
{
   variable modstr = extract_element (mode, 0, ' ');
   if (modstr == "")
     modstr = "no";
   if (is_defined (modstr))
     return modstr;
   modstr += "_mode";
   if (is_defined (modstr))
     return modstr;
   modstr = strlow (modstr);
   if (is_defined (modstr))
     return modstr;
   error ("Mode " + modstr + " is not defined.");
}

% return to the mode that was used before
static define quit_foo()
{
   runhooks(normalize_modename(get_blocal_var("previous_mode")));
   update(0);
}

Commenting

Instead of defining your own (un)commenting functions, provide an interface to comments.sl:

set_comment_info (mode, "#foo: ", " :foo#", 7);

Syntax highlight

Syntax table (non-DFA):

create_syntax_table (mode);
define_syntax ("#foo:", ":foo#", '%', mode); % Comments
define_syntax ("([{", ")]}", '(', mode);     % Delimiters
define_syntax ("0-9a-zA-Z", 'w', mode);      % Words
define_syntax ("-+0-9.", '0', mode);         % Numbers
define_syntax (",", ',', mode);              % Delimiters
define_syntax (";", ',', mode);              % Delimiters
define_syntax ("-+/&*=<>|!~^", '+', mode);   % Operators

set_syntax_flags (mode, 0);

Keywords:

() = define_keywords_n(mode, "foo", 3, 0);

DFA syntax highlight

More versatile but also more complicated. See also Help>Browse_Docs>dfa.

DFA is not available for all versions of Jed, therefore we put the code in a preprocessor section.

Simple setup

#ifdef HAS_DFA_SYNTAX
create_syntax_table (mode);  % clear the non-DFA entries
private define setup_dfa_callback (mode)
{
   dfa_define_highlight_rule("[0-9]*", "number", mode);
   dfa_define_highlight_rule (sprintf("[%s]*foo[%s]*",
                                      foo_word_chars, foo_word_chars),
                              "keyword", mode);
   dfa_define_highlight_rule ("#foo:.*:foo#", "comment", mode);
   dfa_build_highlight_table(mode);
}
dfa_set_init_callback (&setup_dfa_callback, mode);
enable_dfa_syntax_for_mode(mode);
#endif

Setup with cache file

Building a DFA highlight table can take considerable time. For complex highlight schemes, it makes sense to cache the table. The cache file will be created

  • at first use of the mode, or
  • in a preprocessing step (preparse.sl or update_dfa_cache_files() from make_ini.

In the second case, the section between the lines %%% DFA_CACHE_BEGIN %%% and %%% DFA_CACHE_END %%% will be evaluated in a temporary buffer. Therefore, no references to non-public variables or functions are allowed here. (Notice that mode inside the setup_dfa_callback() refers to the argument of the function.):

#ifdef HAS_DFA_SYNTAX
%%% DFA_CACHE_BEGIN %%%
private define setup_dfa_callback(mode)
{
   variable foo_word_chars = get_word_chars()+ "_";
   dfa_enable_highlight_cache("foo.dfa", mode);
   dfa_define_highlight_rule("[0-9]*", "number", mode);
   dfa_define_highlight_rule ("[%s]*foo[%s]*",
                                      foo_word_chars, foo_word_chars,
                              "keyword", mode);
   dfa_define_highlight_rule ("#foo:.*:foo#", "comment", mode);
   dfa_build_highlight_table(mode);
}
dfa_set_init_callback (&setup_dfa_callback, "foo");
%%% DFA_CACHE_END %%%
enable_dfa_syntax_for_mode(mode);
#endif

Keybindings

!if (keymap_p(mode))
  make_keymap(mode);
definekey_reserved(mode+"->small_help",  "?",  mode);
definekey_reserved(mode+"->quit_foo",     "q", mode);
definekey_reserved("start_line_with_foo","^M", mode);  % Return

Mode menu

The Mode menu is Jed's version of a context sensitive menu. It allows easy access to mode functions and also displays the key-binding (so there is normally no need for a separate help file with keybindings).

private define foo_menu (menu)
{
   menu_append_item (menu, "&Foo Line", "start_line_with_foo");
   menu_append_item (menu, "&Quit Foo", mode + "->quit_foo");
   menu_append_item (menu, "Foo &Help", mode + "->small_help");
}

Mode function

The mode function is called to set a Jed buffer in a special editing mode. It should:

%!%+
%\function{foo_mode}
%\synopsis{An example mode}
%\usage{Void foo_mode()}
%\description
%  A mode to place "foo" at the beginning of a line.
%  Also highlights all foos.
%\example
%#v+
%   foo_mode();
%   () = what_mode(); % returns two items
%   message(());
%#v-
%\notes
%  Actually, foo.sl is a commented template for writing jed modes.
%\seealso{mode_set_mode_info, slang_mode, set_mode, what_mode}
%!%-
public define foo_mode()
{
   ($1, ) = what_mode();
   define_blocal_var("previous_mode", $1);
   set_mode(mode, 4);
   use_syntax_table(mode);
   use_keymap(mode);
   mode_set_mode_info(mode, "init_mode_menu", &foo_menu);
   mode_set_mode_info(mode, "fold_info", "#{{{\r#}}}\r\r");
   mode_set_mode_info(mode, "word_chars", foo_word_chars);
   run_mode_hooks(mode + "_mode_hook");
}