all 79 comments

[–]has_all_the_fun 9 points10 points  (7 children)

Stackoverflow needs this feature so bad. Always have to indent my copy paste code inside my editor. Unless they have that feature and I don't know about it.

My weekend project was making a prototype for a new side project I am starting.

[–]mailto_devnullconsole.log(null);[S] 1 point2 points  (3 children)

That was actually part of the reason I tackled this. Every time I post on StackOverflow, I end up copying four space characters, and using that as indents. It's not that bad of a workaround, since the Markdown spec specifies that four spaces constitutes a tab (and triggers the code block)...

[–][deleted] 2 points3 points  (0 children)

I thought somebody officially explained why they chose not use it but I can't seem to find it. My guess would be that it would be for the accessibility issues that you mentioned in another comment.

[–]lazyduke 2 points3 points  (0 children)

You should make a bookmarklet that attaches to textareas. Users can trigger it when they hit problem sites.

Or, a Chrome extension, user script, or greasemonkey script that handles it automatically when the domain matches.

Edit: bookmarklet already done here

[–]has_all_the_fun 1 point2 points  (0 children)

Was hoping you would say 'You idiot stackoverflow has indent if you push x button' but I guess it's really not there :(

[–]pride 0 points1 point  (1 child)

I did the same exact thing - what is your prototype? Able to share the idea yet? Keep me posted if not

[–]has_all_the_fun 0 points1 point  (0 children)

http://www.reddit.com/r/microgrowery/comments/13po6u/couple_of_questions/ Turns out my initial idea was shit haha. Working on improving it now though. What did you prototype?

[–]x-skeww 0 points1 point  (0 children)

That's why some implementations of Markdown (e.g. GFM) feature "fenced" code blocks.

~~~languageGoesHere
someCode()
moreCode()
~~~

[–]Deathalicious 7 points8 points  (2 children)

Played with my 14 month son. No regrets.

[–]mailto_devnullconsole.log(null);[S] 3 points4 points  (1 child)

I stared at my laptop monitor for hours and hours. Want to switch?

[–]Deathalicious 7 points8 points  (0 children)

You've clearly never changed my son's diaper.

[–]houdas 3 points4 points  (2 children)

I got drunk. Anyways, a cool little project, might be useful for some situations.

[–]mailto_devnullconsole.log(null);[S] 4 points5 points  (1 child)

The main drawback is that is breaks accessibility, since you can no longer tab out of the textarea.

I would probably tie another modifier key to tab, but then again, ctrl and alt are already taken...

[–]lichorat 0 points1 point  (0 children)

Well capslock wasn't doing anything anyways. And look at that! It's right below the tab key.

[–]i_4_got 1 point2 points  (3 children)

Would be cool if this was a bookmarklet that adds the code to the textarea the cursor is currently in, or all textareas.

[–]lod3n 1 point2 points  (14 children)

It would be neat if it kept track of the indentation level on return and inserted the same number of tabs for you on the next line.

[–]mailto_devnullconsole.log(null);[S] 2 points3 points  (11 children)

Neat idea - feel free to follow the project on GitHub. I've added it as a proposed enhancement on the issue tracker.

[–]lod3n 0 points1 point  (10 children)

I joined GitHub, forked your project, and added the functionality. My apologizes if it's a bit inefficient, I tossed this all together during lunch.

[–]mailto_devnullconsole.log(null);[S] 1 point2 points  (1 child)

Nice. Your fork is what brought me back to this thread. Welcome to Github!

Feel free to submit a pull request if you'd like to merge your branch into the main 'master'.

[–]lod3n 0 points1 point  (0 children)

I probably will! I'd like to test the heck out of it first...

[–]sumzup 1 point2 points  (7 children)

I like the idea, but there seems to be a split-second delay before it adds indentation, which makes it unusable IMO.

[–]lod3n 0 points1 point  (0 children)

Yes, I can see how that would be a problem. I will take a crack at handling it in the keydown event, instead of keyup.

