From 8e728aba479362c9f345f28424f758cd98813f66 Mon Sep 17 00:00:00 2001 From: bfelder Date: Mon, 11 Sep 2017 21:59:39 +0200 Subject: [PATCH] Minor cleanup and refactoring --- ColorSchemeEditor-ST2.py | 499 +++++++++++++++++++++------------------ 1 file changed, 270 insertions(+), 229 deletions(-) diff --git a/ColorSchemeEditor-ST2.py b/ColorSchemeEditor-ST2.py index 172eeeb..a3449cf 100644 --- a/ColorSchemeEditor-ST2.py +++ b/ColorSchemeEditor-ST2.py @@ -1,229 +1,270 @@ -import sublime, sublime_plugin, os.path - -# globals suck, but don't know how to pass data between the classes -_schemeEditor = None -_skipOne = 0 -_wasSingleLayout = None -_lastScope = None -_lastScopeIndex = 0 - -def find_matches ( scope, founds ): - global _schemeEditor - - ret = [] - maxscore = 0 - # find the scope in the xml that matches the most - - for found in founds: - foundstr = _schemeEditor.substr( found ) - pos = foundstr.find( '' ) + 8 - foundstr = foundstr[ pos : -9 ] - foundstrs = foundstr.split( ',' ) - fstrlen = 0 - for fstr in foundstrs: - fstrlen = len( fstr ) - fstr = fstr.lstrip( ' ' ) - padleft = fstrlen - len( fstr ) - fstr = fstr.rstrip( ' ' ) - score = sublime.score_selector( scope, fstr ) - # print( fstr, score ) - if score > 0: - a = found.a + pos + padleft - ret.append( [ score, sublime.Region( a, a + len( fstr ) ) ] ) - pos += fstrlen + 1 - - if len( ret ) == 0: - return None - else: - return ret - -def display_scope ( region ): - global _schemeEditor - # doest change the selection if previous selection was on the same line - sel = _schemeEditor.sel() - sel.clear() - sel.add( region ) - _schemeEditor.show_at_center( region ) - - -def update_view_status ( view ): - - global _lastScope, _lastScopeIndex - - found = None - _lastScope = [] - _lastScopeIndex = 0 - - # find the scope under the cursor - scope_name = view.scope_name( view.sel()[0].a ) - pretty_scope = scope_name.strip( ' ' ).replace( ' ', ' > ' ) - scopes = reversed( pretty_scope.split( ' > ' ) ) - - # convert to regex and look for the scope in the scheme editor - for scope in scopes: - if len( scope ) == 0: - continue - dots = scope.count( '.' ) - regex = 'scope\\s*([a-z\\.\\-]* ?, ?)*([a-z\\.\\- ]*' - regex += scope.replace( '.', '(\\.' ) - while dots > 0: - regex += ')?' - dots -= 1 - regex += ')( ?, ?[a-z\\.\\-]*)*' - - # print( regex ) - found = _schemeEditor.find_all( regex, 0 ) - found = find_matches( scope_name, found ) - # print( found ) - if found != None: - _lastScope += found - - # print( _lastScope ) - scopes = len( _lastScope ) - sublime.status_message( 'matches ' + str( scopes ) + ': ' + pretty_scope ) - if scopes == 0: - _lastScope = None - display_scope( sublime.Region( 0, 0 ) ) - else: - _lastScope.sort( key = lambda f: f[1].a ) - _lastScope.sort( key = lambda f: f[0], reverse = True ) - display_scope( _lastScope[0][1] ) - - -def kill_scheme_editor (): - global _schemeEditor, _skipOne, _wasSingleLayout, _lastScope, _lastScopeIndex - if int( sublime.version() ) > 3000 and _wasSingleLayout != None: - _wasSingleLayout.set_layout( { - 'cols': [0.0, 1.0], - 'rows': [0.0, 1.0], - 'cells': [[0, 0, 1, 1]] - } ) - _skipOne = 0 - _wasSingleLayout = None - _schemeEditor = None - _lastScope = None - _lastScopeIndex = 0 - - -# listeners to update our scheme editor -class NavigationListener ( sublime_plugin.EventListener ): - - def on_close ( self, view ): - global _schemeEditor - if _schemeEditor != None: - if _schemeEditor.id() == view.id(): - kill_scheme_editor() - - def on_selection_modified ( self, view ): - global _schemeEditor, _skipOne - if _schemeEditor != None: - if _schemeEditor.id() != view.id() and not view.settings().get( 'is_widget' ): - # for some reason this callback is called twice - for mouse down and mouse up - if _skipOne == 1: - _skipOne = 0 - else: - _skipOne = 1 - update_view_status( view ) - - -class EditColorSchemeNextScopeCommand ( sublime_plugin.TextCommand ): - def run ( self, edit ): - global _schemeEditor, _lastScope, _lastScopeIndex - - if _schemeEditor != None and _lastScope != None: - scopes = len( _lastScope ) - if scopes > 1: - _lastScopeIndex += 1 - if _lastScopeIndex == scopes: - _lastScopeIndex = 0 - display_scope( _lastScope[_lastScopeIndex][1] ) - sublime.status_message( 'Scope ' + str( _lastScopeIndex + 1 ) + ' of ' + str( scopes ) ) - - - -class EditColorSchemePrevScopeCommand ( sublime_plugin.TextCommand ): - def run ( self, edit ): - global _schemeEditor, _lastScope, _lastScopeIndex - - if _schemeEditor != None and _lastScope != None: - scopes = len( _lastScope ) - if scopes > 1: - if _lastScopeIndex == 0: - _lastScopeIndex = scopes - 1 - else: - _lastScopeIndex -= 1 - display_scope( _lastScope[_lastScopeIndex][1] ) - sublime.status_message( 'Scope ' + str( _lastScopeIndex + 1 ) + ' of ' + str( scopes ) ) - - -class EditCurrentColorSchemeCommand ( sublime_plugin.TextCommand ): - - def run ( self, edit ): - global _schemeEditor, _wasSingleLayout - - view = self.view - viewid = view.id() - window = view.window() - if _schemeEditor == None: - - # see if not trying to edit on the scheme file - path = os.path.abspath( sublime.packages_path() + '/../' + view.settings().get( 'color_scheme' ) ) - if path == view.file_name(): - sublime.status_message( 'Select different file from the scheme you want to edit' ) - _schemeEditor = None - return - - # see if we openeded a new view - views = len( window.views() ) - _schemeEditor = window.open_file( path ) - if _schemeEditor == None: - sublime.status_message( 'Could not open the scheme file' ) - return - if views == len( window.views() ): - views = 0 - else: - views = 1 - - # if we have only one splitter, open new one - groups = window.num_groups() - group = -1 - index = 0 - if groups == 1: - _wasSingleLayout = window - group = 1 - window.set_layout( { - 'cols': [0.0, 0.5, 1.0], - 'rows': [0.0, 1.0], - 'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] - } ) - elif views == 1: - activegrp = window.active_group() + 1 - if activegrp == groups: - group = activegrp - 2 - index = len( window.views_in_group( group ) ) - else: - group = activegrp - - if groups == 1 or views == 1: - # move the editor to another splitter - window.set_view_index( _schemeEditor, group, index ) - else: - #if the editor is in different splitter already focus it - window.focus_view( _schemeEditor ) - - window.focus_view( view ) - update_view_status( view ) - - else: - # if it was us who created the other splitter close it - if _wasSingleLayout != None: - _wasSingleLayout.set_layout( { - 'cols': [0.0, 1.0], - 'rows': [0.0, 1.0], - 'cells': [[0, 0, 1, 1]] - } ) - kill_scheme_editor() - - - - +import sublime +import sublime_plugin +import os.path + + +class Vars(object): + """ variables used throughout this plugin """ + schemeEditor = None + skipNext = False + wasSingleLayout = None + lastScope = None + lastScopeIndex = 0 + + +def is_theme_file(view): + if not view: + return False + elif not view.window(): + return False + active_view = view.window().active_view() + file_name = active_view.file_name() + if not file_name: + return False + file_ext = file_name.lower().split('.')[-1] + return "tmtheme" in file_ext + + +def find_matches(scope, founds): + """ find best matching scope in theme file """ + ret = [] + + for found in founds: + foundstr = Vars.schemeEditor.substr(found) + pos = foundstr.find('') + 8 + foundstr = foundstr[pos:-9] + foundstrs = foundstr.split(',') + fstrlen = 0 + for fstr in foundstrs: + fstrlen = len(fstr) + fstr = fstr.lstrip(' ') + padleft = fstrlen - len(fstr) + fstr = fstr.rstrip(' ') + score = sublime.score_selector(scope, fstr) + if score > 0: + a = found.a + pos + padleft + ret.append([score, sublime.Region(a, a + len(fstr))]) + pos += fstrlen + 1 + + if len(ret) != 0: + return ret + + +def display_scope(region): + """ display the currently viewed scope in the Scheme Editor """ + selection = Vars.schemeEditor.sel() + selection.clear() + selection.add(region) + Vars.schemeEditor.show_at_center(region) + # Without window focus, the above `show_at_center` will not visibly modify + # the current selection unless the new region is on a different line than + # the old. The work-around is to momentarily give focus to the schemeEditor + # view, which will force the re-draw. + # We have to capture the current View and Window to make sure the correct + # View receives focus after the re-draw. + current_window = sublime.active_window() + current_view = current_window.active_view() + scheme_window = Vars.schemeEditor.window() + scheme_window.focus_view(Vars.schemeEditor) + current_window.focus_view(current_view) + + +def update_view_status(view): + found = None + Vars.lastScope = [] + Vars.lastScopeIndex = 0 + + # find the scope under the cursor + scope_name = view.scope_name(view.sel()[0].a) + pretty_scope = scope_name.strip(' ').replace(' ', ' > ') + scopes = reversed(pretty_scope.split(' > ')) + + # convert to regex and look for the scope in the scheme editor + for scope in scopes: + if len(scope) == 0: + continue + dots = scope.count('.') + regex = r'scope\s*([a-z\.\-\+]* ?, ?)*([a-z\.\-\+ ]*' + regex += scope.replace('.', r'(\.') + while dots > 0: + regex += ')?' + dots -= 1 + regex += r')( ?, ?[a-z\.\-\+]*)*' + found = Vars.schemeEditor.find_all(regex, 0) + found = find_matches(scope, found) + if found: + Vars.lastScope += found + + scopes = len(Vars.lastScope) + sublime.status_message('matches ' + str(scopes) + ': ' + pretty_scope) + if scopes == 0: + Vars.lastScope = None + display_scope(sublime.Region(0, 0)) + else: + Vars.lastScope.sort(key=lambda f: f[1].a) + Vars.lastScope.sort(key=lambda f: f[0], reverse=True) + display_scope(Vars.lastScope[0][1]) + + +def kill_scheme_editor(): + if int(sublime.version()) > 3000 and Vars.wasSingleLayout: + Vars.wasSingleLayout.set_layout({ + 'cols': [0.0, 1.0], + 'rows': [0.0, 1.0], + 'cells': [[0, 0, 1, 1]] + }) + Vars.skipNext = False + Vars.wasSingleLayout = None + Vars.schemeEditor = None + Vars.lastScope = None + Vars.lastScopeIndex = 0 + + +def adjacent_scope(func): + if Vars.schemeEditor and Vars.lastScope: + scopes = len(Vars.lastScope) + if scopes > 1: + func(scopes) + last_scope = Vars.lastScope[Vars.lastScopeIndex][1] + display_scope(last_scope) + msg = "Scope {} of {}".format(Vars.lastScopeIndex + 1, scopes) + sublime.status_message(msg) + + +class NavigationListener(sublime_plugin.EventListener): + """ listeners to update our scheme editor """ + + def on_close(self, view): + if Vars.schemeEditor: + if Vars.schemeEditor.id() == view.id(): + kill_scheme_editor() + + def on_text_command(self, view, command_name, args): + """skipping unwanted updates""" + if Vars.schemeEditor: + if command_name == ("drag_select" or "show_scope_name"): + Vars.skipNext = True + + def on_selection_modified(self, view): + if not view: + return + elif is_theme_file(view): + # prevent jumping around inside the theme file itself + return + + if Vars.schemeEditor: + is_widget = view.settings().get('is_widget') + if Vars.schemeEditor.id() != view.id() and not is_widget: + if Vars.skipNext: + Vars.skipNext = False + else: + update_view_status(view) + + +class SaveListener(sublime_plugin.EventListener): + """Reloads color scheme after saving theme file.""" + + def on_post_save_async(self, view): + if is_theme_file(view): + [ + v.settings().erase("color_scheme") + for views in [w.views() for w in sublime.windows()] + for v in views + ] + + +class EditColorSchemeNextScopeCommand(sublime_plugin.TextCommand): + def next_scope(self, scopes): + Vars.lastScopeIndex += 1 + if Vars.lastScopeIndex == scopes: + Vars.lastScopeIndex = 0 + + def run(self, edit): + # Giving the schemeEditor focus (as part of the `adjacent_scope` -> + # `display_scope` code path) will trigger an `on_selection_modified` + # event that interrupts cycling through scopes. Skip that event. + Vars.skipNext = True + adjacent_scope(self.next_scope) + + +class EditColorSchemePrevScopeCommand(sublime_plugin.TextCommand): + def prev_scope(self, scopes): + if Vars.lastScopeIndex == 0: + Vars.lastScopeIndex = scopes - 1 + else: + Vars.lastScopeIndex -= 1 + + def run(self, edit): + # Giving the schemeEditor focus (as part of the `adjacent_scope` -> + # `display_scope` code path) will trigger an `on_selection_modified` + # event that interrupts cycling through scopes. Skip that event. + Vars.skipNext = True + adjacent_scope(self.prev_scope) + + +class EditCurrentColorSchemeCommand(sublime_plugin.TextCommand): + def run(self, edit): + view = self.view + window = view.window() + + # If the schemeEditor is currently closed, initialize this plugin. + if not Vars.schemeEditor: + color_scheme = view.settings().get('color_scheme') + path_str = sublime.packages_path() + '/../' + color_scheme + path = os.path.abspath(path_str) + + if not view: + sublime.status_message( + 'Select a file for the scheme you want to edit') + return + elif is_theme_file(view): + sublime.status_message( + 'Select different file for the scheme you want to edit') + Vars.schemeEditor = None + return + + # see if we opened a new view + views = len(window.views()) + Vars.schemeEditor = window.open_file(path) + if not Vars.schemeEditor: + sublime.status_message('Could not open the scheme file') + return + if views == len(window.views()): + views = 0 + else: + views = 1 + + # if we got only one group, open new one + groups = window.num_groups() + group = -1 + index = 0 + if groups == 1: + Vars.wasSingleLayout = window + group = 1 + window.set_layout({ + 'cols': [0.0, 0.5, 1.0], + 'rows': [0.0, 1.0], + 'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] + }) + elif views == 1: + activegrp = window.active_group() + 1 + if activegrp == groups: + group = activegrp - 2 + index = len(window.views_in_group(group)) + else: + group = activegrp + + if (groups or views) == 1: + # move the editor to another group + window.set_view_index(Vars.schemeEditor, group, index) + else: + # if the editor is in different group already focus it + window.focus_view(Vars.schemeEditor) + + window.focus_view(view) + update_view_status(view) + + # If there is a schemeEditor open, simply close it and allow the + # `NavigationListener`s `on_close` logic to tear down the plugin. + else: + Vars.schemeEditor.close()