% Python mode 
% File: pymode.sl v1.3
%
% For editing source code written in the Python programming language.
% Provides basic compatibility with Python mode under real Emacs
%
% Authors: Harri Pasanen <hpa@iki.fi>
%          Brien Barton <brien_barton@hotmail.com>
%
% following keys have python specific bindings:
%
% DELETE deletes to previous indent level
% TAB indents line
% ^C#  comments region or current line
% ^C>  shifts line or region right
% ^C<  shifts line or region left
% ^C^C executes the region, or the buffer if region not marked.
% ^C|  executes the region
% ^C\t reindents the region
% :    colon dedents appropriately
%
% See python_mode function for available hooks
%
% Shortcomings: does not really support triple-quoted strings in any way.
%

% Changes from v1.0:
% 
% Major improvements, mostly done by Brien Barton:
%
% - execution of python code from JED
% - DFA syntax support
% - improved indent - dedent.
%  

% Changes from v1.1:
%
% Minor fixes, by Tom Culliton
%
% - corrected a syntax error
% - fixed non-DFA syntax hilighting tables to work better
% - added the new assert keyword

% Changes from v1.2
% - autoindent correction

$1 = "python";

!if (keymap_p ($1)) make_keymap ($1);

definekey ("py_backspace_key", "^?", $1);
!if (is_defined ("Win_Keys")) {  % Ctrl-C conflicts with windows region copy.
   definekey ("py_comment_region", "^C#", $1);
   definekey ("py_uncomment_region", "^C3", $1);
   definekey ("py_shift_region_right", "^C>", $1);
   definekey ("py_shift_region_left", "^C<", $1);
   definekey ("py_exec", "^C^C", $1);    % Execute buffer, or region if defined
   definekey ("py_exec_region", "^C|", $1);
   definekey ("py_reindent_region", "^C\t", $1);
   definekey ("indent_line", "\t", $1);
}
definekey ("py_electric_colon", ":", $1);
#ifdef MSWINDOWS
definekey ("py_help_on_word", "^@;", $1);
#endif



% Set the following to your favourite indentation level
!if (is_defined ("Py_Indent_Level")) { % users can set this in .jedrc
   variable Py_Indent_Level = 4;
}

static define py_line_ends_with_colon()
{
   eol();
   if (bfind_char(':')) {
      go_right(1);
      skip_white();
      if (eolp() or looking_at_char('#'))
	return 1;
   }
   return 0;
}

static define py_endblock_cmd()
{
   bol_skip_white();
   if (looking_at("return") or
       looking_at("raise")  or
       looking_at("break")  or
       looking_at("pass")  or
       looking_at("continue"))
     return 1;
   return 0;
}

static define py_line_starts_subblock()
{
   bol_skip_white();
   if (looking_at("else") or
      looking_at("elif")  or
      looking_at("except")  or
      looking_at("finally"))
      return 1;
   return 0;
}

static define py_line_starts_block()
{
   bol_skip_white();
   if (looking_at("if") or
      looking_at("try") or
      py_line_starts_subblock())
      return 1;
   return 0;
}

static define py_indent_calculate()
{  % return the indentation of the previous python line
   variable col = 0;
   variable subblock = 0;
    
   EXIT_BLOCK
     {
	pop_spot ();
	return col;
     }
    
   % check if current line starts a sub-block
   subblock = py_line_starts_subblock();
   
   % go to previous non blank line
   push_spot_bol ();
   !if (re_bsearch ("[^ \t\n]"))
     return;
   bol_skip_white();
    
   col = what_column() - 1;
    
   if (py_line_ends_with_colon())
      col += Py_Indent_Level;
   if (py_endblock_cmd() or (subblock and not py_line_starts_block()))
      col -= Py_Indent_Level;
}

define py_indent_line()
{
   variable col;
    
   col = py_indent_calculate();
   bol_trim ();
   whitespace( col );
}

define py_comment_line() 
{
   bol();
   insert("##");
}

define py_electric_colon() 
{
   variable i;
   insert(":");
   push_spot();
   if (py_line_starts_subblock())  % Only dedents on colon
     {
	pop_spot();
	i = what_column();
	bol_skip_white();
	i = i - what_column();
	if (py_indent_calculate() < what_column()) % Ensure dedent only
	  py_indent_line();
	bol_skip_white();
	goto_column( i + what_column() );
     }
   else
     pop_spot();
}