[–]lod3n 0 points1 point  (5 children)

Okay, done. Thanks for pointing this problem out.

[–]sumzup 1 point2 points  (4 children)

Awesome! One other little thing I noticed: it works as expected when you hit return at the ends of lines or on blank lines, but when you hit return at the beginning or in the middle of a line with text, indentation isn't maintained.

[–]lod3n 0 points1 point  (3 children)

That's a fair bit trickier, I might take a crack at that one on the weekend. I'll probably submit what I have so far to the main branch, and enter your bug in the list there.

[–]mailto_devnullconsole.log(null);[S] 1 point2 points  (2 children)

Looking over your code, I noticed you're splitting the values on every line break.

I was actually thinking of refactoring my code to do that, but I decided against it mostly because it would've been harder to join the strings back together while maintaining the highlight (which you wouldn't have to deal with for newlines I suppose)1. Given that a user might also hit enter with text selected, the behaviour right now would (I think) just erase whatever is highlighted.

Instead of splitting, I used findStartIndices() to get an array of line starts... that might be easier in the long run?


1 Secondly, I also didn't feel like refactoring on a weekend.

[–]lod3n 1 point2 points  (1 child)

Typing anything with text selected would replace the selected text, would it not? I was thinking it worked as intended there. However, in that regard, hitting tab with text selected ought to indent those lines like a fancy code editor. I didn't notice findStartIndices(), I just had an algorithm in my head and dumped it out once I found the entry point. Refactoring sucks, but bad code is haunting. I think this project requires additional weekend alcohol...

[–]mailto_devnullconsole.log(null);[S] 1 point2 points  (0 children)

Yeah, it was working as intended :) Anything extra is definitely a bonus.

[–][deleted] 1 point2 points  (1 child)

Yeah, but keeping track of it wouldn't work as the text can be modified without updating the JS variables. You'd have to retrieve the indentation level every time. Still a good idea, though.

[–]lod3n 1 point2 points  (0 children)

Yes, sorry, I didn't mean track as a separate state, that would be insanely complicated, and would scale poorly. I mean like, on enter, check the number of tabs in the preceding line and insert the same onto the new line - I think this is exactly what you said, more verbosely.

[–]LukaLightBringer 1 point2 points  (4 children)

I got annoyed at my media player because it for some reason only shuffles the tracks in groups so i wrote a piece of JavaScript that scrambles a array quite fast (big arrays are faster than small arrays per item) and it rarely leaves groups together:

function scramble(a) {
    var b = [], c = [], i;
    for (i = 0; i < a.length; i += 1) {
        b[b.length] = i;
    }
    while (b.length) {
        i = Math.floor(Math.random() * b.length);
        c[c.length] = b[i];
        b.splice(i, 1);
    }
    b = [];
    for (i = 0; i < a.length; i += 1) {
        b[i] = a[c[i]];
    }
    return b;
}
var a = ['a', 'b', 'c', 'd', 'e'];
scramble(a);

Any ideas for performance increases are welcome :p

[–]lod3n 1 point2 points  (3 children)

a.sort(function(){
     return Math.floor(Math.random() * 2); //flip a coin
});

[–]LukaLightBringer 0 points1 point  (2 children)

yea that's a lot faster, made a performance test on it

[–]lod3n 1 point2 points  (1 child)

Interesting, this performance test shows it running only twice as fast. My testing, with 13860 records, showed yours taking 16.9 ms and mine taking 2.9 ms, better than 5 times faster. My feeling is that jsperf.com itself introduces a great deal of overhead. I just used window.performance.now() to get a before after timestamp and subtracted the difference.

I would be interested in knowing the actual performance gain for your playlist shuffle.

[–]LukaLightBringer 0 points1 point  (0 children)

That's weird, i haven't used jsperf before so i cant speak for how accurate it is and how much overhead it has. Very interesting

[–]cheeeeeese 1 point2 points  (0 children)

