Repository: evennia/evennia

Issue:
Feature Request: `@link` exits always searches globally. Searching local-first is more helpful.
### Brief summary of issue / Description of requested feature:
When `@link` is used on a local exit to link or relink an exit to a proper destination, it returns a global list of exits when searching by name, even though there may be a single match locally.  This then requires all exits to be referenced by `#dbref`.

An initial local search, assuming the builder is in a valid location and exits are also in that location, would allow by-name searches to quickly match the intended local exit instead of returning all global exits.  Listing global exits should only occur under the condition that no local exits match the given exit name.

### Steps to reproduce the issue / Reasons for adding feature:
Example use, not reproduced error.
1.  `@link south=#2`
2. (Have 1000+ exits returned, as "south" is a common exit name.)
3. `@link #523=#2` (after resorting to using the `#dbref`

### Error output / Expected result of feature
`@link south=#2` would first look locally, find the south exit, then link it.  If multiple south exits are local, then multimatch only those.  If no matches are found, then look for a match globally.

#### Extra information, such as Evennia revision/repo/branch, operating system and ideas for how to solve / implement:
Current version, all OS.


Discussion:

Resolving patch:
diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py
--- a/evennia/commands/default/building.py
+++ b/evennia/commands/default/building.py
@@ -10,7 +10,7 @@
 from evennia.locks.lockhandler import LockException
 from evennia.commands.cmdhandler import get_and_merge_cmdsets
 from evennia.utils import create, utils, search
-from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses
+from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses, variable_from_module
 from evennia.utils.eveditor import EvEditor
 from evennia.utils.evmore import EvMore
 from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
@@ -1022,10 +1022,17 @@ def func(self):
 
         object_name = self.lhs
 
-        # get object
-        obj = caller.search(object_name, global_search=True)
-        if not obj:
-            return
+        # try to search locally first
+        results = caller.search(object_name, quiet=True)
+        if len(results) > 1:  # local results was a multimatch. Inform them to be more specific
+            _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
+            return _AT_SEARCH_RESULT(results, caller, query=object_name)
+        elif len(results) == 1:  # A unique local match
+            obj = results[0]
+        else:  # No matches. Search globally
+            obj = caller.search(object_name, global_search=True)
+            if not obj:
+                return
 
         if self.rhs:
             # this means a target name was given


Test patch:
diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py
--- a/evennia/commands/default/tests.py
+++ b/evennia/commands/default/tests.py
@@ -334,6 +334,14 @@ def test_exit_commands(self):
         self.call(building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2")
         self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 -> Room (one way).")
         self.call(building.CmdUnLink(), "TestExit1", "Former exit TestExit1 no longer links anywhere.")
+        self.char1.location = self.room2
+        self.call(building.CmdOpen(), "TestExit2=Room", "Created new Exit 'TestExit2' from Room2 to Room.")
+        # ensure it matches locally first
+        self.call(building.CmdLink(), "TestExit=Room2", "Link created TestExit2 -> Room2 (one way).")
+        # ensure can still match globally when not a local name
+        self.call(building.CmdLink(), "TestExit1=Room2", "Note: TestExit1(#8) did not have a destination set before. "
+                                                         "Make sure you linked the right thing.\n"
+                                                         "Link created TestExit1 -> Room2 (one way).")
 
     def test_set_home(self):
         self.call(building.CmdSetHome(), "Obj = Room2", "Obj's home location was changed from Room")