define py_comment_region()
{
   variable n;
    
   check_region (1);
   n = what_line ();
   pop_mark_1 ();
   loop (n - what_line ())
     {
	py_comment_line();
	go_down_1 ();
     }
   pop_spot();
}

define py_comment() 
{
   push_spot();
   if (markp()) {
      py_comment_region();
   } else {
      py_comment_line();
   }
   pop_spot();
}

define py_uncomment_line() 
{
   bol_skip_white();
   while (looking_at("#")) del();
}

define py_uncomment_region()
{
   variable n;
   
   check_region (1);
   n = what_line ();
   pop_mark_1 ();
   loop (n - what_line ())
     {
	py_uncomment_line();
	go_down_1 ();
     }
   pop_spot();
}

define py_uncomment() {
   push_spot();
   if (markp()) {
      py_uncomment_region();
   } else {
      py_uncomment_line();
   }
   pop_spot();
}

define py_backspace_key() 
{ 
   variable col;                                                    
                                   
   col = what_column(); 
   push_spot(); 
   bskip_white(); 
   if (bolp() and (col > 1)) { 
      pop_spot();                                                     
      bol_trim (); 
      col--;                                                         
      if (col mod Py_Indent_Level == 0) 
        col--; 
      whitespace ( (col / Py_Indent_Level) * Py_Indent_Level ); 
   } 
   else { 
      pop_spot(); 
      call("backward_delete_char_untabify"); 
   } 
} 

define py_shift_line_right()
{
   bol_skip_white();
   whitespace(Py_Indent_Level);
}

define py_shift_region_right()
{
   variable n;
   check_region (1);		       %  spot_pushed, now at end of region
   n = what_line ();
   pop_mark_1 ();
   loop (n - what_line ())
     {
	py_shift_line_right();
	go_down_1 ();
     }
   pop_spot();
}

define py_shift_right() 
{
   push_spot();
   if (markp()) {
      py_shift_region_right();
   } else {
      py_shift_line_right();
   }
   pop_spot();
}

define py_shift_line_left()
{
   bol_skip_white();
   if (what_column() > Py_Indent_Level) {
      push_mark();
      goto_column(what_column() - Py_Indent_Level);
      del_region();
   }
}

define py_shift_region_left()
{
   variable n;
   
   check_region (1);
   n = what_line ();
   pop_mark_1 ();
   loop (n - what_line ())
     {
	py_shift_line_left();
	go_down_1 ();
     }
   pop_spot();
}

define py_shift_left() {
   push_spot();
   if (markp()) {
      py_shift_region_left();
   } else {
      py_shift_line_left();
   }
   pop_spot();
}

define py_newline_and_indent()
{
   newline();
   py_indent_line();
}

define file_path(fullname)
{
   variable filename;
   filename = extract_filename(fullname);
   substr(fullname, 1, strlen(fullname)-strlen(filename));
}

define py_exec_region() 
{ 
   % Run python interpreter on current region. 
   % Display output in *shell-output* buffer window. 
   variable oldbuf, thisbuf, file, line, start_line; 
   variable tmpfile = "_python.tmp"; 
   variable error_regexp = "^  File \"\\([^\"]+\\)\", line \\(\\d+\\).*"; 
   variable py_source = buffer_filename(); 
   change_default_dir(file_path(py_source));
   thisbuf = whatbuf(); 
   % Check if 1st line starts in column 1 
   exchange_point_and_mark(); 
   bol_skip_white(); 
   start_line = what_line(); 
   if (what_column() > 1) { 
      % Workaround in case block is indented 
      write_string_to_file("if 1:\n", tmpfile); bol(); 
      start_line--;   % offset for this extra line 
   } 
   exchange_point_and_mark(); 
   append_region_to_file(tmpfile); 
   oldbuf = pop2buf_whatbuf("*shell-output*"); erase_buffer (); 
#ifdef UNIX 
   run_shell_cmd(sprintf("python %s 2>&1", tmpfile));
#else 
   run_shell_cmd(sprintf("python %s", tmpfile));
#endif 
   () = delete_file(tmpfile); 
 
   % try to restore any window that got replaced by the shell-output 
   if (strlen(oldbuf) and (strcmp(oldbuf, "*shell-output*") != 0) 
       and (strcmp(thisbuf, oldbuf) != 0)) { 
      splitwindow(); sw2buf(oldbuf); pop2buf("*shell-output*"); 
   } 
   eob(); 
   %  Check for error message 
   while (re_bsearch(error_regexp) != 0) { 
      %  Make sure error occurred in the file we were executing 
      file = regexp_nth_match(1); 
      line = integer(regexp_nth_match(2)); 
      if (strcmp(file, tmpfile) == 0) { 
	 %  Move to line in source that generated the error 
	 pop2buf(thisbuf); 
	 goto_line(line + start_line - 1); 
	 break; 
      } else { 
	 %  Error is in another file, try previous error message 
	 continue; 
      } 
   } 
} 


