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)

Contents

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.)

Support loading this mode with require("foo");:

provide("foo");

Usage

To find out what the foo mode does, try:

M-x evalbuffer
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");

Note

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");
}