Repository: xonsh/xonsh

Issue:
Lines that end in colon should be indented on next line
And other auto-indenting tricks.



Discussion:

Resolving patch:
diff --git a/xonsh/environ.py b/xonsh/environ.py
--- a/xonsh/environ.py
+++ b/xonsh/environ.py
@@ -400,19 +400,24 @@ def _replace_home(x):
                       branch_color=branch_color,
                       **TERM_COLORS)
 
-_formatter = string.Formatter()
+_FORMATTER = string.Formatter()
 
 
-def format_prompt(template=DEFAULT_PROMPT):
-    """Formats a xonsh prompt template string.
-    """
-    env = builtins.__xonsh_env__
+def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
+    """Formats a xonsh prompt template string."""
     template = template() if callable(template) else template
-    fmt = env.get('FORMATTER_DICT', FORMATTER_DICT)
-    included_names = set(i[1] for i in _formatter.parse(template))
-    fmt = {k: (v() if callable(v) else v)
-           for (k, v) in fmt.items()
-           if k in included_names}
+    if formatter_dict is None:
+        fmtter = builtins.__xonsh_env__.get('FORMATTER_DICT', FORMATTER_DICT)
+    else:
+        fmtter = formatter_dict
+    included_names = set(i[1] for i in _FORMATTER.parse(template))
+    fmt = {}
+    for k, v in fmtter.items():
+        if k not in included_names:
+            continue
+        val = v() if callable(v) else v
+        val = '' if val is None else val
+        fmt[k] = val
     return template.format(**fmt)
 
 
diff --git a/xonsh/readline_shell.py b/xonsh/readline_shell.py
--- a/xonsh/readline_shell.py
+++ b/xonsh/readline_shell.py
@@ -80,6 +80,17 @@ def rl_completion_suppress_append(val=1):
     RL_COMPLETION_SUPPRESS_APPEND.value = val
 
 
+def _insert_text_func(s, readline):
+    """Creates a function to insert text via readline."""
+    def inserter():
+        readline.insert_text(s)
+        readline.redisplay()
+    return inserter
+
+
+DEDENT_TOKENS = frozenset(['raise', 'return', 'pass', 'break', 'continue'])
+
+
 class ReadlineShell(BaseShell, Cmd):
     """The readline based xonsh shell."""
 
@@ -89,6 +100,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, **kwargs):
                          stdout=stdout,
                          **kwargs)
         setup_readline()
+        self._current_indent = ''
 
     def __del__(self):
         teardown_readline()
@@ -107,6 +119,37 @@ def completedefault(self, text, line, begidx, endidx):
     # tab complete on first index too
     completenames = completedefault
 
+    def postcmd(self, stop, line):
+        """Called just before execution of line. For readline, this handles the 
+        automatic indentation of code blocks.
+        """
+        try:
+            import readline
+        except ImportError:
+            return stop
+        if self.need_more_lines:
+            if len(line.strip()) == 0:
+                readline.set_pre_input_hook(None)
+                self._current_indent = ''
+            elif line.rstrip()[-1] == ':':
+                ind = line[:len(line) - len(line.lstrip())]
+                ind += builtins.__xonsh_env__.get('INDENT', '')
+                readline.set_pre_input_hook(_insert_text_func(ind, readline))
+                self._current_indent = ind
+            elif line.split(maxsplit=1)[0] in DEDENT_TOKENS:
+                env = builtins.__xonsh_env__
+                ind = self._current_indent[:-len(env.get('INDENT', ''))]
+                readline.set_pre_input_hook(_insert_text_func(ind, readline))
+                self._current_indent = ind
+            else:
+                ind = line[:len(line) - len(line.lstrip())]
+                if ind != self._current_indent:
+                    readline.set_pre_input_hook(_insert_text_func(ind, readline))
+                    self._current_indent = ind
+        else:
+            readline.set_pre_input_hook(None)
+        return stop
+
     def cmdloop(self, intro=None):
         while not builtins.__xonsh_exit__:
             try:


Test patch:
diff --git a/tests/test_environ.py b/tests/test_environ.py
--- a/tests/test_environ.py
+++ b/tests/test_environ.py
@@ -5,7 +5,7 @@
 import nose
 from nose.tools import assert_equal, assert_true, assert_not_in
 
-from xonsh.environ import Env
+from xonsh.environ import Env, format_prompt
 
 def test_env_normal():
     env = Env(VAR='wakka')
@@ -35,6 +35,20 @@ def test_env_detype_no_dict():
     det = env.detype()
     assert_not_in('YO', det)
 
+def test_format_prompt():
+    formatter_dict = {
+        'a_string': 'cat',
+        'none': (lambda: None),
+        'f': (lambda: 'wakka'),
+        }
+    cases = {
+        'my {a_string}': 'my cat',
+        'my {none}{a_string}': 'my cat',
+        '{f} jawaka': 'wakka jawaka',
+        }
+    for p, exp in cases.items():
+        obs = format_prompt(template=p, formatter_dict=formatter_dict)
+        yield assert_equal, exp, obs
 
 if __name__ == '__main__':
     nose.runmodule()