Repository: xonsh/xonsh

Issue:
Working directory is locked (Windows)
Python somehow locks the current working directory on Windows. 
```
snail@sea ~\Documents\xonsh delcwd
$ rm -rf ~/x; mkdir ~/x; cd ~/x; rm -r ~/x
rm: remove directory 'C:\Users\mel/x'? y
rm: cannot remove 'C:\Users\mel/x': Device or resource busy
```

Or if I try from the GUI:

![image](https://cloud.githubusercontent.com/assets/1038978/23343355/7a81b202-fc6a-11e6-9170-83b9f8b87315.png)

Does anyone know a way to prevent this? I have search but haven't come up with anything. 



Discussion:
Windows is pretty big on file locks (same thing happens with executing programs). I have a strong suspicion that Windows does this automatically for all current directories of any program.

Resolving patch:
diff --git a/xonsh/ptk/shell.py b/xonsh/ptk/shell.py
--- a/xonsh/ptk/shell.py
+++ b/xonsh/ptk/shell.py
@@ -94,21 +94,21 @@ def singleline(self, store_in_history=True, auto_suggest=None,
 
         with self.prompter:
             prompt_args = {
-                    'mouse_support': mouse_support,
-                    'auto_suggest': auto_suggest,
-                    'get_prompt_tokens': get_prompt_tokens,
-                    'get_rprompt_tokens': get_rprompt_tokens,
-                    'get_bottom_toolbar_tokens': get_bottom_toolbar_tokens,
-                    'completer': completer,
-                    'multiline': multiline,
-                    'get_continuation_tokens': self.continuation_tokens,
-                    'history': history,
-                    'enable_history_search': enable_history_search,
-                    'reserve_space_for_menu': 0,
-                    'key_bindings_registry': self.key_bindings_manager.registry,
-                    'display_completions_in_columns': multicolumn,
-                    'complete_while_typing': complete_while_typing,
-                    }
+                'mouse_support': mouse_support,
+                'auto_suggest': auto_suggest,
+                'get_prompt_tokens': get_prompt_tokens,
+                'get_rprompt_tokens': get_rprompt_tokens,
+                'get_bottom_toolbar_tokens': get_bottom_toolbar_tokens,
+                'completer': completer,
+                'multiline': multiline,
+                'get_continuation_tokens': self.continuation_tokens,
+                'history': history,
+                'enable_history_search': enable_history_search,
+                'reserve_space_for_menu': 0,
+                'key_bindings_registry': self.key_bindings_manager.registry,
+                'display_completions_in_columns': multicolumn,
+                'complete_while_typing': complete_while_typing,
+            }
             if builtins.__xonsh_env__.get('COLOR_INPUT'):
                 if HAS_PYGMENTS:
                     prompt_args['lexer'] = PygmentsLexer(pyghooks.XonshLexer)
diff --git a/xontrib/free_cwd.py b/xontrib/free_cwd.py
new file mode 100644
--- /dev/null
+++ b/xontrib/free_cwd.py
@@ -0,0 +1,94 @@
+""" This will release the lock on the current directory whenever the
+    prompt is shown. Enabling this will allow the other programs or
+    Windows Explorer to delete or rename the current or parent
+    directories. Internally, it is accomplished by temporarily resetting
+    CWD to the root drive folder while waiting at the prompt. This only
+    works with the prompt_toolkit backend and can cause cause issues
+    if any extensions are enabled that hook the prompt and relies on
+    ``os.getcwd()``.
+"""
+import os
+import builtins
+import functools
+from xonsh.tools import print_exception
+
+
+def _chdir_up(path):
+    """ Change directory to path or if path does not exist
+        the first valid parent.
+    """
+    try:
+        os.chdir(path)
+        return path
+    except (FileNotFoundError, NotADirectoryError):
+        parent = os.path.dirname(path)
+        if parent != path:
+            return _chdir_up(parent)
+        else:
+            raise
+
+
+def _cwd_release_wrapper(func):
+    """ Decorator for Windows to the wrap the prompt function and release
+        the process lock on the current directory while the prompt is
+        displayed. This works by temporarily setting
+        the workdir to the users home direcotry.
+    """
+    env = builtins.__xonsh_env__
+    if env.get('UPDATE_PROMPT_ON_KEYPRESS'):
+        return func if not hasattr(func, '_orgfunc') else func._orgfunc
+
+    if hasattr(func, '_orgfunc'):
+        # Already wrapped
+        return func
+    else:
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            rootdir = os.path.splitdrive(os.getcwd())[0] + '\\'
+            os.chdir(rootdir)
+            try:
+                out = func(*args, **kwargs)
+            finally:
+                try:
+                    pwd = env.get('PWD', rootdir)
+                    os.chdir(pwd)
+                except (FileNotFoundError, NotADirectoryError):
+                    print_exception()
+                    newpath = _chdir_up(pwd)
+                    builtins.__xonsh_env__['PWD'] = newpath
+                    raise KeyboardInterrupt
+            return out
+        wrapper._orgfunc = func
+        return wrapper
+
+
+def _cwd_restore_wrapper(func):
+    """ Decorator for Windows which will temporary restore the true working
+        directory. Designed to wrap completer callbacks from the
+        prompt_toolkit or readline.
+    """
+    env = builtins.__xonsh_env__
+    if env.get('UPDATE_PROMPT_ON_KEYPRESS'):
+        return func if not hasattr(func, '_orgfunc') else func._orgfunc
+
+    if hasattr(func, '_orgfunc'):
+        # Already wrapped
+        return func
+    else:
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            workdir = os.getcwd()
+            _chdir_up(env.get('PWD', workdir))
+            out = func(*args, **kwargs)
+            _chdir_up(workdir)
+            return out
+        wrapper._orgfunc = func
+        return wrapper
+
+
+@events.on_ptk_create
+def setup_release_cwd_hook(prompter, history, completer, bindings, **kw):
+    prompter.prompt = _cwd_release_wrapper(prompter.prompt)
+    if completer.completer:
+        # Temporarily restore cwd for callbacks to the completer
+        completer.completer.complete = _cwd_restore_wrapper(completer.completer.complete)


Test patch: