/ninja_ide/intellisensei/intellisense_registry.py

https://github.com/ninja-ide/ninja-ide · Python · 168 lines · 114 code · 26 blank · 28 comment · 6 complexity · 80f484c55ea510275ef2a5a8eeddc9a8 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of NINJA-IDE (http://ninja-ide.org).
  4. #
  5. # NINJA-IDE is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 3 of the License, or
  8. # any later version.
  9. #
  10. # NINJA-IDE is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with NINJA-IDE; If not, see <http://www.gnu.org/licenses/>.
  17. import time
  18. import abc
  19. from collections import namedtuple
  20. from collections import Callable
  21. from PyQt5.QtCore import QObject
  22. from PyQt5.QtCore import QThread
  23. from PyQt5.QtCore import pyqtSignal
  24. from ninja_ide.gui.ide import IDE
  25. from ninja_ide.tools.logger import NinjaLogger
  26. logger = NinjaLogger(__name__)
  27. CodeInfo = namedtuple("CodeInfo", "pservice source line col path")
  28. class IntelliSenseWorker(QThread):
  29. workerFailed = pyqtSignal(str)
  30. def __init__(self, parent):
  31. super().__init__(parent)
  32. self.__result = None
  33. def request(self, runnable, *args, **kwargs):
  34. self.__runnable = runnable
  35. self.__args = args
  36. self.__kwargs = kwargs
  37. self.start()
  38. @property
  39. def result(self):
  40. return self.__result
  41. def run(self):
  42. try:
  43. self.__result = self.__runnable(*self.__args, **self.__kwargs)
  44. except Exception as reason:
  45. self.workerFailed.emit(str(reason))
  46. class IntelliSense(QObject):
  47. resultAvailable = pyqtSignal("PyQt_PyObject")
  48. services = ("completions", "calltips")
  49. def __init__(self):
  50. QObject.__init__(self)
  51. self.__providers = {}
  52. self.__thread = None
  53. # Register service
  54. IDE.register_service("intellisense", self)
  55. def providers(self):
  56. return self.__providers.keys()
  57. def register_provider(self, provider):
  58. provider_object = provider()
  59. self.__providers[provider.language] = provider_object
  60. provider_object.load()
  61. def provider(self, language):
  62. return self.__providers.get(language)
  63. def _code_info(self, editor, kind):
  64. line, col = editor.cursor_position
  65. return CodeInfo(
  66. kind,
  67. editor.text,
  68. line + 1,
  69. col,
  70. editor.file_path
  71. )
  72. def process(self, kind, neditor):
  73. """Handle request from IntelliSense Assistant"""
  74. if self.__thread is not None:
  75. if self.__thread.isRunning():
  76. logger.debug("Waiting...")
  77. return
  78. code_info = self._code_info(neditor, kind)
  79. logger.debug("Running '{}'".format(code_info.pservice))
  80. provider = self.__providers.get(neditor.neditable.language())
  81. setattr(provider, "_code_info", code_info)
  82. provider_service = getattr(provider, kind, None)
  83. if isinstance(provider_service, Callable):
  84. self.__thread = IntelliSenseWorker(self)
  85. self.__thread.finished.connect(self._on_worker_finished)
  86. self.__thread.finished.connect(self.__thread.deleteLater)
  87. self.__thread.request(provider_service)
  88. def _on_worker_finished(self):
  89. if self.__thread is None:
  90. return
  91. result = self.__thread.result
  92. self.__thread = None
  93. self.resultAvailable.emit(result)
  94. def provider_services(self, language):
  95. """Returns the services available for a provider"""
  96. return [service for service in dir(self.provider(language))
  97. if service in IntelliSense.services]
  98. class Provider(abc.ABC):
  99. """
  100. Any IntelliSense Provider defined should inherit from this class
  101. The only mandatory method is Provider.completions.
  102. """
  103. language = "python"
  104. triggers = ["."] # FIXME: only works with one char
  105. @classmethod
  106. def register(cls):
  107. """Register this provider in the IntelliSense service"""
  108. intellisense = IDE.get_service("intellisense")
  109. intellisense.register_provider(cls)
  110. def load(self):
  111. """This will load things before it is used."""
  112. pass
  113. @abc.abstractmethod
  114. def completions(self):
  115. """
  116. Here we expect you to return a list of dicts.
  117. The dict must have the following structure:
  118. {
  119. "text": "completion_name",
  120. "type": "completion_type",
  121. "detail": "completion_detail"
  122. }
  123. The "text" key is the text that will be displayed in the list.
  124. The "type" key can be: function, class, instance.
  125. The "detail" key is a text that will be displayed next to the list
  126. as a tool tip.
  127. """
  128. def calltips(self):
  129. pass
  130. # Register service
  131. IntelliSense()