/* ########################################################################

		    SMAC FILE USED BY XCORAL EDITOR

   File: shell-script.sc
   Path: /home/thierry/Xcoral/env/shell-script.sc
   Description: 
   Created: Sun Apr  8 09:45:13 2001
   Author: Thierry Emery
   Modified: Mon May 21 22:36:01 2001
   Last maintained by: Thierry Emery

   RCS $Revision$ $State$
   
   ########################################################################

   Note: minimal indent functions for Bourne Shell mode (sh, bash etc.)
         /!\ no support for csh (nor tcsh etc.) Cf. unix-faq/shell/csh-whynot

   Requires: mode.sc
	     mode-ext.sc

   Defines: shell_script_indent_step (default 3)
	    shell_script_single_quote_program_start_regexp (default awk/nawk/perl/ruby/sed)
	    shell_script_indent_line()
	    shell_script_insert_and_indent()
	    shell_script_indent_region()

   Predefined bindings: TAB : `shell_script_indent_line'
			Return / C-m : `shell_script_insert_and_indent'

   Suggested additional bindings: 
    - BS (<-) a.k.a. ^h : `delete_previous_char_untabify' (from mode-ext.sc):
      * either in shell-script mode:
      key_def("shell", "^h", "delete_previous_char_untabify");
      * or in all global modes:
        globalize_mode("shell");
        ...
        global_key_def("^h", "delete_previous_char_untabify");
    - ESC TAB :
      * either `shell_script_indent_region' in shell-script mode:
        key_def("shell", "^[\t", "shell_script_indent_region");
      * or `indent_region' (from mode-ext.sc) in all global modes:
        globalize_mode("shell");
        ...
        global_key_def("^[\t", "indent_region");
    - in shell-script mode, bind `shell_script_insert_and_indent' to other
      characters (e.g. { } )
 
   Procedure:
    - TAB and Return indent automatically, then any additional TAB at the
      same position indents manually by `shell_script_indent_step'
    - if BS has been bound to `delete_previous_char_untabify' it can be used
      to manually decrease indentation by 1, even when the preceding char
      was a TAB
    - backquote or `shell_script_single_quote_program_start_regexp' single
      quote programs have to be indented manually - by the way they are
      not modified by `shell_script_indent_region'
    - statements which increase indentation should either stand alone on
      their line or be balanced by the closing statement on the same line,
      e.g.:
      * items (i.e. "...) ... ;;") following a "case ... in" should start on
        a new line (unless the whole "case ... esac" fits on a single line)
      * nested cases should also start on a new line (unless the whole
        "...) case ... in ... esac ;;" fits on a single line)
      * "if" should not be appended to a "then" or "else" line (anyhow "elif"
        is supported)
    - statements which decrease indentation should either stand alone on
      their line or be preceded by their opening statement on the same line,
      e.g.:
      * "esac" should not be appended to a case item
      * "fi" should not be appended to a conditional branch

   ########################################################################

   Copyright (c) : Thierry Emery

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

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   ######################################################################## */


{
  if (! function("current_indent"))
    load_file("mode-ext.sc");
}

/* --------------------------------------------------------------------------------
   key bindings
   -------------------------------------------------------------------------------- */

{
  key_def("shell", "\r", "shell_script_insert_and_indent");
  key_def("shell", "\t", "shell_script_indent_line");
}

/* --------------------------------------------------------------------------------
   indentation step
   -------------------------------------------------------------------------------- */

int shell_script_indent_step = 3;

/* --------------------------------------------------------------------------------
   comment line regexp
   -------------------------------------------------------------------------------- */

char* shell_script_comment_line_regexp = "^[ \t]*#";

/* --------------------------------------------------------------------------------
   open regexp
   -------------------------------------------------------------------------------- */

char* shell_script_open_regexp =
  "[ \t]*\\({\\|[^(\r\n \t]+[^(\r\n]*).*\\|\\<\\(case\\>.*\\(\\\\\n\\)?.*\\<in\\|do\\>.*\\|else\\|\\(el\\)?if\\>.*\\(\\\\\n\\)?.*\\<then\\|then\\>.*\\|\\(for\\|while\\)\\>.*\\(\\\\\n\\)?.*\\<do\\)\\)\\([ \t\\\\]*\\|[ \t]*\\(#.*\\)?\\)$";

