Klimi's new dotfiles with stow.
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.

270 lines
8.7 KiB

5 years ago
  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. if jedibackend:
  43. self.backend = jedibackend.JediBackend(self.project_root)
  44. else:
  45. self.backend = None
  46. return {
  47. 'jedi_available': (self.backend is not None)
  48. }
  49. def rpc_get_calltip(self, filename, source, offset):
  50. """Get the calltip for the function at the offset.
  51. """
  52. return self._call_backend("rpc_get_calltip", None, filename,
  53. get_source(source), offset)
  54. def rpc_get_oneline_docstring(self, filename, source, offset):
  55. """Get a oneline docstring for the symbol at the offset.
  56. """
  57. return self._call_backend("rpc_get_oneline_docstring", None, filename,
  58. get_source(source), offset)
  59. def rpc_get_completions(self, filename, source, offset):
  60. """Get a list of completion candidates for the symbol at offset.
  61. """
  62. results = self._call_backend("rpc_get_completions", [], filename,
  63. get_source(source), offset)
  64. # Uniquify by name
  65. results = list(dict((res['name'], res) for res in results)
  66. .values())
  67. results.sort(key=lambda cand: _pysymbol_key(cand["name"]))
  68. return results
  69. def rpc_get_completion_docstring(self, completion):
  70. """Return documentation for a previously returned completion.
  71. """
  72. return self._call_backend("rpc_get_completion_docstring",
  73. None, completion)
  74. def rpc_get_completion_location(self, completion):
  75. """Return the location for a previously returned completion.
  76. This returns a list of [file name, line number].
  77. """
  78. return self._call_backend("rpc_get_completion_location", None,
  79. completion)
  80. def rpc_get_definition(self, filename, source, offset):
  81. """Get the location of the definition for the symbol at the offset.
  82. """
  83. return self._call_backend("rpc_get_definition", None, filename,
  84. get_source(source), offset)
  85. def rpc_get_assignment(self, filename, source, offset):
  86. """Get the location of the assignment for the symbol at the offset.
  87. """
  88. return self._call_backend("rpc_get_assignment", None, filename,
  89. get_source(source), offset)
  90. def rpc_get_docstring(self, filename, source, offset):
  91. """Get the docstring for the symbol at the offset.
  92. """
  93. return self._call_backend("rpc_get_docstring", None, filename,
  94. get_source(source), offset)
  95. def rpc_get_pydoc_completions(self, name=None):
  96. """Return a list of possible strings to pass to pydoc.
  97. If name is given, the strings are under name. If not, top
  98. level modules are returned.
  99. """
  100. return get_pydoc_completions(name)
  101. def rpc_get_pydoc_documentation(self, symbol):
  102. """Get the Pydoc documentation for the given symbol.
  103. Uses pydoc and can return a string with backspace characters
  104. for bold highlighting.
  105. """
  106. try:
  107. docstring = pydoc.render_doc(str(symbol),
  108. "Elpy Pydoc Documentation for %s",
  109. False)
  110. except (ImportError, pydoc.ErrorDuringImport):
  111. return None
  112. else:
  113. if isinstance(docstring, bytes):
  114. docstring = docstring.decode("utf-8", "replace")
  115. return docstring
  116. def rpc_get_refactor_options(self, filename, start, end=None):
  117. """Return a list of possible refactoring options.
  118. This list will be filtered depending on whether it's
  119. applicable at the point START and possibly the region between
  120. START and END.
  121. """
  122. try:
  123. from elpy import refactor
  124. except:
  125. raise ImportError("Rope not installed, refactorings unavailable")
  126. ref = refactor.Refactor(self.project_root, filename)
  127. return ref.get_refactor_options(start, end)
  128. def rpc_refactor(self, filename, method, args):
  129. """Return a list of changes from the refactoring action.
  130. A change is a dictionary describing the change. See
  131. elpy.refactor.translate_changes for a description.
  132. """
  133. try:
  134. from elpy import refactor
  135. except:
  136. raise ImportError("Rope not installed, refactorings unavailable")
  137. if args is None:
  138. args = ()
  139. ref = refactor.Refactor(self.project_root, filename)
  140. return ref.get_changes(method, *args)
  141. def rpc_get_usages(self, filename, source, offset):
  142. """Get usages for the symbol at point.
  143. """
  144. source = get_source(source)
  145. if hasattr(self.backend, "rpc_get_usages"):
  146. return self.backend.rpc_get_usages(filename, source, offset)
  147. else:
  148. raise Fault("get_usages not implemented by current backend",
  149. code=400)
  150. def rpc_get_names(self, filename, source, offset):
  151. """Get all possible names
  152. """
  153. source = get_source(source)
  154. if hasattr(self.backend, "rpc_get_names"):
  155. return self.backend.rpc_get_names(filename, source, offset)
  156. else:
  157. raise Fault("get_names not implemented by current backend",
  158. code=400)
  159. def rpc_fix_code(self, source, directory):
  160. """Formats Python code to conform to the PEP 8 style guide.
  161. """
  162. source = get_source(source)
  163. return fix_code(source, directory)
  164. def rpc_fix_code_with_yapf(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_with_yapf(source, directory)
  169. def rpc_fix_code_with_black(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_black(source, directory)
  174. def get_source(fileobj):
  175. """Translate fileobj into file contents.
  176. fileobj is either a string or a dict. If it's a string, that's the
  177. file contents. If it's a string, then the filename key contains
  178. the name of the file whose contents we are to use.
  179. If the dict contains a true value for the key delete_after_use,
  180. the file should be deleted once read.
  181. """
  182. if not isinstance(fileobj, dict):
  183. return fileobj
  184. else:
  185. try:
  186. with io.open(fileobj["filename"], encoding="utf-8",
  187. errors="ignore") as f:
  188. return f.read()
  189. finally:
  190. if fileobj.get('delete_after_use'):
  191. try:
  192. os.remove(fileobj["filename"])
  193. except: # pragma: no cover
  194. pass
  195. def _pysymbol_key(name):
  196. """Return a sortable key index for name.
  197. Sorting is case-insensitive, with the first underscore counting as
  198. worse than any character, but subsequent underscores do not. This
  199. means that dunder symbols (like __init__) are sorted after symbols
  200. that start with an alphabetic character, but before those that
  201. start with only a single underscore.
  202. """
  203. if name.startswith("_"):
  204. name = "~" + name[1:]
  205. return name.lower()