# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1422304993 18000
#      Mon Jan 26 15:43:13 2015 -0500
# Node ID 4e240d6ab8981932dc75ecb3129d08c9c59a8649
# Parent  fe195d41f7d2a9cb5e2c6890a6a233f866e36275
dispatch: offer near-edit-distance suggestions for {file,rev}set functions

Before this patch, when I have a brain fart and type `hg log -r
'add(foo)'`, hg exits and just says add isn't a function, leading me
to the help page for revset to figure out how to spell the
function. With this patch, it suggests 'adds' as a function I might
have meant.

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -7,6 +7,7 @@
 
 from i18n import _
 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
+import difflib
 import util, commands, hg, fancyopts, extensions, hook, error
 import cmdutil, encoding
 import ui as uimod
@@ -27,7 +28,17 @@
     "run the command in sys.argv"
     sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
 
+def _getsimilar(symbols, value):
+    sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
+    # The cutoff for similarity here is pretty arbitrary. It should
+    # probably be investigated and tweaked.
+    return [s for s in symbols if sim(s) > 0.6]
+
 def _formatparse(write, inst):
+    similar = []
+    if isinstance(inst, error.UnknownIdentifier):
+        # make sure to check fileset first, as revset can invoke fileset
+        similar = _getsimilar(inst.symbols, inst.function)
     if len(inst.args) > 1:
         write(_("hg: parse error at %s: %s\n") %
                          (inst.args[1], inst.args[0]))
@@ -35,6 +46,12 @@
             write(_("unexpected leading whitespace\n"))
     else:
         write(_("hg: parse error: %s\n") % inst.args[0])
+        if similar:
+            if len(similar) == 1:
+                write(_("(did you mean %r?)\n") % similar[0])
+            else:
+                ss = ", ".join(sorted(similar))
+                write(_("(did you mean one of %s?)\n") % ss)
 
 def dispatch(req):
     "run the command specified in req.args"
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -891,12 +891,15 @@
 Bogus function gets suggestions
   $ log 'add()'
   hg: parse error: not a function: add
+  (did you mean 'adds'?)
   [255]
   $ log 'added()'
   hg: parse error: not a function: added
+  (did you mean 'adds'?)
   [255]
   $ log 'remo()'
   hg: parse error: not a function: remo
+  (did you mean one of remote, removes?)
   [255]
   $ log 'babar()'
   hg: parse error: not a function: babar