% File:          latex.sl    -*- mode: SLang; mode: fold -*-
%
% Copyright (c)
%       2007         Jörg Sommer <joerg@alea.gnuu.de>
%       $Id: latex_typo.sl 206 2007-09-08 13:38:39Z joerg $
%
%       -*- This file is part of Jörg's LaTeX Mode (JLM) -*-
%
% Description: This file includes all functions related to typography.
%
% 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.

if (current_namespace() != "latex")
  throw UsageError, "You must load this file into the namespace \"latex\"";

private define is_active()
{
    return andelse {LaTeX_Typo_Active} {not is_verbatim()};
}

private variable b_n_c_del;
private variable b_n_c_insert;
private variable b_n_c_fun;

private define before_next_char_hook(fun);
private define before_next_char_hook(fun)
{
    debug_msg(sprintf("%s: %c, %S, %s, %s, %S", _function_name(), LAST_CHAR,
                      fun, b_n_c_del, b_n_c_insert, b_n_c_fun) );

    EXIT_BLOCK
    {
        remove_from_hook("_jed_before_key_hooks", &before_next_char_hook());
        __uninitialize(&b_n_c_del);
    }

    if (andelse {typeof(fun) == String_Type}
        {orelse {fun == "self_insert_cmd"} {strncmp(fun, "latex->", 7) == 0}}
        {@b_n_c_fun(LAST_CHAR)} )
    {
        variable len = strlen(b_n_c_del);
        () = left(len);
        if ( looking_at(b_n_c_del) )
          () = replace_chars(len, b_n_c_insert);
        else
          () = right(len);
    }
}

private define enable_before_next_char_hook(del, ins, fun)
{
    if ( __is_initialized(&b_n_c_del) )
      throw UsageError, "before_next_char_hook already active";

    b_n_c_del = del;
    b_n_c_insert = ins;
    b_n_c_fun = fun;

    add_to_hook("_jed_before_key_hooks", &before_next_char_hook());
}

private define is_alpha_or_hyphen(char)
{
    return orelse {char == '-'} {isalpha(char)};
}

%!%+
%\function{typo_slash()}
%\synopsis{Replaces a / after a word by \\slash{} or adds ""}
%\usage{typo_slash()}
%\description
%  LaTeX does not break words combined with / after the slash, e.g. the
%  text "input/output" is one block and is not wrapped before output.
%
%  If the number of characters before the / is at least \var{LaTeX_Typo_Word_Size},
%  the / is replaced by \\slash{}, if the package babel is not loaded, or a
%  "" is appended, if the package babel is loaded. \\slash{} is a / with an
%  hyphenpoint after it. Unfortunely, it removes all other hyphenpoints
%  from word after the slash. This does not happen with "" from babel.
%
%\notes
%  You can disable this function by setting the variable \var{LaTeX_Typo_Active}
%  to 0. This still inserts the / character as \sfun{self_insert_cmd} would do.
%
%\seealso{LaTeX_Typo_Active, LaTeX_Typo_Word_Size}
%!%-
static define typo_slash()
{
    if (LAST_CHAR != '/')
      throw UsageError, "typo_slash called for anything else than /";

    % Insert the / and do abbrev expansion
    call("self_insert_cmd");

    !if ( is_active() )
      return;

    push_spot();
    EXIT_BLOCK
    {
        pop_spot();
    }

    () = left(1);
    if ( blooking_at("-") or blooking_at("\"~") or blooking_at("\"\"") )
    {
        EXIT_BLOCK {};
        pop_spot();

        if ( pkg_loaded("babel") )
          insert("\"\"");
        else
          () = replace_chars(1, "\\slash{}");

        return;
    }

    _get_point();
    bskip_word_chars();
    if ( () - _get_point() < LaTeX_Typo_Word_Size )
      return;

    if ( pkg_loaded("babel") )
      enable_before_next_char_hook("", "\"\"", &is_alpha_or_hyphen());
    else
      enable_before_next_char_hook("/", "\\slash{}", &isalpha());
}