define py_exec() 
{
   % Run python interpreter on current region if one is defined, otherwise
   % on the whole buffer.
   % Display output in *shell-output* buffer window.
   !if (markp()) {		% create region containing entire buffer
      push_spot_bob ();
      push_mark_eob ();
   }
   py_exec_region();
}

define py_reindent() {
   % Reindents a (correctly) indented buffer using the current
   % value of Py_Indent_Level.
   % Warning: Current version can be fooled by implicit or explicit
   %   continuation lines.
   variable indent_level = create_array('i', 64, 1);
   variable level = -1;
   variable current_indent = -1;
   variable errmsg, i, col, ignore, oldlevel;
   
   for (i = 0; i < 64; i++) {
      indent_level[i] = -1;
   }
   bob();
   do {
      bol_skip_white();
      ignore = looking_at_char('#') or eolp();
      if (ignore) continue;	% skip comments and blank lines
      col = what_column() - 1;
      oldlevel = level;		% save current level
      if (col > current_indent) {	% indenting
	 level++;
      } else if (col < current_indent) {	% dedent
	 while ((level > 0) and (indent_level[level] > col)) {
	    indent_level[level] = -1;  % clear current level setting
	    level--;
	 }
      } 
      if ((indent_level[level] != -1) and (indent_level[level] != col)) {
	 % Indent is wrong.  Hopefully it's a continuation line.
	 level = oldlevel;	% reset level
	 bol_trim();
	 whitespace(level * Py_Indent_Level + (col - current_indent));
      } else {
	 current_indent = col;
	 indent_level[level] = col;
	 bol_trim();
	 whitespace(level * Py_Indent_Level);
      }
   } while (down(1) == 1);
}

define py_reindent_region()
{
   narrow();
   py_reindent();
   widen();
}

#ifdef MSWINDOWS
define py_help_on_word()
{
   variable tag = "0-9A-Z_a-z";

   push_spot ();
   skip_white ();
   bskip_chars (tag);
   push_mark ();
   skip_chars (tag);
   tag = bufsubstr ();		% leave on the stack
   pop_spot ();
   message( strcat("Help on ", tag) );
   msw_help( getenv("PYLIBREF"), tag, 0);
}


#endif

create_syntax_table ($1);
define_syntax ("#", "", '%', $1);		% comments
define_syntax ("([{", ")]}", '(', $1);		% delimiters
define_syntax ('"', '"', $1);			% quoted strings
define_syntax ('\'', '\'', $1);			% quoted strings
define_syntax ('\\', '\\', $1);			% continuations
define_syntax ("0-9a-zA-Z_", 'w', $1);		% words
define_syntax ("-+0-9a-fA-FjJlLxX.", '0', $1);	% Numbers
define_syntax (",;.:", ',', $1);		% punctuation
define_syntax ("%-+/&*=<>|!~^`", '+', $1);	% operators
set_syntax_flags ($1, 0);			% keywords ARE case-sensitive

() = define_keywords ($1, "ifinisor", 2); % all keywords of length 2
() = define_keywords ($1, "anddefdelfornottry", 3); % of length 3 ....
() = define_keywords ($1, "elifelseexecfrompass", 4);
() = define_keywords ($1, "breakclassprintraisewhile", 5);
() = define_keywords ($1, "assertexceptglobalimportlambdareturn", 6);
() = define_keywords ($1, "finally", 7);
() = define_keywords ($1, "continue", 8);

