├── .gitignore ├── Default.sublime-commands ├── Main.sublime-menu ├── PackageResourceViewer.sublime-settings ├── README.md ├── messages.json ├── messages ├── 1.txt └── 2.txt ├── package_resource_viewer.py └── package_resources.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "PackageResourceViewer: Open Resource", 4 | "command": "package_resource_viewer" 5 | }, 6 | { 7 | "caption": "PackageResourceViewer: View Package Resource", 8 | "command": "view_package_file" 9 | }, 10 | { 11 | "caption": "PackageResourceViewer: Edit Package Resource", 12 | "command": "edit_package_file" 13 | }, 14 | { 15 | "caption": "PackageResourceViewer: Extract Package", 16 | "command": "extract_package" 17 | }, 18 | { 19 | "caption": "PackageResourceViewer: Extract All Packages", 20 | "command": "extract_all_packages" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "PackageResourceViewer", 16 | "children": 17 | [ 18 | { 19 | "command": "open_file", 20 | "args": {"file": "${packages}/PackageResourceViewer/README.md"}, 21 | "caption": "README" 22 | }, 23 | { "caption": "-" }, 24 | { 25 | "command": "open_file", 26 | "args": {"file": "${packages}/PackageResourceViewer/PackageResourceViewer.sublime-settings"}, 27 | "caption": "Settings – Default" 28 | }, 29 | { 30 | "command": "open_file", 31 | "args": {"file": "${packages}/User/PackageResourceViewer.sublime-settings"}, 32 | "caption": "Settings – User" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | ] 41 | -------------------------------------------------------------------------------- /PackageResourceViewer.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // List of packages to ignore, Package Control is ignored by default 3 | // because extracting it cause errors 4 | "ignored_packages" : [ 5 | "Package Control", 6 | "0_packagesmanager_loader", 7 | "0_package_control_loader", 8 | "0_settings_loader" 9 | ], 10 | 11 | // A list of regular expressions patterns to ignore. Note that 12 | // these regular expressions are compared against the file or 13 | // directory name. 14 | "ignore_patterns": [ 15 | "\\.(git|hg|svn|DS_Store)" 16 | ], 17 | 18 | // Boolean setting to keep selection panel open after selecting a 19 | // resource to open. 20 | "open_multiple": true, 21 | 22 | // Boolean setting specifying if a single command should be listed 23 | // in the command palette for viewing and editing files or 24 | // if multiple commands should be used. 25 | "single_command": true, 26 | 27 | // True if, when moving up a directory, you would like the previous 28 | // selection to be automatically chosen. False otherwise. 29 | "return_to_previous": false 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PackageResourceViewer 2 | Plugin to assist viewing and editing package resources in Sublime Text 2 and Sublime Text 3. 3 | 4 | ## Installation 5 | Note with either method, you may need to restart Sublime Text for the plugin to load properly. 6 | 7 | ### Package Control 8 | Installation through [package control](http://wbond.net/sublime_packages/package_control) is recommended. It will handle updating your packages as they become available. To install, do the following. 9 | 10 | * In the Command Palette, enter `Package Control: Install Package` 11 | * Search for `PackageResourceViewer` 12 | 13 | ### Manual 14 | Clone or copy this repository into the packages directory. Ensure it is placed in a folder named `PackageResourceViewer`. By default, the Packages directories for Sublime Text 2 are located at: 15 | 16 | * OS X: ~/Library/Application Support/Sublime Text 2/Packages/ 17 | * Windows: %APPDATA%/Roaming/Sublime Text 2/Packages/ 18 | * Linux: ~/.config/sublime-text-2/Packages/ 19 | 20 | By default, the Packages directories for Sublime Text 3 are located at: 21 | 22 | * OS X: ~/Library/Application Support/Sublime Text 3/Packages/ 23 | * Windows: %APPDATA%/Roaming/Sublime Text 3/Packages/ 24 | * Linux: ~/.config/sublime-text-3/Packages/ 25 | 26 | ## Usage 27 | 28 | ### Commands 29 | `PackageResourceViewer: Open Resource`: 30 | 31 | Command to open the resource. If saved, the correct directory structure will be created in the `Packages` folder. This command will only be displayed if the `single_command` setting is true. 32 | 33 | `PackageResourceViewer: Extract Package`: 34 | 35 | Command to extract a package to the `Packages` directory. This command is only available in ST3. 36 | 37 | `PackageResourceViewer: View Package Resource`: 38 | 39 | Open package resource as read only. This command will only be displayed if the `single_command` setting is false. 40 | 41 | `PackageResourceViewer: Edit Package Resource`: 42 | 43 | Open package resources as an editable file. Upon execution, this plugin will create a directory (if necessary) as well as save the resource. This command will only be displayed if the `single_command` setting is false. 44 | 45 | ## Settings 46 | `ignore_patterns`: 47 | 48 | A list of regular expressions patterns to ignore. Note that these regular expressions are compared against the file or directory name. 49 | 50 | `open_multiple`: 51 | 52 | Boolean to keep selection panel open after selecting a resource to open. 53 | 54 | `single_command`: 55 | 56 | Boolean setting specifying if a single command should be listed in the command palette for viewing and editing files or if multiple commands should be used. 57 | 58 | `return_to_previous`: 59 | 60 | True if, when moving up a directory, you would like the previous selection to be automatically chosen. False otherwise. 61 | 62 | # License 63 | 64 | The MIT License (MIT) 65 | 66 | Copyright (c) 2013 Scott Kuroda 67 | 68 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 69 | associated documentation files (the "Software"), to deal in the Software without restriction, 70 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 71 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 72 | is furnished to do so, subject to the following conditions: 73 | 74 | The above copyright notice and this permission notice shall be included in all copies or 75 | substantial portions of the Software. 76 | 77 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 78 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 79 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 80 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 81 | sOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 82 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "2013.06.13.08.00.00": "messages/1.txt", 3 | "2013.11.02.08.00.00": "messages/2.txt" 4 | } 5 | -------------------------------------------------------------------------------- /messages/1.txt: -------------------------------------------------------------------------------- 1 | Consolidated to a single command "PackageResourceViewer: Open Resource". Upon saving, the correct directory structure will be created if necessary. 2 | -------------------------------------------------------------------------------- /messages/2.txt: -------------------------------------------------------------------------------- 1 | New Features: 2 | - New command to extract an entire package. This is only available in ST3. 3 | -------------------------------------------------------------------------------- /package_resource_viewer.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | import threading 5 | import errno 6 | 7 | VERSION = int(sublime.version()) 8 | IS_ST3 = VERSION >= 3006 9 | if IS_ST3: 10 | from PackageResourceViewer.package_resources import * 11 | else: 12 | from package_resources import * 13 | 14 | def no_packages_available_message(): 15 | sublime.message_dialog("PackageResourceViewer\n\nThere are no more packages available to extract.") 16 | 17 | def show_quick_panel(window, informations, on_done, selected_index=0): 18 | window.show_quick_panel( informations, on_done, sublime.KEEP_OPEN_ON_FOCUS_LOST, selected_index ) 19 | 20 | def format_packages_list(packages_list, maximum_length=500): 21 | length = 0 22 | contents = [] 23 | 24 | for index, name in enumerate( packages_list ): 25 | contents.append( "%s. %s" % ( index + 1, name ) ) 26 | length += len( contents[-1] ) 27 | 28 | if length > maximum_length: 29 | remaining = len( packages_list ) - index - 1 30 | if remaining > 0: contents.append( "and {} more package{}!".format(remaining, '' if remaining == 1 else 's' ) ) 31 | break 32 | 33 | return ", ".join( contents ) 34 | 35 | class PackageResourceViewerBase(sublime_plugin.WindowCommand): 36 | def run(self): 37 | self.previous_index = -1 38 | self.settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 39 | self.packages = get_packages_list(True, self.settings.get("ignore_patterns", [])) 40 | self.path = [] 41 | self.path_objs = [] 42 | self.path_index = [] 43 | self.show_quick_panel(self.packages, self.package_list_callback) 44 | 45 | def package_list_callback(self, index): 46 | if index == -1: 47 | return 48 | 49 | self.package = self.packages[index] 50 | self.package_files = {} 51 | self.quick_panel_files = self.create_quick_panel_file_list(self.package_files) 52 | self.add_entry_to_path_obj() 53 | self.path_index.append(index) 54 | self.show_quick_panel(self.quick_panel_files, self.package_file_callback) 55 | 56 | def add_entry_to_path_obj(self, entry=""): 57 | if len(self.path_objs) == 0 and entry == "": 58 | self.path_objs.append(self.package_files) 59 | else: 60 | self.path.append(entry) 61 | self.path_objs.append(self.path_objs[-1][entry]) 62 | 63 | def pop_entry_from_path_obj(self): 64 | if len(self.path_objs) > 0: 65 | if len(self.path) > 0: 66 | self.path.pop() 67 | self.path_objs.pop() 68 | 69 | def is_file(self, entry): 70 | return len(self.path_objs[-1][entry]) == 0 71 | 72 | 73 | def create_quick_panel_file_list(self, files_obj): 74 | quick_panel_files = [".."] 75 | if len(files_obj) == 0: 76 | ignore_patterns = self.settings.get("ignore_patterns", []) 77 | files_list = list_package_files(self.package, ignore_patterns) 78 | for entry in files_list: 79 | self.create_file_entry(entry, self.package_files) 80 | dirs, files = self.split_dirs_and_files(self.package_files) 81 | else: 82 | dirs, files = self.split_dirs_and_files(files_obj) 83 | 84 | quick_panel_files += dirs 85 | quick_panel_files += files 86 | return quick_panel_files 87 | 88 | def create_file_entry(self, file_path, obj): 89 | split_file = file_path.split("/", 1) 90 | if len(split_file) > 1: 91 | if split_file[0] not in obj: 92 | obj[split_file[0]] = {} 93 | self.create_file_entry(split_file[1], obj[split_file[0]]) 94 | else: 95 | obj[file_path] = {} 96 | 97 | def split_dirs_and_files(self, obj): 98 | files = [] 99 | dirs = [] 100 | 101 | for key in obj.keys(): 102 | entry = obj[key] 103 | if len(entry) == 0: 104 | files.append(key) 105 | else: 106 | dirs.append(key + "/") 107 | 108 | return sorted(dirs), sorted(files) 109 | 110 | def package_file_callback(self, index): 111 | if index == -1: 112 | return 113 | entry = self.quick_panel_files[index] 114 | 115 | if entry == "..": 116 | if len(self.path_index) != 0: 117 | index = self.path_index.pop() 118 | self.pop_entry_from_path_obj() 119 | if len(self.path_objs) == 0: 120 | self.show_quick_panel(self.packages, self.package_list_callback, index) 121 | else: 122 | self.quick_panel_files = self.create_quick_panel_file_list(self.path_objs[-1]) 123 | self.show_quick_panel(self.quick_panel_files, self.package_file_callback, index) 124 | else: 125 | entry = entry.replace("/", "") 126 | self.path_index.append(index) 127 | if self.is_file(entry): 128 | self.pre_open_file_setup(entry) 129 | view = self.open_file(self.package, "/".join(self.path + [entry])) 130 | sublime.set_timeout(lambda: self.setup_view(view), 10) 131 | if self.settings.get("open_multiple", False): 132 | self.show_quick_panel(self.quick_panel_files, self.package_file_callback) 133 | else: 134 | self.add_entry_to_path_obj(entry) 135 | self.quick_panel_files = self.create_quick_panel_file_list(self.path_objs[-1]) 136 | self.show_quick_panel(self.quick_panel_files, self.package_file_callback) 137 | 138 | def pre_open_file_setup(self, entry): 139 | pass 140 | 141 | def setup_view(self, view): 142 | pass 143 | 144 | def show_quick_panel(self, options, done_callback, index=None): 145 | if index is None or not IS_ST3 or not self.settings.get("return_to_previous", False): 146 | sublime.set_timeout(lambda: self.window.show_quick_panel(options, done_callback), 10) 147 | else: 148 | sublime.set_timeout(lambda: self.window.show_quick_panel(options, done_callback, selected_index=index), 10) 149 | 150 | def open_file(self, package, resource): 151 | resource_path = os.path.join(sublime.packages_path(), package, resource) 152 | view = self.find_open_file(resource_path) 153 | if view: 154 | self.window.focus_view(view) 155 | else: 156 | view = self.window.open_file(resource_path) 157 | if not os.path.exists(resource_path): 158 | content = get_resource(package, resource) 159 | view.settings().set("buffer_empty", True) 160 | sublime.set_timeout(lambda: self.insert_text(content, view), 10) 161 | if self.settings.get("single_command", True): 162 | view.settings().set("create_dir", True) 163 | view.set_scratch(True) 164 | 165 | return view 166 | 167 | def insert_text(self, content, view): 168 | if not view.is_loading(): 169 | view.run_command("insert_content", {"content": content}) 170 | view.settings().set("buffer_empty", False) 171 | else: 172 | sublime.set_timeout(lambda: self.insert_text(content, view), 10) 173 | 174 | def find_open_file(self, path): 175 | view = None 176 | if IS_ST3: 177 | view = self.window.find_open_file(path) 178 | return view 179 | 180 | 181 | class PackageResourceViewerCommand(PackageResourceViewerBase): 182 | def is_visible(self): 183 | settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 184 | return settings.get("single_command", True) 185 | 186 | 187 | class ViewPackageFileCommand(PackageResourceViewerBase): 188 | def setup_view(self, view): 189 | if not view.is_loading(): 190 | view.set_read_only(True) 191 | view.set_scratch(True) 192 | else: 193 | sublime.set_timeout(lambda: self.setup_view(view), 10) 194 | 195 | def is_visible(self): 196 | settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 197 | return not settings.get("single_command", True) 198 | 199 | class EditPackageFileCommand(PackageResourceViewerBase): 200 | def pre_open_file_setup(self, entry): 201 | package_path = os.path.join(sublime.packages_path(), self.package, os.sep.join(self.path)) 202 | self.create_folder(package_path) 203 | 204 | def create_folder(self, path): 205 | try: 206 | os.makedirs(path) 207 | except OSError as ex: 208 | if ex.errno != errno.EEXIST: 209 | raise 210 | 211 | def setup_view(self, view): 212 | if not view.is_loading(): 213 | view.set_read_only(False) 214 | view.run_command("save") 215 | else: 216 | sublime.set_timeout(lambda: self.setup_view(view), 15) 217 | 218 | def is_visible(self): 219 | settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 220 | return not settings.get("single_command", True) 221 | 222 | class PackageResourceViewerEvents(sublime_plugin.EventListener): 223 | def on_pre_save(self, view): 224 | if view.settings().get("create_dir", False): 225 | if not os.path.exists(view.file_name()): 226 | directory = os.path.dirname(view.file_name()) 227 | self.create_folder(directory) 228 | 229 | def on_modified(self, view): 230 | if view.settings().get("create_dir", False): 231 | if not view.settings().get("buffer_empty", False): 232 | if view.is_scratch(): 233 | view.set_scratch(False) 234 | 235 | 236 | def create_folder(self, path): 237 | try: 238 | os.makedirs(path) 239 | except OSError as ex: 240 | if ex.errno != errno.EEXIST: 241 | raise 242 | 243 | class ExtractPackageCommand(sublime_plugin.WindowCommand): 244 | def run(self): 245 | thread = ExtractPackagesThread(self.window) 246 | thread.start() 247 | 248 | def is_visible(self): 249 | return VERSION >= 3006 250 | 251 | class ExtractPackagesThread(threading.Thread): 252 | 253 | def __init__(self, window): 254 | threading.Thread.__init__(self) 255 | self.window = window 256 | 257 | self.exclusion_flag = " (excluded)" 258 | self.inclusion_flag = " (selected)" 259 | self.last_picked_item = 0 260 | self.last_excluded_items = 0 261 | 262 | def run(self): 263 | self.settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 264 | self.repositories_list = [""] 265 | self.repositories_list.extend( get_sublime_packages(True, self.settings.get("ignore_patterns", [])) ) 266 | 267 | if len( self.repositories_list ) < 2: 268 | no_packages_available_message() 269 | return 270 | 271 | self.update_start_item_name() 272 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 273 | 274 | def on_done(self, picked_index): 275 | 276 | if picked_index < 0: 277 | return 278 | 279 | if picked_index == 0: 280 | 281 | # No repositories selected, reshow the menu 282 | if self.get_total_items_selected() < 1: 283 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 284 | 285 | else: 286 | packages = [] 287 | 288 | for index in range( 1, self.last_picked_item + 1 ): 289 | package_name = self.repositories_list[index] 290 | 291 | if package_name.endswith( self.exclusion_flag ): 292 | continue 293 | 294 | if package_name.endswith( self.inclusion_flag ): 295 | package_name = package_name[:-len( self.inclusion_flag )] 296 | 297 | packages.append( package_name ) 298 | 299 | def extract(): 300 | packages_path = sublime.packages_path() 301 | 302 | for package_name in packages: 303 | full_path = os.path.join(packages_path, package_name, '.extracted-sublime-package') 304 | 305 | if not os.path.exists(full_path): 306 | extract_package(package_name) 307 | 308 | sublime.message_dialog("PackageResourceViewer\n\nSuccessfully extracted the packages:\n%s" % ( 309 | format_packages_list(packages, 1000) ) ) 310 | 311 | thread = threading.Thread( target=extract ) 312 | thread.start() 313 | 314 | else: 315 | 316 | if picked_index <= self.last_picked_item: 317 | picked_package = self.repositories_list[picked_index] 318 | 319 | if picked_package.endswith( self.inclusion_flag ): 320 | picked_package = picked_package[:-len( self.inclusion_flag )] 321 | 322 | if picked_package.endswith( self.exclusion_flag ): 323 | 324 | if picked_package.endswith( self.exclusion_flag ): 325 | picked_package = picked_package[:-len( self.exclusion_flag )] 326 | 327 | self.last_excluded_items -= 1 328 | self.repositories_list[picked_index] = picked_package + self.inclusion_flag 329 | 330 | else: 331 | self.last_excluded_items += 1 332 | self.repositories_list[picked_index] = picked_package + self.exclusion_flag 333 | 334 | else: 335 | self.last_picked_item += 1 336 | self.repositories_list[picked_index] = self.repositories_list[picked_index] + self.inclusion_flag 337 | 338 | self.update_start_item_name() 339 | self.repositories_list.insert( 1, self.repositories_list.pop( picked_index ) ) 340 | 341 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 342 | 343 | def update_start_item_name(self): 344 | items = self.get_total_items_selected() 345 | 346 | if items: 347 | self.repositories_list[0] = "Start Extraction (%s of %s items selected)" % ( items, len( self.repositories_list ) - 1 ) 348 | 349 | else: 350 | self.repositories_list[0] = "Select all the packages you would like to extract" 351 | 352 | def get_total_items_selected(self): 353 | return self.last_picked_item - self.last_excluded_items 354 | 355 | class ExtractAllPackagesCommand(sublime_plugin.WindowCommand): 356 | def run(self): 357 | thread = ExtractAllPackagesThread(self.window) 358 | thread.start() 359 | 360 | def is_visible(self): 361 | return VERSION >= 3006 362 | 363 | class ExtractAllPackagesThread(threading.Thread): 364 | 365 | def __init__(self, window): 366 | threading.Thread.__init__(self) 367 | self.window = window 368 | 369 | self.exclusion_flag = " (selected)" 370 | self.inclusion_flag = " (excluded)" 371 | self.last_picked_item = 0 372 | self.last_excluded_items = 0 373 | 374 | def run(self): 375 | self.settings = sublime.load_settings("PackageResourceViewer.sublime-settings") 376 | self.repositories_list = [""] 377 | self.packages = get_sublime_packages(True, self.settings.get("ignore_patterns", [])) 378 | self.repositories_list.extend( self.packages ) 379 | 380 | if len( self.repositories_list ) < 2: 381 | no_packages_available_message() 382 | return 383 | 384 | self.update_start_item_name() 385 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 386 | 387 | def on_done(self, picked_index): 388 | 389 | if picked_index < 0: 390 | return 391 | 392 | if picked_index == 0: 393 | 394 | # No repositories selected, reshow the menu 395 | if self.get_total_items_selected() == len( self.packages ): 396 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 397 | 398 | else: 399 | packages = set() 400 | extracted = [] 401 | 402 | for index in range( 1, self.last_picked_item + 1 ): 403 | package_name = self.repositories_list[index] 404 | 405 | if package_name.endswith( self.exclusion_flag ): 406 | continue 407 | 408 | if package_name.endswith( self.inclusion_flag ): 409 | package_name = package_name[:-len( self.inclusion_flag )] 410 | 411 | packages.add( package_name ) 412 | 413 | def extract(): 414 | packages_path = sublime.packages_path() 415 | 416 | for package_name in self.packages: 417 | 418 | if package_name not in packages: 419 | full_path = os.path.join(packages_path, package_name, '.extracted-sublime-package') 420 | 421 | if not os.path.exists(full_path): 422 | extracted.append(package_name) 423 | extract_package(package_name) 424 | 425 | sublime.message_dialog("PackageResourceViewer\n\nSuccessfully extracted the packages:\n%s" % ( 426 | format_packages_list(extracted, 1000) ) ) 427 | 428 | thread = threading.Thread( target=extract ) 429 | thread.start() 430 | 431 | else: 432 | 433 | if picked_index <= self.last_picked_item: 434 | picked_package = self.repositories_list[picked_index] 435 | 436 | if picked_package.endswith( self.inclusion_flag ): 437 | picked_package = picked_package[:-len( self.inclusion_flag )] 438 | 439 | if picked_package.endswith( self.exclusion_flag ): 440 | 441 | if picked_package.endswith( self.exclusion_flag ): 442 | picked_package = picked_package[:-len( self.exclusion_flag )] 443 | 444 | self.last_excluded_items -= 1 445 | self.repositories_list[picked_index] = picked_package + self.inclusion_flag 446 | 447 | else: 448 | self.last_excluded_items += 1 449 | self.repositories_list[picked_index] = picked_package + self.exclusion_flag 450 | 451 | else: 452 | self.last_picked_item += 1 453 | self.repositories_list[picked_index] = self.repositories_list[picked_index] + self.inclusion_flag 454 | 455 | self.update_start_item_name() 456 | self.repositories_list.insert( 1, self.repositories_list.pop( picked_index ) ) 457 | 458 | show_quick_panel( self.window, self.repositories_list, self.on_done ) 459 | 460 | def update_start_item_name(self): 461 | items = self.get_total_items_selected() 462 | total = len( self.packages ) 463 | self.repositories_list[0] = "Start Extraction (%s of %s items selected)" % ( total - items, total ) 464 | 465 | def get_total_items_selected(self): 466 | return self.last_picked_item - self.last_excluded_items 467 | 468 | class InsertContentCommand(sublime_plugin.TextCommand): 469 | def run(self, edit, content): 470 | self.view.insert(edit, 0, content) 471 | -------------------------------------------------------------------------------- /package_resources.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | Copyright (c) 2015 Scott Kuroda 4 | 5 | SHA: 32c74e6071f870217d5b407742bf41865f00a788 6 | """ 7 | import sublime 8 | import os 9 | import zipfile 10 | import tempfile 11 | import re 12 | import codecs 13 | 14 | __all__ = [ 15 | "get_resource", 16 | "get_binary_resource", 17 | "find_resource", 18 | "list_package_files", 19 | "get_package_and_resource_name", 20 | "get_packages_list", 21 | "extract_package", 22 | "get_sublime_packages" 23 | ] 24 | 25 | 26 | VERSION = int(sublime.version()) 27 | 28 | def get_resource(package_name, resource, encoding="utf-8"): 29 | return _get_resource(package_name, resource, encoding=encoding) 30 | 31 | def get_binary_resource(package_name, resource): 32 | return _get_resource(package_name, resource, return_binary=True) 33 | 34 | def _get_resource(package_name, resource, return_binary=False, encoding="utf-8"): 35 | packages_path = sublime.packages_path() 36 | content = None 37 | if VERSION > 3013: 38 | try: 39 | if return_binary: 40 | content = sublime.load_binary_resource("Packages/" + package_name + "/" + resource) 41 | else: 42 | content = sublime.load_resource("Packages/" + package_name + "/" + resource) 43 | except IOError: 44 | pass 45 | else: 46 | path = None 47 | if os.path.exists(os.path.join(packages_path, package_name, resource)): 48 | path = os.path.join(packages_path, package_name, resource) 49 | content = _get_directory_item_content(path, return_binary, encoding) 50 | 51 | if VERSION >= 3006: 52 | sublime_package = package_name + ".sublime-package" 53 | 54 | packages_path = sublime.installed_packages_path() 55 | if content is None: 56 | if os.path.exists(os.path.join(packages_path, sublime_package)): 57 | content = _get_zip_item_content(os.path.join(packages_path, sublime_package), resource, return_binary, encoding) 58 | 59 | packages_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 60 | 61 | if content is None: 62 | if os.path.exists(os.path.join(packages_path, sublime_package)): 63 | content = _get_zip_item_content(os.path.join(packages_path, sublime_package), resource, return_binary, encoding) 64 | 65 | return content.replace("\r\n", "\n").replace("\r", "\n") 66 | 67 | 68 | def find_resource(resource_pattern, package=None): 69 | file_set = set() 70 | if package == None: 71 | for package in get_packages_list(): 72 | file_set.update(find_resource(resource_pattern, package)) 73 | 74 | ret_list = list(file_set) 75 | else: 76 | file_set.update(_find_directory_resource(os.path.join(sublime.packages_path(), package), resource_pattern)) 77 | 78 | if VERSION >= 3006: 79 | zip_location = os.path.join(sublime.installed_packages_path(), package + ".sublime-package") 80 | file_set.update(_find_zip_resource(zip_location, resource_pattern)) 81 | zip_location = os.path.join(os.path.dirname(sublime.executable_path()), "Packages", package + ".sublime-package") 82 | file_set.update(_find_zip_resource(zip_location, resource_pattern)) 83 | ret_list = map(lambda e: package + "/" + e, file_set) 84 | 85 | return sorted(ret_list) 86 | 87 | 88 | def list_package_files(package, ignore_patterns=[]): 89 | """ 90 | List files in the specified package. 91 | """ 92 | package_path = os.path.join(sublime.packages_path(), package, "") 93 | path = None 94 | file_set = set() 95 | file_list = [] 96 | if os.path.exists(package_path): 97 | for root, directories, filenames in os.walk(package_path): 98 | temp = root.replace(package_path, "") 99 | for filename in filenames: 100 | file_list.append(os.path.join(temp, filename)) 101 | 102 | file_set.update(file_list) 103 | 104 | if VERSION >= 3006: 105 | sublime_package = package + ".sublime-package" 106 | packages_path = sublime.installed_packages_path() 107 | 108 | if os.path.exists(os.path.join(packages_path, sublime_package)): 109 | file_set.update(_list_files_in_zip(packages_path, sublime_package)) 110 | 111 | packages_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 112 | 113 | if os.path.exists(os.path.join(packages_path, sublime_package)): 114 | file_set.update(_list_files_in_zip(packages_path, sublime_package)) 115 | 116 | file_list = [] 117 | 118 | for filename in file_set: 119 | if not _ignore_file(filename, ignore_patterns): 120 | file_list.append(_normalize_to_sublime_path(filename)) 121 | 122 | return sorted(file_list) 123 | 124 | def _ignore_file(filename, ignore_patterns=[]): 125 | ignore = False 126 | directory, base = os.path.split(filename) 127 | for pattern in ignore_patterns: 128 | if re.match(pattern, base): 129 | return True 130 | 131 | if len(directory) > 0: 132 | ignore = _ignore_file(directory, ignore_patterns) 133 | 134 | return ignore 135 | 136 | 137 | def _normalize_to_sublime_path(path): 138 | path = os.path.normpath(path) 139 | path = re.sub(r"^([a-zA-Z]):", "/\\1", path) 140 | path = re.sub(r"\\", "/", path) 141 | return path 142 | 143 | def get_package_and_resource_name(path): 144 | """ 145 | This method will return the package name and resource name from a path. 146 | 147 | Arguments: 148 | path Path to parse for package and resource name. 149 | """ 150 | package = None 151 | resource = None 152 | path = _normalize_to_sublime_path(path) 153 | if os.path.isabs(path): 154 | packages_path = _normalize_to_sublime_path(sublime.packages_path()) 155 | if path.startswith(packages_path): 156 | package, resource = _search_for_package_and_resource(path, packages_path) 157 | 158 | if int(sublime.version()) >= 3006: 159 | packages_path = _normalize_to_sublime_path(sublime.installed_packages_path()) 160 | if path.startswith(packages_path): 161 | package, resource = _search_for_package_and_resource(path, packages_path) 162 | 163 | packages_path = _normalize_to_sublime_path(os.path.dirname(sublime.executable_path()) + os.sep + "Packages") 164 | if path.startswith(packages_path): 165 | package, resource = _search_for_package_and_resource(path, packages_path) 166 | else: 167 | path = re.sub(r"^Packages/", "", path) 168 | split = re.split(r"/", path, 1) 169 | package = split[0] 170 | package = package.replace(".sublime-package", "") 171 | resource = split[1] 172 | 173 | return (package, resource) 174 | 175 | def get_packages_list(ignore_packages=True, ignore_patterns=[]): 176 | """ 177 | Return a list of packages. 178 | """ 179 | package_set = set() 180 | package_set.update(_get_packages_from_directory(sublime.packages_path())) 181 | 182 | if int(sublime.version()) >= 3006: 183 | package_set.update(_get_packages_from_directory(sublime.installed_packages_path(), ".sublime-package")) 184 | 185 | executable_package_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 186 | package_set.update(_get_packages_from_directory(executable_package_path, ".sublime-package")) 187 | 188 | ignore_set = [] 189 | if ignore_packages: 190 | ignore_set = set(sublime.load_settings( 191 | "PackageResourceViewer.sublime-settings").get("ignored_packages", [])) 192 | 193 | for package in list(package_set): 194 | for pattern in ignore_patterns: 195 | if re.match(pattern, package): 196 | package_set.discard(package) 197 | break 198 | if package in ignore_set: 199 | package_set.discard(package) 200 | 201 | return sorted(list(package_set)) 202 | 203 | def get_sublime_packages(ignore_packages=True, ignore_patterns=[]): 204 | package_list = get_packages_list(ignore_packages, ignore_patterns) 205 | extracted_list = _get_packages_from_directory(sublime.packages_path()) 206 | return [x for x in package_list if x not in extracted_list] 207 | 208 | def _get_packages_from_directory(directory, file_ext=""): 209 | package_list = [] 210 | for package in os.listdir(directory): 211 | if not package.endswith(file_ext): 212 | continue 213 | else: 214 | package = package.replace(file_ext, "") 215 | 216 | package_list.append(package) 217 | return package_list 218 | 219 | def _search_for_package_and_resource(path, packages_path): 220 | """ 221 | Derive the package and resource from a path. 222 | """ 223 | relative_package_path = path.replace(packages_path + "/", "") 224 | 225 | package, resource = re.split(r"/", relative_package_path, 1) 226 | package = package.replace(".sublime-package", "") 227 | return (package, resource) 228 | 229 | 230 | def _list_files_in_zip(package_path, package): 231 | if not os.path.exists(os.path.join(package_path, package)): 232 | return [] 233 | 234 | ret_value = [] 235 | with zipfile.ZipFile(os.path.join(package_path, package)) as zip_file: 236 | ret_value = zip_file.namelist() 237 | return ret_value 238 | 239 | def _get_zip_item_content(path_to_zip, resource, return_binary, encoding): 240 | if not os.path.exists(path_to_zip): 241 | return None 242 | 243 | ret_value = None 244 | 245 | with zipfile.ZipFile(path_to_zip) as zip_file: 246 | namelist = zip_file.namelist() 247 | if resource in namelist: 248 | ret_value = zip_file.read(resource) 249 | if not return_binary: 250 | ret_value = ret_value.decode(encoding) 251 | 252 | return ret_value 253 | 254 | def _get_directory_item_content(filename, return_binary, encoding): 255 | content = None 256 | if os.path.exists(filename): 257 | if return_binary: 258 | mode = "rb" 259 | encoding = None 260 | else: 261 | mode = "r" 262 | with codecs.open(filename, mode, encoding=encoding) as file_obj: 263 | content = file_obj.read() 264 | return content 265 | 266 | def _find_zip_resource(path_to_zip, pattern): 267 | ret_list = [] 268 | if os.path.exists(path_to_zip): 269 | with zipfile.ZipFile(path_to_zip) as zip_file: 270 | namelist = zip_file.namelist() 271 | for name in namelist: 272 | if re.search(pattern, name): 273 | ret_list.append(name) 274 | 275 | return ret_list 276 | 277 | def _find_directory_resource(path, pattern): 278 | ret_list = [] 279 | if os.path.exists(path): 280 | path = os.path.join(path, "") 281 | for root, directories, filenames in os.walk(path): 282 | temp = root.replace(path, "") 283 | for filename in filenames: 284 | if re.search(pattern, os.path.join(temp, filename)): 285 | ret_list.append(os.path.join(temp, filename)) 286 | return ret_list 287 | 288 | def extract_zip_resource(path_to_zip, resource, extract_dir=None): 289 | if extract_dir is None: 290 | extract_dir = tempfile.mkdtemp() 291 | 292 | file_location = None 293 | if os.path.exists(path_to_zip): 294 | with zipfile.ZipFile(path_to_zip) as zip_file: 295 | file_location = zip_file.extract(resource, extract_dir) 296 | 297 | return file_location 298 | 299 | def extract_package(package): 300 | if VERSION >= 3006: 301 | package_location = os.path.join(sublime.installed_packages_path(), package + ".sublime-package") 302 | if not os.path.exists(package_location): 303 | package_location = os.path.join(os.path.dirname(sublime.executable_path()), "Packages", package + ".sublime-package") 304 | if not os.path.exists(package_location): 305 | package_location = None 306 | if package_location: 307 | with zipfile.ZipFile(package_location) as zip_file: 308 | extract_location = os.path.join(sublime.packages_path(), package) 309 | zip_file.extractall(extract_location) 310 | 311 | full_path = os.path.join(extract_location, '.extracted-sublime-package') 312 | if not os.path.exists(full_path): 313 | open(full_path, 'a').close() 314 | 315 | 316 | ####################### Force resource viewer to reload ######################## 317 | import sys 318 | 319 | if VERSION > 3000: 320 | from imp import reload 321 | if "PackageResourceViewer.package_resource_viewer" in sys.modules: 322 | reload(sys.modules["PackageResourceViewer.package_resource_viewer"]) 323 | else: 324 | if "package_resource_viewer" in sys.modules: 325 | reload(sys.modules["package_resource_viewer"]) 326 | --------------------------------------------------------------------------------