diff --git a/IPython/html/static/texteditor/js/editor.js b/IPython/html/static/texteditor/js/editor.js
new file mode 100644
index 0000000..60c9def
--- /dev/null
+++ b/IPython/html/static/texteditor/js/editor.js
@@ -0,0 +1,80 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+define([
+    'jquery',
+    'base/js/utils',
+    'codemirror/lib/codemirror',
+    'codemirror/mode/meta',
+    ],
+function($,
+    utils,
+    CodeMirror
+) {
+    var Editor = function(selector, options) {
+        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.save_enabled = true;
+    };
+    
+    // TODO: Remove this once the contents API is refactored to just use paths
+    Editor.prototype._split_path = function() {
+        var ix = this.file_path.lastIndexOf("/");
+        if (ix === -1) {
+            return ['', this.file_path];
+        } else {
+            return [
+                this.file_path.substring(0, ix),
+                this.file_path.substring(ix+1)
+            ];
+        }
+    };
+    
+    Editor.prototype.load = function() {
+        var split_path = this._split_path();
+        var cm = this.codemirror;
+        this.contents.load(split_path[0], split_path[1], {
+            success: function(model) {
+                if (model.type === "file" && model.format === "text") {
+                    cm.setValue(model.content);
+                    
+                    // Find and load the highlighting mode
+                    var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
+                    if (modeinfo) {
+                        utils.requireCodeMirrorMode(modeinfo.mode, function() {
+                            cm.setOption('mode', modeinfo.mode);
+                        });
+                    }
+                } else {
+                    this.codemirror.setValue("Error! Not a text file. Saving disabled.");
+                    this.save_enabled = false;
+                }
+            }
+        });
+    };
+
+    Editor.prototype.save = function() {
+        var split_path = this._split_path();
+        var model = {
+            path: split_path[0],
+            name: split_path[1],
+            type: 'file',
+            format: 'text',
+            content: this.codemirror.getValue(),
+        };
+        var that = this;
+        this.contents.save(split_path[0], split_path[1], model, {
+            success: function() {
+                that.events.trigger("save_succeeded.TextEditor");
+            }
+        });
+    };
+
+    return {Editor: Editor};
+});
diff --git a/IPython/html/static/texteditor/js/main.js b/IPython/html/static/texteditor/js/main.js
index 9866646..330f314 100644
--- a/IPython/html/static/texteditor/js/main.js
+++ b/IPython/html/static/texteditor/js/main.js
@@ -3,70 +3,45 @@
 
 require([
     'jquery',
+    'base/js/namespace',
     'base/js/utils',
     'base/js/page',
     'base/js/events',
     'contents',
-    'codemirror/lib/codemirror',
+    'texteditor/js/editor',
     'texteditor/js/menubar',
-    'codemirror/mode/meta',
     'custom/custom',
 ], function(
     $,
+    IPython,
     utils,
     page,
     events,
     contents,
-    CodeMirror,
+    editor,
     menubar
     ){
     page = new page.Page();
 
     var base_url = utils.get_body_data('baseUrl');
-    contents = new contents.Contents({base_url: base_url});
-
     var file_path = utils.get_body_data('filePath');
-    var ix = file_path.lastIndexOf("/");
-    var dir_path, basename;
-    if (ix == -1) {
-        dir_path = '';
-        basename = file_path;
-    } else {
-        dir_path = file_path.substring(0, ix);
-        basename = file_path.substring(ix+1);
-    }
-    contents.load(dir_path, basename, {
-        success: function(model) {
-            page.show();
-            if (model.type === "file" && model.format === "text") {
-                console.log(modeinfo);
-                var cm = CodeMirror($('#texteditor-container')[0], {
-                    value: model.content,
-                });
-                
-                var menus = new menubar.MenuBar('#menubar', {
-                    base_url: base_url,
-                    codemirror: cm,
-                    contents: contents,
-                    events: events,
-                    file_path: file_path
-                });
-                
-                // Find and load the highlighting mode
-                var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
-                if (modeinfo) {
-                    utils.requireCodeMirrorMode(modeinfo.mode, function() {
-                        cm.setOption('mode', modeinfo.mode);
-                    });
-                }
-                
-                // Attach to document for debugging
-                document.cm_instance = cm;
-            } else {
-                $('#texteditor-container').append(
-                    $('<p/>').text(dir_path + " is not a text file")
-                );
-            }
-        }
+    contents = new contents.Contents({base_url: base_url});
+    
+    var editor = new editor.Editor('#texteditor-container', {
+        base_url: base_url,
+        events: events,
+        contents: contents,
+        file_path: file_path,
     });
+    
+    // Make it available for debugging
+    IPython.editor = editor;
+    
+    var menus = new menubar.MenuBar('#menubar', {
+        base_url: base_url,
+        editor: editor,
+    });
+
+    editor.load();
+    page.show();
 });
diff --git a/IPython/html/static/texteditor/js/menubar.js b/IPython/html/static/texteditor/js/menubar.js
index 552f9c8..dd8a5ce 100644
--- a/IPython/html/static/texteditor/js/menubar.js
+++ b/IPython/html/static/texteditor/js/menubar.js
@@ -26,10 +26,7 @@ define([
         options = options || {};
         this.base_url = options.base_url || utils.get_body_data("baseUrl");
         this.selector = selector;
-        this.codemirror = options.codemirror;
-        this.contents = options.contents;
-        this.events = options.events;
-        this.file_path = options.file_path;
+        this.editor = options.editor;
 
         if (this.selector !== undefined) {
             this.element = $(selector);
@@ -41,28 +38,7 @@ define([
         //  File
         var that = this;
         this.element.find('#save_file').click(function () {
-            var ix = that.file_path.lastIndexOf("/");
-            var dir_path, basename;
-            if (ix === -1) {
-                dir_path = '';
-                basename = that.file_path;
-            } else {
-                dir_path = that.file_path.substring(0, ix);
-                basename = that.file_path.substring(ix+1);
-            }
-            var model = {
-                path: dir_path,
-                name: basename,
-                type: 'file',
-                format: 'text',
-                content: that.codemirror.getValue(),
-            };
-            console.log(model);
-            that.contents.save(dir_path, basename, model, {
-                success: function() {
-                    that.events.trigger("save_succeeded.TextEditor");
-                }
-            });
+            that.editor.save();
         });
     };