/* --------------------------------------------------------------------------------
   open and close regexp
   -------------------------------------------------------------------------------- */

char* shell_script_open_and_close_regexp =
  "[ \t]*\\({.*}\\|[^(\r\n]+).*;;[ \t]*\\(#.*\\)?$\\|\\<\\(case\\>.*\\<esac\\|\\(\\(el\\)?if\\|then\\)\\>.*\\<fi\\|\\(for\\|while\\)\\>.*\\<done\\)\\>\\)";

/* --------------------------------------------------------------------------------
   intermediate regexp
   -------------------------------------------------------------------------------- */

char* shell_script_intermediate_regexp = ".*\\<\\(elif\\|else\\)\\>";

/* --------------------------------------------------------------------------------
   ajar regexp
   -------------------------------------------------------------------------------- */

char* shell_script_ajar_regexp = ".*;;\\([ \t]*esac\\)?[ \t]*$";

/* --------------------------------------------------------------------------------
   close regexp
   -------------------------------------------------------------------------------- */

char* shell_script_close_regexp = "\\([^{\r\n]*}\\|.*\\<\\(done\\|esac\\|fi\\)\\>\\)";

/* --------------------------------------------------------------------------------
   decrease indentation regexp
   -------------------------------------------------------------------------------- */

char* shell_script_inter_or_close_regexp = (char*) malloc(strlen(shell_script_intermediate_regexp)
							   + strlen(shell_script_close_regexp)
							   + 10);
{
  sprintf(shell_script_inter_or_close_regexp, "\\(%s\\|%s\\)",
	  shell_script_intermediate_regexp, shell_script_close_regexp);
}

/* --------------------------------------------------------------------------------
   commands which argument is a program specified between single quotes
   -------------------------------------------------------------------------------- */

char* shell_script_single_quote_program_start_regexp = ".*\\<\\(awk\\|nawk\\|perl\\|ruby\\)\\>[^'\r\n]*'";

/* --------------------------------------------------------------------------------
   last window in which `shell_script_indent_line' was invoked
   -------------------------------------------------------------------------------- */

int shell_script_indent_line_last_window = -1;

/* --------------------------------------------------------------------------------
   last position where `shell_script_indent_line' was invoked
   -------------------------------------------------------------------------------- */

int shell_script_indent_line_last_pos = -1;

/* --------------------------------------------------------------------------------
   buffer size where `shell_script_indent_line' was invoked
   -------------------------------------------------------------------------------- */

int shell_script_indent_line_last_buffer_size = -1;

/* --------------------------------------------------------------------------------
   indicates when a shell script line is a continuation
   -------------------------------------------------------------------------------- */

int shell_script_continuation_p() {

  int origin = current_position(), result = 0;

  goto_beginning_of_line();
  if (current_position() != 0) {
    goto_previous_char();
    if (previous_char() == '\\') {
      goto_beginning_of_line();
      if (! (looking_at(".*;[ \t\\\\]*")
	     || looking_at(shell_script_comment_line_regexp)
	     || (looking_at(shell_script_open_regexp)
		 && !looking_at(shell_script_close_regexp)))) {
	result = 1;
      }
    }
  }
  goto_char(origin);
  return result;
}

/* --------------------------------------------------------------------------------
   computes indentation for a shell script line based on the current and preceding
   line(s)

   N.B. does not return to origin point
   -------------------------------------------------------------------------------- */

