import logging import string import logging.config import threading import socketserver from functools import partial from pyls.python_ls import PythonLanguageServer, _StreamHandlerWrapper, PARENT_PROCESS_WATCH_INTERVAL, flatten, merge import pyls.lsp as lsp from pyls.workspace import Workspace import pyls.uris as uris import pyls._utils as _utils from .config import config log = logging.getLogger(__name__) def start_tcp_lang_server(bind_addr, port, check_parent_process, handler_class): if not issubclass(handler_class, PythonLanguageServer): raise ValueError('Handler class must be an instance of PythonLanguageServer') def shutdown_server(check_parent_process, *args): # pylint: disable=unused-argument if check_parent_process: log.debug('Shutting down server') # Shutdown call must be done on a thread, to prevent deadlocks stop_thread = threading.Thread(target=server.shutdown) stop_thread.start() # Construct a custom wrapper class around the user's handler_class wrapper_class = type( handler_class.__name__ + 'Handler', (_StreamHandlerWrapper,), {'DELEGATE_CLASS': partial(handler_class, check_parent_process=check_parent_process), 'SHUTDOWN_CALL': partial(shutdown_server, check_parent_process)} ) server = socketserver.TCPServer((bind_addr, port), wrapper_class, bind_and_activate=False) server.allow_reuse_address = True try: server.server_bind() server.server_activate() log.info('Serving %s on (%s, %s)', handler_class.__name__, bind_addr, port) server.serve_forever() finally: log.info('Shutting down') server.server_close() def start_io_lang_server(rfile, wfile, check_parent_process, handler_class): if not issubclass(handler_class, PythonLanguageServer): raise ValueError('Handler class must be an instance of PythonLanguageServer') log.info('Starting %s IO language server', handler_class.__name__) server = handler_class(rfile, wfile, check_parent_process) server.start() class ConfigurationLanguageServer(PythonLanguageServer): def __init__(self, rx, tx, check_parent_process): PythonLanguageServer.__init__(self, rx, tx, check_parent_process) def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, **_kwargs): log.debug('Language server initialized with %s %s %s %s', processId, rootUri, rootPath, initializationOptions) if rootUri is None: rootUri = uris.from_fs_path(rootPath) if rootPath is not None else '' self.workspaces.pop(self.root_uri, None) self.root_uri = rootUri self.config = config.ConfigurationConfig(rootUri, initializationOptions or {}, processId, _kwargs.get('capabilities', {})) self.workspace = Workspace(rootUri, self._endpoint, self.config) self.workspaces[rootUri] = self.workspace self._dispatchers = self._hook('pyls_dispatchers') self._hook('pyls_initialize') if self._check_parent_process and processId is not None and self.watching_thread is None: def watch_parent_process(pid): # exit when the given pid is not alive if not _utils.is_process_alive(pid): log.info("parent process %s is not alive, exiting!", pid) self.m_exit() else: threading.Timer(PARENT_PROCESS_WATCH_INTERVAL, watch_parent_process, args=[pid]).start() self.watching_thread = threading.Thread(target=watch_parent_process, args=(processId,)) self.watching_thread.daemon = True self.watching_thread.start() # Get our capabilities return {'capabilities': self.capabilities()} def completions(self, doc_uri, position): completions = self._hook('pyls_completions', doc_uri, position=position) return { 'isIncomplete': False, 'items': flatten(completions) } def capabilities(self): server_capabilities = { 'codeActionProvider': True, 'codeLensProvider': { 'resolveProvider': False, # We may need to make this configurable }, 'completionProvider': { 'resolveProvider': False, # We know everything ahead of time 'triggerCharacters': [l for l in string.ascii_letters] }, 'documentFormattingProvider': True, 'documentHighlightProvider': True, 'documentRangeFormattingProvider': True, 'documentSymbolProvider': True, 'definitionProvider': True, 'executeCommandProvider': { 'commands': flatten(self._hook('pyls_commands')) }, 'hoverProvider': True, 'referencesProvider': True, 'renameProvider': True, 'foldingRangeProvider': True, 'signatureHelpProvider': { 'triggerCharacters': [], }, 'hover': { "contentFormat": "markdown", }, 'textDocumentSync': { 'change': lsp.TextDocumentSyncKind.INCREMENTAL, 'save': { 'includeText': True, }, 'openClose': True, }, 'workspace': { 'workspaceFolders': { 'supported': True, 'changeNotifications': True } }, 'experimental': merge(self._hook('pyls_experimental_capabilities')) } log.info('Server capabilities: %s', server_capabilities) return server_capabilities