import os.path import sublime import sublime_plugin class EditSettingsCommand(sublime_plugin.ApplicationCommand): def run(self, base_file, user_file=None, default=None): """ :param base_file: A unicode string of the path to the base settings file. Typically this will be in the form: "${packages}/PackageName/Package.sublime-settings" :param user_file: An optional file path to the user's editable version of the settings file. If not provided, the filename from base_file will be appended to "${packages}/User/". :param default: An optional unicode string of the default contents if the user version of the settings file does not yet exist. Use "$0" to place the cursor. """ if base_file is None: raise ValueError('No base_file argument was passed to edit_settings') platform_name = { 'osx': 'OSX', 'windows': 'Windows', 'linux': 'Linux', }[sublime.platform()] variables = { 'packages': '${packages}', 'platform': platform_name, } base_file = sublime.expand_variables(base_file.replace('\\', '\\\\'), variables) if user_file is not None: user_file = sublime.expand_variables(user_file.replace('\\', '\\\\'), variables) base_path = base_file.replace('${packages}', 'res://Packages') is_resource = base_path.startswith('res://') file_name = os.path.basename(base_file) resource_exists = is_resource and base_path[6:] in sublime.find_resources(file_name) filesystem_exists = (not is_resource) and os.path.exists(base_path) if not resource_exists and not filesystem_exists: sublime.error_message('The settings file "' + base_path + '" could not be opened') return if user_file is None: user_package_path = os.path.join(sublime.packages_path(), 'User') user_file = os.path.join(user_package_path, file_name) # If the user path does not exist, and it is a supported # platform-variant file path, then try and non-platform-variant # file path. if not os.path.exists(os.path.join(user_package_path, file_name)): for suffix in {'.sublime-keymap', '.sublime-mousemap', '.sublime-menu'}: platform_suffix = ' (%s)%s' % (platform_name, suffix) if not file_name.endswith(platform_suffix): continue non_platform_file_name = file_name[:-len(platform_suffix)] + suffix non_platform_path = os.path.join(user_package_path, non_platform_file_name) if os.path.exists(non_platform_path): user_file = non_platform_path break # If the user has previously opened the settings from the menu, locate and bring to front the respective window windows = sublime.windows() previous_settings_index = -1 view_to_focus = None for window in windows: user_view = window.find_open_file(user_file) base_view = window.find_open_file(base_file.replace("${packages}", sublime.packages_path())) if (window.num_groups() == 2 and window.get_view_index(user_view) == (1, 0) and window.get_view_index(base_view) == (0, 0)): previous_settings_index = windows.index(window) view_to_focus = user_view break if previous_settings_index > -1: settings_window = windows[previous_settings_index] settings_window.bring_to_front() settings_window.focus_view(view_to_focus) else: sublime.run_command('new_window') new_window = sublime.active_window() new_window.run_command( 'set_layout', { 'cols': [0.0, 0.5, 1.0], 'rows': [0.0, 1.0], 'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] }) new_window.focus_group(0) new_window.run_command('open_file', {'file': base_file}) new_window.focus_group(1) new_window.run_command('open_file', {'file': user_file, 'contents': default}) new_window.set_tabs_visible(True) new_window.set_sidebar_visible(False, animate=False) base_view = new_window.active_view_in_group(0) user_view = new_window.active_view_in_group(1) base_settings = base_view.settings() base_settings.set('edit_settings_view', 'base') base_settings.set('edit_settings_other_view_id', user_view.id()) user_settings = user_view.settings() user_settings.set('edit_settings_view', 'user') user_settings.set('edit_settings_other_view_id', base_view.id()) if not os.path.exists(user_file): user_view.set_scratch(True) user_settings.set('edit_settings_default', default.replace('$0', '')) class EditSyntaxSettingsCommand(sublime_plugin.WindowCommand): """ Opens the syntax-specific settings file for the currently active view """ def run(self): view = self.window.active_view() syntax, _ = os.path.splitext(os.path.basename(view.settings().get('syntax'))) self.window.run_command( 'edit_settings', { 'base_file': '${packages}/Default/Preferences.sublime-settings', 'user_file': os.path.join(sublime.packages_path(), 'User', syntax + '.sublime-settings'), 'default': ( '// These settings override both User and Default settings ' 'for the %s syntax\n{\n\t$0\n}\n') % syntax }) def is_enabled(self): return self.window.active_view() is not None class EditSettingsListener(sublime_plugin.ViewEventListener): """ Closes the base and user settings files together, and then closes the window if no other views are opened """ @classmethod def is_applicable(cls, settings): return settings.get('edit_settings_view') is not None def on_modified(self): """ Prevents users from editing the base file """ view_settings = self.view.settings() # If any edits are made to the user version, we unmark it as a # scratch view so that the user is prompted to save any changes if view_settings.get('edit_settings_view') == 'user' and self.view.is_scratch(): file_region = sublime.Region(0, self.view.size()) if view_settings.get('edit_settings_default') != self.view.substr(file_region): self.view.set_scratch(False) def on_pre_close(self): """ Grabs the window id before the view is actually removed """ if self.view.window() is None: return self.view.settings().set('window_id', self.view.window().id()) def on_close(self): """ Closes the other settings view when one of the two is closed """ view_settings = self.view.settings() window_id = view_settings.get('window_id') window = None for win in sublime.windows(): if win.id() == window_id: window = win break if not window: return other_view_id = view_settings.get('edit_settings_other_view_id') views = window.views() views_left = len(views) # Don't close the window if the user opens another view in the window # or adds a folder, since they likely didn't realize this is a settings # window if views_left == 1 and len(window.folders()) < 1: # If the user closes the window containing the settings views, and # this is not delayed, the close_window command will be run on any # other window that is open. def close_window(): if window.id() == sublime.active_window().id() and window.project_data() is None: window.run_command("close_window") sublime.set_timeout(close_window, 50) else: for other in views: if other.id() == other_view_id: window.focus_view(other) # Prevent the handler from running on the other view other.settings().erase('edit_settings_view') # Run after timeout so the UI doesn't block with the view half closed sublime.set_timeout(lambda: window.run_command('close'), 50) class OpenFileSettingsCommand(sublime_plugin.WindowCommand): """ Old syntax-specific settings command - preserved for backwards compatibility """ def run(self): view = self.window.active_view() settings_name, _ = os.path.splitext(os.path.basename(view.settings().get('syntax'))) dir_name = os.path.join(sublime.packages_path(), 'User') self.window.open_file(os.path.join(dir_name, settings_name + '.sublime-settings')) def is_enabled(self): return self.window.active_view() is not None