├── .python-version ├── .gitignore ├── .no-sublime-package ├── reloader ├── __init__.py ├── dprint.py ├── stack_meter.py ├── resolver.py ├── importer.py └── reloader.py ├── .package_reloader.json ├── shot.png ├── utils ├── __init__.py ├── config.py ├── progress_bar.py └── package.py ├── package_reloader.sublime-settings ├── Default.sublime-commands ├── Main.sublime-menu ├── LICENSE.txt ├── README.md └── package_reloader.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /reloader/__init__.py: -------------------------------------------------------------------------------- 1 | from .reloader import reload_package, load_dummy 2 | -------------------------------------------------------------------------------- /.package_reloader.json: -------------------------------------------------------------------------------- 1 | { 2 | "siblings": ["AutomaticPackageReloader33"] 3 | } 4 | -------------------------------------------------------------------------------- /shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randy3k/AutomaticPackageReloader/HEAD/shot.png -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .progress_bar import ProgressBar 2 | from .config import read_config 3 | from .package import has_package, package_of, package_python_version 4 | 5 | 6 | __all__ = [ 7 | "ProgressBar", 8 | "read_config", 9 | "has_package", 10 | "package_of", 11 | "package_python_version" 12 | ] 13 | -------------------------------------------------------------------------------- /utils/config.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | def read_config(package, key, default=None): 5 | try: 6 | context = sublime.load_resource( 7 | "Packages/{}/.package_reloader.json".format(package)) 8 | value = sublime.decode_value(context).get(key, default) 9 | except Exception: 10 | value = default 11 | 12 | return value 13 | -------------------------------------------------------------------------------- /reloader/dprint.py: -------------------------------------------------------------------------------- 1 | def dprint(*args, fill=None, fill_width=60, **kwargs): 2 | if fill is not None: 3 | sep = str(kwargs.get('sep', ' ')) 4 | caption = sep.join(args) 5 | args = "{0:{fill}<{width}}".format(caption and caption + sep, 6 | fill=fill, width=fill_width), 7 | print("[Package Reloader]", *args, **kwargs) 8 | -------------------------------------------------------------------------------- /reloader/stack_meter.py: -------------------------------------------------------------------------------- 1 | class StackMeter: 2 | """Reentrant context manager counting the reentrancy depth.""" 3 | 4 | def __init__(self, depth=0): 5 | super().__init__() 6 | self.depth = depth 7 | 8 | def __enter__(self): 9 | depth = self.depth 10 | self.depth += 1 11 | return depth 12 | 13 | def __exit__(self, *exc_info): 14 | self.depth -= 1 15 | -------------------------------------------------------------------------------- /package_reloader.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // always open console when reloading a package 3 | "open_console": false, 4 | // only relevent when "open_console" is false 5 | "open_console_on_failure": true, 6 | // only relevent when "open_console" is true 7 | "close_console_on_success": false, 8 | // the default behaviour when saving a document, can be overrided per session 9 | "reload_on_save": false, 10 | // Enable verbose console messages. 11 | "verbose": true 12 | } 13 | -------------------------------------------------------------------------------- /utils/progress_bar.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | class ProgressBar: 5 | def __init__(self, label, width=10): 6 | self.label = label 7 | self.width = width 8 | 9 | def start(self): 10 | self.done = False 11 | self.update() 12 | 13 | def stop(self): 14 | sublime.status_message("") 15 | self.done = True 16 | 17 | def update(self, status=0): 18 | if self.done: 19 | return 20 | status = status % (2 * self.width) 21 | before = min(status, (2 * self.width) - status) 22 | after = self.width - before 23 | sublime.status_message("%s [%s=%s]" % (self.label, " " * before, " " * after)) 24 | sublime.set_timeout(lambda: self.update(status+1), 100) 25 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Automatic Package Reloader: Reload Current Package", 4 | "command": "package_reloader_reload" 5 | }, 6 | { 7 | "caption": "Automatic Package Reloader", 8 | "command": "package_reloader_reload", "args": {"package": ""} 9 | }, 10 | { 11 | "caption": "Automatic Package Reloader: Toggle Reload on Save", 12 | "command": "package_reloader_toggle_reload_on_save" 13 | }, 14 | { 15 | "caption": "Preferences: Automatic Package Reloader Settings", 16 | "command": "edit_settings", 17 | "args": 18 | { 19 | "base_file": "${packages}/AutomaticPackageReloader/package_reloader.sublime-settings", 20 | "default": "{\n\t$0\n}\n" 21 | } 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "preferences", 4 | "children": 5 | [ 6 | { 7 | "id": "package-settings", 8 | "children": 9 | [ 10 | { 11 | "caption": "Automatic Package Reloader", 12 | "children": 13 | [ 14 | { 15 | "caption": "Settings", 16 | "command": "edit_settings", 17 | "args": 18 | { 19 | "base_file": "${packages}/AutomaticPackageReloader/package_reloader.sublime-settings", 20 | "default": "{\n\t$0\n}\n" 21 | } 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /reloader/resolver.py: -------------------------------------------------------------------------------- 1 | try: 2 | from package_control.package_manager import PackageManager 3 | 4 | except ImportError: 5 | def resolve_parents(root_name): 6 | return {root_name} 7 | 8 | else: 9 | def resolve_parents(root_name): 10 | """Given the name of a dependency, return all dependencies and packages 11 | that require that dependency, directly or indirectly. 12 | """ 13 | manager = PackageManager() 14 | packages = manager.list_packages() 15 | 16 | recursive_dependencies = set() 17 | 18 | dependency_relationships = { 19 | name: manager.get_libraries(name) for name in packages 20 | } 21 | 22 | def rec(name): 23 | if name in recursive_dependencies: 24 | return 25 | 26 | recursive_dependencies.add(name) 27 | 28 | for pkg_name in packages: 29 | if name in dependency_relationships[pkg_name]: 30 | rec(pkg_name) 31 | 32 | rec(root_name) 33 | 34 | recursive_dependencies.remove(root_name) 35 | 36 | return recursive_dependencies 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Randy Lai 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /reloader/importer.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | from inspect import ismodule 3 | 4 | try: 5 | # python 3.8+ 6 | from importlib import reload as reload_module 7 | except: 8 | # python 3.3 9 | from imp import reload as reload_module 10 | 11 | from .stack_meter import StackMeter 12 | from .dprint import dprint 13 | 14 | 15 | class ReloadingImporter: 16 | def __init__(self, modules, verbose): 17 | self._modules_to_reload = set(modules) 18 | self._stack_meter = StackMeter() 19 | self._verbose = verbose 20 | 21 | def reload(self, module): 22 | try: 23 | self._modules_to_reload.remove(module) 24 | except KeyError: 25 | return module 26 | 27 | with self._stack_meter as depth: 28 | if self._verbose: 29 | dprint("reloading", ('| ' * depth) + '|--', module.__name__) 30 | 31 | return reload_module(module) 32 | 33 | def __import__(self, name, globals=None, locals=None, fromlist=(), level=0): 34 | module = self.reload(self._orig___import__(name, globals, locals, fromlist, level)) 35 | 36 | if fromlist: 37 | from_names = [ 38 | name 39 | for item in fromlist 40 | for name in ( 41 | getattr(module, '__all__', []) if item == '*' else (item,) 42 | ) 43 | ] 44 | 45 | for name in from_names: 46 | value = getattr(module, name, None) 47 | if ismodule(value): 48 | self.reload(value) 49 | 50 | return module 51 | 52 | def __enter__(self): 53 | self._orig___import__ = __import__ 54 | builtins.__import__ = self.__import__ 55 | return self 56 | 57 | def __exit__(self, exc_type, exc_value, traceback): 58 | builtins.__import__ = self._orig___import__ 59 | del self._orig___import__ 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automatic Package Reloader 2 | 3 | Sublime Text package developers may find themselves have to close and re-open 4 | ST multiple times when developing a package since ST doesn't reload all the 5 | submodules of a package when the files are edited. This tiny package helps in 6 | reloading the package without the need of reopening ST. The reloading order 7 | of the modules is in the exact same order as if they are loaded by Sublime 8 | Text. 9 | 10 | ### Installation 11 | 12 | [Package Control](https://packagecontrol.io/) please. 13 | 14 | ### Usage 15 | 16 | To reload the package in the current window, use `Automatic Package Reloader: Reload Current Project`. 17 | 18 | To activate reload on saving a `*py` file, use `Automatic Package Reloader: Toggle Reload On Save`. 19 | Package Reloader will guess the package name from the file path in order to reload the submodules 20 | and to reload the package. 21 | 22 | The console panel will be shown when reloading fails, this behavior can be modified by 23 | the settings. 24 | 25 | ![](shot.png) 26 | 27 | ### Add `Reload Current Package` build 28 | 29 | It is recommended to add the following in your `.sublime-project` file so that c+b would invoke the reload action. 30 | 31 | ``` 32 | "build_systems": 33 | [ 34 | { 35 | "name": "Reload Current Package", 36 | "target": "package_reloader_reload", 37 | } 38 | ] 39 | ``` 40 | 41 | ### Additional modules 42 | 43 | APR would try its best to guess the dependent modules of the package. Sometimes, it may fail to detect all the dependencies. In those cases, developers could specify extra modules to be reloaded in the `.package_reloader.json` file. 44 | 45 | ```js 46 | { 47 | "dependencies" : ["", ""] 48 | } 49 | ``` 50 | 51 | ### Credits 52 | This is derived from the [code](https://github.com/divmain/GitSavvy/blob/599ba3cdb539875568a96a53fafb033b01708a67/common/util/reload.py) of Eldar Abusalimov. 53 | -------------------------------------------------------------------------------- /utils/package.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | import os 4 | import sys 5 | import re 6 | from glob import glob 7 | 8 | 9 | if sys.platform.startswith("win"): 10 | def realpath(path): 11 | # path on Windows may not be properly cased 12 | # https://github.com/randy3k/AutomaticPackageReloader/issues/10 13 | r = glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', os.path.realpath(path))) 14 | return r and r[0] or path 15 | else: 16 | def realpath(path): 17 | return os.path.realpath(path) 18 | 19 | 20 | def package_of(path): 21 | spp = sublime.packages_path() 22 | spp_real = realpath(spp) 23 | for p in {path, realpath(path)}: 24 | for sp in {spp, spp_real}: 25 | if p.startswith(sp + os.sep): 26 | return p[len(sp):].split(os.sep)[1] 27 | 28 | if not sys.platform.startswith("win"): 29 | # we try to follow symlink if the real file is not located in spp 30 | for d in os.listdir(spp): 31 | subdir = os.path.join(spp, d) 32 | subdir_real = realpath(subdir) 33 | if not (os.path.islink(subdir) and os.path.isdir(subdir)): 34 | continue 35 | for sd in {subdir, subdir_real}: 36 | for p in {path, realpath(path)}: 37 | if p.startswith(sd + os.sep): 38 | return d 39 | 40 | return None 41 | 42 | 43 | def has_package(package): 44 | zipped_file = os.path.join( 45 | sublime.installed_packages_path(), "{}.sublime-package".format(package)) 46 | unzipped_folder = os.path.join(sublime.packages_path(), package) 47 | if not os.path.exists(zipped_file) and not os.path.exists(unzipped_folder): 48 | return False 49 | preferences = sublime.load_settings("Preferences.sublime-settings") 50 | if package in preferences.get("ignored_packages", []): 51 | return False 52 | return True 53 | 54 | 55 | def package_python_version(package): 56 | try: 57 | version = sublime.load_resource("Packages/{}/.python-version".format(package)).strip() 58 | except (FileNotFoundError, IOError): 59 | version = "3.3" 60 | return version 61 | 62 | 63 | def package_python_matched(package): 64 | ver = package_python_version(package) 65 | if sys.version_info >= (3, 8) and ver == "3.8": 66 | return True 67 | if sys.version_info >= (3, 3) and ver == "3.3": 68 | return True 69 | return False 70 | -------------------------------------------------------------------------------- /reloader/reloader.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | import os.path 5 | import posixpath 6 | import threading 7 | import sys 8 | import functools 9 | 10 | 11 | from .dprint import dprint 12 | from .importer import ReloadingImporter 13 | from .resolver import resolve_parents 14 | from ..utils.package import package_python_matched 15 | 16 | 17 | def get_package_modules(package_names): 18 | package_names = set(package_names) 19 | package_path_bases = [ 20 | p 21 | for pkg_name in package_names 22 | for p in ( 23 | os.path.join( 24 | sublime.installed_packages_path(), 25 | pkg_name + '.sublime-package' 26 | ), 27 | os.path.join(sublime.packages_path(), pkg_name), 28 | ) 29 | ] 30 | 31 | def module_paths(module): 32 | try: 33 | yield module.__file__ 34 | except AttributeError: 35 | pass 36 | 37 | try: 38 | yield from module.__path__ 39 | except AttributeError: 40 | pass 41 | 42 | @functools.lru_cache(1024) 43 | def _package_python_matched(package): 44 | return package_python_matched(package) 45 | 46 | for module in sys.modules.values(): 47 | try: 48 | base, path = next( 49 | (base, path) 50 | for path in module_paths(module) 51 | for base in package_path_bases 52 | if path and (path == base or path.startswith(base + os.sep)) 53 | ) 54 | except StopIteration: 55 | continue 56 | else: 57 | pkg_name = module.__name__.split(".")[0] 58 | is_plugin = (os.path.dirname(path) == base) and _package_python_matched(pkg_name) 59 | yield module.__name__, is_plugin 60 | 61 | # get all the top level plugins in case they were removed from sys.modules 62 | for path in sublime.find_resources("*.py"): 63 | for pkg_name in package_names: 64 | if not _package_python_matched(pkg_name): 65 | continue 66 | if posixpath.dirname(path) == 'Packages/'+pkg_name: 67 | yield pkg_name + '.' + posixpath.basename(posixpath.splitext(path)[0]), True 68 | 69 | 70 | def reload_package(package, dependencies=[], extra_modules=[], dummy=True, verbose=True): 71 | if verbose: 72 | dprint("begin", fill='=') 73 | 74 | if dummy: 75 | load_dummy(verbose) 76 | 77 | packages = [package] + dependencies 78 | parents = set() 79 | for package in packages: 80 | for parent in resolve_parents(package): 81 | parents.add(parent) 82 | parents = list(parents) 83 | 84 | modules = sorted( 85 | list(set(get_package_modules(packages + parents))), 86 | key=lambda x: x[0].split('.') 87 | ) 88 | 89 | plugins = [m for m, is_plugin in modules if is_plugin] 90 | # Tell Sublime to unload plugin_modules 91 | for plugin in plugins: 92 | if plugin in sys.modules: 93 | sublime_plugin.unload_module(sys.modules[plugin]) 94 | 95 | # these are modules marked to be reloaded, they are not necessarily reloaded 96 | modules_to_reload = [sys.modules[m] for m, is_plugin in modules if m in sys.modules] 97 | extra_modules_to_reload = [sys.modules[m] for m in extra_modules if m in sys.modules] 98 | 99 | with ReloadingImporter(modules_to_reload + extra_modules_to_reload, verbose) as importer: 100 | if plugins: 101 | # we only reload top level plugin_modules to mimic Sublime Text natural order 102 | for plugin in plugins: 103 | if plugin in sys.modules: 104 | module = sys.modules[plugin] 105 | importer.reload(module) 106 | 107 | for plugin in plugins: 108 | if plugin in sys.modules: 109 | module = sys.modules[plugin] 110 | sublime_plugin.load_module(module) 111 | else: 112 | # in case we missed something 113 | sublime_plugin.reload_plugin(plugin) 114 | else: 115 | # it is possibly a dependency but no packages use it 116 | for module in modules_to_reload: 117 | importer.reload(module) 118 | 119 | for module in extra_modules_to_reload: 120 | importer.reload(module) 121 | 122 | if dummy: 123 | load_dummy(verbose) 124 | 125 | if verbose: 126 | dprint("end", fill='-') 127 | 128 | 129 | def load_dummy(verbose): 130 | """ 131 | Hack to trigger automatic "reloading plugins". 132 | 133 | This is needed to ensure TextCommand's and WindowCommand's are ready. 134 | """ 135 | if verbose: 136 | dprint("installing dummy package") 137 | 138 | if sys.version_info >= (3, 8): 139 | # in ST 4, User package is always loaded in python 3.8 140 | dummy_name = "User._dummy" 141 | dummy_py = os.path.join(sublime.packages_path(), "User", "_dummy.py") 142 | else: 143 | # in ST 4, packages under Packages are always loaded in python 3.3 144 | dummy_name = "_dummy" 145 | dummy_py = os.path.join(sublime.packages_path(), "_dummy.py") 146 | 147 | with open(dummy_py, "w"): 148 | pass 149 | 150 | def remove_dummy(trial=0): 151 | if dummy_name in sys.modules: 152 | if verbose: 153 | dprint("removing dummy package") 154 | try: 155 | os.unlink(dummy_py) 156 | except FileNotFoundError: 157 | pass 158 | after_remove_dummy() 159 | elif trial < 300: 160 | threading.Timer(0.1, lambda: remove_dummy(trial + 1)).start() 161 | else: 162 | try: 163 | os.unlink(dummy_py) 164 | except FileNotFoundError: 165 | pass 166 | 167 | condition = threading.Condition() 168 | 169 | def after_remove_dummy(trial=0): 170 | if dummy_name not in sys.modules: 171 | condition.acquire() 172 | condition.notify() 173 | condition.release() 174 | elif trial < 300: 175 | threading.Timer(0.1, lambda: after_remove_dummy(trial + 1)).start() 176 | 177 | threading.Timer(0.1, remove_dummy).start() 178 | condition.acquire() 179 | condition.wait(30) # 30 seconds should be enough for all regular usages 180 | condition.release() 181 | -------------------------------------------------------------------------------- /package_reloader.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | import sublime 3 | import os 4 | import sys 5 | import shutil 6 | from textwrap import dedent 7 | from threading import Thread, Lock 8 | 9 | from .reloader import reload_package 10 | from .utils import ProgressBar, read_config, has_package, package_of, package_python_version 11 | 12 | 13 | try: 14 | reload_lock # Preserve same lock across reloads 15 | except NameError: 16 | reload_lock = Lock() 17 | 18 | 19 | class PackageReloaderListener(sublime_plugin.EventListener): 20 | def on_post_save(self, view): 21 | if view.is_scratch() or view.settings().get('is_widget'): 22 | return 23 | file_name = view.file_name() 24 | 25 | if file_name and file_name.endswith(".py") and package_of(file_name): 26 | package_reloader_settings = sublime.load_settings("package_reloader.sublime-settings") 27 | if package_reloader_settings.get("reload_on_save"): 28 | view.window().run_command("package_reloader_reload") 29 | 30 | 31 | class PackageReloaderToggleReloadOnSaveCommand(sublime_plugin.WindowCommand): 32 | def run(self): 33 | package_reloader_settings = sublime.load_settings("package_reloader.sublime-settings") 34 | reload_on_save = not package_reloader_settings.get("reload_on_save") 35 | package_reloader_settings.set("reload_on_save", reload_on_save) 36 | onoff = "on" if reload_on_save else "off" 37 | sublime.status_message("Package Reloader: Reload on Save is %s." % onoff) 38 | 39 | 40 | class PackageReloaderReloadCommand(sublime_plugin.WindowCommand): 41 | @property 42 | def current_package_name(self): 43 | view = self.window.active_view() 44 | if view and view.file_name(): 45 | file_path = view.file_name() 46 | package = package_of(file_path) 47 | if package and file_path.endswith(".py"): 48 | return package 49 | 50 | folders = self.window.folders() 51 | if folders and len(folders) > 0: 52 | package = package_of(folders[0]) 53 | if package: 54 | return package 55 | 56 | return None 57 | 58 | def prompt_package(self, callback): 59 | package = self.current_package_name 60 | if not package: 61 | package = "" 62 | view = sublime.active_window().show_input_panel( 63 | 'Package:', package, callback, None, None) 64 | view.run_command("select_all") 65 | 66 | def run(self, package=None, pkg_name=None, extra_pkgs=[], verbose=None): 67 | if package is None and pkg_name is not None: 68 | print("`pkg_name` is an deprecated option, use `package`.") 69 | package = pkg_name 70 | 71 | if package == "": 72 | self.prompt_package(lambda x: self.run(package=x)) 73 | return 74 | 75 | if package is None: 76 | package = self.current_package_name 77 | if package is None: 78 | print("Cannot detect package name.") 79 | return 80 | 81 | if not has_package(package): 82 | raise RuntimeError("{} is not installed.".format(package)) 83 | 84 | if sys.version_info >= (3, 8) and package_python_version(package) == "3.3": 85 | print("run reloader in python 3.3") 86 | self.window.run_command( 87 | "package_reloader33_reload", {"package": package, "extra_pkgs": extra_pkgs}) 88 | return 89 | 90 | Thread( 91 | name="AutomaticPackageReloader", 92 | target=self.run_async, 93 | args=(package, extra_pkgs, verbose) 94 | ).start() 95 | 96 | def run_async(self, package, extra_pkgs=[], verbose=None): 97 | if not reload_lock.acquire(blocking=False): 98 | print("Reloader is running.") 99 | return 100 | 101 | pr_settings = sublime.load_settings("package_reloader.sublime-settings") 102 | open_console = pr_settings.get("open_console") 103 | open_console_on_failure = pr_settings.get("open_console_on_failure") 104 | close_console_on_success = pr_settings.get("close_console_on_success") 105 | 106 | progress_bar = ProgressBar("Reloading %s" % package) 107 | progress_bar.start() 108 | 109 | console_opened = self.window.active_panel() == "console" 110 | if not console_opened and open_console: 111 | self.window.run_command("show_panel", {"panel": "console"}) 112 | dependencies = read_config(package, "dependencies", []) 113 | extra_modules = read_config(package, "extra_modules", []) 114 | if verbose is None: 115 | verbose = pr_settings.get('verbose') 116 | try: 117 | reload_package( 118 | package, dependencies=dependencies, extra_modules=extra_modules, verbose=verbose) 119 | if close_console_on_success: 120 | self.window.run_command("hide_panel", {"panel": "console"}) 121 | 122 | sublime.status_message("{} reloaded.".format(package)) 123 | except Exception: 124 | if open_console_on_failure: 125 | self.window.run_command("show_panel", {"panel": "console"}) 126 | sublime.status_message("Fail to reload {}.".format(package)) 127 | raise 128 | finally: 129 | progress_bar.stop() 130 | reload_lock.release() 131 | 132 | extra_pkgs = read_config(package, "siblings", []) + extra_pkgs 133 | if extra_pkgs: 134 | next_package = extra_pkgs.pop(0) 135 | if not has_package(next_package): 136 | print("{} is not installed.".format(next_package)) 137 | return 138 | sublime.set_timeout(lambda: sublime.active_window().run_command( 139 | "package_reloader_reload", 140 | {"package": next_package, "extra_pkgs": extra_pkgs, "verbose": verbose})) 141 | 142 | 143 | def plugin_loaded(): 144 | if sys.version_info >= (3, 8): 145 | APR33 = os.path.join(sublime.packages_path(), "AutomaticPackageReloader33") 146 | os.makedirs(APR33, exist_ok=True) 147 | # hide auto-generated package from Package Control's quick panels 148 | open(os.path.join(APR33, ".hidden-sublime-package"), 'a').close() 149 | 150 | try: 151 | # write only if not exists to avoid ST reloading the package twice at each startup 152 | with open(os.path.join(APR33, "package_reloader.py"), 'x') as f: 153 | f.write( 154 | dedent( 155 | """ 156 | from AutomaticPackageReloader import package_reloader as package_reloader38 # noqa 157 | 158 | 159 | class PackageReloader33ReloadCommand(package_reloader38.PackageReloaderReloadCommand): 160 | pass 161 | """ 162 | ).lstrip() 163 | ) 164 | except FileExistsError: 165 | pass 166 | 167 | try: 168 | with open(os.path.join(APR33, ".package_reloader.json"), 'w') as f: 169 | f.write("{\"dependencies\" : [\"AutomaticPackageReloader\"]}") 170 | except FileExistsError: 171 | pass 172 | 173 | 174 | def plugin_unloaded(): 175 | if sys.version_info >= (3, 8): 176 | APR33 = os.path.join(sublime.packages_path(), "AutomaticPackageReloader33") 177 | lock = reload_lock 178 | # do not remove AutomaticPackageReloader33 if it is being reloaded by APR 179 | if os.path.exists(APR33) and lock.acquire(blocking=False): 180 | try: 181 | shutil.rmtree(APR33) 182 | except Exception: 183 | pass 184 | finally: 185 | lock.release() 186 | --------------------------------------------------------------------------------