diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index 70a1b13..14e5487 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -363,7 +363,8 @@ def json_errors(method):
             message = e.log_message
             self.log.warn(message)
             self.set_status(e.status_code)
-            self.finish(json.dumps(dict(message=message)))
+            reply = dict(message=message, reason=e.reason)
+            self.finish(json.dumps(reply))
         except Exception:
             self.log.error("Unhandled error in API request", exc_info=True)
             status = 500
@@ -371,7 +372,7 @@ def json_errors(method):
             t, value, tb = sys.exc_info()
             self.set_status(status)
             tb_text = ''.join(traceback.format_exception(t, value, tb))
-            reply = dict(message=message, traceback=tb_text)
+            reply = dict(message=message, reason=None, traceback=tb_text)
             self.finish(json.dumps(reply))
         else:
             return result
diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py
index 51e0d85..707ca93 100644
--- a/IPython/html/services/contents/filemanager.py
+++ b/IPython/html/services/contents/filemanager.py
@@ -281,7 +281,7 @@ class FileContentsManager(ContentsManager):
                     model['content'] = bcontent.decode('utf8')
                 except UnicodeError as e:
                     if format == 'text':
-                        raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
+                        raise web.HTTPError(400, "%s is not UTF-8 encoded" % path, reason='bad format')
                 else:
                     model['format'] = 'text'
                     default_mime = 'text/plain'
@@ -348,14 +348,14 @@ class FileContentsManager(ContentsManager):
         if os.path.isdir(os_path):
             if type_ not in (None, 'directory'):
                 raise web.HTTPError(400,
-                                u'%s is a directory, not a %s' % (path, type_))
+                                u'%s is a directory, not a %s' % (path, type_), reason='bad type')
             model = self._dir_model(path, content=content)
         elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
             model = self._notebook_model(path, content=content)
         else:
             if type_ == 'directory':
                 raise web.HTTPError(400,
-                                u'%s is not a directory')
+                                u'%s is not a directory', reason='bad type')
             model = self._file_model(path, content=content, format=format)
         return model
 
diff --git a/IPython/html/services/contents/handlers.py b/IPython/html/services/contents/handlers.py
index 121e325..a03a2e2 100644
--- a/IPython/html/services/contents/handlers.py
+++ b/IPython/html/services/contents/handlers.py
@@ -47,6 +47,7 @@ class ContentsHandler(IPythonHandler):
             location = self.location_url(model['path'])
             self.set_header('Location', location)
         self.set_header('Last-Modified', model['last_modified'])
+        self.set_header('Content-Type', 'application/json')
         self.finish(json.dumps(model, default=date_default))
 
     @web.authenticated
