Patchwork [9,of,9,sethelp] dispatch: offer near-edit-distance suggestions for {file, rev}set functions

login
register
mail settings
Submitter Augie Fackler
Date Feb. 4, 2015, 6:15 p.m.
Message ID <74cc05d6c966733e4d97.1423073714@arthedain.pit.corp.google.com>
Download mbox | patch
Permalink /patch/7663/
State Changes Requested
Headers show

Comments

Augie Fackler - Feb. 4, 2015, 6:15 p.m.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1422304993 18000
#      Mon Jan 26 15:43:13 2015 -0500
# Node ID 74cc05d6c966733e4d97594486a51666bb59a87a
# Parent  b3e1bbb22c67b519680f1d75c2c1e0399a0ab1bb
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.

Patch

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -8,7 +8,7 @@ 
 from i18n import _
 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
 import util, commands, hg, fancyopts, extensions, hook, error
-import cmdutil, encoding
+import cmdutil, encoding, levenshtein
 import ui as uimod
 
 class request(object):
@@ -27,7 +27,20 @@  def run():
     "run the command in sys.argv"
     sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
 
+def _getsimilar(symbols, value):
+    by_similiarity = {}
+    for s in symbols:
+        d = levenshtein.levenshtein(value, s)
+        by_similiarity.setdefault(d, []).append(s)
+    d, values = sorted(by_similiarity.items())[0]
+    if d < 3: # arbitrary limit
+        return values
+
 def _formatparse(write, inst):
+    similar = []
+    if isinstance(inst, error.NotAFunction):
+        # 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 +48,12 @@  def _formatparse(write, inst):
             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
@@ -864,12 +864,15 @@  parentrevspec
 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, rev?)
   [255]
   $ log 'babar()'
   hg: parse error: not a function: babar