int shell_script_compute_indent() {

  int indent = 0, cont = 1, quote_skipped;

  goto_beginning_of_line();		      /* look at current line */
  if (!looking_at(shell_script_comment_line_regexp)
      && !looking_at(shell_script_open_and_close_regexp)
      && looking_at(shell_script_inter_or_close_regexp)) {
    indent -= shell_script_indent_step;	      /* decrease indent */
  }
  if (shell_script_continuation_p())
    indent += shell_script_indent_step;	      /* increase indent */

  do { goto_previous_line();			      /* skip backward to last significant line */
       quote_skipped = 0;
       if (looking_at(".*\\(['`]\\)[ \t]*$")) {
	 goto_char(re_match_beginning(1));
	 backward_search(window_substring(re_match_beginning(1),re_match_end(1)+1)); /* skip quote expression */
	 goto_beginning_of_line();
	 quote_skipped = 1;
      }
    }
  while (current_position() != 0 && 
	 (looking_at("[ \t]*$") || looking_at(shell_script_comment_line_regexp) || shell_script_continuation_p()));
    
  if (!looking_at("[ \t]*$")) {		      /* look at previous significant line */
    if (!quote_skipped && looking_at(shell_script_single_quote_program_start_regexp)) /* single quote program */
     indent += c_column(re_match_end(0)) + shell_script_indent_step; /* align on opening quote + indent step */
    else {
      indent += current_indent();	      /* previous indent */
      if (!looking_at(shell_script_open_and_close_regexp))
       if (looking_at(shell_script_ajar_regexp))
	indent -= shell_script_indent_step;   /* decrease indent */
       else
	if (!looking_at(shell_script_comment_line_regexp)
	    && !looking_at(shell_script_close_regexp)
	    && looking_at(shell_script_open_regexp)) {
	  indent += shell_script_indent_step; /* increase indent */
      }
    }
  }
  return indent;
}

/* --------------------------------------------------------------------------------
   indents a shell script line either automatically (first invocation at a given
   position) or manually (on subsequent invocations from the same position)

   plus either moves to the first non-blank character (when point was between
   beginning of line and this character) or stays on its original relative position
   -------------------------------------------------------------------------------- */

void shell_script_indent_line() {

  int origin = current_position(), indent_origin, original_indent, new_indent, correction;

  goto_beginning_of_line();

  c_jump_spaces_forward();
  indent_origin = current_position();
  original_indent = current_indent();

  if (current_window() == shell_script_indent_line_last_window
      && origin == shell_script_indent_line_last_pos
      && end_of_file() == shell_script_indent_line_last_buffer_size)
   new_indent = original_indent + shell_script_indent_step; /* manual indent */
  else /* automatic indent */
   if (current_position() == 0)
    new_indent = 0;
   else
    new_indent = shell_script_compute_indent();

  goto_char(origin);
  goto_beginning_of_line();
  if (new_indent < 0)
   new_indent = 0;

  correction += reindent_to(new_indent);
  if (origin >= indent_origin)
   origin += correction;
  else
   origin = current_position();

  shell_script_indent_line_last_window = current_window();
  shell_script_indent_line_last_pos = origin;
  shell_script_indent_line_last_buffer_size = end_of_file();
  goto_char(origin);
}
  
/* --------------------------------------------------------------------------------
   insert and indent : copied and simplified from `c_insert_and_indent'
   -------------------------------------------------------------------------------- */

void shell_script_insert_and_indent()
{
  char c = last_key();

  insert_char((c == '\r') ? '\n' : c);
  if (c_nothing_before())
   shell_script_indent_line();
}

/* --------------------------------------------------------------------------------
   indent shell script region : copied and adapted from `latex_indent_region'
   -------------------------------------------------------------------------------- */

void shell_script_indent_region()
{
  int m = mark_position(), distance, line;
  
  if (m < 0) {
    display_message("No region specified");
    return;
  }
  watch_on();
  {
    int beginning = m;
    int end = current_position();
    
    if (end < beginning) {
      end = beginning;
      beginning = current_position();
    }
    goto_char(beginning);
    distance = end_of_file() - end;
  }  
  while ((end_of_file() - current_position()) > distance) {
    /* indent one line */
    shell_script_indent_line();
    goto_beginning_of_line();
    line = current_line();
    if (looking_at(shell_script_single_quote_program_start_regexp)) {
      /* skip single quote program */
      goto_char(re_match_end(0));
      forward_search("'");
      goto_next_line();
    }
    else if (forward_search("`") && current_line() == line) {
      /* skip backquote expression */
      forward_search("`");
      goto_next_line();
    }
    else {
     /* proceed to next line*/
     goto_line(line+1);
    }
    goto_beginning_of_line();
  }
  watch_off();
}