%!%+
%\function{typo_percent()}
%\synopsis{Inserts a \\, before \\%}
%\usage{typo_percent()}
%\description
%  In german typography, a spatium (\\,) should be before a percent sign.
%  This function removes all whitespaces before \\% and insert an \\,.
%
%\notes
%  You can disable this function by setting the variable \var{LaTeX_Typo_Active}
%  to 0. This still inserts the % character as \sfun{self_insert_cmd} would do.
%
%\seealso{LaTeX_Typo_Active}
%!%-
static define typo_percent()
{
    if (LAST_CHAR != '%')
      throw UsageError, "typo_percent called for anything else than %";

    if (andelse {is_active()} {is_escaped()})
    {
        () = left(1);
        trim();
        !if ( blooking_at("\\,") )
          insert("\\,");
        () = right(1);
    }

    call("self_insert_cmd");
}

%!%+
%\function{typo_abbrev()}
%\synopsis{Inserts a \\, between abbreviations}
%\usage{typo_abbrev()}
%\description
%  In german typography, a spatium (\\,) should be after the dot inside of
%  an abbreviation, e.g. z.\\,B. or i.\\,d.\\,R. This functions checks if the
%  number of characters before the first dot and before the current point is
%  less than LaTeX_Typo_Word_Size and inserts a \\, after the previous dot,
%  if it is so.
%
%\notes
%  You can disable this function by setting the variable \var{LaTeX_Typo_Active}
%  to 0. This still inserts the % character as \sfun{self_insert_cmd} would do.
%
%\seealso{LaTeX_Typo_Active, LaTeX_Typo_Word_Size}
%!%-
static define typo_abbrev()
{
    if (LAST_CHAR != '.')
      throw UsageError, "typo_abbrev called for anything else than .";

    call("self_insert_cmd");

    if ( is_active() )
    {
        push_spot();
        EXIT_BLOCK
        {
            pop_spot();
        }
        () = left(1);

        _get_point();
        bskip_word_chars();
        variable len = () - _get_point();
        if (len == 0 or len >= LaTeX_Typo_Word_Size)
          return;
        bskip_white();

        () = left(1);
        !if ( looking_at_char('.') )
          return;

        push_spot();
        EXIT_BLOCK
        {
            pop_spot();
            pop_spot();
        }

        _get_point();
        bskip_word_chars();
        if ( () - _get_point() >= LaTeX_Typo_Word_Size )
          return;

        pop_spot();
        () = right(1);
        trim();
        insert("\\,");
    }
}

%!%+
%\function{typo_german_decimal_point()}
%\synopsis{Puts a comma between digits into \\mathord{}}
%\usage{typo_german_decimal_point()}
%\description
%  In german, the comma is the separator of the fractional part of
%  numbers. In LaTeX, the default meaning of , in math mode is as a list
%  separator. Due to this it's necessary to tell LaTeX that the , is the
%  decimal point.
%
%  This functions checks, if the characters before the comma are digits
%  with a leading space and if the characters behind the comma are digits.
%  Then it replaces the comma by \\mathord{,}. In text mode, this functions
%  does nothing special.
%
%\notes
%  You can disable this function by setting the variable \var{LaTeX_Typo_Active}
%  to 0. This still inserts the , character as \sfun{self_insert_cmd} would do.
%
%\seealso{LaTeX_Typo_Active}
%!%-
static define typo_german_decimal_point()
{
    if (LAST_CHAR != ',')
      throw UsageError, "typo_german_decimal_point called for anything else than ,";

    call("self_insert_cmd");

    !if (andelse {is_active()} {is_math()})
      return;

    push_spot();
    EXIT_BLOCK
    {
        pop_spot();
    }

    () = left(1);
    _get_point();
    bskip_chars("0-9");
    if ( andelse {() - _get_point() > 0}
         {orelse {bolp()} {left(1) and is_substr(" \t$", char(what_char()))}} )
      enable_before_next_char_hook(",", "\\mathord{,}", &isdigit());
}

