Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
9.4 KiB

  1. """Method implementations for the Elpy JSON-RPC server.
  2. This file implements the methods exported by the JSON-RPC server. It
  3. handles backend selection and passes methods on to the selected
  4. backend.
  5. """
  6. import io
  7. import os
  8. import pydoc
  9. from elpy.pydocutils import get_pydoc_completions
  10. from elpy.rpc import JSONRPCServer, Fault
  11. from elpy.auto_pep8 import fix_code
  12. from elpy.yapfutil import fix_code as fix_code_with_yapf
  13. from elpy.blackutil import fix_code as fix_code_with_black
  14. try:
  15. from elpy import jedibackend
  16. except ImportError: # pragma: no cover
  17. jedibackend = None
  18. class ElpyRPCServer(JSONRPCServer):
  19. """The RPC server for elpy.
  20. See the rpc_* methods for exported method documentation.
  21. """
  22. def __init__(self, *args, **kwargs):
  23. super(ElpyRPCServer, self).__init__(*args, **kwargs)
  24. self.backend = None
  25. self.project_root = None
  26. def _call_backend(self, method, default, *args, **kwargs):
  27. """Call the backend method with args.
  28. If there is currently no backend, return default."""
  29. meth = getattr(self.backend, method, None)
  30. if meth is None:
  31. return default
  32. else:
  33. return meth(*args, **kwargs)
  34. def rpc_echo(self, *args):
  35. """Return the arguments.
  36. This is a simple test method to see if the protocol is
  37. working.
  38. """
  39. return args
  40. def rpc_init(self, options):
  41. self.project_root = options["project_root"]
  42. self.env = options["environment"]
  43. if jedibackend:
  44. self.backend = jedibackend.JediBackend(self.project_root, self.env)
  45. else:
  46. self.backend = None
  47. return {
  48. 'jedi_available': (self.backend is not None)
  49. }
  50. def rpc_get_calltip(self, filename, source, offset):
  51. """Get the calltip for the function at the offset.
  52. """
  53. return self._call_backend("rpc_get_calltip", None, filename,
  54. get_source(source), offset)
  55. def rpc_get_oneline_docstring(self, filename, source, offset):
  56. """Get a oneline docstring for the symbol at the offset.
  57. """
  58. return self._call_backend("rpc_get_oneline_docstring", None, filename,
  59. get_source(source), offset)
  60. def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset):
  61. """Get a calltip or a oneline docstring for the symbol at the offset.
  62. """
  63. return self._call_backend("rpc_get_calltip_or_oneline_docstring",
  64. None, filename,
  65. get_source(source), offset)
  66. def rpc_get_completions(self, filename, source, offset):
  67. """Get a list of completion candidates for the symbol at offset.
  68. """
  69. results = self._call_backend("rpc_get_completions", [], filename,
  70. get_source(source), offset)
  71. # Uniquify by name
  72. results = list(dict((res['name'], res) for res in results)
  73. .values())
  74. results.sort(key=lambda cand: _pysymbol_key(cand["name"]))
  75. return results
  76. def rpc_get_completion_docstring(self, completion):
  77. """Return documentation for a previously returned completion.
  78. """
  79. return self._call_backend("rpc_get_completion_docstring",
  80. None, completion)
  81. def rpc_get_completion_location(self, completion):
  82. """Return the location for a previously returned completion.
  83. This returns a list of [file name, line number].
  84. """
  85. return self._call_backend("rpc_get_completion_location", None,
  86. completion)
  87. def rpc_get_definition(self, filename, source, offset):
  88. """Get the location of the definition for the symbol at the offset.
  89. """
  90. return self._call_backend("rpc_get_definition", None, filename,
  91. get_source(source), offset)
  92. def rpc_get_assignment(self, filename, source, offset):
  93. """Get the location of the assignment for the symbol at the offset.
  94. """
  95. return self._call_backend("rpc_get_assignment", None, filename,
  96. get_source(source), offset)
  97. def rpc_get_docstring(self, filename, source, offset):
  98. """Get the docstring for the symbol at the offset.
  99. """
  100. return self._call_backend("rpc_get_docstring", None, filename,
  101. get_source(source), offset)
  102. def rpc_get_pydoc_completions(self, name=None):
  103. """Return a list of possible strings to pass to pydoc.
  104. If name is given, the strings are under name. If not, top
  105. level modules are returned.
  106. """
  107. return get_pydoc_completions(name)
  108. def rpc_get_pydoc_documentation(self, symbol):
  109. """Get the Pydoc documentation for the given symbol.
  110. Uses pydoc and can return a string with backspace characters
  111. for bold highlighting.
  112. """
  113. try:
  114. docstring = pydoc.render_doc(str(symbol),
  115. "Elpy Pydoc Documentation for %s",
  116. False)
  117. except (ImportError, pydoc.ErrorDuringImport):
  118. return None
  119. else:
  120. if isinstance(docstring, bytes):
  121. docstring = docstring.decode("utf-8", "replace")
  122. return docstring
  123. def rpc_get_usages(self, filename, source, offset):
  124. """Get usages for the symbol at point.
  125. """
  126. source = get_source(source)
  127. return self._call_backend("rpc_get_usages",
  128. None, filename, source, offset)
  129. def rpc_get_names(self, filename, source, offset):
  130. """Get all possible names
  131. """
  132. source = get_source(source)
  133. return self._call_backend("rpc_get_names",
  134. None, filename, source, offset)
  135. def rpc_get_rename_diff(self, filename, source, offset, new_name):
  136. """Get the diff resulting from renaming the thing at point
  137. """
  138. source = get_source(source)
  139. return self._call_backend("rpc_get_rename_diff",
  140. None, filename, source, offset, new_name)
  141. def rpc_get_extract_variable_diff(self, filename, source, offset, new_name,
  142. line_beg, line_end, col_beg, col_end):
  143. """Get the diff resulting from extracting the selected code
  144. """
  145. source = get_source(source)
  146. return self._call_backend("rpc_get_extract_variable_diff",
  147. None, filename, source, offset,
  148. new_name, line_beg, line_end, col_beg,
  149. col_end)
  150. def rpc_get_extract_function_diff(self, filename, source, offset, new_name,
  151. line_beg, line_end, col_beg, col_end):
  152. """Get the diff resulting from extracting the selected code
  153. """
  154. source = get_source(source)
  155. return self._call_backend("rpc_get_extract_function_diff",
  156. None, filename, source, offset, new_name,
  157. line_beg, line_end, col_beg, col_end)
  158. def rpc_get_inline_diff(self, filename, source, offset):
  159. """Get the diff resulting from inlining the thing at point.
  160. """
  161. source = get_source(source)
  162. return self._call_backend("rpc_get_inline_diff",
  163. None, filename, source, offset)
  164. def rpc_fix_code(self, source, directory):
  165. """Formats Python code to conform to the PEP 8 style guide.
  166. """
  167. source = get_source(source)
  168. return fix_code(source, directory)
  169. def rpc_fix_code_with_yapf(self, source, directory):
  170. """Formats Python code to conform to the PEP 8 style guide.
  171. """
  172. source = get_source(source)
  173. return fix_code_with_yapf(source, directory)
  174. def rpc_fix_code_with_black(self, source, directory):
  175. """Formats Python code to conform to the PEP 8 style guide.
  176. """
  177. source = get_source(source)
  178. return fix_code_with_black(source, directory)
  179. def get_source(fileobj):
  180. """Translate fileobj into file contents.
  181. fileobj is either a string or a dict. If it's a string, that's the
  182. file contents. If it's a string, then the filename key contains
  183. the name of the file whose contents we are to use.
  184. If the dict contains a true value for the key delete_after_use,
  185. the file should be deleted once read.
  186. """
  187. if not isinstance(fileobj, dict):
  188. return fileobj
  189. else:
  190. try:
  191. with io.open(fileobj["filename"], encoding="utf-8",
  192. errors="ignore") as f:
  193. return f.read()
  194. finally:
  195. if fileobj.get('delete_after_use'):
  196. try:
  197. os.remove(fileobj["filename"])
  198. except: # pragma: no cover
  199. pass
  200. def _pysymbol_key(name):
  201. """Return a sortable key index for name.
  202. Sorting is case-insensitive, with the first underscore counting as
  203. worse than any character, but subsequent underscores do not. This
  204. means that dunder symbols (like __init__) are sorted after symbols
  205. that start with an alphabetic character, but before those that
  206. start with only a single underscore.
  207. """
  208. if name.startswith("_"):
  209. name = "~" + name[1:]
  210. return name.lower()