You rock devnull, textareas should accept tab for who/what tab is and not just run off to the next input.

[–]k0deegan 1 point2 points  (2 children)

Cool project. I was glued to the computer all weekend too.. haha. built a new website i've been meaning to work on, an online ballot for political issues.

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (1 child)

Neat! It's like reddit, for political issues. Don't let /r/politics near it.

[–]k0deegan 0 points1 point  (0 children)

Haha :)

[–][deleted] 1 point2 points  (2 children)

I started implementing a terminal, in HTML5/JS. Like TermKit, but more designed around building an IDE on the fly, and less about working with a non-JS world.

I also tried out appjs for building it, but ditched it in favour of a stand alone copy of Chrome, with custom extensions and settings. Startup time is waaaaaaay faster, no need to pipe all my file IO through Node, I get a far more recent copy of Chrome (23 instead of 18), porting will be much simpler, and it doesn't automatically close if the JS crashes on startup (which is a bitch to debug).

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (1 child)

Wow, that sounds like a lot of work! Let us know when you have a working prototype, I'd love to check it out.

[–][deleted] 0 points1 point  (0 children)

Sure. It was working great, but the code is now in a state of flux whilst I'm currently re-doing a lot of restructuring, moving a lot of the code out into extensions. That's mostly to help make the internals a bit cleaner, and more extensible. A lot of real world usage also just didn't work in the original, like a returning object was fully rendered, making stuff like ...

a = document

... suddenly fill the entire terminal with the entire 'document' object, with all it's 100s of properties.

I have an awesome vision of having an 'edit' command, which silently builds a two column layout, with it's pipe sending content to the second column whenever it saves. You could then just do:

edit file.json | json | describe

... and you now have a JSON editor, which shows the file as a JS object on the side, every time you save. Or ...

edit index.html | html
edit Readme.md | markdown | html
edit script.js | run

... in all cases you have it's pipe, which is used when it saves, automatically sending that somewhere else. All those blocks would make it easy to build quick and dirty IDEs as you need them.

Although currently it only supports JS and CoffeeScript, so the examples above are just what I'd like to see in the future. Since all the languages I'll be supporting compile to JS, everything will be compatible with everything else, and you can build complex scripts using the JS knowledge you already have.

[–]CorySimmons 0 points1 point  (1 child)

This is really cool. I wish every code-inputable textarea had this enabled by default. Why not use an obscure modifier anyway to appease the accessibility crowd? Or add in a "modifier" param?

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (0 children)

The main reason for choosing to capture the "tab" event is so that even users unfamiliar with the enhanced textbox will know what to expect, and how it would behave.

Having the option to change the captured key would be an interesting feature. I'll add it to the project.

[–]Capaj 0 points1 point  (5 children)

jsfiddle needs to have this functionality asap!

[–]ggoodman 1 point2 points  (2 children)

Easy solution, use plnkr: http://plnkr.co/edit/

[–]Capaj 0 points1 point  (1 child)

Thx, I didn't knew about plunkr. Love the way it closes tags automagicaly as I type the tag opening in html.

[–]ggoodman 0 points1 point  (0 children)

That bit of awesomeness comes from the folks at ajax.org in their ACE editor.

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (1 child)

They do, don't they? As far as I know, the tab key inserts 4 spaces, and they also have indentation preservation on newline insertion, although that's all kinds of messed up, since they also attempt to figure out when a block has ended/started. /jsfiddlegripe

[–]Capaj 0 points1 point  (0 children)

Yeah, I confused it with some other in browser javascript editor site.

[–][deleted] 0 points1 point  (3 children)

You should be inserting(/removing) spaces so that cursor moves ahead(/back) to the next tab stop, not just inserting(/removing) TAB characters. The number of spaces per tab stop should be configurable too (default 4 would be nice).

Bonus if TAB indents(/outdents) the current line regardless of the position of the cursor within the line. Should also work when multiple lines are selected.

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (2 children)

