Screenshot / Code Snippet

enter image description here


To use, type an "@" then start typing a few characters of the user's name. Then hit Ctrl+Space. The script will then search for usernames that match what you've entered so far. If there is exactly one match, it will autocomplete to "@username: ". If there is more than one match, it will autocomplete to the longest common substring of those entries matching what you've entered. For example, if you typed "@j", and both "Jeff Atwood" and "Jeff Probst" are active, it would autocomplete to "@Jeff".

The usernames that will be searched for autocompletion consist of:

  1. The person who asked this question.
  2. The person who provided this answer (if you are commenting on an answer).
  3. Anyone who has left a comment in the same comment thread you are writing your comment for.

Multi-lingual support

Those of us without fancy foreign keyboards can type "@ol" to get Ólafur Waage, or "@a" or "@ae" to get Æther. You can also still type the foreign characters if you know how. Of course, the name has to use characters that look kind of like English characters. (Sorry, ベレアー アダム.)

Optional configuration

There is a variable called suffix in the code. It is defaulted to ': '. This suffix will be appended after a complete match. Some people don't like the colon; if you're one of those people, just change the suffix to a space (or whatever you want it to be).


Install | View source


Tested on Firefox 4.0 and Chrome 10.0, on Windows 7. Requires Greasemonkey on Firefox.


Do whatever you want, as long as you don't sell it or claim you wrote it. If you make any cool modifications, let me know!


It seems that userscripts.org is dying. Here is the code:

// ==UserScript==
// @name           auto-completion of @replies in stackexchange comments
// @namespace      stackoverflow
// @description    Provides auto-completion of @replies in comments on Stack Exchange sites. Type "@" followed by the beginning of the username, then hit Ctrl+Space to auto-complete.
// @include       http://stackoverflow.com/*
// @include       https://meta.stackoverflow.com/*
// @include       http://superuser.com/*
// @include       http://meta.superuser.com/*
// @include       http://serverfault.com/*
// @include       http://meta.serverfault.com/*
// @include       http://askubuntu.com/*
// @include       http://meta.askubuntu.com/*
// @include       http://answers.onstartups.com/*
// @include       http://meta.answers.onstartups.com/*
// @include       http://nothingtoinstall.com/*
// @include       http://meta.nothingtoinstall.com/*
// @include       http://seasonedadvice.com/*
// @include       http://meta.seasonedadvice.com/*
// @include       http://crossvalidated.com/*
// @include       http://askdifferent.com/*
// @include       http://meta.crossvalidated.com/*
// @include       http://stackapps.com/*
// @include       http://*.stackexchange.com/*
// @exclude       http://chat.stackexchange.com/*
// @exclude       http://api.*.stackexchange.com/*
// @exclude       http://data.stackexchange.com/*
// @exclude       http://area51.stackexchange.com/*
// ==/UserScript==

//Script "homepage": http://stackapps.com/questions/2341/auto-completion-of-user-name-for-replies-in-comments

  function with_jquery(f) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.textContent = "(" + f.toString() + ")(jQuery)";

    var suffix = ': ';

    $('textarea[name=comment]').live('keypress', function(e) {
      if(!(e.ctrlKey && e.which == 0x20 && !e.altKey))

      //how far back from current cursor position to look for @ symbol
      var MAX_LEN = 15;
      if(this.selectionStart != this.selectionEnd)

      var cursor = this.selectionStart;
      var text = $(this).val();
      var before = text.substring(0, cursor);
      var after = text.substring(cursor, text.length);

      var names = getNames($(this));
      var started = '';

      //scan backwards through text before cursor, looking for @ symbol
      for(var i = before.length - 1; i>=0 && i>= before.length - MAX_LEN; i--)
        if(before.charAt(i) == '@')
          //started now contains the part of the user name that has been typed
          started = before.substring(i+1, before.length);

      var startedLen = started.length; //because this might change when anglicized...

      started = anglicize(started);
      started = started.replace(/\s+/g, ''); //remove all spaces

      if(started.length == 0)

      var matches = new Array();
      $.each(names, function() {
        if(startsWithIgnoreCase(anglicize(this.toString()), started))

      if(matches.length < 1)

      var name = commonStartIgnoreCase(matches);

      before = before.substring(0, before.length - startedLen) + name;
      if(matches.length == 1)
        before += suffix;

      $(this).val(before + after);
      this.selectionStart = this.selectionEnd = before.length;
      return false;

    //Get a list of names of users who have either asked this question, provided this
    //answer (if comment is left on an answer), or left a comment on this answer.
    function getNames($ta)
      var names = $ta.data('names'); //array of user names
      if(names != undefined)
        return names;

      var names = new Array();

      //callback for $().each(). adds element's .text() to the names array
      var addName = function() {
        var name = $(this).text();

        name = name.replace('\u2666',''); //remove moderator diamond
        name = name.replace(/\s+/g, ''); //remove whitespace

        if(name == '')

        //if it's not in the list, add it
        if($.inArray(name, names) < 0)

      //get the name of the person who asked this question

      //get the name of the person who provided this answer (if comment is left on answer)
      var $ans = $ta.parents('.answer:first');
      $('.post-signature>.user-info>.user-details>a[href^="/users/"]', $ans).each(addName);

      //add each person active in this comment thread
      var $com = $ta.parents('.comments:first');
      $('.comment-user', $com).each(addName);


      //store list of names so we don't have to look it up again next time
      $ta.data('names', names);

      return names;

    //Returns true if s starts with t, ignoring case
    function startsWithIgnoreCase(s, t)
      if(typeof s != 'string')
        return false;
      if(typeof t != 'string')
        return false;

      s = s.toUpperCase();
      t = t.toUpperCase();

      if(t.length == 0)
        return true;
      if(s.length < t.length)
        return false;

      return s.substring(0, t.length) == t;

    //Returns the common starting substring of all strings in the given array
    function commonStartIgnoreCase(matches)
      if(matches.length < 1)
        return '';
      if(matches.length == 1)
        return matches[0];

      var ret = '';
      for(var i = 0; i < matches[0].length; i++)
        var chr = matches[0].charAt(i).toUpperCase();
        var allMatch = true;
        for(var j = 1; allMatch && j < matches.length; j++)
          allMatch = (chr == matches[j].charAt(i).toUpperCase());

          ret += matches[0].charAt(i);
      return ret;

    //makes string englishier
    function anglicize(s)
      var foreign = ['\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5'

      var english = ['A'

      for(var i = 0; i < foreign.length; i++)
        s = s.replace(new RegExp('[' + foreign[i] + ']', "g"), english[i]);

      return s;

  • Yes, it now works for me in Chrome 10.
    – Mark Hurd
    Apr 14, 2011 at 16:32


You must log in to answer this question.

Browse other questions tagged .