% Type 1 keywords (actually these are what's in __builtins__)
() = define_keywords_n ($1, "id", 2, 1);
() = define_keywords_n ($1, "abschrcmpdirhexintlenmapmaxminoctordpowstr", 3, 1);
() = define_keywords_n ($1, "Noneevalhashlongopenreprtypevars", 4, 1);
() = define_keywords_n ($1, "applyfloatinputrangeroundtuple", 5, 1);
() = define_keywords_n ($1, "coercedivmodfilterlocalsreducereloadxrange", 6, 1);
() = define_keywords_n ($1, "IOError__doc__compiledelattrgetattrglobalshasattrsetattr", 7, 1);
() = define_keywords_n ($1, "EOFErrorKeyError__name__callableexecfile", 8, 1);
() = define_keywords_n ($1, "NameErrorTypeErrorraw_input", 9, 1);
() = define_keywords_n ($1, "IndexErrorSystemExitValueError__import__", 10, 1);
() = define_keywords_n ($1, "AccessErrorImportErrorMemoryErrorSyntaxErrorSystemError", 11, 1);
() = define_keywords_n ($1, "RuntimeError", 12, 1);
() = define_keywords_n ($1, "ConflictErrorOverflowError", 13, 1);
() = define_keywords_n ($1, "AttributeError", 14, 1);
() = define_keywords_n ($1, "KeyboardInterruptZeroDivisionError", 17, 1);

#ifdef HAS_DFA_SYNTAX
enable_highlight_cache("python.dfa", $1);
define_highlight_rule("\"\"\".+\"\"\"", "string", $1);	% long string (""")
define_highlight_rule("'''.+'''", "string", $1);	% long string (''')
define_highlight_rule("\"[^\"]*\"", "string", $1);	% normal string
define_highlight_rule("'[^']*'", "string", $1);		% normal string
define_highlight_rule("#.*", "comment", $1);		% comment
define_highlight_rule("[A-Za-z_][A-Za-z_0-9]*", "Knormal", $1); % identifier
define_highlight_rule("[1-9][0-9]*[lL]?", "number", $1);	% decimal int
define_highlight_rule("0[0-7]*[lL]?", "number", $1);		% octal int
define_highlight_rule("0[xX][0-9a-fA-F]+[lL]?", "number", $1);	% hex int
define_highlight_rule("[1-9][0-9]*\\.[0-9]*([Ee][\\+\\-]?[0-9]+)?",
		      "number", $1);				% float n.[n]
define_highlight_rule("0?\\.[0-9]+([Ee][\\+\\-]?[0-9]+)?",
		      "number", $1);				% float [n].n
define_highlight_rule("[ \t]+", "normal", $1);
define_highlight_rule("[\\(\\[{}\\]\\),:\\.\"`'=;]", "delimiter", $1);
define_highlight_rule("[\\+\\-\\*/%<>&\\|\\^~]", "operator", $1); % 1 char
define_highlight_rule("<<|>>|==|<=|>=|<>|!=", "operator", $1);	  % 2 char

% Flag badly formed numeric literals or identifiers.  This is more effective
% if you change the error colors so they stand out.
define_highlight_rule("[1-9][0-9]*[lL]?[0-9A-Za-z\\.]+", "error", $1);	% bad decimal
define_highlight_rule("0[0-7]+[lL]?[0-9A-Za-z\\.]+", "error", $1); % bad octal
define_highlight_rule("0[xX][0-9a-fA-F]+[lL]?[0-9A-Za-z\\.]+", "error", $1); % bad hex
define_highlight_rule("\\.[0-9]+([Ee][\\+\\-]?[0-9]+)?[A-Za-z]+", "error", $1);	% bad float
define_highlight_rule("[A-Za-z_][A-Za-z_0-9]*\\.[0-9]+[A-Za-z]*", "error", $1); % bad identifier

build_highlight_table($1);
#endif

%!%+
%\function{python_mode}
%\synopsis{python_mode}
%\usage{python_mode ()}
%\description
% A major mode for editing python files.
% 
% The following keys have python specific bindings:
%#v+
% DELETE deletes to previous indent level
% TAB indents line
% ^C# comments region or current line
% ^C> shifts line or region right
% ^C< shifts line or region left
% ^C^C executes the region, or the buffer if region not marked.
% ^C|  executes the region
% ^C\t reindents the region
% :    colon dedents appropriately
%#v- 
% Hooks: \var{python_mode_hook}
% 
%\seealso{Py_Indent_Level}
%\seealso{set_mode, c_mode}
%!%-
define python_mode ()
{
   variable python = "python";
   
   TAB = 8;
   set_mode (python, 0x4); % flag value of 4 is generic language mode
   use_keymap(python);
   set_buffer_hook ("indent_hook", "py_indent_line");
   set_buffer_hook ("newline_indent_hook", "py_newline_and_indent");
   use_syntax_table (python);
   run_mode_hooks ("python_mode_hook");
}