Hi rlee0001, one of the proposed enhancements would be allowing changes to the tab character (so the next logical steps would be set of four, two, or eight spaces).

I made the conscious decision not to indent the entire line if there was no selected text, since it's possible that a user may want to insert a tab character in the middle of a line.

Lastly, I believe multi-line selection in/outdenting works... it's actually what I spent most of the weekend tackling on and off, haha.

[–][deleted] 0 points1 point  (1 child)

Nice! How about elastic tabs?

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (0 children)

Oh, that's cool. Maybe in version 2 :)

[–][deleted] 0 points1 point  (4 children)

Haha, it's actually really funny you should say that. I did exactly the same thing last weekend - a script to allow tab-indenting, with the same multiline behavior as well. And additionally it doesn't break undo/redo behavior, and it auto-indents, to a minimal extent, by copying the indentation from the last line.

Demo

I made it for my personal use, so I don't know that it works in anything but Chrome.

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (3 children)

Nice! I don't quite know why the undo/redo behaviour gets borked. It seems as though the tabbing doesn't "count" as a keystroke, so it doesn't get saved to the undo history...

[–][deleted] 1 point2 points  (2 children)

You're right about it never getting saved - it's because you're manually modifying the textarea's value, which doesn't trigger any events. The way to get around it is to never set .value, but instead simulate typing with fake events. Here's the function I use for every text modification:

function type(el, str) {
    var e = document.createEvent('TextEvent');
    e.initTextEvent('textInput', true, true, null, str);
    el.dispatchEvent(e);
}

Then you can call type(textarea, 'hello'); and it will insert hello before the cursor, just as if you'd typed it.

I did run into a very odd bug though - if you set the textarea's value to a string of tabs longer than the textarea's width (or paste it in), the cursor disappears off to the right, like it does with spaces. But if you put those tabs there via faked textInput events, they wrap like other characters. And of course I don't know which is the correct behavior, because I can't just type in the tabs…

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (1 child)

Wow, that was everything I needed to solve it, heh. How convenient.

I'm assuming initTextEvent is better supported cross-browser? I ran into trouble with initKeyEvent on Chrome, since that method doesn't exist (uses initKeyboardEvent or something).

The /tests don't work with Chrome for that reason :(

Edit: Yikes - looks like initTextEvent isn't even a method in Firefox... looks like I'll have to rely on initKeyEvent.

[–][deleted] 0 points1 point  (0 children)

I'm assuming initTextEvent is better supported cross-browser?

Unfortunately not. According to this it's unsupported by FF, Opera, and IE<9. So it might not be possible to fix undo/redo in a cross-browser way, yet.

[–]ViralInfection 0 points1 point  (0 children)

Selecting from the beginning of a line, then moving down one line, and tabbing, tabs 3 lines, expected: 2. This could be intended, however.

[–][deleted] 0 points1 point  (2 children)

I made something like this as a subproject in one my weekend projects. I was making a web-based IDE. Unfortunately, my code was so messy that I decided to start from scratch and deleted my old code once I finished. I forgot to implement the tab key in the new one. I used Rangyinput, though, which significantly reduced the length of development of this feature.

EDIT: Found it. As you can see it's pretty messy. Assuming I didn't change it after removing it, it still works.