diff --git a/IPython/html/static/edit/js/editor.js b/IPython/html/static/edit/js/editor.js
index 53320b6..319b472 100644
--- a/IPython/html/static/edit/js/editor.js
+++ b/IPython/html/static/edit/js/editor.js
@@ -6,20 +6,32 @@ define([
     'base/js/utils',
     'codemirror/lib/codemirror',
     'codemirror/mode/meta',
-    'codemirror/addon/search/search'
+    'codemirror/addon/comment/comment',
+    'codemirror/addon/dialog/dialog',
+    'codemirror/addon/edit/closebrackets',
+    'codemirror/addon/edit/matchbrackets',
+    'codemirror/addon/search/searchcursor',
+    'codemirror/addon/search/search',
+    'codemirror/keymap/emacs',
+    'codemirror/keymap/sublime',
+    'codemirror/keymap/vim',
     ],
 function($,
     utils,
     CodeMirror
 ) {
+    "use strict";
+
     var Editor = function(selector, options) {
+        var that = this;
         this.selector = selector;
         this.contents = options.contents;
         this.events = options.events;
         this.base_url = options.base_url;
         this.file_path = options.file_path;
-        
-        this.codemirror = CodeMirror($(this.selector)[0]);
+        this.config = options.config;
+        this.codemirror = new CodeMirror($(this.selector)[0]);
+        this.generation = -1;
         
         // It appears we have to set commands on the CodeMirror class, not the
         // instance. I'd like to be wrong, but since there should only be one CM
@@ -27,27 +39,55 @@ function($,
         CodeMirror.commands.save = $.proxy(this.save, this);
         
         this.save_enabled = false;
+        
+        this.config.loaded.then(function () {
+            // load codemirror config
+            var cfg = that.config.data.Editor || {};
+            var cmopts = $.extend(true, {}, // true = recursive copy
+                Editor.default_codemirror_options,
+                cfg.codemirror_options || {}
+            );
+            that._set_codemirror_options(cmopts);
+            that.events.trigger('config_changed.Editor', {config: that.config});
+        });
+    };
+    
+    // default CodeMirror options
+    Editor.default_codemirror_options = {
+        extraKeys: {
+            "Tab" :  "indentMore",
+        },
+        indentUnit: 4,
+        theme: "ipython",
+        lineNumbers: true,
+        lineWrapping: true,
     };
     
     Editor.prototype.load = function() {
+        /** load the file */
         var that = this;
         var cm = this.codemirror;
-        this.contents.get(this.file_path, {type: 'file', format: 'text'})
+        return this.contents.get(this.file_path, {type: 'file', format: 'text'})
             .then(function(model) {
                 cm.setValue(model.content);
 
                 // Setting the file's initial value creates a history entry,
                 // which we don't want.
                 cm.clearHistory();
-
-                // Find and load the highlighting mode
-                utils.requireCodeMirrorMode(model.mimetype, function(spec) {
-                    var mode = CodeMirror.getMode({}, spec);
-                    cm.setOption('mode', mode);
-                });
+                that._set_mode_for_model(model);
                 that.save_enabled = true;
+                that.generation = cm.changeGeneration();
+                that.events.trigger("file_loaded.Editor", model);
             },
             function(error) {
+                that.events.trigger("file_load_failed.Editor", error);
+                if (error.xhr.responseJSON.reason === 'bad format') {
+                    window.location = utils.url_path_join(
+                        that.base_url,
+                        'files',
+                        that.file_path
+                    );
+                }
                 cm.setValue("Error! " + error.message +
                                 "\nSaving disabled.");
                 that.save_enabled = false;
@@ -55,7 +95,55 @@ function($,
         );
     };
 
-    Editor.prototype.save = function() {
+    Editor.prototype._set_mode_for_model = function (model) {
+        /** Set the CodeMirror mode based on the file model */
+        
+        // Find and load the highlighting mode,
+        // first by mime-type, then by file extension
+        var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
+        if (modeinfo.mode === "null") {
+            // find by mime failed, use find by ext
+            var ext_idx = model.name.lastIndexOf('.');
+            
+            if (ext_idx > 0) {
+                // CodeMirror.findModeByExtension wants extension without '.'
+                modeinfo = CodeMirror.findModeByExtension(model.name.slice(ext_idx + 1));
+            }
+        }
+        if (modeinfo) {
+            this.set_codemirror_mode(modeinfo);
+        }
+    };
+    
+    Editor.prototype.set_codemirror_mode = function (modeinfo) {
+        /** set the codemirror mode from a modeinfo struct */
+        var that = this;
+        utils.requireCodeMirrorMode(modeinfo, function () {
+            that.codemirror.setOption('mode', modeinfo.mode);
+            that.events.trigger("mode_changed.Editor", modeinfo);
+        });
+    };
+    
+    Editor.prototype.get_filename = function () {
+        return utils.url_path_split(this.file_path)[1];
+    };
+
+    Editor.prototype.rename = function (new_name) {
+        /** rename the file */
+        var that = this;
+        var parent = utils.url_path_split(this.file_path)[0];
+        var new_path = utils.url_path_join(parent, new_name);
+        return this.contents.rename(this.file_path, new_path).then(
+            function (model) {
+                that.file_path = model.path;
+                that.events.trigger('file_renamed.Editor', model);
+                that._set_mode_for_model(model);
+            }
+        );
+    };
+    
+    Editor.prototype.save = function () {
+        /** save the file */
         if (!this.save_enabled) {
             console.log("Not saving, save disabled");
             return;
@@ -67,10 +155,36 @@ function($,
             content: this.codemirror.getValue(),
         };
         var that = this;
-        this.contents.save(this.file_path, model).then(function() {
-            that.events.trigger("save_succeeded.TextEditor");
+        // record change generation for isClean
+        this.generation = this.codemirror.changeGeneration();
+        return this.contents.save(this.file_path, model).then(function(data) {
+            that.events.trigger("file_saved.Editor", data);
         });
     };
+    
+    Editor.prototype._set_codemirror_options = function (options) {
+        // update codemirror options from a dict
+        var codemirror = this.codemirror;
+        $.map(options, function (value, opt) {
+            if (value === null) {
+                value = CodeMirror.defaults[opt];
+            }
+            codemirror.setOption(opt, value);
+        });
+    };
+    
+    Editor.prototype.update_codemirror_options = function (options) {
+        /** update codemirror options locally and save changes in config */
+        var that = this;
+        this._set_codemirror_options(options);
+        return this.config.update({
+            Editor: {
+                codemirror_options: options
+            }
+        }).then(
+            that.events.trigger('config_changed.Editor', {config: that.config})
+        );
+    };
 
     return {Editor: Editor};
 });
diff --git a/IPython/html/static/edit/js/main.js b/IPython/html/static/edit/js/main.js
index ec787d4..2ff2845 100644
--- a/IPython/html/static/edit/js/main.js
+++ b/IPython/html/static/edit/js/main.js
@@ -10,6 +10,7 @@ require([
     'services/config',
     'edit/js/editor',
     'edit/js/menubar',
+    'edit/js/savewidget',
     'edit/js/notificationarea',
     'custom/custom',
 ], function(
@@ -19,8 +20,9 @@ require([
     events,
     contents,
     configmod,
-    editor,
+    editmod,
     menubar,
+    savewidget,
     notificationarea
     ){
     page = new page.Page();
@@ -28,22 +30,30 @@ require([
     var base_url = utils.get_body_data('baseUrl');
     var file_path = utils.get_body_data('filePath');
     contents = new contents.Contents({base_url: base_url});
-    var config = new configmod.ConfigSection('edit', {base_url: base_url})
+    var config = new configmod.ConfigSection('edit', {base_url: base_url});
     config.load();
     
-    var editor = new editor.Editor('#texteditor-container', {
+    var editor = new editmod.Editor('#texteditor-container', {
         base_url: base_url,
         events: events,
         contents: contents,
         file_path: file_path,
+        config: config,
     });
     
     // Make it available for debugging
     IPython.editor = editor;
     
+    var save_widget = new savewidget.SaveWidget('span#save_widget', {
+        editor: editor,
+        events: events,
+    });
+    
     var menus = new menubar.MenuBar('#menubar', {
         base_url: base_url,
         editor: editor,
+        events: events,
+        save_widget: save_widget,
     });
     
     var notification_area = new notificationarea.EditorNotificationArea(
@@ -61,4 +71,11 @@ require([
     });
     editor.load();
     page.show();
+
+    window.onbeforeunload = function () {
+        if (editor.save_enabled && !editor.codemirror.isClean(editor.generation)) {
+            return "Unsaved changes will be lost. Close anyway?";
+        }
+    };
+
 });
diff --git a/IPython/html/static/edit/js/menubar.js b/IPython/html/static/edit/js/menubar.js
index 15b1b4e..374ebe2 100644
--- a/IPython/html/static/edit/js/menubar.js
+++ b/IPython/html/static/edit/js/menubar.js
@@ -2,11 +2,14 @@
 // Distributed under the terms of the Modified BSD License.
 
 define([
-    'base/js/namespace',
     'jquery',
+    'base/js/namespace',
     'base/js/utils',
+    'base/js/dialog',
+    'codemirror/lib/codemirror',
+    'codemirror/mode/meta',
     'bootstrap',
-], function(IPython, $, utils, bootstrap) {
+], function($, IPython, utils, dialog, CodeMirror) {
     "use strict";
     
     var MenuBar = function (selector, options) {
@@ -29,21 +32,123 @@ define([
         this.base_url = options.base_url || utils.get_body_data("baseUrl");
         this.selector = selector;
         this.editor = options.editor;
+        this.events = options.events;
+        this.save_widget = options.save_widget;
 
         if (this.selector !== undefined) {
             this.element = $(selector);
             this.bind_events();
         }
+        this._load_mode_menu();
+        Object.seal(this);
     };
 
     MenuBar.prototype.bind_events = function () {
-        /**
-         *  File
-         */
         var that = this;
-        this.element.find('#save_file').click(function () {
-            that.editor.save();
+        var editor = that.editor;
+        
+        //  File
+        this.element.find('#new-file').click(function () {
+            var w = window.open();
+            // Create a new file in the current directory
+            var parent = utils.url_path_split(editor.file_path)[0];
+            editor.contents.new_untitled(parent, {type: "file"}).then(
+                function (data) {
+                    w.location = utils.url_join_encode(
+                        that.base_url, 'edit', data.path
+                    );
+                },
+                function(error) {
+                    w.close();
+                    dialog.modal({
+                        title : 'Creating New File Failed',
+                        body : "The error was: " + error.message,
+                        buttons : {'OK' : {'class' : 'btn-primary'}}
+                    });
+                }
+            );
+        });
+        this.element.find('#save-file').click(function () {
+            editor.save();
+        });
+        this.element.find('#rename-file').click(function () {
+            that.save_widget.rename();
+        });
+        
+        // Edit
+        this.element.find('#menu-find').click(function () {
+            editor.codemirror.execCommand("find");
+        });
+        this.element.find('#menu-replace').click(function () {
+            editor.codemirror.execCommand("replace");
+        });
+        this.element.find('#menu-keymap-default').click(function () {
+            editor.update_codemirror_options({
+                vimMode: false,
+                keyMap: 'default'
+            });
+        });
+        this.element.find('#menu-keymap-sublime').click(function () {
+            editor.update_codemirror_options({
+                vimMode: false,
+                keyMap: 'sublime'
+            });
         });
+        this.element.find('#menu-keymap-emacs').click(function () {
+            editor.update_codemirror_options({
+                vimMode: false,
+                keyMap: 'emacs'
+            });
+        });
+        this.element.find('#menu-keymap-vim').click(function () {
+            editor.update_codemirror_options({
+                vimMode: true,
+                keyMap: 'vim'
+            });
+        });
+        
+        // View
+        this.element.find('#menu-line-numbers').click(function () {
+            var current = editor.codemirror.getOption('lineNumbers');
+            var value = Boolean(1-current);
+            editor.update_codemirror_options({lineNumbers: value});
+        });
+        
+        this.events.on("config_changed.Editor", function () {
+            var keyMap = editor.codemirror.getOption('keyMap') || "default";
+            that.element.find(".selected-keymap").removeClass("selected-keymap");
+            that.element.find("#menu-keymap-" + keyMap).addClass("selected-keymap");
+        });
+        
+        this.events.on("mode_changed.Editor", function (evt, modeinfo) {
+            that.element.find("#current-mode")
+                .text(modeinfo.name)
+                .attr(
+                    'title',
+                    "The current language is " + modeinfo.name
+                );
+        });
+    };
+    
+    MenuBar.prototype._load_mode_menu = function () {
+        var list = this.element.find("#mode-menu");
+        var editor = this.editor;
+        function make_set_mode(info) {
+            return function () {
+                editor.set_codemirror_mode(info);
+            };
+        }
+        for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
+            var info = CodeMirror.modeInfo[i];
+            list.append($("<li>").append(
+                $("<a>").attr("href", "#")
+                    .text(info.name)
+                    .click(make_set_mode(info))
+                    .attr('title',
+                        "Set language to " + info.name
+                    )
+            ));
+        }
     };
 
     return {'MenuBar': MenuBar};
diff --git a/IPython/html/static/edit/js/savewidget.js b/IPython/html/static/edit/js/savewidget.js
new file mode 100644
index 0000000..5688780
--- /dev/null
+++ b/IPython/html/static/edit/js/savewidget.js
@@ -0,0 +1,202 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+define([
+    'base/js/namespace',
+    'jquery',
+    'base/js/utils',
+    'base/js/dialog',
+    'base/js/keyboard',
+    'moment',
+], function(IPython, $, utils, dialog, keyboard, moment) {
+    "use strict";
+
+    var SaveWidget = function (selector, options) {
+        this.editor = undefined;
+        this.selector = selector;
+        this.events = options.events;
+        this.editor = options.editor;
+        this._last_modified = undefined;
+        this.keyboard_manager = options.keyboard_manager;
+        if (this.selector !== undefined) {
+            this.element = $(selector);
+            this.bind_events();
+        }
+    };
+
+
+    SaveWidget.prototype.bind_events = function () {
+        var that = this;
+        this.element.find('span.filename').click(function () {
+            that.rename();
+        });
+        this.events.on('file_loaded.Editor', function (evt, model) {
+            that.update_filename(model.name);
+            that.update_document_title(model.name);
+            that.update_last_modified(model.last_modified);
+        });
+        this.events.on('file_saved.Editor', function (evt, model) {
+            that.update_filename(model.name);
+            that.update_document_title(model.name);
+            that.update_last_modified(model.last_modified);
+        });
+        this.events.on('file_renamed.Editor', function (evt, model) {
+            that.update_filename(model.name);
+            that.update_document_title(model.name);
+            that.update_address_bar(model.path);
+        });
+        this.events.on('file_save_failed.Editor', function () {
+            that.set_save_status('Save Failed!');
+        });
+    };
+
+
+    SaveWidget.prototype.rename = function (options) {
+        options = options || {};
+        var that = this;
+        var dialog_body = $('<div/>').append(
+            $("<p/>").addClass("rename-message")
+                .text('Enter a new filename:')
+        ).append(
+            $("<br/>")
+        ).append(
+            $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
+            .val(that.editor.get_filename())
+        );
+        var d = dialog.modal({
+            title: "Rename File",
+            body: dialog_body,
+            buttons : {
+                "OK": {
+                    class: "btn-primary",
+                    click: function () {
+                        var new_name = d.find('input').val();
+                        d.find('.rename-message').text("Renaming...");
+                        d.find('input[type="text"]').prop('disabled', true);
+                        that.editor.rename(new_name).then(
+                            function () {
+                                d.modal('hide');
+                            }, function (error) {
+                                d.find('.rename-message').text(error.message || 'Unknown error');
+                                d.find('input[type="text"]').prop('disabled', false).focus().select();
+                            }
+                        );
+                        return false;
+                    }
+                },
+                "Cancel": {}
+                },
+            open : function () {
+                // Upon ENTER, click the OK button.
+                d.find('input[type="text"]').keydown(function (event) {
+                    if (event.which === keyboard.keycodes.enter) {
+                        d.find('.btn-primary').first().click();
+                        return false;
+                    }
+                });
+                d.find('input[type="text"]').focus().select();
+            }
+        });
+    };
+
+
+    SaveWidget.prototype.update_filename = function (filename) {
+        this.element.find('span.filename').text(filename);
+    };
+
+    SaveWidget.prototype.update_document_title = function (filename) {
+        document.title = filename;
+    };
+
+    SaveWidget.prototype.update_address_bar = function (path) {
+        var state = {path : path};
+        window.history.replaceState(state, "", utils.url_join_encode(
+            this.editor.base_url,
+            "edit",
+            path)
+        );
+    };
+
+    SaveWidget.prototype.update_last_modified = function (last_modified) {
+        if (last_modified) {
+            this._last_modified = new Date(last_modified);
+        } else {
+            this._last_modified = null;
+        }
+        this._render_last_modified();
+    };
+    
+    SaveWidget.prototype._render_last_modified = function () {
+        /** actually set the text in the element, from our _last_modified value
+        
+        called directly, and periodically in timeouts.
+        */
+        this._schedule_render_last_modified();
+        var el = this.element.find('span.last_modified');
+        if (!this._last_modified) {
+            el.text('').attr('title', 'never saved');
+            return;
+        }
+        var chkd = moment(this._last_modified);
+        var long_date = chkd.format('llll');
+        var human_date;
+        var tdelta = Math.ceil(new Date() - this._last_modified);
+        if (tdelta < 24 * H){
+            // less than 24 hours old, use relative date
+            human_date = chkd.fromNow();
+        } else {
+            // otherwise show calendar 
+            // otherwise update every hour and show
+            // <Today | yesterday|...> at hh,mm,ss
+            human_date = chkd.calendar();
+        }
+        el.text(human_date).attr('title', long_date);
+    };
+
+    
+    var S = 1000;
+    var M = 60*S;
+    var H = 60*M;
+    var thresholds = {
+        s: 45 * S,
+        m: 45 * M,
+        h: 22 * H
+    };
+    var _timeout_from_dt = function (ms) {
+        /** compute a timeout to update the last-modified timeout
+        
+        based on the delta in milliseconds
+        */
+        if (ms < thresholds.s) {
+            return 5 * S;
+        } else if (ms < thresholds.m) {
+            return M;
+        } else {
+            return 5 * M;
+        }
+    };
+    
+    SaveWidget.prototype._schedule_render_last_modified = function () {
+        /** schedule the next update to relative date
+        
+        periodically updated, so short values like 'a few seconds ago' don't get stale.
+        */
+        var that = this;
+        if (!this._last_modified) {
+            return;
+        }
+        if ((this._last_modified_timeout)) {
+            clearTimeout(this._last_modified_timeout);
+        }
+        var dt = Math.ceil(new Date() - this._last_modified);
+        if (dt < 24 * H) {
+            this._last_modified_timeout = setTimeout(
+                $.proxy(this._render_last_modified, this),
+                _timeout_from_dt(dt)
+            );
+        }
+    };
+
+    return {'SaveWidget': SaveWidget};
+
+});
diff --git a/IPython/html/static/edit/less/edit.less b/IPython/html/static/edit/less/edit.less
new file mode 100644
index 0000000..072ffd5
--- /dev/null
+++ b/IPython/html/static/edit/less/edit.less
@@ -0,0 +1,9 @@
+#texteditor-container {
+    border-bottom: 1px solid #ccc;
+}
+
+#filename {
+    font-size: 16pt;
+    display: table;
+    padding: 0px 5px;
+}
diff --git a/IPython/html/static/edit/less/menubar.less b/IPython/html/static/edit/less/menubar.less
new file mode 100644
index 0000000..f709333
--- /dev/null
+++ b/IPython/html/static/edit/less/menubar.less
@@ -0,0 +1,14 @@
+.selected-keymap {
+  i.fa {
+    padding: 0px 5px;
+  }
+  i.fa:before {
+    content: @fa-var-check;
+  }
+}
+
+#mode-menu {
+  // truncate mode-menu, so it doesn't get longer than the screen
+  overflow: auto;
+  max-height: 20em;
+}
\ No newline at end of file
diff --git a/IPython/html/static/edit/less/style.less b/IPython/html/static/edit/less/style.less
new file mode 100644
index 0000000..b365b93
--- /dev/null
+++ b/IPython/html/static/edit/less/style.less
@@ -0,0 +1,7 @@
+/*!
+*
+* IPython text editor webapp
+*
+*/
+@import "menubar.less";
+@import "edit.less";
diff --git a/IPython/html/static/notebook/js/savewidget.js b/IPython/html/static/notebook/js/savewidget.js
index d69b32c..6b9db96 100644
--- a/IPython/html/static/notebook/js/savewidget.js
+++ b/IPython/html/static/notebook/js/savewidget.js
@@ -29,7 +29,7 @@ define([
 
     SaveWidget.prototype.bind_events = function () {
         var that = this;
-        this.element.find('span#notebook_name').click(function () {
+        this.element.find('span.filename').click(function () {
             that.rename_notebook({notebook: that.notebook});
         });
         this.events.on('notebook_loaded.Notebook', function () {
@@ -130,7 +130,7 @@ define([
 
     SaveWidget.prototype.update_notebook_name = function () {
         var nbname = this.notebook.get_notebook_name();
-        this.element.find('span#notebook_name').text(nbname);
+        this.element.find('span.filename').text(nbname);
     };
 
 
@@ -152,11 +152,11 @@ define([
 
 
     SaveWidget.prototype.set_save_status = function (msg) {
-        this.element.find('span#autosave_status').text(msg);
+        this.element.find('span.autosave_status').text(msg);
     };
 
     SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
-        var el = this.element.find('span#checkpoint_status');
+        var el = this.element.find('span.checkpoint_status');
         if(human_date){
             el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
         } else {
@@ -223,7 +223,7 @@ define([
 
         // update regularly for the first 6hours and show
         // <x time> ago
-        if(tdelta < tdelta < 6*3600*1000){  
+        if(tdelta < 6*3600*1000){  
             recall(_next_timeago_update(tdelta));
             this._set_checkpoint_status(chkd.fromNow(), longdate);
         // otherwise update every hour and show
diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less
index 58ac2df..86b5cb7 100644
--- a/IPython/html/static/notebook/less/notebook.less
+++ b/IPython/html/static/notebook/less/notebook.less
@@ -15,20 +15,6 @@ body {
     .border-box-sizing();
 }
 
-span#notebook_name {
-    height: 1em;
-    line-height: 1em;
-    padding: 3px;
-    border: none;
-    font-size: 146.5%;
-    &:hover{
-        // ensure body is lighter on dark palette, 
-        // and vice versa
-        background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
-    }
-    .corner-all;
-}
-
 div#notebook_panel {
     margin: 0px 0px 0px 0px;
     padding: 0px;
diff --git a/IPython/html/static/notebook/less/savewidget.less b/IPython/html/static/notebook/less/savewidget.less
index 73c7922..28954a1 100644
--- a/IPython/html/static/notebook/less/savewidget.less
+++ b/IPython/html/static/notebook/less/savewidget.less
@@ -1,33 +1,41 @@
-span#save_widget {
+span.save_widget {
     margin-top: 6px;
+    
+    span.filename {
+        height: 1em;
+        line-height: 1em;
+        padding: 3px;
+        border: none;
+        font-size: 146.5%;
+        &:hover{
+            // ensure body is lighter on dark palette, 
+            // and vice versa
+            background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
+        }
+        .corner-all;
+    }
 }
 
-span#checkpoint_status, span#autosave_status {
+span.checkpoint_status, span.autosave_status {
     font-size: small;
 }
 
 @media (max-width: 767px) {
-    span#save_widget {
+    span.save_widget {
         font-size: small;
     }
-    span#checkpoint_status, span#autosave_status {
-        font-size: x-small;
-    }
-}
-
-@media (max-width: 767px) {
-    span#checkpoint_status, span#autosave_status {
-        display: none;
+    span.checkpoint_status, span.autosave_status {
+      display: none;
     }
 }
 
 @media (min-width: 768px) and (max-width: 979px) {
-    span#checkpoint_status {
+    span.checkpoint_status {
         display: none;
     }
-    span#autosave_status {
+    span.autosave_status {
         font-size: x-small;
     }
 }
 
-  
+
diff --git a/IPython/html/static/services/contents.js b/IPython/html/static/services/contents.js
index 768bdd4..994244d 100644
--- a/IPython/html/static/services/contents.js
+++ b/IPython/html/static/services/contents.js
@@ -162,6 +162,7 @@ define([
         var settings = {
             processData : false,
             type : "PUT",
+            dataType: "json",
             data : JSON.stringify(model),
             contentType: 'application/json',
         };
diff --git a/IPython/html/static/style/style.less b/IPython/html/static/style/style.less
index ad5130d..b4113bc 100644
--- a/IPython/html/static/style/style.less
+++ b/IPython/html/static/style/style.less
@@ -23,6 +23,9 @@
 // tree
 @import "../tree/less/style.less";
 
+// edit
+@import "../edit/less/style.less";
+
 // notebook
 @import "../notebook/less/style.less";
 
diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
index 4a59dd8..c42bee0 100644
--- a/IPython/html/static/style/style.min.css
+++ b/IPython/html/static/style/style.min.css
@@ -7752,9 +7752,6 @@ div#header {
   /* Initially hidden to prevent FLOUC */
   display: none;
   background-color: #ffffff;
-  box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  -webkit-box-sizing: border-box;
   /* Display over codemirror */
   z-index: 100;
 }
@@ -8119,6 +8116,29 @@ ul#new-notebook-menu {
 }
 /*!
 *
+* IPython text editor webapp
+*
+*/
+.selected-keymap i.fa {
+  padding: 0px 5px;
+}
+.selected-keymap i.fa:before {
+  content: "\f00c";
+}
+#mode-menu {
+  overflow: auto;
+  max-height: 20em;
+}
+#texteditor-container {
+  border-bottom: 1px solid #ccc;
+}
+#filename {
+  font-size: 16pt;
+  display: table;
+  padding: 0px 5px;
+}
+/*!
+*
 * IPython notebook
 *
 */
@@ -9421,17 +9441,6 @@ body {
   -moz-box-sizing: border-box;
   -webkit-box-sizing: border-box;
 }
-span#notebook_name {
-  height: 1em;
-  line-height: 1em;
-  padding: 3px;
-  border: none;
-  font-size: 146.5%;
-  border-radius: 4px;
-}
-span#notebook_name:hover {
-  background-color: #e6e6e6;
-}
 div#notebook_panel {
   margin: 0px 0px 0px 0px;
   padding: 0px;
@@ -9681,7 +9690,6 @@ fieldset[disabled] #kernel_selector_widget > button.active {
   margin-top: 0px;
 }
 #menubar {
-  margin-top: 0px;
   box-sizing: border-box;
   -moz-box-sizing: border-box;
   -webkit-box-sizing: border-box;
@@ -10159,33 +10167,38 @@ div#pager .ui-resizable-handle {
   /* Modern browsers */
   flex: 1;
 }
-span#save_widget {
+span.save_widget {
   margin-top: 6px;
 }
-span#checkpoint_status,
-span#autosave_status {
+span.save_widget span.filename {
+  height: 1em;
+  line-height: 1em;
+  padding: 3px;
+  border: none;
+  font-size: 146.5%;
+  border-radius: 4px;
+}
+span.save_widget span.filename:hover {
+  background-color: #e6e6e6;
+}
+span.checkpoint_status,
+span.autosave_status {
   font-size: small;
 }
 @media (max-width: 767px) {
-  span#save_widget {
+  span.save_widget {
     font-size: small;
   }
-  span#checkpoint_status,
-  span#autosave_status {
-    font-size: x-small;
-  }
-}
-@media (max-width: 767px) {
-  span#checkpoint_status,
-  span#autosave_status {
+  span.checkpoint_status,
+  span.autosave_status {
     display: none;
   }
 }
 @media (min-width: 768px) and (max-width: 979px) {
-  span#checkpoint_status {
+  span.checkpoint_status {
     display: none;
   }
-  span#autosave_status {
+  span.autosave_status {
     font-size: x-small;
   }
 }
diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js
index 984faf5..40c3f04 100644
--- a/IPython/html/static/tree/js/notebooklist.js
+++ b/IPython/html/static/tree/js/notebooklist.js
@@ -229,7 +229,7 @@ define([
     NotebookList.uri_prefixes = {
         directory: 'tree',
         notebook: 'notebooks',
-        file: 'files',
+        file: 'edit',
     };
 
 
diff --git a/IPython/html/templates/edit.html b/IPython/html/templates/edit.html
index 7841bd4..fdf30ca 100644
--- a/IPython/html/templates/edit.html
+++ b/IPython/html/templates/edit.html
@@ -5,18 +5,6 @@
 {% block stylesheet %}
 <link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
 <link rel="stylesheet" href="{{ static_url('components/codemirror/addon/dialog/dialog.css') }}">
-<style>
-#texteditor-container {
-    border-bottom: 1px solid #ccc;
-}
-
-#filename {
-    font-size: 16pt;
-    display: table;
-    padding: 0px 5px;
-}
-</style>
-
 {{super()}}
 {% endblock %}
 
@@ -27,38 +15,70 @@ data-file-path="{{file_path}}"
 
 {% endblock %}
 
-{% block header %}
+{% block headercontainer %}
 
-<span id="filename">{{ basename }}</span>
+<span id="save_widget" class="pull-left save_widget">
+    <span class="filename"></span>
+    <span class="last_modified"></span>
+</span>
 
 {% endblock %}
 
-{% block site %}
+{% block header %}
 
 <div id="menubar-container" class="container">
-<div id="menubar">
+  <div id="menubar">
     <div id="menus" class="navbar navbar-default" role="navigation">
-        <div class="container-fluid">
-            <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
-              <i class="fa fa-bars"></i>
-              <span class="navbar-text">Menu</span>
-            </button>
-            <ul class="nav navbar-nav navbar-right">
-              <li id="notification_area"></li>
-            </ul>
-            <div class="navbar-collapse collapse">
-              <ul class="nav navbar-nav">
-                <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
-                    <ul id="file_menu" class="dropdown-menu">
-                        <li id="save_file"><a href="#">Save</a></li>
-                    </ul>
-                </li>
+      <div class="container-fluid">
+        <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+          <i class="fa fa-bars"></i>
+          <span class="navbar-text">Menu</span>
+        </button>
+        <ul class="nav navbar-nav navbar-right">
+          <li id="notification_area"></li>
+        </ul>
+        <div class="navbar-collapse collapse">
+          <ul class="nav navbar-nav">
+            <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
+              <ul id="file-menu" class="dropdown-menu">
+                <li id="new-file"><a href="#">New</a></li>
+                <li id="save-file"><a href="#">Save</a></li>
+                <li id="rename-file"><a href="#">Rename</a></li>
+              </ul>
+            </li>
+            <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
+              <ul id="edit-menu" class="dropdown-menu">
+                <li id="menu-find"><a href="#">Find</a></li>
+                <li id="menu-replace"><a href="#">Find &amp; Replace</a></li>
+                <li class="divider"></li>
+                <li class="dropdown-header">Key Map</li>
+                <li id="menu-keymap-default"><a href="#">Default<i class="fa"></i></a></li>
+                <li id="menu-keymap-sublime"><a href="#">Sublime Text<i class="fa"></i></a></li>
+                <li id="menu-keymap-vim"><a href="#">Vim<i class="fa"></i></a></li>
+                <li id="menu-keymap-emacs"><a href="#">emacs<i class="fa"></i></a></li>
+              </ul>
+            </li>
+            <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
+              <ul id="view-menu" class="dropdown-menu">
+                <li id="menu-line-numbers"><a href="#">Toggle Line Numbers</a></li>
+              </ul>
+            </li>
+            <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Language</a>
+              <ul id="mode-menu" class="dropdown-menu">
               </ul>
-            </div>
+            </li>
+          </ul>
+          <p id="current-mode" class="navbar-text navbar-right">current mode</p>
         </div>
+      </div>
     </div>
+  </div>
 </div>
-</div>
+
+{% endblock %}
+
+{% block site %}
+
 
 <div id="texteditor-container" class="container"></div>
 
diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html
index 3cf4b0e..ec1c1e2 100644
--- a/IPython/html/templates/notebook.html
+++ b/IPython/html/templates/notebook.html
@@ -35,10 +35,10 @@ class="notebook_app"
 {% block headercontainer %}
 
 
-<span id="save_widget" class="nav pull-left">
-    <span id="notebook_name"></span>
-    <span id="checkpoint_status"></span>
-    <span id="autosave_status"></span>
+<span id="save_widget" class="pull-left save_widget">
+    <span class="filename"></span>
+    <span class="checkpoint_status"></span>
+    <span class="autosave_status"></span>
 </span>
 
 <span id="kernel_selector_widget" class="pull-right dropdown">