% File:          latex.sl    -*- mode: SLang; mode: fold -*-
%
% Copyright (c)
%       until 2003  Guido Gonzato <guido.gonzato@univr.it> (as Latex4Jed)
%       2003--2007  Jörg Sommer <joerg@alea.gnuu.de>
%       $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
