diff --git a/editors/codemirror.inc b/editors/codemirror.inc
new file mode 100644
index 0000000..39d9d84
--- /dev/null
+++ b/editors/codemirror.inc
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for codemirror.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_codemirror_editor() {
+  $editor['codemirror'] = array(
+    'title' => 'CodeMirror',
+    'vendor url' => 'http://codemirror.net',
+    'download url' => 'http://codemirror.net/codemirror-2.38.zip',
+    'library path' => wysiwyg_get_path('codemirror'),
+    'libraries' => array(
+      '' => array(
+        'title' => 'CodeMirror',
+        'files' => array(
+          'lib/codemirror.js',
+        ),
+      ),
+    ),
+    'version callback' => 'wysiwyg_codemirror_version',
+    'settings callback' => 'wysiwyg_codemirror_settings',
+    'plugin callback' => 'wysiwyg_codemirror_plugins',
+    'load callback' => 'wysiwyg_codemirror_load',
+    'versions' => array(
+      '2' => array(
+        'js files' => array('codemirror-2.js'),
+        'css files' => array('codemirror-2.css'),
+      ),
+      '3' => array(
+        'js files' => array('codemirror-3.js'),
+        'css files' => array('codemirror-3.css'),
+      ),
+    ),
+  );
+  return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ *   An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ *   The installed editor version.
+ */
+function wysiwyg_codemirror_version($editor) {
+  $fp = $editor['library path'] . '/lib/codemirror.js';
+  if (!file_exists($fp)) {
+    return;
+  }
+  $fp = fopen($fp, 'r');
+  $line = fgets($fp);
+  if (preg_match('@([0-9\.]+)$@', $line, $version)) {
+    fclose($fp);
+    return $version[1];
+  }
+  fclose($fp);
+}
+
+/**
+ * Perform additional actions upon loading this editor.
+ *
+ * @param $editor
+ *   A processed hook_editor() array of editor properties.
+ * @param $library
+ *   The internal library name (array key) to use.
+ */
+function wysiwyg_codemirror_load($editor, $library) {
+  // @TODO: Make themes and modes into editor settings
+  drupal_add_css($editor['library path'] . '/lib/codemirror.css');
+  drupal_add_css($editor['library path'] . '/theme/default.css');
+  
+  drupal_add_js($editor['library path'] . '/lib/codemirror.js');
+  drupal_add_js($editor['library path'] . '/mode/clike/clike.js');
+  drupal_add_js($editor['library path'] . '/mode/css/css.js');
+  drupal_add_js($editor['library path'] . '/mode/diff/diff.js');
+  drupal_add_js($editor['library path'] . '/mode/haskell/haskell.js');
+  drupal_add_js($editor['library path'] . '/mode/htmlmixed/htmlmixed.js');
+  drupal_add_js($editor['library path'] . '/mode/javascript/javascript.js');
+  drupal_add_js($editor['library path'] . '/mode/php/php.js');
+  drupal_add_js($editor['library path'] . '/mode/stex/stex.js');
+  drupal_add_js($editor['library path'] . '/mode/xml/xml.js');
+    
+  if (version_compare($editor['installed version'], '3', '<')) {
+    drupal_add_css($editor['library path'] . '/lib/util/simple-hint.css');
+    drupal_add_js($editor['library path'] . '/lib/util/simple-hint.js');
+    drupal_add_js($editor['library path'] . '/lib/util/xml-hint.js');
+    drupal_add_js($editor['library path'] . '/lib/util/closetag.js');
+  }
+  if (version_compare($editor['installed version'], '3', '>')) {
+    drupal_add_css($editor['library path'] . '/addon/hint/show-hint.css');
+    drupal_add_js($editor['library path'] . '/addon/hint/show-hint.js');
+    drupal_add_js($editor['library path'] . '/addon/hint/xml-hint.js');
+    drupal_add_js($editor['library path'] . '/addon/edit/closetag.js');
+  }
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ *   A processed hook_editor() array of editor properties.
+ * @param $config
+ *   An array containing wysiwyg editor profile settings.
+ * @param $theme
+ *   The name of a theme/GUI/skin to use.
+ *
+ * @return
+ *   A settings array to be populated in
+ *   Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_codemirror_settings($editor, $config, $theme) {
+  /* Defaults */
+  $settings = array(
+    'mode'           => 'text/html',
+    'indentUnit'     => 2,
+    'indentWithTabs' => FALSE,
+    'smartIndent'    => FALSE,
+    'tabMode'        => 'shift',
+    'enterMode'      => 'indent',
+    'electricChars'  => FALSE,
+    'lineNumbers'    => FALSE,
+    'gutter'         => FALSE,
+    'readOnly'       => FALSE,
+    'matchBrackets'  => FALSE,
+    'autoComplete'   => FALSE,
+  );
+
+  $all_settings = array_keys($settings);
+
+  if (is_array($config['buttons']['default'])) {
+    foreach ($config['buttons']['default'] as $key => $value) {
+      if ($value) {
+        if (in_array($key, $all_settings)) {
+          $settings[$key] = ($value != 0);
+        }
+      }
+    }
+  }
+
+  return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_codemirror_plugins($editor) {
+  return array(
+    'default' => array(
+      'buttons' => array(
+        'autoComplete'  => t('Autocomplete'),
+        'lineNumbers'    => t('Line Numbers'),
+        'matchBrackets'  => t('Match Brackets'),
+        'electricChars'  => t('Electric Characters'),
+        'smartIndent'    => t('Smart Indent'),
+        'indentWithTabs' => t('Indent With Tabs'),
+        'gutter'         => t('Gutter'),
+        'matchBrackets'  => t('Match Brackets')
+      )
+    ),
+    'internal' => TRUE,
+  );
+}
diff --git a/editors/css/codemirror-2.css b/editors/css/codemirror-2.css
new file mode 100644
index 0000000..6dae03e
--- /dev/null
+++ b/editors/css/codemirror-2.css
@@ -0,0 +1,7 @@
+
+/**
+ * CodeMirror
+ */
+.CodeMirror {
+  border: 1px solid #ddd
+}
diff --git a/editors/css/codemirror-3.css b/editors/css/codemirror-3.css
new file mode 100644
index 0000000..6dae03e
--- /dev/null
+++ b/editors/css/codemirror-3.css
@@ -0,0 +1,7 @@
+
+/**
+ * CodeMirror
+ */
+.CodeMirror {
+  border: 1px solid #ddd
+}
diff --git a/editors/js/codemirror-2.js b/editors/js/codemirror-2.js
new file mode 100644
index 0000000..67516b3
--- /dev/null
+++ b/editors/js/codemirror-2.js
@@ -0,0 +1,52 @@
+(function($) {
+
+/* Maintain a list of active editors on the page */
+var instances = new Object;
+
+/**
+ * Attach this editor to a target element.
+ *
+ * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.attach.codemirror = function(context, params, settings) {
+  if (settings.autoComplete) {
+    var pairedTags = ("a abbr acronym address applet b bdo big blockquote body button" + 
+                    " caption center cite code colgroup del dfn dir div dl em fieldset font form" +
+                    " frameset h1 h2 h3 h4 h5 h6 head html i iframe ins kbd label legend li map" +
+                    " menu noframes noscript object ol optgroup option p pre q s samp script select small" + 
+                    " span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var").split(" "); 
+    CodeMirror.xmlHints['<'] = pairedTags;
+    CodeMirror.xmlHints['/'] = pairedTags;
+    settings.extraKeys = {
+      "'<'": function(cm) { CodeMirror.xmlHint(cm, '<'); },
+      "'>'": function(cm) { cm.closeTag(cm, '>'); },
+      "'/'": function(cm) { cm.closeTag(cm, '/'); },
+      "Ctrl-Space": function(cm) { CodeMirror.xmlHint(cm, ''); },
+    }
+  }
+  var textArea = document.getElementById(params.field);
+  if (textArea) {
+    instances[params.field] = CodeMirror.fromTextArea(textArea, settings);
+  }
+
+  if (params.resizable) {
+    jQuery('.CodeMirror-scroll').css({
+      // @TODO: Get these from editor settings
+      //'height'     : 'auto',
+      'overflow-y' : 'hidden',
+      'overflow-x' : 'auto'
+    });
+  }
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.detach.codemirror = function(context, params) {
+  instances[params.field].toTextArea();
+  delete instances[params.field];
+};
+
+})(jQuery);
diff --git a/editors/js/codemirror-3.js b/editors/js/codemirror-3.js
new file mode 100644
index 0000000..9c1200c
--- /dev/null
+++ b/editors/js/codemirror-3.js
@@ -0,0 +1,63 @@
+(function($) {
+
+/* Maintain a list of active editors on the page */
+var instances = new Object;
+
+/**
+ * Attach this editor to a target element.
+ *
+ * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.attach.codemirror = function(context, params, settings) {
+  if (settings.autoComplete) {
+    var pairedTags = ("a abbr acronym address applet b bdo big blockquote body button" + 
+                    " caption center cite code colgroup del dfn dir div dl em fieldset font form" +
+                    " frameset h1 h2 h3 h4 h5 h6 head html i iframe ins kbd label legend li map" +
+                    " menu noframes noscript object ol optgroup option p pre q s samp script select small" + 
+                    " span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var").split(" "); 
+    CodeMirror.xmlHints['<'] = pairedTags;
+    CodeMirror.xmlHints['/'] = pairedTags;
+    
+    function passAndHint(cm) {
+        setTimeout(function() {cm.execCommand("autocomplete");}, 100);
+        throw CodeMirror.Pass;
+    }
+    
+    settings.extraKeys = {
+      "'<'": passAndHint,
+      "'/'": passAndHint,
+      "' '": passAndHint,
+      "Ctrl-Space": "autocomplete",
+    }
+    settings.autoCloseTags = true;
+    
+    CodeMirror.commands.autocomplete = function(cm) {
+        CodeMirror.showHint(cm, CodeMirror.xmlHint);
+    }
+  }
+  var textArea = document.getElementById(params.field);
+  if (textArea) {
+    instances[params.field] = CodeMirror.fromTextArea(textArea, settings);
+  }
+
+  if (params.resizable) {
+    jQuery('.CodeMirror-scroll').css({
+      // @TODO: Get these from editor settings
+      //'height'     : 'auto',
+      'overflow-y' : 'hidden',
+      'overflow-x' : 'auto'
+    });
+  }
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.detach.codemirror = function(context, params) {
+  instances[params.field].toTextArea();
+  delete instances[params.field];
+};
+
+})(jQuery);

