% File:          latex.sl    -*- mode: SLang; mode: fold -*-
%
% Copyright (c)
%       until 2003  Guido Gonzato <[email protected]> (as Latex4Jed)
%       2003--2007  Jörg Sommer <[email protected]>
%       $Id: latex.sl 205 2007-09-08 13:38:06Z joerg $
%
%       -*- This file is part of Jörg's LaTeX Mode (JLM) -*-
%
% Description:   In this file are all functions for composing, viewing,
%                printing and any related problem. All what have to do
%                with an external program and a latex file.
%
% License: This program is free software; you can redistribute it and/or
%          modify it under the terms of the GNU General Public License as
%          published by the Free Software Foundation; either version 2 of
%          the License, or (at your option) any later version.
%
%          This program is distributed in the hope that it will be
%          useful, but WITHOUT ANY WARRANTY; without even the implied
%          warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
%          PURPOSE.  See the GNU General Public License for more details.

%!Fix me -- TODO:
%   font_resize(n): n variable machen
%   indent line in Verbatim environment like comments
%   use replace_chars()
%   right, left bei |
%   g\bigl( ... )
%   \{ confuses indention
%   auto-sizing of braces, e.g. "( ()" + ")" -> "\big( () \big)"
%   customization stuff
%   move to comment.sl
%   preview of equations with UTF-8
%   dialog for open subfiles (\input, \include)

if (length(where("latex" == _get_namespaces())))
 use_namespace("latex");
else
 implements("latex");

#ifnexists profile_on
% profiling does not work with stack check enabled.
autoload("Global->enable_stack_check", "stkcheck");
try
{
   enable_stack_check();
}
catch OpenError: {} % stkcheck not found in the path
#endif

static variable MODE="LaTeX";

define debug_msg(msg)
{
#if (BATCH)
   message(msg);
#else
   whatbuf();
   setbuf("*traceback*");
   eob();
   variable x = make_printable_string(string(msg));
   insert( substr(x, 2, strlen(x) - 2) );
   newline();
   setbuf( () );
#endif
}

define debug_pos()
{
   variable arg;
   if (_NARGS)
     arg = string( () );
   else
     arg = "";
   debug_msg("line = $what_line, col = "$ + string(what_column()) + "; $arg"$);
}

define debug_print_buf()
{
   push_spot();
   bob();
   push_mark();
   eob();
   message( bufsubstr() );
   pop_spot();
}

public variable Key_Enter = "\r", Key_Space = " ";

foreach ("latex_external->" +
        ["bibtex", "clearup", "compose", "makeindex", "mrproper",
         "pop_log_file", "print", "select_master_file",
         "cust_view", "view", "jump_to_master_buffer",
         "show_bibtex_log", "show_mkidx_log"])
{
   autoload((), "latex_external.sl");
}

foreach ("latex_conv->" +
        ["colon", "german_lat1", "german_utf8", "native_lat1", "native_utf8",
         "ltx209_ltx2e"])
{
   autoload((), "latex_conv.sl");
}

foreach ("latex->" +
        ["pst_update_pic_size", "pst_move_points"])
{
   autoload((), "latex_pst.sl");
}

foreach ("latex->typo_" + ["abbrev", "dots", "german_decimal_point", "hyphen",
                          "percent", "slash"])
{
   autoload((), "latex_typo.sl");
}

#if (_jed_version < 9919)
require("x-keydefs");
require("read_with_description");
#else
require("x-keydefs", "Global");
require("read_with_description", "Global");
#endif

%!%+
%\variable{String_Type LaTeX_Template_Dir}
%\synopsis{Directories of template files}
%\description
%  You can insert text blocks via Mode->Templates or ^ct. These blocks
%  are read from a file that is placed in a directory in this comma
%  separated list.
%
%  The default is Jed_Home_Directory/latex-templ
%\seealso{templ_insert()}
%!%-
custom_variable("LaTeX_Template_Dir",
               path_concat(Jed_Home_Directory, "latex-templ"));

%!%+
%\variable{String_Type LaTeX_Default_Packages}
%\synopsis{List of packages to insert when a template will be inserted}
%\description
%  You can insert text blocks via Mode->Templates or ^ct. These blocks can
%  include the special sequences %:default:pkgs:% that is replaced by a list
%  of \\usepackage commands with the packages given with this variable.
%
%  The default is "inputenc:fontenc:babel:fixltx2e:microtype".
%\seealso{templ_insert(), insert_pkgs()}
%!%-
% http://homepage.ruhr-uni-bochum.de/Georg.Verweyen/latexfuerword.html
custom_variable("LaTeX_Default_Packages",
               "inputenc:babel:fontenc:fixltx2e:microtype");

%!%+
%\variable{String_Type LaTeX_Default_Class_Options}
%\synopsis{Default class options for a template}
%\description
%  You can insert text blocks via Mode->Templates or ^ct. These blocks can
%  include one of the special sequences %:default:classopt:,% or
%  %:default:classopt:[% that is replaced by the content of this variable.
%
%  The default is "draft".
%\seealso{templ_insert()}
%!%-
custom_variable("LaTeX_Default_Class_Options", "draft");

%!%+
%\variable{Integer_Type LaTeX_Auto_Space_After_Commands}
%\synopsis{Insert a space after a command, if a letter is inserted}
%\description
%  If this variable is unequal 0 and you insert a command with
%  cmd_insert() which have no arguments the cursor is placed after this
%  command. If you then enter a letter, a space is inserted, because would
%  treat LaTeX this as part of command which is normally an error.
%
%  The default is 1, which means to insert a space, if necessary.
%\seealso{templ_insert()}
%!%-
custom_variable("LaTeX_Auto_Space_After_Commands", 1);

%!%+
%\variable{Integer_Type LaTeX_Indent_First}
%\synopsis{Defines the amount of spaces used for first indention level}
%\description
%  Defines the default indention in all environments and open braces.
%
%  \begin{xyz}
%    text                                  indented by LaTeX_Indent_First
%    \cmd{foo%                             indented by LaTeX_Indent_First
%      bar}                  indented a second time by LaTeX_Indent_First
%  \end{xyz}
%
%  The default is 2.
%\seealso{LaTeX_Indent_Continued}
%\seealso{LaTeX_Indent_Item}
%!%-
custom_variable("LaTeX_Indent_First", 2);

%!%+
%\variable{Integer_Type LaTeX_Indent_Continued}
%\synopsis{Defines the amount of spaces added for each deeper indention level}
%\description
%  Some environments like table, array, align or gather don't allow empty
%  lines to structure the LaTeX code. JLM tries to detect the end of line
%  in the output and helps structuring the code in this way that all text
%  in the same line in the output, but on different lines in the code is
%  indented deeper than the first line.
%
%  \begin{tabular}{l}
%    text              this line is indented by LaTeX_Indent_First spaces
%       continued l.          LaTeX_Indent_First + LaTeX_Indent_Continued
%       third line\\          LaTeX_Indent_First + LaTeX_Indent_Continued
%    text                                              LaTeX_Indent_First
%  \end{tabular}
%
%  The default is 3.
%\seealso{LaTeX_Indent_First}
%\seealso{LaTeX_Indent_Item}
%!%-
custom_variable("LaTeX_Indent_Continued", 3);

%!%+
%\variable{Integer_Type LaTeX_Indent_Item}
%\synopsis{Defines the amount of spaces used to indent \item}
%\description
%  \begin{itemize}
%   \item                                   indented by LaTeX_Indent_Item
%    text                                  indented by LaTeX_Indent_First
%  \end{itemize}
%
%  The default is 1.
%\seealso{LaTeX_Indent_Continued}
%\seealso{LaTeX_Indent_First}
%!%-
custom_variable("LaTeX_Indent_Item", 1);

%!%+
%\variable{Integer_Type LaTeX_Register_New}
%\synopsis{Ask the user about informations for new environments/commands}
%\description
%  When a environment or command is inserted with *_prompt and
%  it is not known, the user is prompted for the missing informations
%  like number of arguments, needed packages and a description.
%
%  The default is 1.
%!%-
custom_variable("LaTeX_Register_New", 1);

%!%+
%\variable{String_Type LaTeX_File_New}
%\synopsis{Name of a file, where options of new cmds, envs and pkgs are saved}
%\description
%  When a command, environment or package is inserted with *_prompt and
%  this variable is a non-empty string, it is used as the name of a file
%  where a command is appended to save the new data. Later, you can load
%  this file, with evalfile(), to import your config.
%
%  The default is NULL.
%\seealso{LaTeX_Register_New}
%!%-
custom_variable("LaTeX_File_New", NULL);

%!%+
%\variable{Integer_Type LaTeX_Typo_Active}
%\synopsis{Enables/Disables typographic functions}
%\description
%  Setting this variable to 0 disables all typographic functions. They
%  might be work for you language or do silly things, so you don't want
%  to use them. Every value different from 0 enables the functions.
%
%  The default is 1.
%!%-
custom_variable("LaTeX_Typo_Active", 1);

%!%+
%\variable{Integer_Type LaTeX_Typo_Word_Size}
%\synopsis{Defines the lowest number of characters in a word}
%\description
%  Some typographic functions, like typo_slash(), do different things
%  depending on the leading/following number of characters is more or
%  less than \var{LaTeX_Typo_Word_Size}. The typo_slash() function, e.g.,
%  do not replace the / by \slash{} if the number of characters before
%  the / is not at least \var{LaTeX_Typo_Word_Size}.
%
%  The default is 4.
%!%-
custom_variable("LaTeX_Typo_Word_Size", 4);

%%%%%%%%%%
%
% Declarations needed somewhere before the definition
%

static define array_edit_column_format() { throw NotImplementedError; }
static define boenv() { throw NotImplementedError; }
static define bsearch_matching_brace() { throw NotImplementedError; }
static define cmd_insert() { throw NotImplementedError; }
static define cmd_parse_args() { throw NotImplementedError; }
static define env_name() { throw NotImplementedError; }
static define eoenv() { throw NotImplementedError; }
private define pkg_find() { throw NotImplementedError; }
static define is_escaped() { throw NotImplementedError; }
static define texdoc() { throw NotImplementedError; }

%%%%%%%%%%
%
% Some constants
%
static variable TeX_Command_Chars = "a-zA-Z@*";

%%%%%%%%%%
%
% Tools
%
static define is_commented()
{
   % When parse_to_point() says inside a string we can't trust him. The X in
   % the following text is not inside of a comment for parse_to_point()
   %     $\alpha % X
   %      \beta$
   % But parse_to_point() has no informations (define_syntax()) about
   % strings, so this should not be a problem.
   return parse_to_point() == -2;
}

private define bocomment()
{
   !if ( is_commented() )
     return 0;

   while ( andelse {bfind_char('%')} {is_commented()} );
   return 1;
}

private define chop_star(str)
{
   if (andelse {str != NULL} {str[-1] == '*'})
     return str[[:-2]];
   else
     return str;
}

static define is_escaped()
{
   variable pnt = _get_point();
   bskip_chars("\\");
   variable pnt2 = _get_point();
   _set_point(pnt);
   return (pnt - pnt2) mod 2 == 1;
}

private define trim()
{
   skip_white();
   push_mark();
   bskip_white();
   if ( looking_at_char(' ') and is_escaped() )
     () = right(1);
   del_region();
}

static variable verbatim_commands = "bibitem,cite,href,include,includegraphics,input,label,nolinkurl,ref,url";
static variable verbatim_environments = "verbatim,Verbatim";

static define is_verbatim()
{
   if ( is_commented() )
     return 1;

   variable start_mark = create_user_mark();
   try
   {
       bsearch_matching_brace();

       push_mark();
       bskip_chars(TeX_Command_Chars);
       if ( blooking_at("\\") )
       {
           if ( is_list_element(verbatim_commands, bufsubstr(), ',') )
             return 1;
       }
       else
         pop_mark(0);
   }
   catch DataError;
   finally
     goto_user_mark(start_mark);

   try
   {
       if ( bfind("\\verb") )
       {
           () = right(5);
           what_char();
           () = right(1);
           if ( ffind_char( () ) )
             () = right(1);
           else
             return 1;

           if (create_user_mark() > start_mark)
             return 1;
       }
   }
   finally
     goto_user_mark(start_mark);

   variable e_name = env_name();
   if ( andelse {e_name != NULL}
        {is_list_element(verbatim_environments, __tmp(e_name), ',')} )
     return 1;

   return 0;
}

private variable math_environments = "gather,align,flalign,alignat,multline";
private variable anti_math_commands = "text,mbox,intertext,label,raisebox,footnote,makebox";

static define is_math()
{
   if ( is_verbatim() )
     return 0;

   push_spot();
   try
   {
       variable math_open = 0, math_seen = 0;
       forever
       {
           bskip_chars("^{}$_^[]");
           !if ( left(1) )          % bobp() == TRUE
             return math_open;

           variable is_bracket = what_char() == '[' or what_char() == ']';

           if ( orelse {bocomment()} {is_escaped() != is_bracket} )
             continue;

           switch ( what_char() )
           { case '$':
               math_open = not math_open;
               math_seen = 1;
           }
           { case '^' or case '_': return not math_open; }
           { case '{':
               if (math_seen)
                 return math_open;

               % This is for \raisebox, because the text is in the
               % second argument
               while ( left(1) and looking_at_char('}') )
                 bsearch_matching_brace();
               () = right(1);

               push_mark();
               bskip_chars(TeX_Command_Chars);
               variable cmd_name = bufsubstr();
               () = left(1);
               if ( looking_at_char('\\') and
                    is_list_element(anti_math_commands, cmd_name, ',') )
                 return 0;
           }
           { case '}':
               bsearch_matching_brace();
               push_mark();
               bskip_chars(TeX_Command_Chars);
               variable cmd = chop_star( bufsubstr() );

               () = left(1);
               if ( looking_at_char('\\') )
               {
                   switch (cmd)
                   { case "begin":
                       () = right(7);
                       push_mark();
                       () = ffind_char('}');
                       variable e_name = bufsubstr();
                       if (e_name == "document")
                         return math_open;

                       if ( is_list_element(math_environments,
                                            chop_star(e_name), ',') )
                         return 1;

                       if ( is_list_element(verbatim_environments,
                                            chop_star(e_name), ',') )
                         return 0;

                       if (math_seen)
                         return math_open;

                       () = left(strlen(e_name) + 7);
                   }
                   { case "end":
                       () = right(5);
                       push_mark();
                       () = ffind_char('}');
                       if ( is_list_element("document,"+math_environments,
                                            chop_star(bufsubstr()), ',') )
                         return math_open;

                       () = bfind("\\end");
                       boenv();
                   }
                   { is_list_element("chapter,section,subsection,subsubsection,paragraph",
                                     cmd, ','):
                       return math_open;
                   }
                   { is_list_element("frac,overline,underbrace", cmd, ','):
                       % cmd_list[cmd].math
                       return not math_open;
                   }
               }
               else
                 () = right(1);
           }
           { case '[': return 1; }
           { case ']': return math_open; }
       }
   }
   finally
     pop_spot();
}

private define str_compress_tex(str)
{
   variable a_str = bstring_to_array(str), ins = -1, i;

   for (i=0; i < length(a_str); ++i)
   {
       switch (a_str[i])
       { case '%':                              % "%.*\n[ \t]*"  ->  ""
           if (ins < 0)
             ins = i;

           do
             ++i;
           while (andelse {i < length(a_str)} {a_str[i] != '\n'});

           do
             ++i;
           while (andelse {i < length(a_str)}
                    {a_str[i] == '\t' or a_str[i] == ' '});

           --i;
           continue;
       }
       { case '\n':                             % "\n[ \t]*"  ->  " "
           if (ins < 0)
             ins = i;

           do
             ++i;
           while (andelse {i < length(a_str)}
                    {a_str[i] == '\t' or a_str[i] == ' '});

           a_str[ins] = ' ';
           ++ins;
           --i;
           continue;
       }
       { case '\\':                             % escape seqences \% and \
           if (ins >= 0)
           {
               a_str[ins] = a_str[i];
               ++ins;
               ++i;
               if (i >= length(a_str))
                 break;
           }
       }
       { case ' ' or case '\t':
           variable old_i = i;
           while (andelse {i+1 < length(a_str)}
                    {a_str[i+1] == '\t' or a_str[i+1] == ' '})
             ++i;
           if (i - old_i > 0 and ins < 0)
             ins = old_i;
       }

       if (ins >= 0)
       {
           a_str[ins] = a_str[i];
           ++ins;
       }
   }

   if (ins == -1)
     return str;
   else
     return unpack("s$ins"$, array_to_bstring(a_str));
}

static define fsearch_matching_brace()
{
   if ( is_commented() )
     throw UsageError, "Starting inside of a comment is not possible";

   variable start_mark = create_user_mark(), open_lbraces = list_new();

   if ( looking_at_char('}') )
     return;

   forever
   {
       () = right(1);
       skip_chars("^{}");
       !if ( looking_at_char('{') or looking_at_char('}') )
         break;

       if ( is_commented() )
       {
           eol();
           continue;
       }
       if ( is_escaped() )
         continue;

       switch ( what_char() )
       { case '{':
           list_append(open_lbraces, [what_line, what_column()], -1);
       }
       { case '}':
           if (length(open_lbraces) == 0)
             return;

           list_delete(open_lbraces, -1);
       }
   }

   goto_user_mark(start_mark);
   if (length(open_lbraces) > 0)
     throw DataError, "No matching } found for { in line " +
       string(open_lbraces[-1][0]) + " column " + string(open_lbraces[-1][1]);
   else
     throw DataError, "No matching } found";
}

static define bsearch_matching_brace()
{
   if ( is_commented() )
     throw UsageError, "Starting inside of a comment is not possible";

   variable start_mark = create_user_mark(), open_rbraces = list_new();
   forever
   {
       bskip_chars("^{}");
       !if ( left(1) )         % bobp() == TRUE
         break;

       if ( orelse {bocomment()} {is_escaped()} )
         continue;

       switch ( what_char() )
       { case '{':
           if (length(open_rbraces) == 0)
             return;

           list_delete(open_rbraces, -1);

           push_mark();
           bskip_chars(TeX_Command_Chars);
           variable cmd = chop_star(bufsubstr());
           if ( andelse {strlen(cmd) > 0} {blooking_at("\\")} )
           {
               () = left(1);
               if (cmd == "end")
                 boenv();
               else if ( is_list_element("begin,section,chapter,subsection,subsubsection,paragraph",
                                   cmd, ',') )
                 break;
           }
       }
       { case '}':
           list_append(open_rbraces, [what_line, what_column()], -1);
       }
   }

   goto_user_mark(start_mark);
   if (length(open_rbraces) > 0)
     throw DataError, "No matching } found for { in line " +
       string(open_rbraces[-1][0]) + " column " + string(open_rbraces[-1][1]);
   else
     throw DataError, "No matching } found";
}

static define search_not_commented(func, arg, skip)
{
   forever
   {
       variable ret = @func(arg);
       !if ( andelse {ret} {is_commented()} )
         return ret;

       () = right(skip);
   }
}

private define make_sorted_desc_list(list)
{
   variable compl = {}, keys = assoc_get_keys(list);
   foreach ( keys[array_sort(keys)] )
   {
       variable key = ();
       list_append(compl, list[key], -1);
   }
   return compl;
}

%%%%%%%%%%
%
% Stuff for \documentclass[]{}
%

private define doc_find()
{
   bob();
   !if ( search_not_commented(&fsearch, "\\documentclass", 1) )
     throw DataError, "Your document misses a \\documentclass";

   () = right(14);                      % skip \documentclass
}

private define doc_options_class()
{
   variable old_buf = whatbuf();
   latex_external->jump_to_master_buffer();
   push_spot();
   try
   {
       doc_find();
       variable args, class;
       (args,class) = cmd_parse_args(1,1);
       if (length(args) == 0)
         return ("", class[0]);
       else
         return (str_delete_chars(args[0], "\\s"), class[0]);
   }
   finally
   {
       pop_spot();
       setbuf(old_buf);
   }
}

%%%%%%%%%%
%
% Package stuff
%

typedef struct {
   options, desc, texdoc, hook, compl
} Pkg_Type;
private variable pkg_list = Assoc_Type[Pkg_Type];

static define pkg_register(name, opt, desc, texdoc, hook)
{
   try
   {
       % This is a tricky way to check if all variables are strings
       () = _typeof([name, desc, texdoc, opt, ""]);
   }
   catch TypeMismatchError:
   {
       throw InvalidParmError, "One of the given arguments has invalid type";
   }
   if (strlen(name) == 0)
     throw InvalidParmError, "name can not be empty";

   if (hook != NULL and typeof(hook) != Ref_Type)
     throw InvalidParmError, "The argument hook must be a reference or NULL";

   variable tmp;
   if ( assoc_key_exists(pkg_list, name) )
     tmp = pkg_list[name];
   else
   {
       tmp = @Pkg_Type;
       pkg_list[name] = tmp;
   }

   tmp.compl = name;
   tmp.options = opt;
   tmp.desc = desc;
   tmp.texdoc = texdoc;
   tmp.hook = hook;
}

private define pkg_hook_autoloaded_by_powerdot(name)
{
   doc_options_class();
   exch;
   pop;
   if ( () == "powerdot" )
     return 1;
   return pkg_find(name);
}

private define pkg_hook_babel(name)
{
   if ( pkg_find(name) )
     return 1;

   variable mark = create_user_mark();
   doc_find();
   push_spot();
   variable class;
   (,class) = cmd_parse_args(1,1);
   pop_spot();
   if ( class[0] == "powerdot" )
     % babel must be loaded before \documentclass, otherwise hyperref
     % (loaded by powerdot) didn't know it.
     () = left(14);
   else
     % suggest the point that pkg_find() suggested
     goto_user_mark(mark);

   return 0;
}

pkg_register("amsmath",        "", "The main package for mathematical stuff", "amsldoc", NULL);
pkg_register("amssymb",        "", "Symbols from AMS-Math", "", NULL);
pkg_register("avant",          "", "Sets Avant Garde as default sans serif font", "psnfss2e", NULL);
private variable _lang = getenv("LANG"), _babel_opt = "";
if (_lang != NULL and _lang != "C")
{
   switch (_lang[[0:1]])
   { case "de": _babel_opt = "ngerman"; }
   { case "it": _babel_opt = "italian"; }
   { case "en": _babel_opt = "english"; }
}
pkg_register("babel",          _babel_opt, "Provides local hyphenation", "", &pkg_hook_babel());
__uninitialize(&_lang);
__uninitialize(&_babel_opt);
pkg_register("bookman",        "", "Sets Bookman (roman), Avant Garde (sans serif) and Courier (typewriter) as default font", "psnfss2e", NULL);
pkg_register("chancery",       "", "Sets Zapf Chancery as default roman font", "psnfss2e", NULL);
pkg_register("charter",        "", "Sets Charter as default roman font", "psnfss2e", NULL);
pkg_register("courier",        "", "Sets Courier as default typewriter font", "psnfss2e", NULL);
pkg_register("eulervm",        "euler-digits", "Sets Euler fonts as default math font", "", NULL);
pkg_register("fontenc",        "T1", "", "", NULL);
pkg_register("graphicx",       "final", "Package for graphics", "graphics", NULL);
pkg_register("helvet",         "scaled=.92", "Sets Helvetica as default sans serif font", "psnfss2e", NULL);
pkg_register("hyperref",       "draft=false,colorlinks,urlcolor=blue,breaklinks", "", "hyperref/manual", &pkg_hook_autoloaded_by_powerdot());
#if (_slang_utf8_ok)
pkg_register("inputenc",       "utf8", "Provides direct input of non-ascii characters", "", NULL);
#else
pkg_register("inputenc",       "latin1", "Provides direct input of non-ascii characters", "", NULL);
#endif
pkg_register("lmodern",        "", "Sets Latin Modern as default font", "", NULL);
pkg_register("mathpazo",       "osf,slantedGreek", "Sets Adobe Palatino as default roman font", "psnfss2e", NULL);
pkg_register("mathptmx",       "", "Sets Times as default roman font", "psnfss2e", NULL);
pkg_register("newcent",        "", "Sets New Century Schoolbook (roman), Avant Garde (sans serif) and Courier (typewriter) as default font", "psnfss2e", NULL);
pkg_register("pstricks",       "", "Draws PS graphics with LaTeX commands", "pstricks-doc", &pkg_hook_autoloaded_by_powerdot());
pkg_register("suetterl",       "", "Provides Suetterlin kind font (use \textsuetterlin and \suetterlin)", "", NULL);
pkg_register("type1ec",        "", "Sets Computer Modern super as default font", "", NULL);
pkg_register("xcolor",         "", "Colors for LaTeX", "", &pkg_hook_autoloaded_by_powerdot());

private define pkg_find(name)
{
   eob();
   variable after_last_pkg = create_user_mark();
   bob();
   while ( re_fsearch("\\[buR][es][geq][ipu]"R) )    % Fixme: PCRE \\(begin|(Require|use)package)
   {
       if ( is_commented() )
       {
           eol();
           continue;
       }

       if ( looking_at("\\begin{document}") )
       {
           goto_user_mark(after_last_pkg);
           return 0;
       }

       variable is_usepackage = 0;
       if ( looking_at("\\usepackage") )
       {
           is_usepackage = 1;
           () = right(11);
       }
       else if ( looking_at("\\RequirePackage") )
         () = right(15);
       else
       {
           () = right(1);
           continue;
       }

       variable mark_before_args = create_user_mark();

       variable p;
       (, p) = cmd_parse_args(1, 1);
       if ( is_list_element(str_delete_chars(p[0], "\\s"), name, ',') )
       {
           goto_user_mark(mark_before_args);
           return 1;
       }

       if (is_usepackage)
         % If this wasn't the expected package, move the mark behind the
         % \usepackage command
         move_user_mark(after_last_pkg);
   }

   throw DataError, "no \\begin{document} found";
}

private define pkg_insert()
{
   variable opt;
   if (_NARGS >= 2)
     opt = ();

   variable name = ();

   if (name == "")
     return;

   variable old_buf = whatbuf();
   latex_external->jump_to_master_buffer();
   push_spot();
   try
   {
       variable hook = NULL;
       if ( assoc_key_exists(pkg_list, name) )
         hook = pkg_list[name].hook;

       if (hook != NULL)
       {
           if ( @hook(name) )
             return;
       }
       else if ( pkg_find(name) )
         return;

       if ( eobp() )
         % pkg_find() or the hook didn't found any \usepackage
       {
           doc_find();
           (,) = cmd_parse_args(1,1);

           !if ( down(1) )
             newline();
       }
       else
       {
           forever
           {
               push_spot();
               skip_white();
               looking_at_char('%');
               pop_spot();
               !if ( () )
                 break;
               () = down(1);
           }
           if ( bolp() )
             % We are not in the line with the last \usepackage and
             % skipped a comment
             () = up(1);
       }

       push_spot();
       bsearch("\\documentclass");
       pop_spot();
       if ( () )
         % after \documentclass
         insert("\n\\usepackage");
       else
         % before \documentclass
         insert("\n\\RequirePackage");

       if ( __is_initialized(&opt) )
       {
           if (opt != "")
             insert("[$opt]"$);
       }
       else if ( assoc_key_exists(pkg_list, name) )
       {
           opt = pkg_list[name].options;
           if (opt != "")
             insert("[$opt]"$);
       }

       insert("{$name}"$);
       !if ( eolp() )
         newline();
   }
   finally
   {
       pop_spot();
       setbuf(old_buf);
   }
}

private define insert_pkgs(str)
{
   if (str == "")
     return;

   foreach ( strchop(str, ':', 0) )
   {
       variable pkg = (), pos = is_substr(pkg, ","), opt;

       if (pos)
         pkg_insert(substr(pkg, 1, pos-1), substr(pkg, pos+1, strlen(pkg)) );
       else
         pkg_insert(pkg);
   }
}

static define pkg_prompt()
{
   try
   {
       variable pkg = read_with_description("Select a package:", "", "",
                                            make_sorted_desc_list(pkg_list));

       if (pkg == "")
         return;

       variable options, new_pkg = not assoc_key_exists(pkg_list, pkg);
       if (new_pkg)
         options = "";
       else
         options = pkg_list[pkg].options;

       options = read_mini("Options:", "", options);

       pkg_insert(pkg, options);
       if (andelse {new_pkg} {LaTeX_File_New != NULL} {strlen(LaTeX_File_New) > 0})
       {
           variable desc = read_mini("A description for the new package:", "", "");
           variable fp = fopen(LaTeX_File_New, "a");
           () = fprintf(fp, "latex->pkg_register(\"%- 21s \"%s\"R, \"%s\"R, \"\", NULL);\n",
                        pkg + "\",", options, desc);
           () = fclose(fp);
       }
       message("\\usepackage{$pkg} inserted."$);
   }
   catch UserBreakError;
}

static define pkg_loaded(pkg)
{
   variable old_buf = whatbuf();
   latex_external->jump_to_master_buffer();
   push_spot();
   try
   {
       variable hook = NULL;
       if ( assoc_key_exists(pkg_list, pkg) )
         hook = pkg_list[pkg].hook;

       if (hook != NULL)
         return @hook(pkg);
       else
         return pkg_find(pkg);
   }
   finally
   {
       pop_spot();
       setbuf(old_buf);
   }
}

private define pkg_options(pkg)
{
   variable old_buf = whatbuf();
   latex_external->jump_to_master_buffer();
   push_spot();
   try
   {
       variable hook = NULL;
       if ( assoc_key_exists(pkg_list, pkg) )
         hook = pkg_list[pkg].hook;

       if (hook != NULL)
       {
           !if ( @hook(pkg) )
             throw InternalError, "Package $pkg not found"$;
       }
       else !if ( pkg_find(pkg) )
         throw InternalError, "Package $pkg not found"$;

       variable args;
       (args,) = cmd_parse_args(1, 0);
       if (length(args) == 0)
         return "";
       else
         return args[0];
   }
   finally
   {
       pop_spot();
       setbuf(old_buf);
   }
}

static define pkg_help()
{
   try
   {
       variable pkg;
       if (_NARGS)
       {
           pkg = ();
           if ( is_substr(pkg, ":") )
             pkg = strchop(pkg, ':', 0)[0];
       }
       else
       {
           pkg = read_with_description("Help for which package:", "", "",
                                       make_sorted_desc_list(pkg_list));
           if (pkg == "")
             return;
       }

       variable help;
       if ( assoc_key_exists(pkg_list, pkg) )
       {
           help = pkg_list[pkg].texdoc;
           if (strlen(help) == 0)
             help = pkg;
       }
       else
         help = pkg;

       texdoc(help);
   }
   catch UserBreakError;
}

%%%%%%%%%%
%
% All about indentation
%

%!%+
%\function{env_name_indent}
%\synopsis{Gets the name and the indention of the current environment}
%\usage{(String_Type name, Integer_type indent) env_name_indent()}
%\description
% This function returns the name of the environment the editing point is in
% and the count of characters before \begin{...} in this line. If it isn't
% within a environment, name is NULL and the value of indent is undefined.
%
%\seealso{boenv(), env_name()}
%!%-
%\usage{(String_Type name, Integer_type indent) env_name_indent([Integer_Type])}
% If the optional parameter is unequal to zero to editing point is moved
% there.
private define env_name_indent()
{
   push_spot();
   try
   {
       variable name = env_name(1);
       return (name, what_column()-1);
   }
   finally
     pop_spot();
}

private define bsearch_command_indent()
{
   variable backslash_mark, start_mark = create_user_mark();
   forever {
       !if ( bsearch_char('\\') )
         error("Unknown case: opening brace, but no \\");

       backslash_mark = create_user_mark();

       % fix me!
       % \framebox[.8\textwidth]{
       variable found = orelse {find_matching_delimiter('{') != 1}
                               {create_user_mark() > start_mark};

       goto_user_mark(backslash_mark);

       if (found)
         % start_mark is the opening brace for the command at
         % backslash_mark
         break;
       % else
       %   the found backslash is inside of {} which are closed before
       %   start_mark
   }
   return what_column()-1;
}

private define calc_indention(env_name, line)
{
   if (line != NULL)
   {
       line = strtrim_beg(line);
       foreach ( [ {"\\item", LaTeX_Indent_Item},
                  {"\\intertext{", LaTeX_Indent_First},
                  {"\\bibitem{", LaTeX_Indent_Item}, {"\\end{", 0}] )
       {
           variable tmp = ();
           if (strncmp(line, tmp[0], strlen(tmp[0])) == 0)
             return tmp[1];
       }
   }

   switch ( chop_star(env_name) )
   { case "document" or case "verbatim" or case "Verbatim": return 0; }
   { case "gather" or case "align" or case "tabular" or case "tabularx":
       () = up(1);

       variable indent = LaTeX_Indent_First;
       !if ( orelse {blooking_at("\\\\")}
            {blooking_at("\\hline")}
            {bfind("\\intertext{")}
            {bfind("\\begin{"+env_name+"}")} % first line after \begin
            )
         indent += LaTeX_Indent_Continued;

       () = down(1);
       return indent;
   }
   { case NULL: return 0; }

   return LaTeX_Indent_First;
}

private define newline_indent_hook()
{
   variable indent, start_mark = create_user_mark(), name, env_mark;

   () = bocomment();

   name = env_name(1);
   if (name == NULL) {
       % we are outside of \begin{document}
       indent = 0;
       env_mark = NULL;
   }
   else {
       indent = what_column()-1;
       env_mark = create_user_mark();
       goto_user_mark(start_mark);
   }

   if (andelse {find_matching_delimiter('}') == 1}
       {orelse {env_mark == NULL}
           {create_user_mark() > env_mark} }) {
       % we are inside of braces
       name = "{";
       indent = what_column()-1;
       bskip_white();
       !if (bolp())
         indent = bsearch_command_indent();
   }
   goto_user_mark(start_mark);
   trim();
   newline();

   indent += calc_indention(name, NULL);

   whitespace(indent);
}

private define indent_hook()
{
   variable mark = create_user_mark();
   bol();
   trim();
   if (eolp()) mark = NULL;   % catch the case of a line with only whitespaces

   variable start_mark = create_user_mark();
   variable indent;
   switch ( what_char() )
   { case '}':
       if (find_matching_delimiter('}') != 1)
         error("No opening '{' found");

       indent = what_column()-1;
       bskip_white();
       !if (bolp()) % if opening brace is behind a command \...{
         indent = bsearch_command_indent();
   }
   { case '{' and up(1) and left(1) and looking_at_char('%'):
       indent = bsearch_command_indent() + LaTeX_Indent_First;
       ()=right(1);
       skip_word_chars();
       skip_chars("%");
       !if (eolp())
         %   indent to the level of the command end
         indent = what_column()-1;
   }
   % This looks somewhat tricky, but we only handle cases where the
   % previous line is a comment. But we must move the editing point for
   % this so we must reset it.
   { case '%' and up(1) and is_commented() or
         (goto_user_mark(start_mark),0):
       ()=up(1);
       () = bfind_char('%');
       indent = what_column()-1;
   }
   {
       variable line = line_as_string();
       bol();

       variable env_mark, e_name, e_indent;

       e_name = env_name(1);
       if (e_name == NULL) {
           % we are outside of \begin{document}
           e_indent = 0;
           env_mark = NULL;
       }
       else {
           e_indent = what_column()-1;
           env_mark = create_user_mark();
           goto_user_mark(start_mark);
       }

       variable name;
       if (andelse {find_matching_delimiter('}') == 1}
           {orelse {env_mark == NULL} {create_user_mark() > env_mark} }) {
           % we are inside of braces
           name = "{";
           %! Fix me!
           indent = what_column()-1;
           bskip_white();
           !if (bolp()) {
               % { is not the begin of a line
               indent = bsearch_command_indent();
               bskip_white();
               !if (bolp()) {
                   % the command isn't the start of the line
                   indent = e_indent;
                   name = e_name;
               }
           }
       }
       else {
           indent = e_indent;
           name = e_name;
       }

       goto_user_mark(start_mark);
       indent += calc_indention(name, line);
   }

   goto_user_mark(start_mark);
   whitespace(indent);
   if (mark != NULL) goto_user_mark(mark);
}

private define wrap_hook()
{
   variable mark = create_user_mark();
   ()=up(1);
   !if ( is_commented() ) {
       goto_user_mark(mark);
       indent_hook();
       return;
   }

   % The wrap happened in a comment, so continue the comment
   () = bfind_char('%');

   variable indent = what_column()-1;
   () = right(1);
   skip_white();
   variable inner_indent = what_column() - indent - 2;
   () = down(1);

   whitespace(indent);
   insert_char('%');
   whitespace(inner_indent);

   goto_user_mark(mark);
}

private define wrapok_hook()
{
   push_spot();
   variable e_n;
   try
   {
       () = bocomment();
       e_n = env_name();
   }
   finally
     pop_spot();

   if ( andelse {e_n != NULL}
        {is_list_element("verbatim,Verbatim", e_n, ',')} )
     return 0;

   push_spot();
   try
   {
       bskip_chars("^ \t");
       bskip_chars(" \t%");
       return not bolp();
   }
   finally
     pop_spot();
}

static define indent_region()
{
   variable start_mark = create_user_mark();
   check_region(0);

   variable end_mark = create_user_mark();
   pop_mark(1);
   variable beg_mark = create_user_mark();

   variable env_stack = {};
   variable level;
   do
   {
       level = struct { name, indent };
       try
         level.name = env_name(1);
       catch AnyError:
         break;

       if (level.name == NULL)
         level.indent = 0;
       else
         level.indent = what_column()-1;
       list_insert(env_stack, level, 0);
   }
   while (level.name != NULL);

   goto_user_mark(beg_mark);

   level = list_pop(env_stack, -1);
   () = up(1);
   while (down(1) and create_user_mark() < end_mark)
   {
       bol();
       trim();
       if ( eolp() )
         continue;

       variable line = line_as_string();
       bol();
       variable this_line_indention = level.indent +
         calc_indention(level.name, line);

       whitespace(this_line_indention);

       while ( andelse {ffind_char('\\')} {not is_commented()} )
       {
           () = right(1);
           if ( looking_at("begin{") )
           {
               () = right(6);
               list_append(env_stack, level, -1);
               level = struct { name, indent };
               push_mark();
               () = ffind_char('}');
               level.name = bufsubstr();
               level.indent = this_line_indention;
           }
           else
           {
               if ( looking_at("end{") )
               {
                   () = right(4);
                   if ( looking_at(level.name+"}") )
                     level = list_pop(env_stack, -1);
                   else
                     throw ApplicationError, "Expected \\end{"+level.name+"}";
               }
           }
       }
   }
   goto_user_mark(start_mark);
}

%%%%%%%%%%
%
% Completion in environments
%

typedef struct { pre_nl, post_nl } nl_completion_type;
private variable nl_completion = Assoc_Type[nl_completion_type];

static define set_nl_completion(env, pre, post)
{
   if ( typeof(env) != String_Type )
     throw InvalidParmError, "env must be a string";
   if ( pre != NULL and typeof(pre) != String_Type )
     throw InvalidParmError, "pre must be a string or NULL";
   if ( post != NULL and typeof(post) != String_Type )
     throw InvalidParmError, "post must be a string or NULL";

   if ( assoc_key_exists(nl_completion, env) )
   {
       if (pre == NULL and post == NULL)
         assoc_delete_key(nl_completion, env);
       else
       {
           if (pre != NULL)
             nl_completion[env].pre_nl = pre;
           if (post != NULL)
             nl_completion[env].post_nl = post;
       }
   }
   else
   {
       if (pre == NULL) pre = "";
       if (post == NULL) post = "";
       nl_completion[env] = @nl_completion_type;
       nl_completion[env].pre_nl = pre;
       nl_completion[env].post_nl = post;
   }
}

set_nl_completion("align", "\\\\", "");
set_nl_completion("array", "\\\\", "");
set_nl_completion("cases", "\\\\", "");
set_nl_completion("compactdesc", "", "\\item[]");
set_nl_completion("compactenum", "", "\\item ");
set_nl_completion("compactitem", "", "\\item ");
set_nl_completion("description", "", "\\item[]");
set_nl_completion("enumerate", "", "\\item ");
set_nl_completion("flalign", "\\\\", "");
set_nl_completion("gather", "\\\\", "");
set_nl_completion("itemize", "", "\\item ");
set_nl_completion("pmatrix", "\\\\", "");
set_nl_completion("tabular", "\\\\", "");
set_nl_completion("tabularx", "\\\\", "");
set_nl_completion("thebibliography", "", "\\bibitem{}");

static define newline_with_completion()
{
   if ( is_commented() ) {
       variable mark = create_user_mark();
       bol();
       skip_chars(" \t%");
       % new line become a comment, too
       bol();
       ()=ffind_char('%');
       variable indent = what_column()-1;

       () = right(1);
       skip_white();
       variable inner_indent = 0;
       !if (eolp())
         inner_indent = what_column() - indent - 2;

       goto_user_mark(mark);
       newline();
       trim();
       whitespace(indent);
       insert_char('%');
       whitespace(inner_indent);
       return;
   }

   variable name;
   (name, indent) = env_name_indent();
   name = chop_star(name);

   variable compl;
   if ( assoc_key_exists(nl_completion, name) )
     compl = nl_completion[name];
   else
   {
       compl = @nl_completion_type;
       compl.pre_nl = "";
       compl.post_nl = "";
   }

   switch (name)
   { andelse {case "tabular" or case "tabularx"} {blooking_at("---")}:
       () = left(3);
       () = replace_chars(3, "\\hline");
   }
   {
       push_spot();
       variable stop_str = strtrim(compl.post_nl);
       if (stop_str == "")
         stop_str = strtrim(compl.pre_nl);
       variable skip_chars = "^$(){}[]" + substr(stop_str, 1, 1);
       variable close_stack = list_new(), open_stack = list_new();
       forever
       {
           bskip_chars(skip_chars);
           () = left(1);

           if ( looking_at(stop_str) )
             break;

           variable ch = what_char(), counterpart, alt_counterpart = "";
           switch (ch)
           { case ')' or case ']' or case '}':
               if ( andelse {ch == '}'} {is_escaped()} )
                 ch = "\\}";
               else
                 ch = char(ch);

               list_append(close_stack, ch, -1);
               continue;
           }
           { case '\\':
               if ( looking_at("\\end") )
                 boenv();
               else if ( looking_at("\\begin") )
                 break;

               continue;
           }
           { case '$':
               if ( is_escaped() )
               {
                   bskip_chars("\\");
                   continue;
               }
               else
               {
                   if (length(close_stack) == 0)
                   {
                       list_append(close_stack, "$", -1);
                   }
                   else if (close_stack[-1] == "$")
                     list_delete(close_stack, -1);

                   continue;
               }
           }
           { case '(': counterpart = ")"; alt_counterpart = "]"; }
           { case '[': counterpart = "]"; alt_counterpart = ")";  }
           { case '{':
               if ( is_escaped() )
                 counterpart = "\\}";
               else
                 counterpart = "}";
           }

           if (andelse {length(close_stack) != 0} {close_stack[-1] == "$"})
           {
               list_append(open_stack, "$", -1);
               list_delete(close_stack, -1);
           }

           if (length(close_stack) == 0)
             list_append(open_stack, counterpart, -1);
           else
           {
               if (close_stack[-1] == counterpart or
                   close_stack[-1] == alt_counterpart)
                 list_delete(close_stack, -1);
               else
                 list_append(open_stack, counterpart, -1);
           }
       }
       pop_spot();

       if (andelse {length(close_stack) != 0} {close_stack[-1] == "$"})
         list_append(open_stack, "$", -1);

       while (length(open_stack) > 0)
       {
           variable looking_for = list_pop(open_stack, 0);
           if ( looking_at(looking_for) )
             () = right( strlen(looking_for) );
           else
             insert(looking_for);
       }

       insert( compl.pre_nl );
   }

   trim();
   newline();
   whitespace( indent + calc_indention(name, compl.post_nl) );
   insert( compl.post_nl );
   if ( andelse {strlen(compl.post_nl) > 0}
        {is_substr("]}", char(compl.post_nl[-1]))} )
     () = left(1);
}

%%%%%%%%%%
%
% Environemts -- all between \begin{} and \end{}
%

typedef struct {
   args, desc, deps, hook, compl
} Env_Type;
private variable env_list = Assoc_Type[Env_Type];

static define env_register(name, args, desc, deps, hook)
{
   try
   {
       % This is a tricky way to check if all variables are strings
       () = _typeof([name, desc, ""]);
   }
   catch TypeMismatchError:
   {
       throw InvalidParmError, "One of the given arguments has invalid type";
   }
   if (strlen(name) == 0)
     throw InvalidParmError, "name can not be empty";
   if (typeof(args) != Integer_Type)
     throw InvalidParmError, "The argument args must be an integer";

   if (typeof(deps) != String_Type and typeof(deps) != Ref_Type)
     throw InvalidParmError, "The argument deps must be a string or a reference";

   if (hook != NULL and typeof(hook) != Ref_Type)
     throw InvalidParmError, "The argument hook must be a reference or NULL";

   variable tmp;
   if ( assoc_key_exists(env_list, name) )
     tmp = env_list[name];
   else
   {
       tmp = @Env_Type;
       env_list[name] = tmp;
   }

   tmp.compl = name;
   tmp.args = args;
   tmp.deps = deps;
   tmp.desc = desc;
   tmp.hook = hook;
}

private define env_hook_Verbatim(name, mark_after_begin)
{
   variable indent = what_column() - 1;
   if (indent > 0)
   {
       push_spot();
       goto_user_mark(mark_after_begin);
       insert("[gobble=$indent]"$);
       pop_spot();
   }
}

private define env_hook_star_label(name, mark_after_begin)
{
   if (name[-1] != '*')
     cmd_insert("label");
}

private define env_hook_array_format(name, mark_after_begin)
{
   eoenv();
   () = left(1);
   array_edit_column_format();
}

private define env_hook_gmatrix(name, mark_after_begin)
{
   variable type
     = char(get_mini_response("Which type do you want? (p) {b} [B] |v| ||V|| "));
   if ( is_substr("pbBvV", type) )
   {
       push_spot();
       goto_user_mark(mark_after_begin);
       insert("[$type]"$);
       pop_spot();
   }
}

env_register("abstract",       0, "", "", NULL);
env_register("align",          0, "Math environment with alignment", "amsmath", &env_hook_star_label());
env_register("array",          1, "", "", &env_hook_array_format());
env_register("bmatrix",        0, "", "amsmath", NULL);
env_register("Bmatrix",        0, "", "amsmath", NULL);
env_register("cases",          0, "", "amsmath", NULL);
env_register("center",         0, "", "", NULL);
env_register("compactdesc",    0, "", "paralist", NULL);
env_register("compactenum",    0, "", "paralist", NULL);
env_register("compactitem",    0, "", "paralist", NULL);
env_register("description",    0, "", "paralist", NULL);       % fixme: deps as hook
% env_register("displaymath",    0, "", "", NULL);
env_register("enumerate",      0, "", "paralist", NULL);       % fixme: deps as hook
env_register("figure",         0, "", "", NULL);
env_register("flushleft",      0, "", "", NULL);
env_register("flushright",     0, "", "", NULL);
env_register("gather",         0, "", "amsmath", &env_hook_star_label());
env_register("gmatrix",        0, "", "gauss", &env_hook_gmatrix());
env_register("itemize",        0, "", "paralist", NULL);       % fixme: deps as hook
env_register("list",           0, "", "", NULL);
env_register("longtable",      1, "", "longtable", &env_hook_array_format());
env_register("matrix",         0, "", "amsmath", NULL);
env_register("minipage",       1, "", "", NULL);
env_register("picture",        0, "", "", NULL);
env_register("pmatrix",        0, "", "amsmath", NULL);
env_register("proof",          0, "", "", NULL);
env_register("pspicture",      0, "A PSTricks picture", "pstricks", NULL);
env_register("quotation",      0, "", "", NULL);
env_register("quote",          0, "", "", NULL);
env_register("smallmatrix",    0, "", "amsmath", NULL);
env_register("tabbing",        0, "", "", NULL);
env_register("table",          0, "", "", NULL);
env_register("tabular",        1, "", "", &env_hook_array_format());
env_register("tabularx",       2, "", "tabularx", NULL);      % Fixme: hook = array_edit_column_format()
env_register("thebibliography", 1, "", "", NULL);
env_register("theorem",        0, "", "", NULL);
env_register("titlepage",      0, "", "", NULL);
env_register("verbatim",       0, "", "", NULL);
env_register("Verbatim",       0, "", "fancyvrb", &env_hook_Verbatim());
env_register("verse",          0, "", "", NULL);
env_register("vmatrix",        0, "", "amsmath", NULL);
env_register("Vmatrix",        0, "", "amsmath", NULL);

private define env_lookup()
{
   variable default;
   if (_NARGS >= 2)
     default = ();

   variable name = ();
   if (strlen(name) > 0)
   {
       if ( assoc_key_exists(env_list, name) )
         return env_list[name];

       if (name[-1] == '*')
         name = chop_star(name);
       else
         name = name + "*";
       if ( assoc_key_exists(env_list, name) )
         return env_list[name];
   }

   if (_NARGS >= 2)
     return default;
   else
     throw InternalError, "Environment $name not found and no default given"$;
}

%!%+
%\function{boenv}
%\synopsis{Go to the beginning of the environment}
%\usage{Integer_Type boenv()}
%\description
% This function searches for the beginning of the environment the editing
% point is in. If it finds a "\begin{...}" it moves the editing point to the
% begin of "\begin{...}" and returns 1. If no environment beginning is
% found (maybe the editing point is before \begin{document}) the editing
% point isn't moved and 0 is returned.
%!%-
static define boenv()
{
   if ( is_commented() )
     throw UsageError, "Starting inside of a comment is not possible";

   variable start_mark = create_user_mark(), open_ends = list_new();

   while ( re_bsearch("\\[be][en][gd][i{]"R) ) %! Fixme: PCRE: \\(begin|end)\{
   {
       if ( bocomment() )
         continue;

       if ( looking_at("\\begin{" ) )
       {
           if (length(open_ends) == 0)
             return;

           list_delete(open_ends, -1);
       }
       else if ( looking_at("\\end{") )
         list_append(open_ends, [what_line, what_column()], -1);
   }

   goto_user_mark(start_mark);
   if (length(open_ends) > 0)
     throw DataError, "No matching \\begin found for \\end in line " +
       string(open_ends[-1][0]) + " column " + string(open_ends[-1][1]);
   else
     throw DataError, "No matching \\begin found";
}

%!%+
%\function{eoenv}
%\synopsis{Go to the end of the environment}
%\usage{Integer_Type eoenv()}
%\description
% This function searches for the end of the environment the editing
% point is in. If it finds a "\end{...}" it moves the editing point to the
% begin of "\end{...}" and returns 1. If no environment end is
% found (maybe the editing point is before \begin{document}) the editing
% point isn't moved and 0 is returned.
%!%-
static define eoenv()
{
   if ( is_commented() )
     throw UsageError, "Starting inside of a comment is not possible";

   variable start_mark = create_user_mark(), open_begins = list_new();

   if ( looking_at("\\begin{") )
     () = right(1);

   while ( re_fsearch("\\[be][en][gd][i{]"R) ) %! Fixme: PCRE: \\(begin|end)\{
   {
       if ( is_commented() )
       {
           eol();
           continue;
       }

       if ( looking_at("\\end{" ) )
       {
           if (length(open_begins) == 0)
             return;
           list_delete(open_begins, -1);
       }
       else if ( looking_at("\\begin{") )
         list_append(open_begins, [what_line, what_column], -1);

       () = right(1);
   }

   goto_user_mark(start_mark);
   if (length(open_begins) > 0)
     throw DataError, "No matching \\end found for \\begin in line " +
       string(open_begins[-1][0]) + " column " + string(open_begins[-1][1]);
   else
     throw DataError, "No matching \\end found";
}

%!%+
%\function{env_name}
%\synopsis{Gets the name of the current environment}
%\usage{String_Type env_name([Integer_Type])}
%\description
% This function returns the name of the environment the editing point is in.
% If it isn't within an environment, NULL is returned.
%
% If the optional parameter is unequal to zero the editing point is placed
% at the beginning.
%
%\seealso{boenv()}
%!%-
static define env_name()
{
   variable stay_there = 0, spot = create_user_mark();
   if (_NARGS)
     stay_there = ();

   try
     boenv();
   catch DataError:
     % No \begin found
     return NULL;

   () = right(7);             % skip \begin{
   push_mark();
   if ( ffind_char('}') )
   {
       variable name = bufsubstr();
       () = left(strlen(name) + 7);                  % \begin{$name}
       !if (stay_there)
         goto_user_mark(spot);
       return name;
   }
   else
   {
       pop_mark(1);
       goto_user_mark(spot);
       throw DataError, "malformed \\begin{}";
   }
}

static define env_close ()
{
   variable mark = create_user_mark();
   bskip_white();

   variable e_name;
   if ( bolp() )
   {
       trim();
       variable e_indent;
       (e_name, e_indent) = env_name_indent();
       whitespace(e_indent);
   }
   else
   {
       goto_user_mark(mark);
       e_name = env_name();
   }
   if (e_name == NULL)
     throw UsageError, "Not within an environment";

   insert("\\end{$e_name}"$);
}

static define env_insert()
{
   variable def_args;
   if (_NARGS >= 2)
     def_args = ();
   else
     def_args = String_Type[0];

   variable name = ();

   variable env_data = env_lookup(name, NULL);

   if (env_data == NULL)
   {
       env_data = @Env_Type;
       env_data.args = 0;
       env_data.deps = "";
       env_data.hook = NULL;
   }

  if (typeof(env_data.deps) == String_Type)
     insert_pkgs(env_data.deps);
   else
     (@env_data.deps)(name);

   variable body;
   if ( dupmark() )
   {
       body = bufsubstr();
       del_region();
   }

   variable mark = create_user_mark();
   bskip_white();
   variable in_one_line = not bolp();
   if (in_one_line)
     goto_user_mark(mark);
   else
   {
       indent_line();
       skip_white();
   }

   insert("\\begin{$name}"$);

   variable point_after_begin = create_user_mark();

   if (length(def_args) > 0)
     insert("{" + strjoin(def_args, "}{") + "}");

   variable point_after_insertion;
   if (env_data.args - length(def_args) > 0)
   {
       insert_char('{');
       point_after_insertion = create_user_mark();
       loop (env_data.args - length(def_args) - 1)
         insert("}{");
       insert_char('}');
   }

   if (in_one_line)
   {
       if ( __is_initialized(&body) )
         insert(body);
       !if ( __is_initialized(&point_after_insertion) )
         point_after_insertion = create_user_mark();
   }
   else
   {
       newline();
       if ( __is_initialized(&body) )
       {
           push_mark();

           insert(body);
           !if ( __is_initialized(&point_after_insertion) )
             point_after_insertion = create_user_mark();

           !if ( bolp() )
             newline();
           indent_region();
       }
       else
       {
           variable compl;
           if ( assoc_key_exists(nl_completion, chop_star(name)) )
           {
               compl = nl_completion[chop_star(name)].post_nl;
               insert(compl);
           }

           indent_line();

           !if ( __is_initialized(&point_after_insertion) )
           {
               if (andelse {__is_initialized(&compl)} {strlen(compl) != 0})
               {
                   is_substr("]}", substr(compl, strlen(compl), 1));
                   dup();
                   if ( () )
                     () = left(1);
                   point_after_insertion = create_user_mark();
                   if ( () )
                     () = right(1);
               }
               else
                 point_after_insertion = create_user_mark();
           }
           newline();
       }
   }
   env_close();
   !if (in_one_line or eolp())
     newline();

   goto_user_mark(point_after_insertion);
   if (env_data.hook != NULL)
     (@env_data.hook)(name, point_after_begin);
}

private variable env_last_env = "";

static define env_prompt()
{
   try
   {
       env_last_env = read_with_description("Select an environment:",
                                            env_last_env, "",
                                            make_sorted_desc_list(env_list));

       if (env_last_env == "")
         return;

       variable env_data = env_lookup(env_last_env, NULL), desc;
       if (env_data != NULL)
         desc = env_data.desc;
       else if (LaTeX_Register_New)
       {
           variable args, deps;
           args = integer( read_mini("How much arguments this environment " +
                                     "have?", "0", "") );
           desc = read_mini("A description for the new environment:", "", "");
           deps = read_mini("Colon separated list of packages needed for " +
                            "this environment:", "", "");

           env_register(env_last_env, args, desc, deps, NULL);
           if (andelse {LaTeX_File_New != NULL} {strlen(LaTeX_File_New) > 0})
           {
               variable fp = fopen(LaTeX_File_New, "a");
               () = fprintf(fp, "latex->env_register(\"%- 21s %u, \"%s\"R, \"%s\", NULL);\n",
                            env_last_env + "\",", args, desc, deps);
               () = fclose(fp);
           }
       }

       env_insert(env_last_env);
       message(desc);
   }
   catch UserBreakError;
}

static define env_rename()
{
   variable spot = create_user_mark();

   try
   {
       variable old_name = env_name(1);

       if (old_name == NULL)
         throw UsageError, "You aren't within an environment";

       variable new_name = read_with_description("Rename environment:",
                                                 old_name, "",
                                          make_sorted_desc_list(env_list));
       if (new_name == old_name)
         return;

       variable env_data = env_lookup(new_name, NULL);
       if (env_data == NULL and LaTeX_Register_New)
       {
           variable args, desc, deps;
           args = integer( read_mini("How much arguments this environment " +
                                     "have?", "0", "") );
           desc = read_mini("A description for the new environment:", "", "");
           deps = read_mini("Colon separated list of packages needed for " +
                            "this environment:", "", "");

           env_register(new_name, args, desc, deps, NULL);
           if (andelse {LaTeX_File_New != NULL} {strlen(LaTeX_File_New) > 0})
           {
               variable fp = fopen(LaTeX_File_New, "a");
               () = fprintf(fp, "latex->env_register(\"%- 21s %u, \"%s\"R, \"%s\", NULL);\n",
                            new_name + "\",", args, desc, deps);
               () = fclose(fp);
           }
           env_data = env_lookup(new_name);
       }

       if (env_data != NULL)
       {
           if (typeof(env_data.deps) == String_Type)
             insert_pkgs(env_data.deps);
           else
             (@env_data.deps)(new_name);
       }

       () = right(7);
       () = replace_chars(strlen(old_name), new_name);

       goto_user_mark(spot);
       eoenv();

       () = right(5);
       () = replace_chars(strlen(old_name), new_name);
   }
   catch UserBreakError;
   finally
     goto_user_mark(spot);
}

static define env_help()
{
   try
   {
       variable env;
       if (_NARGS)
         env = ();
       else
       {
           env = env_name();
           if (env == NULL)
             env = "";

           env = read_with_description("Help for which environment:", env,
                                       "", make_sorted_desc_list(env_list));
           if (env == "")
             return;
       }

       variable env_data = env_lookup(env, NULL), help;
       if (env_data != NULL)
       {
           help = env_data.deps;
           if (typeof(help) != String_Type)
             __uninitialize(&help);
       }

       if (andelse {__is_initialized(&help)} {strlen(help) != 0})
         pkg_help(help);
       else
         throw UsageError, "No information found about how to get help";
   }
   catch UserBreakError;
}

%%%%%%%%%%
%
% Command stuff, e.g. \foo[]{}
%

typedef struct {
   args, desc, deps, hook, math, compl
} Cmd_Type;
private variable cmd_list = Assoc_Type[Cmd_Type];

% compl: the name of the command, e.g. the string after the backslash; the
%   name compl makes it easier to pass the struct to read_with_description()
% args: how many arguments the command has?
% need_math: is it a command for a mathematical environment?
% desc: a description of the command
% deps: which packages the command depend on (colon separated list)
% hook: NULL or a reference to a function that gets called after inserting the
%   command from inside the first brace or after the command if the
%   command has no arguments
%   Call the function with the name of the command and a mark of the
%   point after the command.
static define cmd_register(name, args, need_math, desc, deps, hook)
{
   try
   {
       % This is a tricky way to check if all variables are strings
       () = _typeof([name, desc, deps, ""]);
   }
   catch TypeMismatchError:
   {
       throw InvalidParmError, "One of the given arguments has invalid type";
   }
   if (strlen(name) == 0)
     throw InvalidParmError, "name can not be empty";
   if (typeof(args) != Integer_Type or typeof(need_math) != Integer_Type)
     throw InvalidParmError, "The arguments args and need_math must be integers";

   if (hook != NULL and typeof(hook) != Ref_Type)
     throw InvalidParmError, "The argument hook must be a reference or NULL";

   variable tmp;
   if ( assoc_key_exists(cmd_list, name) )
     tmp = cmd_list[name];
   else
   {
       tmp = @Cmd_Type;
       cmd_list[name] = tmp;
   }

   tmp.compl = name;
   tmp.args = args;
   tmp.math = need_math;
   tmp.deps = deps;
   tmp.desc = desc;
   tmp.hook = hook;
}

() = evalfile("latex_cmds", current_namespace());

private define cmd_lookup()
{
   variable default;
   if (_NARGS >= 2)
     default = ();

   variable name = ();
   if (strlen(name) > 0)
   {
       if ( assoc_key_exists(cmd_list, name) )
         return cmd_list[name];

       if (name[-1] == '*')
         name = chop_star(name);
       else
         name = name + "*";
       if ( assoc_key_exists(cmd_list, name) )
         return cmd_list[name];
   }

   if (_NARGS >= 2)
     return default;
   else
     throw InternalError, "Command \\$name not found and no default given"$;
}

private define insert_space_after_cmd_hook(fun);
private define insert_space_after_cmd_hook(fun)
{
   if (andelse {typeof(fun) == String_Type} {fun == "self_insert_cmd"}
        { ('A' <= LAST_CHAR and LAST_CHAR <= 'Z') or
          ('a' <= LAST_CHAR and LAST_CHAR <= 'z') })
     insert_char(' ');

   remove_from_hook("_jed_before_key_hooks", &insert_space_after_cmd_hook());
}

static define cmd_insert()
{
   variable prefix = prefix_argument(0);

   variable def_args, ins_pkg;
   if (_NARGS >= 3)
     def_args = ();
   else
     def_args = String_Type[0];

   if (_NARGS >= 2)
     ins_pkg = ();
   else
     ins_pkg = 1;

   variable name = ();

   variable cmd_data = cmd_lookup(name, NULL);
   if (cmd_data == NULL)
   {
       insert_char('\\');
       insert(name);

       loop (prefix)
         insert("[]");

       if (prefix != 0)
         () = left(2 * prefix - 1);

       return;
   }

   if (ins_pkg)
     insert_pkgs(cmd_data.deps);
   else
     !if ( orelse {cmd_data.deps == ""} {pkg_loaded(cmd_data.deps)} )
       throw ApplicationError, "The depencies \"" + cmd_data.deps +
               "\" for the command \"$name\" aren't satisfied"$;

   if ( andelse {cmd_data.args != 0} {dupmark()} )
   {
       def_args = [def_args, bufsubstr()];
       del_region();
   }

   variable behind_insertion;

   if (andelse {cmd_data.math} {not is_math()} )
   {
       !if ( blooking_at("$") )
         insert("$$");

       behind_insertion = create_user_mark();
       () = left(1);
   }

   insert_char('\\');
   insert(name);

   push_spot();
   variable next_point;

   if (prefix)
   {
       insert_char('[');
       next_point = create_user_mark();
       loop (prefix - 1)
         insert("][");
       insert_char(']');
   }

   if (length(def_args) > 0)
     insert("{" + strjoin(def_args, "}{") + "}");

   loop (cmd_data.args - length(def_args))
     insert("{}");

   !if ( __is_initialized(&behind_insertion) )
     behind_insertion = create_user_mark();

   !if ( __is_initialized(&next_point) )
   {
       if (cmd_data.args - length(def_args) > 0)
       {
           () = left(2 * (cmd_data.args - length(def_args)) - 1);
           next_point = create_user_mark();
       }
       else
         next_point = behind_insertion;
   }

   pop_spot();

   if (cmd_data.hook == NULL)
     goto_user_mark(next_point);
   else
     (@cmd_data.hook)(name, behind_insertion);

   if (LaTeX_Auto_Space_After_Commands)
   {
       () = left(1);
       variable ch = what_char();
       () = right(1);

       if ( ('A' <= ch and ch <= 'Z') or ('a' <= ch and ch <= 'z') )
         add_to_hook("_jed_before_key_hooks", &insert_space_after_cmd_hook());
   }
}

private variable cmd_last_cmd = "";

static define cmd_prompt()
{
   try
   {
       cmd_last_cmd = read_with_description("Select a command:", cmd_last_cmd, "",
                                            make_sorted_desc_list(cmd_list));

       if (cmd_last_cmd == "")
         return;

       variable cmd_data = cmd_lookup(cmd_last_cmd, NULL), desc;
       if (cmd_data != NULL)
         desc = cmd_data.desc;
       else if (LaTeX_Register_New)
       {
           variable args, math, deps;
           args = integer( read_mini("How much arguments this command " +
                                     "have?", "0", "") );
           variable dflt;
           math = int(is_math());
           if (math)
             dflt = "Y/n";
           else
             dflt = "y/N";

           switch ( get_mini_response("Is this a mathematic command? (" +
                                      dflt + ") ") )
           { case 7: throw UserBreakError; }
           { case 'Y' or case 'y': math = 1; }
           { case 'N' or case 'n': math = 0; }

           % Fixme: This should be read_with_description() with the
           %   SYNOPSIS as description
           variable hook_as_string =
             read_string_with_completion("Name of a hook (leave if empty for none)",
                                         "", strjoin("latex->" +
                                                     _apropos("latex",
                                                              "cmd_hook", 3),
                                                     ","));
           variable hook = __get_reference(hook_as_string);
           if ( __is_callable(hook) )
             hook_as_string = "&" + hook_as_string + "()";
           else
           {
               hook = NULL;
               hook_as_string = "NULL";
           }

           desc = read_mini("A description for the new command:", "", "");
           deps = read_mini("Colon separated list of packages needed for " +
                            "this command:", "", "");

           cmd_register(cmd_last_cmd, args, math, desc, deps, hook);
           if (andelse {LaTeX_File_New != NULL} {strlen(LaTeX_File_New) > 0})
           {
               variable fp = fopen(LaTeX_File_New, "a");
               () = fprintf(fp, "latex->cmd_register(\"%- 21s %u, %u, \"%s\"R, \"%s\", %s);\n",
                            cmd_last_cmd + "\",", args, math, desc, deps,
                            hook_as_string);
               () = fclose(fp);
           }
       }

       cmd_insert(cmd_last_cmd);
       message(desc);
   }
   catch UserBreakError;
}

static define cmd_parse_args(opt_args, req_args)
{
   skip_chars(" \n\t");
   variable o_args = list_new();
   while ( andelse {opt_args > 0} {looking_at_char('[')} )
   {
       push_mark();
       try
       {
           forever
           {
               () = right(1);
               skip_chars("^{]");
               !if ( looking_at_char('{') or looking_at_char(']') )
                 throw DataError, "No matching ] found";

               if ( is_escaped() )
                 continue;

               if ( looking_at_char(']') )
               {
                   list_append(o_args,
                               str_compress_tex( substr(bufsubstr(), 2, -1) ),
                               -1);
                   push_mark();
                   break;
               }
               else
                 fsearch_matching_brace();
           }
       }
       finally
       {
           pop_mark(0);
       }
       () = right(1);
       skip_chars(" \n\t");
       --opt_args;
   }

   variable r_args = list_new();
   while (req_args > 0)
   {
       switch ( what_char() )
       { case '{':
           () = right(1);
           push_mark();
           try
           {
               fsearch_matching_brace();
               list_append(r_args, str_compress_tex(bufsubstr()), -1);
               --req_args;
               push_mark();
           }
           finally
             pop_mark(0);

           () = right(1);
       }
       { case '\\':
           push_mark();
           () = right(1);
           skip_chars(TeX_Command_Chars);
           list_append(r_args, bufsubstr(), -1);
           --req_args;
       }
       { case '%':
           () = down(1);
           skip_white();
       }
       { case ' ' or case '\n' or case '\t':
           skip_chars(" \n\t");
       }
       {
           list_append(r_args, char( what_char() ), -1);
           --req_args;
           () = right(1);
       }
   }

   return (o_args, r_args);
}

static define cmd_help()
{
   try
   {
       variable cmd;
       if (_NARGS)
         cmd = ();
       else
       {
           push_spot();
           bskip_chars(TeX_Command_Chars);
           push_mark();
           skip_chars(TeX_Command_Chars);
           cmd = bufsubstr();
           pop_spot();

           cmd = read_with_description("Help for which command:", cmd, "",
                                       make_sorted_desc_list(cmd_list));
           if (cmd == "")
             return;
       }

       variable cmd_data = cmd_lookup(cmd, NULL), help;
       if (cmd_data != NULL)
       {
           help = cmd_data.deps;
           if (typeof(help) != String_Type)
             __uninitialize(&help);
       }

       if (andelse {__is_initialized(&help)} {strlen(help) != 0})
         pkg_help(help);
       else
         throw UsageError, "No information found about how to get help";
   }
   catch UserBreakError;
}

%!%+
% Check if the editing point stays on a command. In this case return the
% command, otherwise NULL
%!%-
static define is_command()
{
   !if ( re_looking_at("\\[$TeX_Command_Chars]"R$) )
   {
       !if ( re_looking_at("[$TeX_Command_Chars]"$) )
         return NULL;

       variable mark = create_user_mark();

       bskip_chars(TeX_Command_Chars);
       !if ( andelse {left(1)} {looking_at_char('\\')} )
       {
           goto_user_mark(mark);
           return NULL;
       }
   }

   push_spot();
   push_mark();
   () = right(1);
   skip_chars(TeX_Command_Chars);
   bufsubstr();
   pop_spot();
   return ();
}

%%%%%%%%%%
%
% All about fonts
%

static define font_resize(decrease)
{
   variable sizes="tiny,scriptsize,footnotesize,small,normalsize,large,Large,LARGE,huge,Huge";
   variable start_mark = create_user_mark(), is_region = markp();

   if (is_region) {
       check_region(0);
       exchange_point_and_mark();
   } else if (orelse {looking_at_char('\\')} {bfind_char('\\')} )
         ()=right(1);

   variable mark = create_user_mark();
   push_mark();
   skip_word_chars();
   variable pos = is_list_element(sizes, bufsubstr(), ',');

   if ( pos ) {
       if (decrease)
         pos -= 2;

       if (pos == -1 or pos == 10) {
           goto_user_mark(start_mark);
           error("Font resizing not possible");
       }

       push_mark();
       goto_user_mark(mark);
       del_region();
   }
   else {
       if (is_region)
         insert_char('{');
       else
         % Maybe bfind_char doesn't find the correct \ - don't write there, it
         % might be wrong
         goto_user_mark(start_mark);

       insert_char('\\');
       if (decrease)
         pos = 3;           % small
       else
         pos = 5;           % large
   }

   insert( string(extract_element(sizes, pos, ',')) );

   mark = create_user_mark();
   skip_non_word_chars();
   if ( mark == create_user_mark() and not eolp() )
     insert_char(' ');

   goto_user_mark(start_mark);

   if (is_region)
     insert_char('}');
}

static define font_cmd()
{
   try
#ifexists jmini_prompt_string
     cmd_insert( jmini_prompt_string("Select a font command:", "", "",
                 ["bfseries", "emph", "itshape", "mathbf", "mathcal", "mathit",
                  "mathnormal", "mathrm", "mathsf", "mathtt", "mdseries",
                  "normalfont", "rmfamily", "scshape", "sffamily", "slshape",
                  "textbf", "textit", "textmd", "textnormal", "textrm",
                  "textsc", "textsf", "textsl", "texttt", "textup", "ttfamily",
                  "upshape"]) );
#else
     cmd_insert( read_with_completion("textrm,rmfamily,textit,"+
                 "itshape,emph,textmd,mdseries,textbf,bfseries,textup,"+
                 "upshape,textsl,slshape,textsf,sffamily,textsc,"+
                 "scshape,texttt,ttfamily,textnormal,normalfont,mathrm"+
                 "mathbf,mathsf,mathtt,mathit,mathnormal,mathcal",
                 "Select a font command:", "", "", 's') );
#endif
   catch UserBreakError: {}
}

static define templ_insert(file)
{
   push_mark();
   narrow_to_region();
   try
   {
       !if ( insert_file(file) )
         return;

       bob();
       if ( fsearch("%:default:classopt:") )
       {
           push_mark();
           () = right(19);
           if ( looking_at(",%") )
           {
               () = right(2);
               del_region();
               if ( strlen(LaTeX_Default_Class_Options) )
                 insert("," + LaTeX_Default_Class_Options);
           }
           else
           {
               if ( looking_at("[%") )
               {
                   () = right(2);
                   del_region();
                   if ( strlen(LaTeX_Default_Class_Options) )
                     insert("[" + LaTeX_Default_Class_Options + "]");
               }
               else
                 pop_mark(1);
           }
       }

       if ( bol_fsearch("%:default:pkgs:%\n") )
       {
           % set a marker for insert_pkgs where it should place the packages
           % if no other packages are present
           () = replace_chars(16, "\\usepackage{JLM marker}");
           bol();
           insert_pkgs(LaTeX_Default_Packages);
           deln(24);  % delete the marker line (with \n)
       }

       bob();
       if ( fsearch("%:start:%") )
         deln(9);
   }
   finally
     widen_region();
}

%%%%%%%%%%
%
% All about arrays like tabular or matrix
%

private define array_what_column()
{
   variable start = create_user_mark(), num = 1;

   forever
   {
       bskip_chars("^&}\\");
       !if ( left(1) )       % == bobp()
         break;
       if ( looking_at_char('&') and not is_escaped() )
       {
           ++num;
           continue;
       }
       if ( looking_at_char('}') and not is_escaped() )
       {
           bsearch_matching_brace();
           continue;
       }
       if ( looking_at("\\end") )
       {
           boenv();
           continue;
       }
       if ( looking_at("\\multicolumn") )
       {
           push_spot();
           () = right(13);
           push_mark();
           skip_chars("0-9");
           num += integer( bufsubstr() );
           pop_spot();
           continue;
       }
       if ( orelse {looking_at("\\\\")} {looking_at("\\begin")} )
         % This must be the begin of the array environment
         break;
   }

   goto_user_mark(start);
   return num;
}

private variable array_columns = Assoc_Type[Integer_Type, 0];

array_columns["align"] = 20;
array_columns["array"] = -1;
array_columns["bmatrix"] = 10;          % [ matrix ]
array_columns["Bmatrix"] = 10;          % \{ matrix \}
array_columns["cases"] = 2;
array_columns["longtable"] = -1;
array_columns["matrix"] = 10;
array_columns["pmatrix"] = 10;          % ( matrix )
array_columns["smallmatrix"] = 10;      % \footnotesize matrix
array_columns["tabular"] = -1;
array_columns["tabular*"] = -2;
array_columns["tabularx"] = -2;
array_columns["vmatrix"] = 10;          % | matrix |
array_columns["Vmatrix"] = 10;          % || matrix ||

static define array_new_cell()
{
   variable start = create_user_mark();
   variable e_name = env_name(1);

   variable num_cols;
   if ( assoc_key_exists(array_columns, e_name) )
     num_cols = array_columns[e_name];
   else
     num_cols = array_columns[ chop_star(e_name) ];

   if (num_cols < 0)
   {
       () = right(strlen(e_name) + 8); % \begin{$e_name}
       (,) = cmd_parse_args(1, -num_cols - 1);
       !if ( looking_at_char('{') )
       {
           goto_user_mark(start);
           throw InternalError;
       }
       () = right(1);

       num_cols = 0;
       variable factor = 1, factors = {};
       forever
       {
           variable point = _get_point();
           skip_chars("a-zA-Z");
           num_cols += factor * (_get_point() - point);

           switch ( what_char() )
           { case '}':
               if (length(factors) == 0)
                 break;
               factor = list_pop(factors);
           }
           { case '*':
               list_append(factors, factor, -1);
               () = right(2);
               push_mark();
               skip_chars("0-9");
               factor *= integer( bufsubstr() );
               () = right(1);
           }
           () = right(1);
           if ( looking_at_char('{') )
           {
               fsearch_matching_brace();
               () = right(1);
           }
       }
   }
   goto_user_mark(start);

   if (array_what_column() < num_cols)
     insert("& ");
   else
   {
       insert("\\\\\n");
       indent_line();
   }
}

static define array_next_cell()
{
   forever
   {
       skip_chars("^&{\\");
       if ( eobp() )
         % This is very curious, better we do nothing
         return;

       if ( andelse {looking_at_char('&')} {not is_escaped()} )
       {
           () = right(1);
           return;
       }
       if ( looking_at("\\\\") )
       {
           () = right(2);
           skip_chars(" \t\n");
           if ( looking_at("\\hline") )
           {
               () = right(6);
               skip_chars(" \t\n");
           }
           if ( looking_at("\\end") )
           {
               bskip_chars(" \t");
               if ( bolp() )
                 () = left(1);
           }
           return;
       }
       if ( looking_at("\\end") )
       {
           bskip_chars(" \t");
           if ( bolp() )
             () = left(1);
           array_new_cell();
           return;
       }
       if ( looking_at("\\begin") )
       {
           eoenv();
           () = ffind_char('}');
           continue;
       }
       if ( andelse {looking_at_char('{')} {not is_escaped()} )
       {
           fsearch_matching_brace();
           continue;
       }

       % this must be the begin of a command
       () = right(1);
   }
}

static define array_prev_cell()
{
   forever
   {
       bskip_chars("^&}\\");
       !if ( left(1) )       % == bobp()
         break;
       if ( andelse {looking_at_char('&')} {not is_escaped()} )
         break;
       if ( andelse {looking_at_char('}')} {not is_escaped()} )
       {
           bsearch_matching_brace();
           continue;
       }
       if ( looking_at("\\end") )
       {
           boenv();
           continue;
       }
       if ( looking_at("\\begin") )
       {
           % This must be the begin of the array environment
           () = right(6);
           do
           {
               fsearch_matching_brace();
               () = right(1);
           } while ( looking_at_char('{') );
           break;
       }
       if ( looking_at("\\\\") )
         break;
   }
}

static define array_edit_column_format()
{
   variable start = create_user_mark();
   variable e_name = env_name(1);

   variable num_cols;
   if ( assoc_key_exists(array_columns, e_name) )
     num_cols = array_columns[e_name];
   else
     num_cols = array_columns[ chop_star(e_name) ];

   if (num_cols >= 0)
   {
       goto_user_mark(start);
       throw UsageError, "environment has no column definition";
   }

   () = right(strlen(e_name) + 8); % \begin{$e_name}
   variable col;
   (,col) = cmd_parse_args(1, -num_cols);
   col = col[-1];
   try
   {
       variable col_len = strlen(col);
       col = read_mini("Column format:", "", col);
       if (col == "")
         return;

       () = left(col_len + 1);                      % $col}
       () = replace_chars(col_len, col);
   }
   catch UserBreakError;
   finally
   {
       goto_user_mark(start);
   }
}

%%%%%%%%%%
%
% Label
%

private variable label_insert_mark = NULL;

static define label_insert_at_mark()
{
   if (label_insert_mark == NULL) {
       label_insert_mark = create_user_mark();
       message("Now go to the point where you want to set the label and hit ^Clm again");
   }
   else {
       cmd_insert("label");
       if (andelse {left(1)} {looking_at_char('}')}) {
           push_mark();
           ()=bfind_char('{');
           ()=right(1);
           variable label = bufsubstr();

           goto_user_mark(label_insert_mark);

           if ( pkg_loaded("hyperref") )
             "\\autoref{";
           else if ( andelse {label[[0:1]] == "eq"} {pkg_loaded("amsmath")} )
             "\\eqref{";
           else
             "\\ref{";

           insert(() + label + "}");
       }
       label_insert_mark = NULL;
   }
}

static define label_ref()
{
   if ( pkg_loaded("hyperref") )
     cmd_insert("autoref");
   else
     cmd_insert("ref");
}

%%%%%%%%%%
%
% Folding
%
static define fold(f_lvl);
static define fold(f_lvl)
{
   variable start_mark = create_user_mark();
   try
   {
       while (f_lvl < 0)
       {
           variable mark = create_user_mark();
           switch ( env_name(1) )
           { case NULL:
               throw UsageError, "There's no level below the file level";
           }
           { case "document":
               goto_user_mark(mark);
               break;
           }
           ++f_lvl;
       }
       if (f_lvl < 0)
       {
           throw NotImplementedError;
           forever
           {
               !if ( search_not_commented(&re_bsearch,
                                      "\\[cs][heu][acb][pts]"R, 0) )        % Fixme: PCRE
               {
                   break;
               }
               if ( looking_at("\\chapter") )
                 f_lvl += 2;
               else if ( looking_at("\\section") )
                 f_lvl += 3;
               else if ( looking_at("\\subsection") )
                 f_lvl += 4;
               else if ( looking_at("\\subsubsection") )
                 f_lvl += 5;
               else
                 continue;
           }
           if (f_lvl < 0)
             throw UsageError, "There's no level below the file level";

           switch (f_lvl)
           { case 1: "\\begin{document}"; }
           { case 2: "\\chapter" or "\\section"; }
           { case 1: "\\begin{document}"; }
       }

       variable first_line, level;
       switch ( env_name(1) )
       { case "document":
           variable beg_doc_mark = create_user_mark(), start_level;

           variable level_names = ["chapter", "section", "subsection", "subsubsection"];
           while (length(level_names) > 0)
           {
               if ( fsearch("\\" + level_names[0]) )
                 break;

               level_names = level_names[[1:]];
           }

           % add the file level (0) and the begin--end{document} level (1)
#if (_slang_version < 20007)
           level_names = ["\\file-level", "\\doc-level", level_names];
#else
           level_names = [NULL, NULL, level_names];
#endif

           goto_user_mark(start_mark);
           forever
           {
               if ( not search_not_commented(&re_bsearch,
                                             "\\[cs][heu][acb][pts]"R, 0) or        % Fixme: PCRE
                    create_user_mark() < beg_doc_mark)
               {
                   goto_user_mark(beg_doc_mark);
                   start_level = 1;
                   () = right(1);
                   break;
               }
               () = right(1);
               push_mark();
               skip_chars(TeX_Command_Chars);

               start_level = wherefirst(level_names == chop_star( bufsubstr() ));
               if (start_level != NULL)
               {
                   (,) = cmd_parse_args(1,1);
                   break;
               }
           }
           f_lvl += start_level;

           if (f_lvl == 1)
             % folding level 1 is easy
           {
               () = down(1);
               push_mark();
               eoenv();
               () = up(1);
               set_region_hidden(1);
               return;
           }

           if (start_level == f_lvl)
             % if we start on the fold level, we must start with a mark
           {
               () = down(1);
               push_mark();
               first_line = what_line;
           }
           level = start_level;
           forever
           {
               !if ( search_not_commented(&re_fsearch,
                                          "\\[cseb][heun][acbdg][ptsi{]"R, 1) )        % Fixme: PCRE
                 eob();

               variable last_level = level;

               () = right(1);
               if ( looking_at("begin{") )
                 ++level;
               else if ( looking_at("end{document}") or eobp() )
                 level = 0;
               else if ( looking_at("end{") )
               {
                   --level;
                   if (f_lvl == level)
                     % this is not a barrier like \section that stops
                     % and starts a folding region
                     continue;
               }
               else
               {
                   push_mark();
                   skip_chars(TeX_Command_Chars);

                   level = wherefirst(level_names == chop_star( bufsubstr() ));
                   if (level == NULL)
                   {
                       level = last_level;
                       continue;
                   }

                   (,) = cmd_parse_args(1,1);
               }

               % f_lvl <= last_level: we come from a level where folding
               %    is active
               % level <= f_lvl: level < f_lvl or level == f_lvl
               % level < f_lvl: we jump to a level where folding is
               %    inactive
               % level == f_lvl: we jumped across a barrier that is on
               %    f_lvl - 1---it must become visible
               if (level <= f_lvl and f_lvl <= last_level)
               {
                   push_spot();
                   () = up(1);
                   if (what_line <= first_line)
                     % there is nothing to fold
                     pop_mark(0);
                   else
                     set_region_hidden(1);
                   pop_spot();
               }

               if (level < start_level)
                 break;

               % last_level < f_lvl and f_lvl <= level:
               %    we come from a level where folding is inactive and
               %    continue on a level where folding is active
               % level == f_lvl and f_lvl <= last_level:
               %    we come from a level where folding is active and
               %    jumped across a barrier on f_lvl - 1 that must
               %    become visible
               if ((last_level < f_lvl and f_lvl <= level) or
                   (level == f_lvl and f_lvl <= last_level))
               {
                   () = down(1);
                   push_mark();
                   first_line = what_line;
               }
           }
       }
       { case NULL:
           doc_find();
           variable in_preample = create_user_mark() < start_mark;

           if (in_preample)
           {
               if (f_lvl != 0)
                 return;
           }
           else if (f_lvl == 0)
             throw InvalidParmError,
               "Folding the while file is not supported";

           if (f_lvl <= 1)
           {
               (,) = cmd_parse_args(1,1);
               () = down(1);
               push_mark();
               first_line = what_line;
               !if ( search_not_commented(&fsearch, "\\begin{document}", 1) )
               {
                   % fixme: throw
                   pop_mark(0);
                   return;
               }
               push_spot();
               () = up(1);
               if (what_line <= first_line)
                 pop_mark(0);
               else
                 set_region_hidden(1);
               pop_spot();

               if (in_preample)
                 return;
           }
           else !if ( search_not_commented(&fsearch, "\\begin{document}", 1) )
           {
               % fixme: throw
               return;
           }

           () = right(1);
           fold(f_lvl - 1);
       }
       {
           level = 0;
           do
           {
               if ( looking_at("\\begin{") )
               {
                   if (level < f_lvl)
                     ++level;
                   else
                   {
                       () = down(1);
                       push_mark();
                       first_line = what_line;
                       eoenv();
                       push_spot();
                       () = up(1);
                       if (what_line <= first_line)
                         % there is nothing to fold
                         pop_mark(0);
                       else
                         set_region_hidden(1);
                       pop_spot();
                   }
               }
               else if ( looking_at("\\end{") )
                 --level;
               () = right(1);
           }
           while ( andelse {level > 0} {re_fsearch("\\[be][en][gd]"R)} );
       }
   }
   finally
   {
       goto_user_mark(start_mark);
       if ( is_line_hidden() )
         skip_hidden_lines_backward(1);
   }
}

%%%%%%%%%%
%
% Helping stuff
%

%!%+
%\variable{User_Mark line_mark}
%\synopsis{holds the mark of the current line}
%\description
% we need this variable as buffer for the line mark.
% if the line mark isn't associated with a variable it isn't shown
%
%\seealso{update_log_hook()}
%!%-
private variable line_mark;

%!%+
%\function{update_log_hook}
%\synopsis{Marks the current line}
%\usage{update_log_hook()}
%\description
% This function marks the current line for highlighting.
%
% It is used in the buffers with the output of latex and other programms
% to show better the line the cursor is in.
%!%-
private define update_log_hook()
{
   line_mark = create_line_mark(color_number("region"));
}

private variable TEXDOC_KEYMAP = "texdoc-help";
!if ( keymap_p(TEXDOC_KEYMAP) ) {
   make_keymap(TEXDOC_KEYMAP);

   definekey("latex->texdoc_show()", "g", TEXDOC_KEYMAP);
   definekey("latex->texdoc_show()", "\n", TEXDOC_KEYMAP);
   definekey("latex->texdoc_show()", "\r", TEXDOC_KEYMAP);

   definekey("delbuf(whatbuf());call(\"delete_window\")", "q", TEXDOC_KEYMAP);
   definekey("delbuf(whatbuf());call(\"delete_window\")", "c", TEXDOC_KEYMAP);
   definekey("delbuf(whatbuf());call(\"delete_window\")", "^G", TEXDOC_KEYMAP);
}

static define texdoc_show()
{
   variable file = line_as_string();
   delbuf( whatbuf() );
   call("delete_window");

   variable file_bn = path_basename(file);
   variable ext = path_extname(file_bn);
   if ( is_list_element(".gz,.bz2", ext, ',') )
   {
       file_bn = path_sans_extname(file_bn);
       ext = path_extname(file_bn);
   }

   if (file_bn == "README" or is_list_element(".tex,.txt", ext, ','))
   {
       () = find_file(file);
       return;
   }

   () = system("texdoc '$file' &"$);
}

private define texdoc_run(td_arg)
{
   try
   {
       variable pattern = read_mini("Search pattern:", "", "");
       pop2buf("*Texdoc help*");
       set_readonly(0);
       erase_buffer();
       () = run_shell_cmd("texdoc "+td_arg+" '"+pattern+"'");

       use_keymap(TEXDOC_KEYMAP);
       set_buffer_hook("update_hook", &update_log_hook);
       set_buffer_modified_flag(0);
       set_readonly(1);
       bob();
   }
   catch UserBreakError:
   { }
}

static define texdoc_help()
{
   texdoc_run("-l");
}

static define texdoc_search()
{
   texdoc_run("-s");
}

static define texdoc(what)
{
   () = system("texdoc '$what' &"$);
}

static define info_page()
{
   info_reader();
   if (_NARGS)
   {
       try
       {
           "(latex)";
           exch;
           info_find_node( () + () );
       }
       catch AnyError:
         info_find_node("(latex)Top");
   }
   else
     info_find_node("(latex)Top");
}

%%%%%%%%%%
%
% Keyboard stuff
%

static define insert_quote()
{
   if ( orelse {is_escaped()} {is_verbatim()}
        {andelse {LAST_CHAR != '"'} {LAST_CHAR != '`'}} )
   {
       call("self_insert_cmd");
       return;
   }

   variable lang = NULL;
   if ( pkg_loaded("babel") )
   {
       variable opt = pkg_options("babel");
       if (opt != "")
         lang = strchop(opt, ',', '\0')[-1];
       else
       {
           (opt,) = doc_options_class();
           if (opt != "")
           {
               variable array = strchop(opt, ',', '\0');
#ifexists array_reverse
               array_reverse(array);
#endif
               foreach opt (array)
                 if ( is_list_element("ngerman,german,french,francais,frenchb",
                                      opt, ',') )
                 {
                     lang = opt;
#ifnexists array_reverse
                     break;
#endif
                 }
           }
       }
   }
   else if ( orelse {pkg_loaded("ngerman")} {pkg_loaded("german")} )
     lang = "german";

   variable lquote, rquote;
   switch (lang)
   { case "french" or case "francais":
       switch (LAST_CHAR)
       { case '"': lquote = "<<~"; rquote = "~>>"; }
       { case '`': lquote = "<~"; rquote = "~>"; }
   }
   { case "frenchb":
       switch (LAST_CHAR)
       { case '"': lquote = "\\og"; rquote = "\\fg"; }
       { case '`': lquote = "`"; rquote = "'"; }
   }
   { case "german" or case "ngerman":
       switch (LAST_CHAR)
       { case '"': lquote = "\"`"; rquote = "\"'"; }
       { case '`': lquote = "\\glq"; rquote = "\\grq"; }
   }
   { case "russian":
       switch (LAST_CHAR)
       { case '"': lquote = "\"`"; rquote = "\"'"; }
       { case '`': lquote = "`"; rquote = "'"; }
   }
   { % case "english":
       switch (LAST_CHAR)
       { case '"': lquote = "``"; rquote = "''"; }
       { case '`': lquote = "`"; rquote = "'"; }
   }

   variable start_mark = create_user_mark();
   variable quote_sign = lquote, search = &bsearch();
   forever
   {
       !if ( @search(quote_sign) )
         break;

       if ( is_commented() )
         continue;

       if (quote_sign == lquote)
       {
           () = left(1);
           if ( looking_at( substr(quote_sign, 1, 1) ) )   % ` \subset ``, ' \subset ''
             continue;
           quote_sign = rquote;
           search = &fsearch();
       }
       else
       {
           if ( looking_at( quote_sign + substr(quote_sign, strlen(quote_sign), 1) ) )   % ` \subset ``, ' \subset ''
             () = right(1);
           else
           {
               if (create_user_mark() < start_mark)
                 quote_sign = lquote;
               break;
           }
       }
   }

   goto_user_mark(start_mark);
   if (substr(quote_sign, 1, 1) == "\\")
     cmd_insert( substr(quote_sign, 2, -1) );
   else
     insert(quote_sign);
}

static define insert_dollar()
{
   if ( orelse {is_escaped()} {is_verbatim()} {not is_math()} )
   {
       call("self_insert_cmd");
       return;
   }

#ifexists abbrev_table_p
   call("self_insert_cmd");               % expand abbreviations
   () = left(1);
   del();
#endif

   push_spot();
   variable close_stack = list_new(), open_stack = list_new();
   forever
   {
       bskip_chars("^$(){}[]");
       () = left(1);

       variable ch = what_char(), counterpart, alt_counterpart = "";
       switch (ch)
       { case ')' or case ']' or case '}':
           if ( andelse {ch == '}'} {is_escaped()} )
             ch = "\\}";
           else
             ch = char(ch);

           list_append(close_stack, ch, -1);
           continue;
       }
       { case '$':
           if ( is_escaped() )
             continue;
           else
             break;
       }
       { case '(': counterpart = ")"; alt_counterpart = "]"; }
       { case '[': counterpart = "]"; alt_counterpart = ")"; }
       { case '{':
           if ( is_escaped() )
             counterpart = "\\}";
           else
             counterpart = "}";
       }

       if (length(close_stack) == 0)
         list_append(open_stack, counterpart, -1);
       else
       {
           if (close_stack[-1] == counterpart or
               close_stack[-1] == alt_counterpart)
             list_delete(close_stack, -1);
           else
             list_append(open_stack, counterpart, -1);
       }
   }

   if (BLINK)
   {
       update_sans_update_hook(0);
       () = input_pending(5);
   }

   pop_spot();

   while (length(open_stack) > 0)
   {
       variable looking_for = list_pop(open_stack, 0);
       if ( looking_at(looking_for) )
         () = right( strlen(looking_for) );
       else
         insert(looking_for);
   }
   if ( looking_at_char('$') )
     () = right(1);
   else
     insert_char('$');
}

static define insert_without_spaces()
{
   variable insert_backslash = is_escaped();

   if (insert_backslash xor (LAST_CHAR == ',' or LAST_CHAR == ' '))
   {
       call("self_insert_cmd");
       return;
   }

   if (insert_backslash)
   {
       () = left(1);
       del();
   }

   trim();
   if ( andelse {bolp()} {left(1)} )
   {
       del();
       trim();
   }

   if (insert_backslash)
     insert_char('\\');

   call("self_insert_cmd");

   update(1);
   forever
   {
       variable ch = getkey();
       if (ch != ' ' and ch != '\t' and ch != '\n')
       {
           ungetkey(ch);
           break;
       }
       flush("Whitespaces around a special space make it meaningless");
   }
}

#ifnexists isalpha
define isalpha(ch)
{
   % Fixme! fails for ß
   return orelse {ch != toupper(ch)} {ch != tolower(ch)};
}
#endif

static define math_arrow();   % declare it for recursion
static define math_arrow()
{
   if ( orelse {is_escaped()} {is_verbatim()} )
   {
       call("self_insert_cmd");
       return;
   }

   variable arrow_str;

   switch (LAST_CHAR)
   { case '>':
       () = left(1);
       switch ( what_char() )
       { case '-':
           () = left(1);
           switch (what_char())
           { case '-':
               () = left(1);
               switch (what_char)
               { case '<':
                   deln(3);
                   arrow_str = "longleftrightarrow";
               }
               { case '|':
                   deln(3);
                   arrow_str = "longmapsto";
               }
               {
                   () = right(1);
                   deln(2);
                   arrow_str = "longrightarrow";
               }
           }
           { case '|':
               deln(2);
               arrow_str = "mapsto";
           }
           { case '<':
               deln(2);
               arrow_str = "leftrightarrow";
           }
           { case '`':
               deln(2);
               arrow_str = "hookrightarrow";
           }
           {
               () = right(1);
               del();
               arrow_str = "rightarrow";
           }
       }
       { case '=':
           () = left(1);
           switch (what_char())
           { case '=':
               () = left(1);
               if (what_char() == '<')
               {
                   deln(3);
                   arrow_str = "Longleftrightarrow";
               }
               else
               {
                   () = right(1);
                   deln(2);
                   arrow_str = "Longrightarrow";
               }
           }
           { case '<':
               deln(2);
               arrow_str = "Leftrightarrow";
           }
           {
               () = right(1);
               del();
               arrow_str = "Rightarrow";
           }
       }
       { case '>':
           del();
           arrow_str = "gg";
       }
       {
           () = right(1);
           call("self_insert_cmd");
           return;
       }
   }
   { case '-' or case '=':
       () = left(1);
       switch ( what_char() )
       { case LAST_CHAR:
           () = left(1);
           switch ( what_char() )
           { case '<':
               () = right(2);
               insert_char(LAST_CHAR);
               update_sans_update_hook(1);

               variable new_char = getkey();
               if (new_char == '>')
               {
                   LAST_CHAR = '>';
                   math_arrow();
                   return;
               }
               else
               {
                   ungetkey(new_char);

                   () = left(3);
                   deln(3);
                   if (LAST_CHAR == '-')
                     arrow_str = "longleftarrow";
                   else
                     arrow_str = "Longleftarrow";
               }
           }
           {
               () = right(2);
               call("self_insert_cmd");
               return;
           }
       }
       { case '<':
           () = right(1);
           insert_char(LAST_CHAR);
           update_sans_update_hook(1);

           new_char = getkey();

           switch (new_char)
           { case '>' or case '-' or case '=':
               LAST_CHAR = new_char;
               math_arrow();
               return;
           }
           {
               ungetkey(new_char);

               () = left(2);
               deln(2);
               if ( LAST_CHAR == '-' )
                 arrow_str = "leftarrow";
               else
                 arrow_str = "Leftarrow";
           }
       }
       {
           () = right(1);
           if (LAST_CHAR == '-')
             typo_hyphen();
           else
             call("self_insert_cmd");
           return;
       }
   }
   {
       throw UsageError, "unknow character: $LAST_CHAR"$;
   }

   cmd_insert(arrow_str);
}

static define math_ll()
{
   if ( left(1) ) {
       if ( what_char() == '<' )
       {
           del();
           cmd_insert("ll");
       }
       else {
           ()=right(1);
           call("self_insert_cmd");
       }
   }
   else
     call("self_insert_cmd");
}

static define math_right_parenthesis()
{
   if (what_char() == '}')
   {
       push_spot();
       find_matching_delimiter(LAST_CHAR) != 1;
       pop_spot();
       if ( () )
         () = right(1);
   }
   push_spot();
   if (find_matching_delimiter(LAST_CHAR) == 1)
   {
       push_mark();
       bskip_chars("leftbigB\\");

       variable size = bufsubstr(), delim;
       switch ( strlow(size) )
       { case "\\left": delim = "\\right"; }
       { case "\\big" or case "\\bigg": delim = size; }
       { case "\\bigl" or case "\\biggl": delim = size[[:-2]] + "r"; }
       { delim = ""; }

       pop_spot();
       insert(delim);
   }
   else
     pop_spot();

   call("self_insert_cmd");
}

static define insert_limits_char()
{
   if ( orelse {is_escaped()} {is_verbatim()} )
   {
       call("self_insert_cmd");
       return;
   }

   variable skip_dollar_after_compl = 0;
   if ( blooking_at("$") )
   {
       skip_dollar_after_compl = 1;
       () = left(1);
   }

   push_spot();
   bskip_chars(TeX_Command_Chars + "0-9");
   () = left(1);
   !if ( looking_at_char('\\') )
     () = right(1);

   variable insert_dollar = 0;
   !if ( is_math() )
   {
       insert_dollar = 1;
       skip_dollar_after_compl = 1;
       insert_char('$');
   }
   push_mark();
   pop_spot();
   variable last_cmd = bufsubstr();

   if (insert_dollar)
   {
       insert_char('$');
       if (last_cmd != "$")
         % if no text was enclosed in $ $, the arrangement of spot and
         % mark is difficult and leads to ^{}$$. This deals with this.
         () = left(1);
   }

   switch (last_cmd)
   { case "\\rightarrow" or case "\\leftarrow":
       () = left( strlen(last_cmd)-1 );
       insert_char('x');
       () = right( strlen(last_cmd)-1 );

       if (LAST_CHAR == '_')
       {
           insert("[]{}");
           () = left(3);
       }
       else
       {
           insert("{}");
           () = left(1);
       }

       return;
   }
   { case "\\cup" or case "\\cap" or case "\\vee" or case "\\wedge":
       () = left( strlen(last_cmd)-1 );
       insert("big");
       () = right( strlen(last_cmd)-1 );
       last_cmd = "\\big" + substr(last_cmd, 2, -1);
   }

   insert(char(LAST_CHAR) + "{}");
   () = left(1);

   if (last_cmd[0] != '\\')
     % completion is only supported for TeX commands
     return;

   push_spot();
   () = left(strlen(last_cmd) + 2);       % $last_cmd$LAST_CHAR{
   % the \ at the begin is the regexp quote of the \ in the command
   !if ( re_bsearch("\\$last_cmd[_^]"$) )
   {
       pop_spot();
       return;
   }

   () = right( strlen(last_cmd) );

   variable counterpart_compl, compl;
   !if ( looking_at_char(LAST_CHAR) )
   {
       () = right(1);
       (,counterpart_compl) = cmd_parse_args(0,1);
   }

   if ( looking_at_char(LAST_CHAR) )
   {
       () = right(1);
       (,compl) = cmd_parse_args(0,1);
   }

   if (not __is_initialized(&counterpart_compl) and
        (looking_at_char('^') or looking_at_char('_')) )
   {
       () = right(1);
       (,counterpart_compl) = cmd_parse_args(0,1);
   }

   pop_spot();

   !if ( __is_initialized(&compl) )
     return;

   push_visible_mark();
   insert( str_compress_tex(compl[0]) );
   update(1);
   variable k = getkey();
   switch ( char(k) )
   { case Key_Return or case Key_Enter:
       pop_mark(0);
       () = right(1);

       !if ( __is_initialized(&counterpart_compl) )
       {
           if (skip_dollar_after_compl)
             () = right(1);
           return;
       }
   }
   {
       del_region();
       ungetkey(k);
       return;
   }

   push_visible_mark();
   if (LAST_CHAR == '_')
     "^{";
   else
     "_{";
   insert(() + str_compress_tex(counterpart_compl[0]) + "}");
   update(1);
   k = getkey();
   switch ( char(k) )
   { case Key_Return or case Key_Enter:
       pop_mark(0);

       if (skip_dollar_after_compl)
         () = right(1);
   }
   {
       del_region();
       ungetkey(k);
   }
}

static define key_fold()
{
   variable arg = prefix_argument();
   try
   {
       switch (arg)
       { case NULL: arg = 0; }        % default: fold this level
       { case 9:  % Fixme: This should be 0 digit_arg doesn't support it
           arg = integer( read_mini("Level to fold:", "9", "") );
       }
       fold(arg);
   }
   catch UserBreakError;
}

static define key_unfold()
{
   try
   {
       variable arg = prefix_argument(0);

       push_spot();
       skip_hidden_lines_backward(1);
       () = down(1);
       push_mark();
       skip_hidden_lines_forward(1);
       () = up(1);
       set_region_hidden(0);
       pop_spot();

       if (arg > 0)
         fold(arg);
   }
   catch UserBreakError;
}

static define textormath(text, math)
{
   variable cmd;
   if ( is_math() )
     cmd = math;
   else
     cmd = text;

   if (cmd[0] == ' ')
     insert( substr(cmd, 2, strlen(cmd)) );
   else
   {
       if ( is_internal(cmd) )
         call(cmd);
       else
         eval(cmd);
   }
}

private define defkeyr_textormath_cmd(text, math, key, mode)
{
   text = make_printable_string("latex->cmd_insert(\"$text\")"$);
   math = make_printable_string("latex->cmd_insert(\"$math\")"$);
   definekey_reserved("latex->textormath($text, $math)"$, key, mode);
}

private variable SIMPLE_KEYMAP = MODE + "-simple";

!if ( keymap_p(SIMPLE_KEYMAP) )
{
   make_keymap(SIMPLE_KEYMAP);

   % templates - ^CT or ^C^T
   definekey_reserved("menu_select_menu(\"Global.M&ode.&Templates\")", "t", SIMPLE_KEYMAP);
   definekey_reserved("menu_select_menu(\"Global.M&ode.&Templates\")", "^T", SIMPLE_KEYMAP);

   % packages - ^CP
   definekey_reserved("latex->pkg_prompt", "p", SIMPLE_KEYMAP);

   % array - ^Ca
   definekey_reserved("latex->array_edit_column_format",      "ae",  SIMPLE_KEYMAP);
   definekey_reserved("latex->array_next_cell",               "a\t", SIMPLE_KEYMAP);
   definekey_reserved("latex->array_next_cell",               "aa",  SIMPLE_KEYMAP);
   definekey_reserved("latex->array_new_cell",                "an",  SIMPLE_KEYMAP);
   definekey_reserved("latex->array_prev_cell",               "ap",  SIMPLE_KEYMAP);

   % environments - ^CE
   definekey_reserved("latex->boenv",                          "e<", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_close",                      "ec", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_close",                      "}", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_prompt",                     "ee", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_prompt",                     "e\r", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_rename",                     "er", SIMPLE_KEYMAP);
   definekey_reserved("latex->eoenv",                          "e>", SIMPLE_KEYMAP);

   % sections - ^Cs
   definekey_reserved("latex->cmd_insert(\"appendix\")", "sa", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"chapter\")", "sc", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"part\")", "sp", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"section\")", "ss", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"subsection\")", "su", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"subsubsection\")", "sb", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"paragraph\")", "sg", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"subparagraph\")", "sh", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"minisec\")", "sm", SIMPLE_KEYMAP);

   % commands - ^Cd
   definekey_reserved("latex->cmd_prompt", "d", SIMPLE_KEYMAP);

   % fonts - ^CF
   definekey_reserved("latex->font_resize(1)",              "f-", SIMPLE_KEYMAP);
   definekey_reserved("latex->font_resize(0)",              "f+", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathcal\")",     "fa", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("textbf", "mathbf",               "fb", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathbf\")",      "fB", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"textsc\")",      "fc", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("underline", "underbar",          "fd", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("underline", "underbar",          "f_", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"underbar\")",    "fD", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"emph\")",        "fe", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("textsf", "mathsf",               "ff", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathsf\")",      "fF", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("textit", "mathit",               "fi", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathit\")",      "fI", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathfrak\")",    "fk", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"textmd\")",      "fm", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("textnormal", "mathnormal",       "fn", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathnormal\")",  "fN", SIMPLE_KEYMAP);
   definekey_reserved("latex->font_cmd",                    "fp", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("textrm", "mathrm",               "fr", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathrm\")",      "fR", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"textsl\")",      "fs", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"textup\")",      "fu", SIMPLE_KEYMAP);
   defkeyr_textormath_cmd("texttt", "mathtt",               "ft", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mathtt\")",      "fT", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"verb\")",        "fv", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"text\")",        "fx", SIMPLE_KEYMAP);
   % definekey_reserved("latex_modify_font(\"\")",            "fD", SIMPLE_KEYMAP);
   % definekey_reserved("latex_rename_font",                  "fN", SIMPLE_KEYMAP);

   % links - ^CL
   definekey_reserved("latex->cmd_insert(\"label\")", "ll", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"label\")", "^L^L", SIMPLE_KEYMAP);
   definekey_reserved("latex->label_insert_at_mark", "lm", SIMPLE_KEYMAP);
   definekey_reserved("latex->label_insert_at_mark", "^L^M", SIMPLE_KEYMAP);
   definekey_reserved("latex->label_ref", "lr", SIMPLE_KEYMAP);
   definekey_reserved("latex->label_ref", "^L^R", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pageref\")", "lp", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pageref\")", "^L^P", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cite\")", "lb", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cite\")", "^l^b", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"url\")", "lu", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"url\")", "^L^U", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nocite\")", "ln", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nocite\")", "^L^N", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"index\")", "li", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"index\")", "^L^I", SIMPLE_KEYMAP);

   % PSTricks - ^Ci
   definekey_reserved("latex->pst_move_points", "im", SIMPLE_KEYMAP);
   definekey_reserved("latex->pst_update_pic_size", "iu", SIMPLE_KEYMAP);

   % math symbols - ^C m
   definekey_reserved("latex->cmd_insert(\"alpha\")",        "ma", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"beta\")",         "mb", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"chi\")",          "mc", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"delta\")",        "md", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"epsilon\")",      "me", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"phi\")",          "mf", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"gamma\")",        "mg", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"eta\")",          "mh", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"kappa\")",        "mk", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"lambda\")",       "ml", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"mu\")",           "mm", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nabla\")",        "mN", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nu\")",           "mn", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"omega\")",        "mo", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pi\")",           "mp", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"theta\")",        "mq", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"rho\")",          "mr", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"sigma\")",        "ms", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"tau\")",          "mt", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"upsilon\")",      "mu", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Xi\")",           "mX", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"xi\")",           "mx", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"psi\")",          "my", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"zeta\")",         "mz", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Delta\")",        "mD", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Gamma\")",        "mG", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Theta\")",        "mQ", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Lambda\")",       "mL", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Phi\")",          "mV", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Psi\")",          "mY", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Pi\")",           "mP", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Sigma\")",        "mS", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Upsilon\")",      "mU", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Omega\")",        "mO", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"rightarrow\")",   "m^f", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"leftarrow\")",    "m^b", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"uparrow\")",      "m^p", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"downarrow\")",    "m^n", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"leq\")",          "m<", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"geq\")",          "m>", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"tilde\")",        "m~", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"infty\")",        "mI", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"forall\")",       "mA", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"exists\")",       "mE", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"neg\")",          "m!", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"in\")",           "mi", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"times\")",        "m*", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cdot\")",         "m.", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"subset\")",       "m{", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"supset\")",       "m}", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"subseteq\")",     "m[", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"supseteq\")",     "m]", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"not\")",          "m/", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"setminus\")",     "m\\", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cup\")",          "m+", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cap\")",          "m-", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"wedge\")",        "m&", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"vee\")",          "m|", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"langle\")",       "m(", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"rangle\")",       "m)", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"exp\")",          "m^e", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"sin\")",          "m^s", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cos\")",          "m^c", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"sup\")",          "m^^", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"inf\")",          "m^_", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"det\")",          "m^d", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"lim\")",          "m^l", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"tan\")",          "m^t", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"hat\")",          "m^", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"vee\")",          "mv", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"emptyset\")",     "m0", SIMPLE_KEYMAP);

   definekey_reserved("latex->cmd_insert(\"colon\")",        "m:", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"infty\")",        "m8", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"ne\")",           "m=", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"overline\")",     "m_", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Phi\")",          "mF", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"omega\")",        "mw", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"Omega\")",        "mW", SIMPLE_KEYMAP);

   % not so common math stuff - ^C n
   definekey_reserved("latex->cmd_insert(\"mathcal\")",      "nc", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\")", "nf", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nicefrac\")",     "nF", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"int\")", "ni", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"log\")", "nl", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pmod\")", "nm", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"oint\")", "no", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"prod\")", "np", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"sum\")", "ns", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"sqrt\")", "nq", SIMPLE_KEYMAP);

   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\"])", "n1", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"2\"])", "n2", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"3\"])", "n3", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"4\"])", "n4", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"5\"])", "n5", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"6\"])", "n6", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"7\"])", "n7", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"8\"])", "n8", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"frac\", 1, [\"1\", \"9\"])", "n9", SIMPLE_KEYMAP);

   % breaks - ^CK
   definekey_reserved("latex->cmd_insert(\"newline\");newline()", "kl", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"newline\");newline()", "^K^L", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"linebreak[1]\");newline()", "kb", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"linebreak[1]\");newline()", "^K^B", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"newpage\");newline()", "kp", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"newpage\");newline()", "^K^P", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"clearpage\");newline()", "kc", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"clearpage\");newline()", "^K^C", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cleardoublepage\");newline()", "kd", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"cleardoublepage\");newline()", "^K^D", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pagebreak\");newline()", "kr", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"pagebreak\");newline()", "^K^R", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nolinebreak[1]\");newline()", "kn", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nolinebreak[1]\");newline()", "^K^N", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nopagebreak\");newline()", "ko", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"nopagebreak\");newline()", "^K^O", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"enlargethispage\")", "ke", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"enlargethispage\")", "^K^E", SIMPLE_KEYMAP);

   % math arrows - ^C + arrow
   definekey_reserved("latex->cmd_insert(\"uparrow\")", Key_Up, SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"downarrow\")", Key_Down, SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"leftarrow\")", Key_Left, SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_insert(\"rightarrow\")", Key_Right, SIMPLE_KEYMAP);

   definekey_reserved("latex->newline_with_completion", Key_Return, SIMPLE_KEYMAP);

   % special characters
   definekey_reserved(" \\$", "$", SIMPLE_KEYMAP);
   definekey_reserved(" \\&", "&", SIMPLE_KEYMAP);
   definekey_reserved(" \\%", "%", SIMPLE_KEYMAP);
   definekey_reserved(" \\_", "_", SIMPLE_KEYMAP);
   definekey_reserved(" \\#", "#", SIMPLE_KEYMAP);
   definekey_reserved(" \\{", "(", SIMPLE_KEYMAP);
   definekey_reserved(" \\}", ")", SIMPLE_KEYMAP);
   definekey_reserved(" \\textless{}", "<", SIMPLE_KEYMAP);
   definekey_reserved(" \\textgreater{}", ">", SIMPLE_KEYMAP);
   definekey_reserved(" \\textbackslash{}", "\\", SIMPLE_KEYMAP);
   definekey_reserved(" \\textbar{}", "|", SIMPLE_KEYMAP);
   definekey_reserved(" \\textasciicircum{}", "^", SIMPLE_KEYMAP);
   definekey_reserved(" \\textasciitilde{}", "~", SIMPLE_KEYMAP);

   % stuff from latex_external - ^C r
   definekey_reserved("latex_external->select_master_file",    "ra", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->bibtex",                "rb", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->show_bibtex_log",       "rv", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->clearup",               "rc", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->makeindex",             "ri", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->show_mkidx_log",        "ru", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->mrproper",              "rm", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->cust_view",             "ro", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->print",                 "rp", SIMPLE_KEYMAP);

   % often used stuff from latex_external
   definekey_reserved("latex_external->compose",               "c", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->compose",               "^C", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->view",                  "v", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->view",                  "^V", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->pop_log_file",          "y", SIMPLE_KEYMAP);
   definekey_reserved("latex_external->pop_log_file",          "^y", SIMPLE_KEYMAP);

   % help
   definekey_reserved("latex->texdoc_help()",                  "ht", SIMPLE_KEYMAP);
   definekey_reserved("latex->texdoc_search()",                "hT", SIMPLE_KEYMAP);
   definekey_reserved("latex->texdoc_search()",                "^h^T", SIMPLE_KEYMAP);
   definekey_reserved("latex->texdoc(\"symbols-a4\")",         "hs", SIMPLE_KEYMAP);
   definekey_reserved("latex->texdoc(\"symbols-a4\")",         "^H^S", SIMPLE_KEYMAP);
   definekey_reserved("latex->info_page",                      "hi", SIMPLE_KEYMAP);
   definekey_reserved("latex->info_page",                      "^h^I", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_help",                       "hc", SIMPLE_KEYMAP);
   definekey_reserved("latex->cmd_help",                       "^H^C", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_help",                       "he", SIMPLE_KEYMAP);
   definekey_reserved("latex->env_help",                       "^H^E", SIMPLE_KEYMAP);
   definekey_reserved("latex->pkg_help",                       "hp", SIMPLE_KEYMAP);
   definekey_reserved("latex->pkg_help",                       "^H^P", SIMPLE_KEYMAP);

   definekey_reserved("latex->indent_region",                  "q", SIMPLE_KEYMAP);


   definekey_reserved("latex->key_fold",                       "oo", SIMPLE_KEYMAP);
   definekey_reserved("latex->key_unfold",                     "ou", SIMPLE_KEYMAP);
}

private define definekey_textormath(text, math, key, mode)
{
   text = make_printable_string(text);
   math = make_printable_string(math);
   definekey("latex->textormath($text, $math)"$, key, mode);
}

static define unfold_or_newline()
{
   if (orelse {is_line_hidden()} {andelse {right(1)} {is_line_hidden()}})
     key_unfold();
   else
   {
       () = left(1);
       call("newline_and_indent");
   }
}

!if ( keymap_p(MODE) )
{
   copy_keymap(MODE, SIMPLE_KEYMAP);

   % misc
   definekey("latex->insert_quote", "\"", MODE);
   definekey("latex->insert_quote", "`",  MODE);
   definekey("latex->insert_dollar", "$",  MODE);
   definekey("latex->math_ll", "<", MODE);
   definekey("latex->math_arrow", ">", MODE);
   definekey("latex->math_arrow", "-", MODE);
   definekey("latex->math_arrow", "=", MODE);

   foreach $1 ([" ", "~"])
     definekey_textormath("latex->insert_without_spaces", "self_insert_cmd",
                          $1, MODE);

   % typo stuff
   definekey_textormath("latex->typo_slash", "self_insert_cmd", "/", MODE);
   definekey("latex->typo_percent", "%", MODE);
   definekey_textormath("latex->insert_without_spaces",
                        "latex->typo_german_decimal_point", ",", MODE);
   definekey("latex->typo_dots", ".",  MODE);

   % math stuff
   definekey_textormath("self_insert_cmd", "latex->cmd_insert(\"colon\")",
                        ":", MODE);
   definekey_textormath("self_insert_cmd", "latex->math_right_parenthesis",
                        ")", MODE);
   definekey_textormath("self_insert_cmd", "latex->math_right_parenthesis",
                        "]", MODE);
   definekey("latex->insert_limits_char()", "^", MODE);
   definekey("latex->insert_limits_char()", "_", MODE);
   definekey("latex->newline_with_completion", Key_Shift_Return, MODE);

   definekey("latex->unfold_or_newline", Key_Return, MODE);
}

%%%%%%%%%%
%
% Menu stuff
%

private define menu_init_helper(menu, list, fun)
{
   foreach (list)
   {
       variable entry = ();
       if (typeof(entry) == Array_Type)
         menu_append_item(menu, entry[0], fun, entry[1]);
       else
         menu_append_item(menu, entry,
                          "latex->" + string(fun)[[1:]] +
                          "(\"" + str_delete_chars(entry, "&\\"R) + "\")");
   }
}

private define menu_init(menu)
{
   % templates
   menu_append_popup(menu, "&Templates");
   $1 = menu+".&Templates";

   if (LaTeX_Template_Dir != NULL)
   {
       variable templates = Assoc_Type[String_Type];
       foreach ( strtok(LaTeX_Template_Dir, ",") )
       {
           variable tmp = ();
           foreach ( listdir(tmp) )
           {
               variable file = ();
               if (strlen( path_sans_extname(file) ) != 0)
                 templates[path_sans_extname(file)] = path_concat(tmp, file);
           }
       }

       variable keys = assoc_get_keys(templates);
       foreach ( keys[array_sort(keys)] )
       {
           variable templ = ();
           menu_append_item($1, templ, &templ_insert(), templates[templ]);
       }
   }

   % packages
   menu_append_popup(menu, "&Packages");
   menu_init_helper(menu+".&Packages",
                    ["alltt", "amsmath", "babel", "booktabs", "calc",
                     "color", "eepic", "fancyhdr", "fancyvrb",
                     "geometry", "graphicx", "hyperref", "isolatin1",
                     "longtable", "makeidx", "moreverb",
                     "psfrag", "pslatex", "rotating", "url"],
                    &pkg_insert() );

   % environments
   menu_append_popup(menu, "&Environments");
   $1 = menu+".&Environments";
   menu_append_item($1, "&array", "latex->env_insert(\"array\", [\"ll\"])");
   menu_init_helper($1,
                    {"&center", "&description", "&enumerate", "&figure",
                     "flush&left", ["flush&Right", "flushright"], "&itemize",
                     ["&List", "list"]},
                    &env_insert() );
   menu_append_item($1, "&minipage",
                    "latex->env_insert(\"minipage\", [\"\\linewidth\"R])");
   menu_init_helper($1,
                    ["&picture", "&quotation", "qu&ote", "ta&bbing", "&table",
                     "tab&ular"],
                    &env_insert() );
   menu_append_item($1, "thebibliograph&y",
                    "latex->env_insert(\"thebibliography\", [\"99\"])");
   menu_init_helper($1,
                    {["t&Heorem", "theorem"], "titlepa&ge", "&verbatim",
                     "ver&se"},
                    &env_insert() );
   menu_append_separator($1);
   menu_append_item($1, "&Custom...", "latex->env_prompt");
   menu_append_item($1, "re&Name...", "latex->env_rename");
   menu_append_item($1, "close...", "latex->env_close");
   menu_append_item($1, "Goto begin...", "latex->boenv");
   menu_append_item($1, "Goto end...", "latex->eoenv");

   % font
   menu_append_popup(menu, "&Font");
   $1 = menu+".&Font";

   menu_append_popup($1, "&Family");
   menu_init_helper($1+".&Family",
                    {["&Roman", "textrm"], ["&Sans serif", "textsf"],
                     ["&Typewriter", "texttt"]},
                    &cmd_insert());

   menu_append_popup($1, "&Shape");
   menu_init_helper($1+".&Shape",
                    {["&Italic", "textit"], ["&Slanted", "textsl"],
                     ["Small &caps", "textsc"],
                     ["&Upright (normal)", "textup"]},
                    &cmd_insert());

   menu_append_popup($1, "S&eries");
   $2 = $1+".S&eries";
   menu_append_item($2, "&Boldface", "latex->cmd_insert(\"textbf\")");
   menu_append_item($2, "&Medium weight (normal)", "latex->cmd_insert(\"textmd\")");

   menu_append_popup($1, "Si&ze");
   menu_init_helper($1+".Si&ze",
                    ["&tiny", "s&criptsize", "&footnotesize", "&small",
                     "&normalsize", "&large", "&Large", "L&ARGE",
                     "&huge", "&Huge"],
                    &cmd_insert() );
   menu_append_separator($2);
   menu_append_item($2, "re&size", "latex_resize_font");

   menu_init_helper($1,
                    {["&Emphasis", "emph"], ["&Underline", "underline"],
                     ["&Normal font", "textnormal"], ["\\&verb", "verb"]},
                    &cmd_insert() );

   menu_append_separator($1);
   menu_append_item($1, "&Delete last setting", "latex_modify_font(\"\")");
   menu_append_item($1, "Re&name", "latex_rename_font");

   % font/environment
   menu_append_popup($1, "As &Environment");
   menu_init_helper($1+".As &Environment",
                    ["&rmfamily", "&itshape", "&mdseries", "&bfseries",
                     "&upshape", "&slshape", "s&ffamily", "s&cshape",
                     "&ttfamily", "&normalfont"],
                    &env_insert() );

   % font/math
   menu_append_popup($1, "&Math");
   menu_init_helper($1+".&Math",
                    ["mathr&m", "math&bf", "math&sf", "math&tt",
                     "math&it", "math&normal"],
                    &cmd_insert() );

   % sections
   menu_append_popup(menu, "&Sections");
   menu_init_helper(menu+".&Sections",
                    ["\\p&art", "\\&chapter", "\\&section", "\\s&ubsection",
                     "\\su&bsubsection", "\\&paragraph", "\\subparagrap&h",
                     "\\&minisec"],
                    &cmd_insert() );

   % paragraph
   menu_append_popup(menu, "&Paragraph");
   $1 = menu+".&Paragraph";
   menu_append_item($1, "F&ramed Paragraph", "latex_par_frame");
   menu_append_item($1, "&background Colour", "latex_par_bgcolour");
   menu_append_item($1, "&foreground Colour", "latex_par_fgcolour");
   menu_append_item($1, "\\par&indent",
                    "insert(\"\\\\setlength{\\\\parindent}{0pt}\\n\")");
   menu_append_item($1, "\\par&skip",
                    "insert(\"\\\\setlength{\\\\parskip}{3pt}\\n\")");
   menu_append_item($1, "\\&marginpar",
                    "latex_cmd(\"marginpar\", 1)");
   menu_append_item($1, "\\foot&note",
                    "latex_cmd(\"footnote\", 1)");
   menu_append_item($1, "\\inc&ludegraphics", "latex_includegraphics");

   % paragraph/margins
   menu_append_popup($1, "&Margins");
   $2 = $1+".&Margins";
   menu_append_item($2, "\\&leftmargin",
                    "latex_cmd(\"setlength{\\\\leftmargin}\", 1)");
   menu_append_item($2, "\\&rightmargin",
                    "latex_cmd(\"setlength{\\\\rightmargin}\", 1)");
   menu_append_item($2, "\\&evensidemargin",
                    "latex_cmd(\"setlength{\\\\evensidemargin}\", 1)");
   menu_append_item($2, "\\&oddsidemargin",
                    "latex_cmd(\"setlength{\\\\oddsidemargin}\", 1)");
   menu_append_item($2, "\\&topmargin",
                    "latex_cmd(\"setlength{\\\\topmargin}\", 1)");
   menu_append_item($2, "\\text&width",
                    "latex_cmd(\"setlength{\\\\textwidth}\", 1)");
   menu_append_item($2, "\\text&height",
                    "latex_cmd(\"setlength{\\\\textheight}\", 1)");

   menu_append_popup($1, "Brea&ks");
   $2 = $1+".Brea&ks";
   menu_append_item($2, "\\new&line", "insert(\"\\\\newline\\n\")");
   menu_append_item($2, "\\\\&*[]", "latex_linebreak");
   menu_append_item($2, "\\line&break", "insert(\"\\\\linebreak[1]\\n\")");
   menu_append_item($2, "\\new&page", "insert(\"\\\\newpage\\n\")");
   menu_append_item($2, "\\&clearpage", "insert(\"\\\\clearpage\\n\")");
   menu_append_item($2, "\\clear&doublepage",
                    "insert(\"\\\\cleardoublepage\\n\")");
   menu_append_item($2, "\\pageb&reak", "insert(\"\\\\pagebreak\\n\")");
   menu_append_item($2, "\\&nolinebreak",
                    "insert(\"\\\\nolinebreak[1]\\n\")");
   menu_append_item($2, "\\n&opagebreak", "insert(\"\\\\nopagebreak\\n\")");
   menu_append_item($2, "\\&enlargethispage",
                    "insert(\"\\\\enlargethispage\\n\")");

   % paragraph/spaces
   menu_append_popup($1, "&Spaces");
   $2 = $1+".&Spaces";
   menu_append_item($2, "\\&frenchspacing",
                    "insert(\"\\\\frenchspacing\\n\")");
   menu_append_item($2, "\\&@.", "insert(\"\\\\@.\\n\")");
   menu_append_item($2, "\\&dotfill", "insert(\"\\\\dotfill\\n\")");
   menu_append_item($2, "\\&hfill", "insert(\"\\\\hfill\\n\")");
   menu_append_item($2, "\\h&rulefill", "insert(\"\\\\hrulefill\\n\")");
   menu_append_item($2, "\\&smallskip", "insert(\"\\\\smallskip\\n\")");
   menu_append_item($2, "\\&medskip", "insert(\"\\\\medskip\\n\")");
   menu_append_item($2, "\\&bigskip", "insert(\"\\\\bigskip\\n\")");
   menu_append_item($2, "\\&vfill", "insert(\"\\\\vfill\\n\")");
   menu_append_item($2, "\\hspace", "insert(\"\\\\hspace\\n\")");
   menu_append_item($2, "\\vs&pace", "insert(\"\\\\vspace\\n\")");
   menu_append_item($2, "Set \\baselines&kip",
                    "insert(\"\\\\baselineskip 2\\\\baselineskip\\n\")");

   % paragraph/boxes
   menu_append_popup($1, "Bo&xes");
   $2 = $1+".Bo&xes";
   menu_append_item($2, "\\&fbox", "latex_cmd(\"fbox\", 1)");
   menu_append_item($2, "\\f&ramebox",
                    "latex_cmd(\"framebox[\\\\width][c]\", 1)");
   menu_append_item($2, "\\&mbox", "latex_cmd(\"mbox\", 1)");
   menu_append_item($2, "\\ma&kebox",
                    "latex_cmd(\"makebox[\\\\width][c]\", 1)");
   menu_append_item($2, "\\&newsavebox", "latex_cmd(\"newsavebox\", 1)");
   menu_append_item($2, "\\ru&le",
                    "latex_cmd(\"rule{\\\\linewidth}\", 1)");
   menu_append_item($2, "\\save&box",
                    "latex_cmd(\"savebox{}[\\\\linewidth][c]\", 1)");
   menu_append_item($2, "\\&sbox",
                    "latex_cmd(\"sbox{}\", 1)");
   menu_append_item($2, "\\&usebox",
                    "latex_cmd(\"usebox\", 1)");

   % links
   menu_append_popup(menu, "&Links");
   menu_init_helper(menu+".&Links",
                    ["\\&label", "\\&ref", "\\&cite", "\\&nocite", "\\&url",
                     "\\n&olinkurl", "\\&index"],
                    &cmd_insert() );

   menu_append_popup(menu + ".&Links", "&More index commands");
   $1 = menu + ".&Links.&More index commands";
   menu_append_item($1, "\\&index{entry!subentry}",
                    "latex->cmd_insert(\"index\", 1, [\"entry!subentry\"])");
   menu_append_item($1, "\\&index{entry|(} (begin range)",
                    "latex->cmd_insert(\"index\", 1, [\"entry|(\"])");
   menu_append_item($1, "\\&index{entry|)} (end range)",
                    "latex->cmd_insert(\"index\", 1, [\"entry|)\"])");
   menu_append_item($1, "\\&index{sortentry@textentry)}",
                    "latex->cmd_insert(\"index\", 1, [\"sortentry@textentry\"])");
   menu_append_item($1, "\\&index{entry|format)}",
                    "latex->cmd_insert(\"index\", 1, [\"entry|format\"])");

   % math
   menu_append_popup(menu, "&Math");
   $1 = menu+".&Math";

   menu_append_item($1, "&Toggle Math Mode", "toggle_math_mode");
   menu_append_item($1, "&Greek Letter...", "latex_greek_letter");
   menu_append_item($1, "&_{}  subscript",
                    "latex_insert_tags(\"_{\", \"}\", 1, 1)");
   menu_append_item($1, "&^{}  superscript",
                    "latex_insert_tags(\"^{\", \"}\", 1, 1)");
   menu_append_item($1, "\\&frac",
                    "latex_insert_tags(\"\\\\frac{\", \"}{}\", 1, 1)");
   menu_append_item($1, "\\&int",
                    "latex_insert_tags(\"\\\\int_{\", \"}^{}\", 1, 1)");
   menu_append_item($1, "\\&lim",
                    "latex_insert_tags(\"\\\\lim_{\", \"}\", 1, 1)");
   menu_append_item($1, "\\&oint",
                    "latex_insert_tags(\"\\\\oint_{\", \"}^{}\", 1, 1)");
   menu_append_item($1, "\\&prod",
                    "latex_insert_tags(\"\\\\prod_{\", \"}^{}\", 1, 1)");
   menu_append_item($1, "\\&sum",
                    "latex_insert_tags(\"\\\\sum_{\", \"}^{}\", 1, 1)");
   menu_append_item($1, "\\s&qrt",
                    "latex_insert_tags(\"\\\\sqrt[]{\", \"}\", 1, 1)");

   % math/accents
   menu_append_popup($1, "&Accents");
   menu_init_helper($1+".&Accents",
                    ["hat", "acute", "bar", "dot", "breve", "check",
                     "grave", "vec", "ddot", "tilde", "widetilde",
                     "widehat", "overleftarrow", "overrightarrow",
                      "overline", "underline", "overbrace", "underbrace"],
                    &cmd_insert() );

   menu_append_popup($1, "&Delimiters");
   menu_append_item($2, "\\left(", "latex_insert(\"left(\")");
   menu_append_item($2, "\\right)", "latex_insert(\"right)\")");
   menu_append_item($2, "\\left[", "latex_insert(\"left[\")");
   menu_append_item($2, "\\right]", "latex_insert(\"right[\")");
   menu_append_item($2, "\\left{", "latex_insert(\"left\\\\{\")");
   menu_append_item($2, "\\right}", "latex_insert(\"right\\\\}\")");
   menu_init_helper($1+".&Delimiters",
                    ["rmoustache", "lmoustache", "rgroup", "lgroup",
                     "arrowvert", "Arrowvert", "bracevert", "lfloor",
                     "rfloor", "lceil", "rceil", "langle", "rangle"],
                    &cmd_insert() );
   menu_append_item($2, "\\|", "latex_insert(\"\\|\")");

   menu_append_popup($1, "F&unctions");
   menu_init_helper($1+".F&unctions",
                    ["arccos", "arcsin", "arctan", "arg", "cos",
                     "cosh", "cot", "coth", "csc", "deg", "det",
                     "dim", "exp", "gcd", "hom", "inf", "ker",
                     "lg", "lim", "liminf", "limsup", "ln", "log",
                     "max", "min", "Pr", "sec", "sin", "sinh",
                     "sup", "tan", "tanh"],
                    &cmd_insert() );

   menu_append_popup($1, "Binary &Relations");
   menu_init_helper($1+".Binary &Relations",
                    ["leq", "geq", "equiv", "models", "prec", "succ",
                     "sim", "perp", "preceq", "succeq", "simeq",
                     "mid", "ll", "gg", "asymp", "parallel", "subset",
                     "supset", "approx", "bowtie", "subseteq",
                     "supseteq", "cong", "Join", "sqsubset", "sqsupset",
                     "neq", "smile", "sqsubseteq", "sqsupseteq", "doteq",
                     "frown", "in", "ni", "propto", "vdash", "dashv",
                     "not"],
                    &cmd_insert() );

   % math/binary operators
   menu_append_popup($1, "&Binary Operators");
   menu_init_helper($1+".&Binary Operators",
                    ["pm", "cap", "diamond", "oplus", "mp", "cup",
                     "bigtriangleup", "ominus", "times", "uplus",
                     "bigtriangledown", "otimes", "div", "sqcap",
                     "triangleleft", "oslash", "ast", "sqcup",
                     "triangleright", "odot", "star", "vee", "bigcirc",
                     "circ", "wedge", "dagger", "bullet", "setminus",
                     "ddagger", "cdot", "wr", "analg"],
                    &cmd_insert() );

   % math/spaces
   menu_append_popup($1, "Spa&ces");
   $2 = $1+".Spa&ces";
   menu_append_item($2, "\\!  -3/18 quad", "insert(\"\\\\! \")");
   menu_append_item($2, "\\,   3/18 quad", "insert(\"\\\\, \")");
   menu_append_item($2, "\\:   4/18 quad", "insert(\"\\\\: \")");
   menu_append_item($2, "\\;   5/18 quad", "insert(\"\\\\; \")");
   menu_append_item($2, "\\quad      1em", &cmd_insert(), "quad");
   menu_append_item($2, "\\qquad     2em", &cmd_insert(), "qquad");

   % math/arrows
   menu_append_popup($1, "Arro&ws");
   menu_init_helper($1+".Arro&ws",
                    {["<-", "leftarrow"], ["<--", "longleftarrow"],
                     ["<=", "Leftarrow"], ["<==", "Longleftarrow"],
                     ["->", "rightarrow"], ["-->", "longrightarrow"],
                     ["=>", "Rightarrow"], ["==>", "Longrightarrow"],
                     "uparrow", "Uparrow", "downarrow", "Downarrow",
                     ["<->", "leftrightarrow"],
                     ["<-->", "longleftrightarrow"],
                     ["<=>", "Leftrightarrow"],
                     ["<==>", "Longleftrightarrow"],
                     "updownarrow", "Updownarrow",
                     ["|->", "mapsto"], ["|-->", "longmapsto"],
                     "hookleftarrow", "hookrightarrow", "leftarpoonup",
                     "rightarpoonup", "leftarpoondown", "rightarpoondown",
                     "nearrow", "searrow", "swarrow", "nwarrow"},
                    &cmd_insert() );

   % math/misc
   menu_append_popup($1, "&Misc");
   menu_init_helper($1+".&Misc",
                    ["ldots", "cdots", "vdots", "ddots", "aleph",
                     "prime", "forall", "infty", "hbar", "emptyset",
                     "exists", "nabla", "surd", "triangle", "imath",
                     "jmath", "ell", "neg", "top", "flat", "natural",
                     "sharp", "wp", "bot", "clubsuit", "diamondsuit",
                     "heartsuit", "spadesuit", "Re", "Im", "angle",
                     "partial"],
                    &cmd_insert() );

   % bibliography
   menu_append_popup(menu, "Bibliograph&y");
   $1 = menu+".Bibliograph&y";
   menu_append_item($1, "&thebibliography",
                    "latex->env_insert(\"thebibliography\", [\"{99}\"])");
   menu_init_helper(menu+".Bibliograph&y",
                    ["\\bib&item", "\\&bibliography", "\\bibliography&style"],
                    &cmd_insert() );

   % PSTricks
   menu_append_popup(menu, "PSTr&icks");
   $1 = menu + ".PSTr&icks";
   menu_append_item($1, "&pspicture", "latex->env_insert(\"pspicture\")");
   menu_init_helper($1,
                    ["\\ps&circle", "\\ps&fram", "\\ps&line", "\\&rput"],
                    &cmd_insert() );
   menu_append_separator($1);
   menu_append_item($1, "Move points in region", "latex->pst_move_points");
   menu_append_item($1, "Update pspicture size", "latex->pst_update_pic_size");

   menu_append_separator(menu);

   menu_append_item(menu, "Select M&aster File",
                    "latex_external->select_master_file");
   menu_append_item(menu, "Customize Build", "latex_external->cust_view");
   menu_append_item(menu, "Compose", "latex_external->compose");
   menu_append_item(menu, "&View", "latex_external->view");
   menu_append_item(menu, "Show LaTeX lo&g", "latex_external->pop_log_file");
   menu_append_item(menu, "Pri&nt", "latex_external->print");
   menu_append_item(menu, "&BibTeX", "latex_external->bibtex");
   menu_append_item(menu, "Show BibTeX log", "latex_external->show_bibtex_log");
   menu_append_item(menu, "Makeinde&x", "latex_external->makeindex");
   menu_append_item(menu, "Show Makeindex log", "latex_external->show_mkidx_log");
   % menu_append_item(menu, "&Document Outline", "latex_browse_tree");
   menu_append_item(menu, "&Remove temp files", "latex_external->clearup");
   menu_append_item(menu, "&Remove all files", "latex_external->mrproper");

   % convert
   menu_append_popup(menu, "&Convert");
   $1 = menu+".&Convert";
   menu_append_item($1, "$:$ -> $\\colon$", "latex_conv->colon");
   menu_append_item($1, "\"a -> ä (Latin 1)", "latex_conv->german_lat1");
   menu_append_item($1, "\"a -> ä (UTF-8)", "latex_conv->german_utf8");
   menu_append_item($1, "\\\"a -> ä (Latin 1)", "latex_conv->native_lat1");
   menu_append_item($1, "\\\"a -> ä (UTF-8)", "latex_conv->native_utf8");
   menu_append_item($1, "2.0 (\\bf) -> 2e (\\textbf)", "latex_conv->ltx209_ltx2e");

   % specials
   menu_append_popup(menu, "Specials");
   $1 = menu + ".Specials";
   menu_append_item($1, "Simple keymap", "use_keymap(\"$SIMPLE_KEYMAP\")"$);
   menu_append_item($1, "Default keymap", "use_keymap(\"$MODE\")"$);

   menu_append_separator(menu);
   menu_append_item(menu, "Latex info page", "latex->info_page");
   menu_append_item(menu, "TeXdoxTk", "() = system(\"texdoctk &\")");
   menu_append_item(menu, "LaTeX Mode &Help", "latex_mode_help");
   menu_append_popup(menu, "Common &documentations");
   $1 = menu + ".Common &documentations";
   menu_init_helper($1, {"&amsldoc", "&de-tex-faq",
                         ["&Hyperref manual", "hyperref/manual"], "&Mathmode",
                         "pst-quickref", "pstricks-doc",
                         "&scrguide", "s&ymbols-a4", "&visualFAQ"},
                    &texdoc());
}
mode_set_mode_info(MODE, "init_mode_menu", &menu_init);

create_syntax_table(MODE);

define_syntax("%", "", '%', MODE);     % Comment Syntax
define_syntax('\\', '\\', MODE);       % Quote character
define_syntax("~^_&#", '+', MODE);     % operators
define_syntax("|&{}[]", ',', MODE);    % delimiters
define_syntax(TeX_Command_Chars, 'w', MODE);
set_syntax_flags(MODE, 8);

#ifdef HAS_DFA_SYNTAX
private define setup_dfa_callback(name)
{
   dfa_enable_highlight_cache("latex.dfa", name);

   % comments:
   dfa_define_highlight_rule("%(.*[^ \t])?", "comment", name);

   dfa_define_highlight_rule("\\documentclass.*}"R, "Qpreprocess", name);
   dfa_define_highlight_rule("\\begin{.*}({.*})*"R, "preprocess", name);
   dfa_define_highlight_rule("\\end{.*}"R, "Qpreprocess", name);

   % % known keywords in curly braces
   % dfa_define_highlight_rule("{article}", "Qstring", name);
   % dfa_define_highlight_rule("{book}", "Qstring", name);
   % dfa_define_highlight_rule("{letter}", "Qstring", name);
   % dfa_define_highlight_rule("{report}", "Qstring", name);
   % dfa_define_highlight_rule("{slides}", "Qstring", name);
   % dfa_define_highlight_rule("{document}", "Qstring", name);
   % dfa_define_highlight_rule("{scrreport}", "Qstring", name);
   % % environments
   % dfa_define_highlight_rule("{abstract}", "Qstring", name);
   % dfa_define_highlight_rule("{array}", "Qstring", name);
   % dfa_define_highlight_rule("{center}", "Qstring", name);
   % dfa_define_highlight_rule("{description}", "Qstring", name);
   % dfa_define_highlight_rule("{displaymath}", "Qstring", name);
   % dfa_define_highlight_rule("{enumerate}", "Qstring", name);
   % dfa_define_highlight_rule("{eqnarray}", "Qstring", name);
   % dfa_define_highlight_rule("{figure}", "Qstring", name);
   % dfa_define_highlight_rule("{flushleft}", "Qstring", name);
   % dfa_define_highlight_rule("{flushright}", "Qstring", name);
   % dfa_define_highlight_rule("{itemize}", "Qstring", name);
   % dfa_define_highlight_rule("{list}", "Qstring", name);
   % dfa_define_highlight_rule("{minipage}", "Qstring", name);
   % dfa_define_highlight_rule("{picture}", "Qstring", name);
   % dfa_define_highlight_rule("{quotation}", "Qstring", name);
   % dfa_define_highlight_rule("{quote}", "Qstring", name);
   % dfa_define_highlight_rule("{tabbing}", "Qstring", name);
   % dfa_define_highlight_rule("{table}", "Qstring", name);
   % dfa_define_highlight_rule("{tabular}", "Qstring", name);
   % dfa_define_highlight_rule("{thebibliography}", "Qstring", name);
   % dfa_define_highlight_rule("{theorem}", "Qstring", name);
   % dfa_define_highlight_rule("{titlepage}", "Qstring", name);
   % dfa_define_highlight_rule("{verbatim}", "Qstring", name);
   % dfa_define_highlight_rule("{verse}", "Qstring", name);
   % % font family
   % dfa_define_highlight_rule("{rmfamily}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{itshape}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{mdseries}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{bfseries}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{upshape}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{slshape}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{sffamily}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{scshape}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{ttfamily}", "Qkeyword2", name);
   % dfa_define_highlight_rule("{normalfont}", "Qkeyword2", name);
   % dfa_define_highlight_rule("\\text[^{][^{]"R, "keyword2", name);

   % dfa_define_highlight_rule("{gather\*?}"R, "Qnumber", name);
   % dfa_define_highlight_rule("{align\*?}"R, "Qnumber", name);

   % % everithing else between curly braces
   % % !!! doesn't span multiple lines !!!
   % dfa_define_highlight_rule("{.*}", "Qkeyword1", name);
   % dfa_define_highlight_rule("^([^{])*}", "Qkeyword1", name);
   % dfa_define_highlight_rule("{.*", "keyword1", name);

   % % short symbols that delimit math: $ \[ \] \( \)
   % dfa_define_highlight_rule("\\\\\\[.*\\\\\\]", "Qstring", name);
   % dfa_define_highlight_rule("\\\\\\(.*\\\\\\)", "Qstring", name);
   % dfa_define_highlight_rule("^.*\\\\[\\)\\]]", "Qstring", name);
   % dfa_define_highlight_rule("\\\\[\\(\\[].*", "string", name);

   % dfa_define_highlight_rule("\\$.*\\$", "Qnumber", name);
   % dfa_define_highlight_rule("\\$.*[^ ]", "number", name);
   % %   dfa_define_highlight_rule("^[^\\$]*\\$", "number", name);

   % % Fundamental delimiters in the TeX language: {}[]
   % dfa_define_highlight_rule("[{}\\[\\]]", "delimiter", name);

   % % \leftX \rightY constructions where X and Y are
   % % one of \| \{ \} [ ]( ) / | .
   % dfa_define_highlight_rule("\\\\(left|right)(\\\\\\||\\\\{|\\\\}|" +
   %                         "[\\[\\]\\(\\)/\\|\\.])",
   %                         "delimiter", name);

   % % type 2 keywords: font definitions
   % dfa_define_highlight_rule("\\\\bfseries", "keyword2", name);
   % dfa_define_highlight_rule("\\\\emph", "keyword2", name);
   % dfa_define_highlight_rule("\\\\itshape", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathbf", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathcal", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathit", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathnormal", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathrm", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathsf", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mathtt", "keyword2", name);
   % dfa_define_highlight_rule("\\\\mdseries", "keyword2", name);
   % dfa_define_highlight_rule("\\\\normalfont", "keyword2", name);
   % dfa_define_highlight_rule("\\\\rmfamily", "keyword2", name);
   % dfa_define_highlight_rule("\\\\scshape", "keyword2", name);
   % dfa_define_highlight_rule("\\\\sffamily", "keyword2", name);
   % dfa_define_highlight_rule("\\\\slshape", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textbf", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textit", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textmd", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textnormal", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textrm", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textsc", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textsf", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textsl", "keyword2", name);
   % dfa_define_highlight_rule("\\\\texttt", "keyword2", name);
   % dfa_define_highlight_rule("\\\\textup", "keyword2", name);
   % dfa_define_highlight_rule("\\\\ttfamily", "keyword2", name);
   % dfa_define_highlight_rule("\\\\upshape", "keyword2", name);
   % % size
   % dfa_define_highlight_rule("\\tiny"R, "keyword2", name);
   % dfa_define_highlight_rule("\\scriptsize"R, "keyword2", name);
   % dfa_define_highlight_rule("\\footnotesize"R, "keyword2", name);
   % dfa_define_highlight_rule("\\small"R, "keyword2", name);
   % dfa_define_highlight_rule("\\normalsize"R, "keyword2", name);
   % dfa_define_highlight_rule("\\large"R, "keyword2", name);
   % dfa_define_highlight_rule("\\Large"R, "keyword2", name);
   % dfa_define_highlight_rule("\\LARGE"R, "keyword2", name);
   % dfa_define_highlight_rule("\\huge"R, "keyword2", name);
   % dfa_define_highlight_rule("\\Huge"R, "keyword2", name);

   % type 1 keywords: a backslash followed by
   % one of -,:;!%$#&_ |\/{}~^´'``.=> :
   dfa_define_highlight_rule("\\[\-,:;!%\$#&_ \|\\/{}~\^'`\.=>]"R,
                             "keyword1", name);

   % type 0 keywords: a backslash followed by alpha characters
   dfa_define_highlight_rule("\\["R +
                             str_quote_string(TeX_Command_Chars, "*", '\\')
                             + "]+", "keyword", name);

   % % a backslash followed by a single char not covered by one of the
   % % previous rules is probably an error
   % dfa_define_highlight_rule("\\.", "error", name);

   % The symbols ~ ^ _
   dfa_define_highlight_rule("[~\\^_]", "operator", name);

   % numbers
   dfa_define_highlight_rule("[0-9]([\.,0-9]*[0-9])?"R, "number", name);
   dfa_define_highlight_rule("\$"R, "number", name);

   % % macro parameters(#1 #2 etc)
   % dfa_define_highlight_rule("#[1-9]", "operator", name);

   % quoted strings
   dfa_define_highlight_rule("\"`.*\"'", "Qstring", name); % german
   dfa_define_highlight_rule("``.*''", "Qstring", name);   % english

   % signle quotes
   dfa_define_highlight_rule("`[^']*'", "string", name);     % english

   % quoted strings accross lines; mark the three charaters after and
   % before the quote characters
   dfa_define_highlight_rule("[\"`]`.?.?.?", "string", name);
   dfa_define_highlight_rule(".?.?.?\"'", "string", name);
   dfa_define_highlight_rule(".?.?.?''", "string", name);

   dfa_define_highlight_rule("[ \t]+$", "trailing_whitespace", name);

   % Workaround to make UTF-8 characters are display correctly, not as
   % <C3><nn> or <E2><nn><nn>
   dfa_define_highlight_rule("\xC2.", "normal", name);
   dfa_define_highlight_rule("\xC3.", "normal", name);
   dfa_define_highlight_rule("\xE2..", "normal", name);
   dfa_define_highlight_rule("\xE3..", "normal", name);
   dfa_define_highlight_rule("\xE4..", "normal", name);
   dfa_define_highlight_rule("\xE5..", "normal", name);
   dfa_define_highlight_rule("\xE6..", "normal", name);
   dfa_define_highlight_rule("\xE7..", "normal", name);
   dfa_define_highlight_rule("\xE8..", "normal", name);
   dfa_define_highlight_rule("\xE9..", "normal", name);

   % all the rest
   % Fixme: Why we need this rule?
   dfa_define_highlight_rule(".", "normal", name);

   dfa_build_highlight_table(name);
}

dfa_set_init_callback(&setup_dfa_callback, MODE);
#endif

%%%%%%%%%%
%
%  Hooks
%

% Fix me!
% we should save the blocal vars on buffer close, but there isn't a hook for
% this
private define save_buf_before_hook(filename)
{
   % variable buf = latex_external->find_buf_of_file(filename);

   % if (buf == NULL)
   %   buf = whatbuf();

   % setbuf(buf);
   % push_spot();
   % eob();

   % variable old_buf_flags;
   % (,,,old_buf_flags) = getbuf_info();
   % setbuf_info( getbuf_info() & ~0x20 );

   % % the buf should end with a newline
   % !if(bolp()) newline();

   % % Fix me!
   % while ( andelse {up(1)} {bol(), looking_at_char('%')} ) {
   %    skip_chars("% ");
   %    if ( looking_at("LaTeX") ) {
   %    bol();
   %    push_mark();
   %    eol();
   %    ()=right(1);
   %    del_region();
   %    }
   % }
   % %   ()=down(1);
   % %   !if (eobp()) { eol(); newline(); }
   % eob();

   % variable error_occured=0;
   % ERROR_BLOCK {
   %    error_occured = 1;
   %    _clear_error();
   % }

   % foreach ( ["LaTeX_master_file", "LaTeX_output_format"] ) {
   %    if (error_occured)
   %   break;

   %    variable name = ();
   %    if ( latex_external->exists_master_file_var(name) )
   %   insert("% "+name+": "+latex_external->get_master_file_var(name)+"\n");
   % }
   % setbuf_info( getbuf_info() | (old_buf_flags&0x20) );
   % pop_spot();
}

% Fixme: better define a own format_paragraph
private define paragraph_separator()
{
   bol(); skip_white();
   return orelse {looking_at_char('\\')} {is_commented()} {eolp()};
}

%!%+
%\function{latex_mode}
%\synopsis{latex_mode}
%\usage{Void latex_mode()}
%\description
%  This mode is designed to facilitate the task of editing LaTeX files. It
%  calls the function \var{latex_mode_hook} on startup if it is defined. In
%  addition, if the abbreviation table \var{"TeX"} is defined, that table is
%  used.
%
%  There are way too many key-bindings for this mode.
%  Please have a look at the menus!
%!%-
public define latex_mode()
{
   set_mode(MODE, 0x21);

   use_keymap(MODE);
   use_syntax_table(MODE);

   set_buffer_hook("indent_hook", &indent_hook);
   set_buffer_hook("newline_indent_hook", &newline_indent_hook);
   set_buffer_hook("wrap_hook", &wrap_hook);
   set_buffer_hook("wrapok_hook", &wrapok_hook);
   append_to_hook("_jed_save_buffer_before_hooks", &save_buf_before_hook);
   set_buffer_hook("par_sep", &paragraph_separator);

   WRAP_INDENTS = 1;
   define_blocal_var("info_page", "latex");

   if ( abbrev_table_p("TeX") )
   {
       set_abbrev_mode(1);
       use_abbrev_table("TeX");
   }

   eob();
   !if (bobp()) {
       % file is not empty
       % Fix me!
       % Bad Hack: bol() doesn't take an argument, so 1 is left on stack for
       % and
       while ( andelse {up(1) and bol(1)} {looking_at_char('%')} ) {
           skip_chars("% ");
           push_mark();
           skip_chars("^:\n");
           variable name = bufsubstr();
           switch (name)
           { case "LaTeX_master_file" or case "LaTeX_output_format":
               create_blocal_var(name);
               skip_chars(": ");
               push_mark();
               eol();
               set_blocal_var(bufsubstr(), name);
           }
       }
       bob();

       while ( fsearch("\\begin{") )
       {
           () = right(7);
           if ( is_commented() )
             continue;

           push_mark();
           if ( ffind_char('}') )
           {
               variable env = chop_star( bufsubstr() );
               if (orelse {env == "document"} {env_lookup(env, NULL) != NULL})
                 continue;

               variable args = 0;
               () = right(1);
               while ( looking_at_char('{') )
               {
                   ++args;
                   fsearch_matching_brace();
                   () = right(1);
               }
               env_register(env, args,
                            "(auto-added on file load, found somewhere in document)",
                            "", NULL);
           }
           else
             pop_mark(0);
       }

       bob();
       if ( fsearch("\\documentclass") )
         while ( re_fsearch("\\[nb]e[wg]"R) )
         {
             () = right(1);
             if ( is_commented() )
               continue;

             if ( looking_at("begin{document}") )
               break;

             variable is_newcmd = 0, is_newtheorem = 0;
             if ( looking_at("newcommand") )
             {
                 is_newcmd = 1;
                 () = right(10);
             }
             else if ( looking_at("newenvironment") )
               () = right(14);
             else if ( looking_at("newtheorem") )
             {
                 is_newtheorem = 1;
                 () = right(10);
             }
             else
               continue;

             if ( looking_at_char('*') )
               () = right(1);

             variable arg_cnt;
             (,name) = cmd_parse_args(0, 1);
             name = name[0];
             (arg_cnt,) = cmd_parse_args(1, 0);

             if (orelse {is_newtheorem} {length(arg_cnt) == 0})
               arg_cnt = int(0);
             else
             {
                 arg_cnt = integer(arg_cnt[0]);
                 if (is_newcmd)
                 {                  % \newcommand with default args
                     variable tmp;
                     (tmp,) = cmd_parse_args(arg_cnt, 0);
                     arg_cnt -= length(tmp);
                 }
             }

             if (is_newcmd)
             {
                 variable def, is_math = 0;
                 (,def) = cmd_parse_args(0,1);
                 def = def[0];
                 if ( is_substr(def, "\\math") )
                   is_math = 1;
                 else if ( string_match(def, "[^\\][_^]"R, 1) >= 1 )
                   is_math = 1;

                 name = name[[1:]];       % remove the \
                 if (cmd_lookup(name, NULL) == NULL)
                   cmd_register(name, arg_cnt[0], int(is_math),
                                "(auto-added on file load, found in preample)",
                                "", NULL);
             }
             else
             {                            % environments and theorems
                 if (env_lookup(name, NULL) == NULL)
                   env_register(name, arg_cnt,
                                "(auto-added on file load, found in preample)",
                                "", NULL);
             }
         }

       bob();
   }

   run_mode_hooks("latex_mode_hook");
}

% -----

provide("latex");
runhooks("after_latex_load_hook", MODE);

% --- End of file latex.sl