diff --git a/elpy-rpc.el b/elpy-rpc.el index ba259f826..92a3db418 100644 --- a/elpy-rpc.el +++ b/elpy-rpc.el @@ -1051,6 +1051,19 @@ Returns a calltip string for the function call at point." success error))) +(defun elpy-rpc-get-calltip-or-oneline-docstring (&optional success error) + "Call the get_calltip_or_oneline_doc API function. + +Returns a calltip string or a oneline docstring for the function call at point." + (when (< (buffer-size) elpy-rpc-ignored-buffer-size) + (elpy-rpc "get_calltip_or_oneline_docstring" + (list buffer-file-name + (elpy-rpc--buffer-contents) + (- (point) + (point-min))) + success error))) + + (defun elpy-rpc-get-oneline-docstring (&optional success error) "Call the get_oneline_docstring API function. diff --git a/elpy.el b/elpy.el index dc8aa1fc9..50865e5fb 100644 --- a/elpy.el +++ b/elpy.el @@ -3115,16 +3115,17 @@ display the current class and method instead." (let ((flymake-error (elpy-flymake-error-at-point))) (if flymake-error flymake-error - ;; Try getting calltip - (elpy-rpc-get-calltip - (lambda (calltip) + (elpy-rpc-get-calltip-or-oneline-docstring + (lambda (info) (cond - ((stringp calltip) - (eldoc-message calltip)) - (calltip - (let ((name (cdr (assq 'name calltip))) - (index (cdr (assq 'index calltip))) - (params (cdr (assq 'params calltip)))) + ;; INFO is a string, just display it + ((stringp info) + (eldoc-message info)) + ;; INFO is a calltip + ((string= (cdr (assq 'kind info)) "calltip") + (let ((name (cdr (assq 'name info))) + (index (cdr (assq 'index info))) + (params (cdr (assq 'params info)))) (when index (setf (nth index params) (propertize (nth index params) @@ -3137,28 +3138,29 @@ display the current class and method instead." (if (version<= emacs-version "25") (format "%s%s" prefix args) (eldoc-docstring-format-sym-doc prefix args nil)))))) + ;; INFO is a oneline docstring + ((string= (cdr (assq 'kind info)) "oneline_doc") + (let ((name (cdr (assq 'name info))) + (docs (cdr (assq 'doc info)))) + (let ((prefix (propertize (format "%s: " name) + 'face + 'font-lock-function-name-face))) + (eldoc-message + (if (version<= emacs-version "25") + (format "%s%s" prefix docs) + (let ((eldoc-echo-area-use-multiline-p nil)) + (eldoc-docstring-format-sym-doc prefix docs nil))))))) + ;; INFO is nil, maybe display the current function (t - ;; Try getting oneline docstring - (elpy-rpc-get-oneline-docstring - (lambda (doc) - (cond - (doc - (let ((name (cdr (assq 'name doc))) - (doc (cdr (assq 'doc doc)))) - (let ((prefix (propertize (format "%s: " name) - 'face - 'font-lock-function-name-face))) - (eldoc-message - (if (version<= emacs-version "25") - (format "%s%s" prefix doc) - (let ((eldoc-echo-area-use-multiline-p nil)) - (eldoc-docstring-format-sym-doc prefix doc nil))))))) - ;; Give the current definition - (elpy-eldoc-show-current-function - (let ((current-defun (python-info-current-defun))) - (when current-defun - (eldoc-message - (format "In: %s()" current-defun)))))))))))) + (if elpy-eldoc-show-current-function + (let ((current-defun (python-info-current-defun))) + (when current-defun + (eldoc-message + (concat "In: " + (propertize + (format "%s()" current-defun) + 'face 'font-lock-function-name-face))))) + (eldoc-message "")))))) ;; Return the last message until we're done eldoc-last-message))) diff --git a/elpy/jedibackend.py b/elpy/jedibackend.py index b731eed43..e33d3e8a2 100644 --- a/elpy/jedibackend.py +++ b/elpy/jedibackend.py @@ -171,6 +171,33 @@ def rpc_get_calltip(self, filename, source, offset): "index": call.index, "params": params} + def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset): + """ + Return the current function calltip or its oneline docstring. + + Meant to be used with eldoc. + """ + # Try to get a oneline docstring then + docs = self.rpc_get_oneline_docstring(filename=filename, + source=source, + offset=offset) + if docs is not None: + if docs['doc'] != "No documentation": + docs['kind'] = 'oneline_doc' + return docs + # Try to get a calltip + calltip = self.rpc_get_calltip(filename=filename, source=source, + offset=offset) + if calltip is not None: + calltip['kind'] = 'calltip' + return calltip + # Ok, no calltip, just display the function name + if docs is not None: + docs['kind'] = 'oneline_doc' + return docs + # Giving up... + return None + def rpc_get_oneline_docstring(self, filename, source, offset): """Return a oneline docstring for the symbol at offset""" line, column = pos_to_linecol(source, offset) @@ -178,6 +205,13 @@ def rpc_get_oneline_docstring(self, filename, source, offset): source=source, line=line, column=column, path=filename, encoding='utf-8', environment=self.environment) + # avoid unintersting stuff + try: + if definitions[0].name in ["str", "int", "float", "bool", "tuple", + "list", "dict"]: + return None + except: + pass assignments = run_with_debug(jedi, 'goto_assignments', source=source, line=line, column=column, path=filename, encoding='utf-8', diff --git a/elpy/server.py b/elpy/server.py index d0d838ab5..1027afce2 100644 --- a/elpy/server.py +++ b/elpy/server.py @@ -79,6 +79,14 @@ def rpc_get_oneline_docstring(self, filename, source, offset): return self._call_backend("rpc_get_oneline_docstring", None, filename, get_source(source), offset) + def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset): + """Get a calltip or a oneline docstring for the symbol at the offset. + + """ + return self._call_backend("rpc_get_calltip_or_oneline_docstring", + None, filename, + get_source(source), offset) + def rpc_get_completions(self, filename, source, offset): """Get a list of completion candidates for the symbol at offset. diff --git a/elpy/tests/support.py b/elpy/tests/support.py index 9b1199931..752e11b26 100644 --- a/elpy/tests/support.py +++ b/elpy/tests/support.py @@ -698,38 +698,46 @@ class RPCGetCalltipTests(GenericRPCTests): METHOD = "rpc_get_calltip" def test_should_get_calltip(self): + expected = self.THREAD_CALLTIP source, offset = source_and_offset( "import threading\nthreading.Thread(_|_") filename = self.project_file("test.py", source) calltip = self.backend.rpc_get_calltip(filename, source, offset) - - expected = self.THREAD_CALLTIP - + self.assertEqual(calltip, expected) + calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + calltip.pop('kind') self.assertEqual(calltip, expected) def test_should_get_calltip_even_after_parens(self): source, offset = source_and_offset( "import threading\nthreading.Thread(foo()_|_") filename = self.project_file("test.py", source) - actual = self.backend.rpc_get_calltip(filename, source, offset) - + self.assertEqual(self.THREAD_CALLTIP, actual) + actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + actual.pop('kind') self.assertEqual(self.THREAD_CALLTIP, actual) def test_should_get_calltip_at_closing_paren(self): source, offset = source_and_offset( "import threading\nthreading.Thread(_|_)") filename = self.project_file("test.py", source) - actual = self.backend.rpc_get_calltip(filename, source, offset) - self.assertEqual(self.THREAD_CALLTIP, actual) + actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + self.assertEqual(actual['kind'], "oneline_doc") def test_should_not_missing_attribute_get_definition(self): # Bug #627 / jedi#573 @@ -747,6 +755,10 @@ def test_should_return_none_for_bad_identifier(self): source, offset) self.assertIsNone(calltip) + calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + self.assertIsNone(calltip) def test_should_remove_self_argument(self): source, offset = source_and_offset( @@ -757,7 +769,11 @@ def test_should_remove_self_argument(self): actual = self.backend.rpc_get_calltip(filename, source, offset) - + self.assertEqual(self.KEYS_CALLTIP, actual) + actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + actual.pop('kind') self.assertEqual(self.KEYS_CALLTIP, actual) def test_should_remove_package_prefix(self): @@ -770,7 +786,11 @@ def test_should_remove_package_prefix(self): actual = self.backend.rpc_get_calltip(filename, source, offset) - + self.assertEqual(self.RADIX_CALLTIP, actual) + actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + actual.pop('kind') self.assertEqual(self.RADIX_CALLTIP, actual) def test_should_return_none_outside_of_all(self): @@ -779,6 +799,10 @@ def test_should_return_none_outside_of_all(self): calltip = self.backend.rpc_get_calltip(filename, source, offset) self.assertIsNone(calltip) + calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + self.assertIsNotNone(calltip) def test_should_find_calltip_different_package(self): # See issue #74 @@ -798,7 +822,11 @@ def test_should_find_calltip_different_package(self): actual = self.backend.rpc_get_calltip(file2, source2, offset) - + self.assertEqual(self.ADD_CALLTIP, actual) + actual = self.backend.rpc_get_calltip_or_oneline_docstring(file2, + source2, + offset) + actual.pop('kind') self.assertEqual(self.ADD_CALLTIP, actual) @@ -853,6 +881,11 @@ def test_should_get_oneline_docstring(self): source, offset) self.check_docstring(docstring) + docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + docstring.pop('kind') + self.check_docstring(docstring) def test_should_get_oneline_docstring_for_modules(self): source, offset = source_and_offset( @@ -862,6 +895,11 @@ def test_should_get_oneline_docstring_for_modules(self): source, offset) self.check_module_docstring(docstring) + docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + docstring.pop('kind') + self.check_module_docstring(docstring) def test_should_return_none_for_bad_identifier(self): source, offset = source_and_offset( @@ -871,6 +909,10 @@ def test_should_return_none_for_bad_identifier(self): source, offset) self.assertIsNone(docstring) + docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, + source, + offset) + self.assertIsNone(docstring) class RPCGetNamesTests(GenericRPCTests): diff --git a/elpy/tests/test_server.py b/elpy/tests/test_server.py index fea561316..1f059dacb 100644 --- a/elpy/tests/test_server.py +++ b/elpy/tests/test_server.py @@ -211,6 +211,18 @@ def test_should_handle_no_backend(self): "offset")) +class TestRPCGetCalltipOrOnelineDocstring(BackendCallTestCase): + def test_should_call_backend(self): + self.assert_calls_backend("rpc_get_calltip_or_oneline_docstring") + + def test_should_handle_no_backend(self): + self.srv.backend = None + self.assertIsNone( + self.srv.rpc_get_calltip_or_oneline_docstring("filname", + "source", + "offset")) + + class TestRPCGetPydocCompletions(ServerTestCase): @mock.patch.object(server, 'get_pydoc_completions') def test_should_call_pydoc_completions(self, get_pydoc_completions): diff --git a/test/elpy-eldoc-documentation-test.el b/test/elpy-eldoc-documentation-test.el index 08e254cdc..76647d8e6 100644 --- a/test/elpy-eldoc-documentation-test.el +++ b/test/elpy-eldoc-documentation-test.el @@ -9,7 +9,7 @@ ;; Calltip available: display that (ert-deftest elpy-eldoc-documentation-should-show-string-calltip () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) (funcall callback "Queue.cancel_join_thread()")) (calltip nil) @@ -21,10 +21,11 @@ (ert-deftest elpy-eldoc-documentation-should-show-object-calltip () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) (funcall callback '((name . "cancel_join_thread") (index . 0) + (kind . "calltip") (params "foo" "bar")))) (calltip nil) (eldoc-message (tip) (setq calltip tip))) @@ -40,10 +41,11 @@ (ert-deftest elpy-eldoc-documentation-should-not-fail-for-index-nil () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) (funcall callback '((name . "cancel_join_thread") (index . nil) + (kind . "calltip") (params . nil)))) (calltip nil) ;; without UI, the minibuffer width is only 9, @@ -59,12 +61,10 @@ ;; No calltip: display function oneline docstring (ert-deftest elpy-eldoc-documentation-should-show-object-onelinedoc () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip - (callback) - (funcall callback nil)) - (elpy-rpc-get-oneline-docstring + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) (funcall callback '((name . "cancel_join_thread()") + (kind . "oneline_doc") (doc . "This function does things.")))) (doc nil) (window-width (buff) 100000000) @@ -84,8 +84,8 @@ ;; No calltip and docstring: display current edited function (ert-deftest elpy-eldoc-documentation-should-use-current-defun-if-nothing-else () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip (callback) (funcall callback nil)) - (elpy-rpc-get-oneline-docstring (callback) (funcall callback nil)) + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) + (funcall callback nil)) (calltip nil) (eldoc-message (tip) (setq calltip tip)) (python-info-current-defun () "FooClass.method")) @@ -97,8 +97,8 @@ ;; No calltip, docstring or current function: display nothing (ert-deftest elpy-eldoc-documentation-should-return-nil-without-defun () (elpy-testcase () - (mletf* ((elpy-rpc-get-calltip (callback) (funcall callback nil)) - (elpy-rpc-get-oneline-docstring (callback) (funcall callback nil)) + (mletf* ((elpy-rpc-get-calltip-or-oneline-docstring (callback) + (funcall callback nil)) (calltip nil) (eldoc-message (tip) (setq calltip tip)) (python-info-current-defun () nil))