class LspIntelephensePlugin(NpmClientHandler): package_name = __package__.split(".")[0] server_directory = "language-server" server_binary_path = os.path.join(server_directory, "node_modules", "intelephense", "lib", "intelephense.js") def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._activity_indicator = None # type: Optional[ActivityIndicator] @classmethod def get_additional_variables(cls) -> Optional[Dict[str, str]]: variables = super().get_additional_variables() or {} variables.update({ "cache_path": sublime.cache_path(), "home": os.path.expanduser("~"), "package_storage": cls.package_storage(), "temp_dir": tempfile.gettempdir(), }) return variables @classmethod def minimum_node_version(cls) -> Tuple[int, int, int]: return (10, 0, 0) # ---------------- # # message handlers # # ---------------- # @notification_handler("indexingStarted") def handle_indexing_started(self, params: None) -> None: self._start_indicator("{}: Indexing...".format(self.package_name)) @notification_handler("indexingEnded") def handle_indexing_ended(self, params: None) -> None: self._stop_indicator() # -------------- # # custom methods # # -------------- # def _start_indicator(self, msg: str = "") -> None: if self._activity_indicator: self._activity_indicator.label = msg else: view = sublime.active_window().active_view() if view: self._activity_indicator = ActivityIndicator(view, msg) self._activity_indicator.start() def _stop_indicator(self) -> None: if self._activity_indicator: self._activity_indicator.stop() self._activity_indicator = None
def test_start_stop(self): target = Mock() indicator = ActivityIndicator(target) indicator.start() target.set.assert_called_once_with('[= ]') target.set.reset_mock() indicator.stop() target.clear.assert_called_once_with() indicator.start() target.set.assert_called_once_with('[= ]') indicator.stop()
class ServerNpmResource(object): """Global object providing paths to server resources. Also handles the installing and updating of the server in cache. setup() needs to be called during (or after) plugin_loaded() for paths to be valid. """ def __init__(self, package_name, server_directory, server_binary_path): self._initialized = False self._is_ready = False self._package_name = package_name self._server_directory = server_directory self._binary_path = server_binary_path self._package_cache_path = None self._activity_indicator = None @property def ready(self): return self._is_ready @property def binary_path(self): return os.path.join(self._package_cache_path, self._binary_path) def setup(self): if self._initialized: return self._initialized = True self._package_cache_path = os.path.join(sublime.cache_path(), self._package_name) self._copy_to_cache() def cleanup(self): if os.path.isdir(self._package_cache_path): shutil.rmtree(self._package_cache_path) def _copy_to_cache(self): src_path = 'Packages/{}/{}/'.format(self._package_name, self._server_directory) dst_path = 'Cache/{}/{}/'.format(self._package_name, self._server_directory) cache_server_path = os.path.join(self._package_cache_path, self._server_directory) if os.path.isdir(cache_server_path): # Server already in cache. Check if version has changed and if so, delete existing copy in cache. try: src_package_json = ResourcePath(src_path, 'package.json').read_text() dst_package_json = ResourcePath(dst_path, 'package.json').read_text() if src_package_json != dst_package_json: shutil.rmtree(cache_server_path) except FileNotFoundError: shutil.rmtree(cache_server_path) if not os.path.isdir(cache_server_path): # create cache folder ResourcePath(src_path).copytree(cache_server_path, exist_ok=True) self._install_dependencies(cache_server_path) def _install_dependencies(self, cache_server_path): dependencies_installed = os.path.isdir( os.path.join(cache_server_path, 'node_modules')) if dependencies_installed: self._is_ready = True return # this will be called only when the plugin gets: # - installed for the first time, # - or when updated on package control install_message = '{}: Installing server'.format(self._package_name) log_and_show_message(install_message, show_in_status=False) active_window = sublime.active_window() if active_window: self._activity_indicator = ActivityIndicator( active_window.active_view(), install_message) self._activity_indicator.start() run_command(self._on_install_success, self._on_install_error, [ "npm", "install", "--verbose", "--production", "--prefix", cache_server_path, cache_server_path ]) def _on_install_success(self): self._is_ready = True self._stop_indicator() log_and_show_message( '{}: Server installed. Supported files might need to be re-opened.' .format(self._package_name)) def _on_install_error(self, error): self._stop_indicator() log_and_show_message( '{}: Error while installing the server.'.format( self._package_name), error) def _stop_indicator(self): if self._activity_indicator: self._activity_indicator.stop() self._activity_indicator = None
class ClangFormatCommand(sublime_plugin.TextCommand): def __init__(self, view): super().__init__(view) self._indicator = None def run(self, edit, only_selection=True): settings = sublime.load_settings(settings_filename()) binary_path = settings.get(PREF_CLANG_FORMAT_PATH) if not binary_path: binary_path = which(binary_name()) if not binary_path or not is_exe(binary_path): sublime.message_dialog(MISSING_BINARY_MESSAGE % binary_name()) return args = [binary_path, '-fallback-style', style] if self.view.file_name(): args.extend(['-assume-filename', self.view.file_name()]) else: print( 'Checking style without knowing file type. Results might be innacurate!' ) if only_selection: for region in self.view.sel(): region_offset = min(region.a, region.b) region_length = abs(region.b - region.a) args.extend([ '-offset', str(region_offset), '-length', str(region_length) ]) buffer_text = self.view.substr(sublime.Region(0, self.view.size())) encoding = self.view.encoding() encoding = encoding if encoding != 'Undefined' else 'utf-8' stdin = buffer_text.encode(encoding) viewport_pos = self.view.viewport_position() # Show progress indicator if formatting takes longer than 1s. self._indicator = ActivityIndicator(self.view, 'ClangFormat: Formatting...') sublime.set_timeout(self.start_indicator, 1000) start_thread( lambda output: self.on_formatting_success( viewport_pos, output, encoding), self.on_formatting_error, args, stdin) def on_formatting_success(self, viewport_pos, output, encoding): self.stop_indicator() self.view.run_command('clang_format_apply', { 'output': output.decode(encoding), 'viewport_pos': viewport_pos, }) def on_formatting_error(self, error): self.stop_indicator() self.view.window().status_message('ClangFormat: Formatting error: %s' % error) def start_indicator(self): if self._indicator: self._indicator.start() def stop_indicator(self): if self._indicator: self._indicator.stop() self._indicator = None
class LspPyrightPlugin(NpmClientHandler): package_name = __package__.split(".")[0] server_directory = "language-server" server_binary_path = os.path.join(server_directory, "node_modules", "pyright", "langserver.index.js") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._activity_indicator = None # type: Optional[ActivityIndicator] @classmethod def install_in_cache(cls) -> bool: return False @classmethod def minimum_node_version(cls) -> Tuple[int, int, int]: return (12, 0, 0) def on_settings_changed(self, settings: DottedDict) -> None: super().on_settings_changed(settings) if self.get_plugin_setting("dev_environment") == "sublime_text": # add package dependencies into "python.analysis.extraPaths" extraPaths = settings.get("python.analysis.extraPaths") or [ ] # type: List[str] extraPaths.extend(self.find_package_dependency_dirs()) settings.set("python.analysis.extraPaths", extraPaths) # ---------------- # # message handlers # # ---------------- # @notification_handler("pyright/beginProgress") def handle_begin_progress(self, params) -> None: # we don't know why we begin this progress # the reason will be updated in "pyright/reportProgress" self._start_indicator("{}: Working...".format(self.package_name)) @notification_handler("pyright/endProgress") def handle_end_progress(self, params) -> None: self._stop_indicator() @notification_handler("pyright/reportProgress") def handle_report_progress(self, params: List[str]) -> None: self._start_indicator("{}: {}".format(self.package_name, "; ".join(params))) # -------------- # # custom methods # # -------------- # @classmethod def get_plugin_setting(cls, key: str, default: Optional[Any] = None) -> Any: return sublime.load_settings(cls.package_name + ".sublime-settings").get(key, default) @staticmethod def find_package_dependency_dirs() -> List[str]: dep_dirs = sys.path.copy() # move the "Packages/" to the last # @see https://github.com/sublimelsp/LSP-pyright/pull/26#discussion_r520747708 packages_path = sublime.packages_path() dep_dirs.remove(packages_path) dep_dirs.append(packages_path) return [path for path in dep_dirs if os.path.isdir(path)] def _start_indicator(self, msg: str = "") -> None: if self._activity_indicator: self._activity_indicator.label = msg # type: ignore self._activity_indicator.update() else: view = sublime.active_window().active_view() if view: self._activity_indicator = ActivityIndicator( view, msg) # type: ignore self._activity_indicator.start() def _stop_indicator(self) -> None: if self._activity_indicator: self._activity_indicator.stop() self._activity_indicator = None