private variable word_char_count;

private define hyphen_hook(fun);
private define hyphen_hook(fun)
{
    debug_msg(_function_name() + ": " + char(LAST_CHAR) + ", $fun"$);

    EXIT_BLOCK
    {
        remove_from_hook("_jed_before_key_hooks", &hyphen_hook());
        __uninitialize(&word_char_count);
    }

    variable replace_str, back = 1;

    if (andelse {typeof(fun) == String_Type} {fun == "self_insert_cmd"}
          {isalpha(LAST_CHAR)})
    {
        if ( __is_initialized(&word_char_count) )
          % 8.
        {
            debug_msg("hyphen_hook: $word_char_count"$);
            ++word_char_count;
            if (word_char_count < LaTeX_Typo_Word_Size)
            {
                EXIT_BLOCK;
                return;
            }

            back = word_char_count;
            replace_str = "\"=";
        }
        else
          % 7.
          replace_str = "\"~";
    }
    else if (LAST_CHAR == ')')
      % 6.
    {
        replace_str = "\"~";
        enable_before_next_char_hook("", "\"\"", &isalpha());
    }
    else if (LAST_CHAR == '/')
      % 5.
      replace_str = "\"~";
    else
      return;

    () = left(back);
    () = replace_chars(1, replace_str);
    () = right(back - 1);
}

