diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js
index 8b8140b4c1ae4d98615f9a3950150c8153d055e3..1c5531742ad3235547ebf208a337ed2b1dfca0e2 100644
--- a/addon/runmode/runmode.node.js
+++ b/addon/runmode/runmode.node.js
@@ -5,17 +5,38 @@
 
 // declare global: StringStream
 
-function splitLines(string){ return string.split(/\r?\n|\r/); };
+function splitLines(string){return string.split(/\r\n?|\n/);};
 
-function StringStream(string) {
+
+// Counts the column offset in a string, taking tabs into account.
+// Used mostly to find indentation.
+var countColumn = function(string, end, tabSize, startIndex, startValue) {
+  if (end == null) {
+    end = string.search(/[^\s\u00a0]/);
+    if (end == -1) end = string.length;
+  }
+  for (var i = startIndex || 0, n = startValue || 0;;) {
+    var nextTab = string.indexOf("\t", i);
+    if (nextTab < 0 || nextTab >= end)
+      return n + (end - i);
+    n += nextTab - i;
+    n += tabSize - (n % tabSize);
+    i = nextTab + 1;
+  }
+};
+
+function StringStream(string, tabSize) {
   this.pos = this.start = 0;
   this.string = string;
+  this.tabSize = tabSize || 8;
+  this.lastColumnPos = this.lastColumnValue = 0;
   this.lineStart = 0;
-}
+};
+
 StringStream.prototype = {
   eol: function() {return this.pos >= this.string.length;},
-  sol: function() {return this.pos == 0;},
-  peek: function() {return this.string.charAt(this.pos) || null;},
+  sol: function() {return this.pos == this.lineStart;},
+  peek: function() {return this.string.charAt(this.pos) || undefined;},
   next: function() {
     if (this.pos < this.string.length)
       return this.string.charAt(this.pos++);
@@ -42,8 +63,17 @@ StringStream.prototype = {
     if (found > -1) {this.pos = found; return true;}
   },
   backUp: function(n) {this.pos -= n;},
-  column: function() {return this.start - this.lineStart;},
-  indentation: function() {return 0;},
+  column: function() {
+    if (this.lastColumnPos < this.start) {
+      this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
+      this.lastColumnPos = this.start;
+    }
+    return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
+  },
+  indentation: function() {
+    return countColumn(this.string, null, this.tabSize) -
+      (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
+  },
   match: function(pattern, consume, caseInsensitive) {
     if (typeof pattern == "string") {
       var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
@@ -94,11 +124,35 @@ exports.resolveMode = function(spec) {
   if (typeof spec == "string") return {name: spec};
   else return spec || {name: "null"};
 };
+
+
+// This can be used to attach properties to mode objects from
+// outside the actual mode definition.
+var modeExtensions = exports.modeExtensions = {};
+exports.extendMode = function(mode, properties) {
+  var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+  copyObj(properties, exts);
+};
+
 exports.getMode = function(options, spec) {
-  spec = exports.resolveMode(spec);
+  var spec = exports.resolveMode(spec);
   var mfactory = modes[spec.name];
-  if (!mfactory) throw new Error("Unknown mode: " + spec);
-  return mfactory(options, spec);
+  if (!mfactory) return exports.getMode(options, "text/plain");
+  var modeObj = mfactory(options, spec);
+  if (modeExtensions.hasOwnProperty(spec.name)) {
+    var exts = modeExtensions[spec.name];
+    for (var prop in exts) {
+      if (!exts.hasOwnProperty(prop)) continue;
+      if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
+      modeObj[prop] = exts[prop];
+    }
+  }
+  modeObj.name = spec.name;
+  if (spec.helperType) modeObj.helperType = spec.helperType;
+  if (spec.modeProps) for (var prop in spec.modeProps)
+    modeObj[prop] = spec.modeProps[prop];
+
+  return modeObj;
 };
 exports.registerHelper = exports.registerGlobalHelper = Math.min;