Multiline Tab & Untab

JavaScript multiline tab-untab.

by Taufik Nurrohman

HTML

<textarea id="target-textarea" spellcheck="false">Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.
    Test lorem ipsum dolor sit amet.
    Test lorem ipsum dolor sit amet.
    Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.
Test lorem ipsum dolor sit amet.</textarea>
<p>Gunakan tombol <kbd>Tab</kbd> dan <kbd>Shift</kbd> + <kbd>Tab</kbd> untuk indentasi dan outdentasi.</p>
<script>
document.getElementById('target-textarea').onkeydown = function(e) {
    // [Shift + Tab] was pressed
    if (e.shiftKey && e.keyCode == 9) {
        _untab(this);
        return false;
    }
    // [Tab] was pressed
    if (e.keyCode == 9) {
        _tab(this);
        return false;
    }
};
</script>

CSS

body {
    padding:10px;
}
textarea {
    width:98%;
    height:200px;
}

JavaScript

(function() {

    var tabCharacter = '    '; // Use `\t` or multiple space character

    var select = function(start, end, target) {
        target.focus();
        target.setSelectionRange(start, end);
    };

    window._tab = function(target) {

        var start = target.selectionStart,
            end = target.selectionEnd,
            value = target.value,
            selections = value.substring(start, end).split('\n');

        for (var i = 0, len = selections.length; i < len; ++i) {
            selections[i] = tabCharacter + selections[i];
        }

        target.value = value.substring(0, start) + selections.join('\n') + value.substring(end);

        // re-select text after tabbing
        var selectEnd = (end + (tabCharacter.length * selections.length));
        if (start == end) {
            select(selectEnd, selectEnd, target);
        } else {
            select(start, selectEnd, target);
        }

    };

    window._untab = function(target) {

        var start = target.selectionStart,
            end = target.selectionEnd,
            value = target.value,
            pattern = new RegExp("^" + tabCharacter),
            edits = 0;

        if (start == end) { // single line

            while (start > 0) {
                if(value.charAt(start) == '\n') {
                    start++;
                    break;
                }
                start--;
            }

            var portion = value.substring(start, end),
                matches = portion.match(pattern);

            if (matches) {
                target.value = value.substring(0, start) + portion.replace(pattern, "") + value.substring(end);
                end--;
            }

            // set caret position after tabbing
            var selectEnd = end <= start ? end : end - tabCharacter.length + 1;
            select(selectEnd, selectEnd, target);

        } else { // multiline

            var selections = value.substring(start, end).split('\n');

            for...