jQuery.fn.enableTabs

enable tabulation and de-tabulation on native textarea fields(without screwing with undo)

HTML

<textarea>
    Row, row, row your boat,
    
Gently down the stream.

        
Ha ha, fooled ya,
I'm a submarine.
</textarea>

CSS

textarea {
    display:block;
    width:100%; height:300px;
    text-rendering: optimizeSpeed;
}

JavaScript

jQuery.fn.enableTabs = function(TAB_TEXT){
	// options
	if(!TAB_TEXT)TAB_TEXT = '\t';
	// text input event for character insertion
	function insertText(el, text){
		var te = document.createEvent('TextEvent');
		te.initTextEvent('textInput', true, true, null, text, 9, "en-US");
		el.dispatchEvent(te);
	}
	// catch tab and filter selection
	jQuery(this).keydown(function(e){
		if((e.which || e.keyCode)!=9)return true;
		e.preventDefault();
		var	contents = this.value,
			sel_start = this.selectionStart,
			sel_end = this.selectionEnd,
			sel_contents_before = contents.substring(0, sel_start),
			first_line_start_search = sel_contents_before.lastIndexOf('\n'),
			first_line_start = first_line_start_search==-1 ? 0 : first_line_start_search+1,
			tab_sel_contents = contents.substring(first_line_start, sel_end),
			tab_sel_contents_find = (e.shiftKey?new RegExp('\n'+TAB_TEXT, 'g'):new RegExp('\n', 'g')),
			tab_sel_contents_replace = (e.shiftKey?'\n':'\n'+TAB_TEXT);
			tab_sel_contents_replaced = (('\n'+tab_sel_contents)
				.replace(tab_sel_contents_find, tab_sel_contents_replace))
				.substring(1),
			sel_end_new = first_line_start+tab_sel_contents_replaced.length;
		this.setSelectionRange(first_line_start, sel_end);
		insertText(this, tab_sel_contents_replaced);
		this.setSelectionRange(first_line_start, sel_end_new);
	});
};

$('textarea').enableTabs();