Cache aliases to speed up subsequent calls and add support to Fish functions
diff --git a/tests/test_shells.py b/tests/test_shells.py
index c08ee67f0..675cdd354 100644
--- a/tests/test_shells.py
+++ b/tests/test_shells.py
@@ -78,6 +78,12 @@ class TestFish(object):
     def shell(self):
         return shells.Fish()
 
+    @pytest.fixture(autouse=True)
+    def Popen(self, mocker):
+        mock = mocker.patch('thefuck.shells.Popen')
+        mock.return_value.stdout.read.return_value = (b'funced\nfuncsave\ngrep')
+        return mock
+
     @pytest.mark.parametrize('before, after', [
         ('pwd', 'pwd'),
         ('ll', 'll')])  # Fish has no aliases but functions
@@ -98,7 +104,9 @@ def test_and_(self, shell):
         assert shell.and_('foo', 'bar') == 'foo; and bar'
 
     def test_get_aliases(self, shell):
-        assert shell.get_aliases() == {}
+        assert shell.get_aliases() == {'funced': 'funced',
+                                       'funcsave': 'funcsave',
+                                       'grep': 'grep'}
 
 
 @pytest.mark.usefixtures('isfile')
diff --git a/thefuck/shells.py b/thefuck/shells.py
index 2749a1c5b..154311c85 100644
--- a/thefuck/shells.py
+++ b/thefuck/shells.py
@@ -12,8 +12,10 @@
 
 
 class Generic(object):
+    _aliases = {}
+
     def get_aliases(self):
-        return {}
+        return self._aliases
 
     def _expand_aliases(self, command_script):
         aliases = self.get_aliases()
@@ -62,11 +64,15 @@ def _parse_alias(self, alias):
         return name, value
 
     def get_aliases(self):
-        proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
-        return dict(
-            self._parse_alias(alias)
-            for alias in proc.stdout.read().decode('utf-8').split('\n')
-            if alias and '=' in alias)
+        if not self._aliases:
+            proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
+                         shell=True)
+            self._aliases = dict(
+                self._parse_alias(alias)
+                for alias in proc.stdout.read().decode('utf-8').split('\n')
+                if alias and '=' in alias)
+
+        return self._aliases
 
     def _get_history_file_name(self):
         return os.environ.get("HISTFILE",
@@ -91,6 +97,15 @@ def app_alias(self):
                 "    end\n"
                 "end")
 
+    def get_aliases(self):
+        if not self._aliases:
+            proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
+                         shell=True)
+            functions = proc.stdout.read().decode('utf-8').strip().split('\n')
+            self._aliases = dict((function, function) for function in functions)
+
+        return self._aliases
+
     def _get_history_file_name(self):
         return os.path.expanduser('~/.config/fish/fish_history')
 
@@ -112,11 +127,15 @@ def _parse_alias(self, alias):
         return name, value
 
     def get_aliases(self):
-        proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
-        return dict(
-            self._parse_alias(alias)
-            for alias in proc.stdout.read().decode('utf-8').split('\n')
-            if alias and '=' in alias)
+        if not self._aliases:
+            proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
+                         shell=True)
+            self._aliases = dict(
+                self._parse_alias(alias)
+                for alias in proc.stdout.read().decode('utf-8').split('\n')
+                if alias and '=' in alias)
+
+        return self._aliases
 
     def _get_history_file_name(self):
         return os.environ.get("HISTFILE",
@@ -135,11 +154,15 @@ def _parse_alias(self, alias):
         return name, value
 
     def get_aliases(self):
-        proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
-        return dict(
-            self._parse_alias(alias)
-            for alias in proc.stdout.read().decode('utf-8').split('\n')
-            if alias and '\t' in alias)
+        if not self._aliases:
+            proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
+                         shell=True)
+            self._aliases = dict(
+                self._parse_alias(alias)
+                for alias in proc.stdout.read().decode('utf-8').split('\n')
+                if alias and '\t' in alias)
+
+        return self._aliases
 
     def _get_history_file_name(self):
         return os.environ.get("HISTFILE",

@nvbn What do you think?