%!%+
%\function{typo_hyphen()}
%\synopsis{Replace a hyphen by a special hyphen}
%\usage{typo_hyphen()}
%\description
%  A simple - is not everywhere what you want. Babel defines some special
%  hypens:
%#v+
%  texdoc gerdoc section 2.2.4 or texdoc babel
%    ""    an unvisible hyphen point
%    "~    an hyphen without an hyphen point
%    "=    an hyphen that doesn't prevents hyphenation in the
%          rest of the word lik - does
%#v-
%
% \sfun{typo_hyphen} makes the following replacements:
%#v+
%  w = word character     s = space ( \t)
%  W = at least LaTeX_Typo_Word_Size word chars
%
%   rule               |     example
%  --------------------+-------------------
%   1. $-w -> $"~w     | $i$-mal, $\alpha$-Teilchen
%   2. }-w -> }"~w     | \LaTeX{}-Aufruf
%   3. \w-w -> \w"~w   | \LaTeX-Aufruf
%   4. s-w -> s"~w     | bergauf und -ab
%   5. w-/ -> w"~/     | Ein-/Ausgabe
%   6. w-) -> w"~)     | (Haupt-)Aufgabe
%   7. w-w -> w"~w     | I-Punkt, HI-Virus
%   8. W-W -> W"=W     | primitiv-rekursiv
%   9. /-w -> /"~w     | Vorlesungsgliederung/-struktur
%  10. /""-w -> /"""~w | Vorlesungsgliederung/-struktur
%#v-
%\notes
%  You can disable this function by setting the variable \var{LaTeX_Typo_Active}
%  to 0. This still inserts the - character as \sfun{self_insert_cmd} would do.
%
%\seealso{LaTeX_Typo_Active}
%!%-
static define typo_hyphen()
{
    if (LAST_CHAR != '-')
      throw UsageError, "typo_hyphen called for anything else than -";

    call("self_insert_cmd");

    !if (andelse {is_active()} {pkg_loaded("babel")})
      return;

    push_spot();
    EXIT_BLOCK
    {
        pop_spot();
    }

    if ( orelse {left(2) and is_substr("$}/ \t", char(what_char()) )}
         {left(2) and looking_at("/\"\"")} )
      % 1. + 2. + 4. + 9. + 10.
    {
        enable_before_next_char_hook("-", "\"~", &isalpha());
        return;
    }

    () = right(3);
    _get_point();
    bskip_word_chars();
    variable char_cnt = () - _get_point();

    if (char_cnt == 0)
      return;

    if ( is_escaped() )
      % 3.
      enable_before_next_char_hook("-", "\"~", &isalpha());
    else
      % 5.--8.
    {
        if (char_cnt >= LaTeX_Typo_Word_Size)
          % 8.
          word_char_count = 0;

        add_to_hook("_jed_before_key_hooks", &hyphen_hook());
    }
}

static define typo_dots()
{
    if ( orelse {is_verbatim()} {not blooking_at("..")} )
    {
        typo_abbrev();
        return;
    }

    () = left(2);
    push_spot();

    variable text;
    !if ( is_math() )
    {
        push_mark();
        bskip_chars(" ");
        () = left(1);
        !if ( looking_at_char(',') )
          () = right(1);
        text = bufsubstr();

        pop_spot();
        deln(2);
        try
          cmd_insert("dots", 0);
        catch ApplicationError:
          cmd_insert("ldots");

        insert(text);
        return;
    }

    bskip_chars(" ");
    () = left(1);

    variable ch = what_char();
    switch (ch)
    { case ',':
        if ( pkg_loaded("amsmath") )
           text = "\\dotsc,";
        else
          text = "\\ldots,";
    }
    { case '+' or case '-' or case '<' or case '>' or case '=':
        if ( pkg_loaded("amsmath") )
          text = "\\dotsb" + char(what_char());
        else
          text = "\\cdots" + char( what_char() );
    }
    { case '}':
        if ( pkg_loaded("amsmath") )
          text = "\\dotso";
        else
          text = "\\ldots";
    }
    {
        variable cmd = is_command();

        switch(cmd)
        { case "\\leq" or case "\\geq" or case "\\pm" or case "\\mp" or
               case "\\cup" or case "\\cap" or case "\\vee" or case "\\wedge":
            if ( pkg_loaded("amsmath") )
              text = "\\dotsb" + cmd;
            else
              text = "\\cdots" + cmd;
        }
        { case "\\int" or case "\\sum" or case "\\prod":
            if ( pkg_loaded("amsmath") )
              text = "\\dotsi";
            else
              text = "\\cdots";

            pop_spot();
            () = replace_chars(2, text+cmd);
            return;
        }
        {
            if ( pkg_loaded("amsmath") )
              text = "\\dotso ";
            else
              text = "\\ldots ";

            pop_spot();
            () = replace_chars(2, text);
            return;
        }
    }
    bskip_chars(" ");
    () = left(1);

    variable looping = 0;
    while ( looking_at_char('}') )
    {
        variable mark = create_user_mark();
        if (find_matching_delimiter('}') != 1)
        {
            goto_user_mark(mark);
            break;
        }
        () = left(1);
        switch ( what_char() )
        { case '_' or case '^':
            if (looping == 0)
            {
                () = right(2);
                push_mark();
                () = left(3);
            }
            else
              () = left(1);

            ++looping;
        }
        {
            goto_user_mark(mark);
            break;
        }
    }

    if (looping == 0)
    {
        pop_spot();
        () = replace_chars(2, text);
        return;
    }

    if ( re_looking_at("[0-9]") )
      bskip_chars("0-9");
    else
    {
        variable found_brakets = 0;
        if ( andelse {looking_at_char('}')} {find_matching_delimiter('}') == 1} )
        {
            found_brakets = 1;
            () = left(1);
        }

        cmd = is_command();
        switch (cmd)
        { case "\\int" or case "\\sum" or case "\\prod":
            if ( pkg_loaded("amsmath") )
              text = "\\dotsi";
            else
              text = "\\cdots";

            pop_mark(0);
            pop_spot();
            () = replace_chars(2, text+cmd);
            return;
        }
        { case NULL:
            if (found_brakets)
              () = right(1);
            else
            {
                if ( re_looking_at("[A-Za-z]") )
                  bskip_chars("A-Za-z");
                text += " ";
            }
        }
    }
    text += bufsubstr();

    pop_spot();
    () = replace_chars(2, text+"}");
    () = left(1);
}
