Skip to content

Commit

Permalink
Merge pull request #1765 from galaunay/faster-eldoc
Browse files Browse the repository at this point in the history
Make eldoc faster (and slightly smarter)
  • Loading branch information
galaunay committed Mar 26, 2020
2 parents d02f4da + 9440115 commit 00d6863
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 51 deletions.
13 changes: 13 additions & 0 deletions elpy-rpc.el
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 32 additions & 30 deletions elpy.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)))

Expand Down
34 changes: 34 additions & 0 deletions elpy/jedibackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,47 @@ 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)
definitions = run_with_debug(jedi, 'goto_definitions',
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',
Expand Down
8 changes: 8 additions & 0 deletions elpy/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 52 additions & 10 deletions elpy/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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)


Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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):
Expand Down
12 changes: 12 additions & 0 deletions elpy/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 00d6863

Please sign in to comment.