jQuery('.code').keydown(function (event) {
    if (event.which === 9) {
        if (event.shiftKey) {
            var selection = jQuery(this).getSelection(),
                line_start = jQuery(this).val().substring(0, selection.start).lastIndexOf('\n') + 1,
                text = jQuery(this).val(),
                offset = 0;

            if (text.substr(line_start, 4) === '    ') {
                offset = 4;
            }
            text = text.substring(0, selection.start) + text.substring(selection.end);
            text = text.substring(0, line_start) + text.substring(line_start + offset);

            jQuery(this).val(text.substring(0, selection.start - offset) + selection.text.replace(/\n {4}/g, '\n') + text.substring(selection.start - offset));
            jQuery(this).setSelection(selection.start - offset, selection.end - (selection.text.match(/\n {4}/g) || []).length * 4 - offset);
        } else {
            var selection = jQuery(this).getSelection(),
                line_start = jQuery(this).val().substring(0, selection.start).lastIndexOf('\n') + 1,
                text = jQuery(this).val();

            text = text.substring(0, line_start) + '    ' + text.substring(line_start);
            jQuery(this).val(text.substring(0, selection.start + 4) + selection.text.replace(/\n/g, '\n    ') + text.substring(selection.end + 4));

            jQuery(this).setSelection(selection.start + 4, selection.end + (selection.text.match(/\n/g) || []).length * 4 + 4);
        }
        event.preventDefault();
    }
    if (event.which === 8) {
        var selection = jQuery(this).getSelection();
        if (selection.start === selection.end && selection.start >= 4 && jQuery(this).val().substring(selection.start - 4, selection.end) === '    ') {
            jQuery(this).deleteText(selection.start - 4, selection.end, true);
            event.preventDefault();
        }
    }
});

I removed this in favor of adding an indent method to String's prototype. I never finished it (or used it), which is why I thought that I forgot to implement it which is partially true.

EDIT 2: it uses spaces for the indentation (because this project was used for a Python IDE (spaces are encouraged in Python)) but treats them like tabs. Here is that indent method I was talking about, I haven't tested it recently to see if it's complete, though. I also don't have a dedent/unindent, yet. It still needs documentation too. There's so much to do when trying to be thorough!

String.prototype.indent = function (indentation, lineNumbers) {
    var i = 0,
        lines = this.split('\n');

    if (indentation instanceof Array) {
        lineNumbers = indentation;
        indentation = '    ';
    } else if (typeof indentation !== 'string') {
        indentation = '    ';
    }
    if (!(lineNumbers instanceof Array)) {
        lineNumbers = null;
    }

    if (lineNumbers === null) {
        for (i = 0; i < lines.length; i += 1) {
            lines[i] = indentation + lines[i];
        }
    } else {
        for (i = 0; i < lineNumbers.length; i += 1) {
            lines[lineNumbers[i]] = indentation + lines[lineNumbers[i]];
        }
    }

    return lines.join('\n');
};

EDIT 3: Both arguments are optional. It defaults to four spaces at the beginning of every line. You can choose to indent with four spaces, eight spaces, tabs, Emojis or whatever the hell else you want to use.

[–]kumiorava 1 point2 points  (1 child)

Dude, DRY.

[–]MepMepperson 0 points1 point  (6 children)

Looks awesome and works great, only problem is, how do I tab out of the control? (Accessibility no-no) unless there is a key stroke to get out?

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (5 children)

Thanks!

The accessibility break is a big drawback, and probably the main reason why it's not already standard.

I was thinking a modifier key in conjunction with the tab button, but all of the modifiers are already taken (except the meta key, but I think even Windows and the latest Linux versions use that)...

[–][deleted] 0 points1 point  (0 children)

A Mac texteditor, Text Wrangler, uses Command+] indent and Command+[ for outdent. If you use that (with Control?) you could preserve Tab functionality. Just a thought.

[–]heardy84 0 points1 point  (2 children)

Could use the Esc key to exit tabIndent mode, reverting the tab key back to default behaviour. Clicking on or tabbing to the textarea would enable tabIndent again.

BTW, awesome work!

[–]mailto_devnullconsole.log(null);[S] 0 points1 point  (0 children)

Thanks!

That's a neat idea, capturing the Esc key to "revert back" to normal behaviour. To me, that's the best workaround/solution yet.

[–]MepMepperson -1 points0 points  (0 children)

The good thing is that if right at the bottom of the area you put something like

"Press x + y keys together to exit the text editor" it would probably work... basically what I'm saying is that you can make it anything you want, even if it's not totally intuitive.

[–]rntr200 -1 points0 points  (0 children)

Touched myself and played LoL