Skip to content

Instantly share code, notes, and snippets.

@evandrocoan
Last active March 10, 2021 16:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evandrocoan/748a0a8bd09dd320dbcac8f7bb94bd9f to your computer and use it in GitHub Desktop.
Save evandrocoan/748a0a8bd09dd320dbcac8f7bb94bd9f to your computer and use it in GitHub Desktop.

Continuing the post from: https://forum.sublimetext.com/t/how-to-replace-dots-with-slashes-when-using-file-regex/45764

I managed to do it as described on the first post. I just need to patch exec.py to allow it to copy the new settings to the output view, and create these new settings on my project build:

My.sublime-project

{
    "folders":
    [
        { "path": ".", },
    ],
    "build_systems":
    [
        {
            "full_regex": "^\\d\\d:\\d\\d:\\d\\d:\\d\\d\\d.\\d\\d\\d\\d\\d\\d \\d.\\d\\de-\\d\\d - (?P<file>\\w+(?:\\.\\w+(?!\\.\\w+:))*)[^:]+:(?P<line>\\d+) - (?P<message>.*)$",
            "result_dir": "$project_path/source",
            "replaceby": [ [ "\\.", "\\\\" ], [ "(.*)", "\\1.py" ] ],

            "name": "Single Test",
            "target": "run_python_tests",
            "shell_cmd": "python unit_tests.py -v {test_class}.{test_func} -f",
            "working_dir": "$project_path/source",
        },
    ],
}

exec.py

--- a/exec.py
+++ b/exec.py
 class FixedToggleFindPanelCommand(sublime_plugin.WindowCommand):
@@ -438,6 +522,9 @@ class ExecCommand(sublime_plugin.WindowCommand, ProcessListener):
             spell_check=None,
             gutter=None,
             syntax="Packages/Text/Plain text.tmLanguage",
+            full_regex="",
+            result_dir="",
+            replaceby={},
             # Catches "path" and "shell"
             **kwargs):
         # print( 'ExecCommand arguments: ', locals())
@@ -475,6 +562,10 @@ class ExecCommand(sublime_plugin.WindowCommand, ProcessListener):
         if spell_check is None: spell_check = view_settings.get("build_view_spell_check", False)
         if gutter is None: gutter = view_settings.get("gutter", True)
 
+        self.output_view.settings().set("result_full_regex", full_regex)
+        self.output_view.settings().set("result_replaceby", replaceby)
+        self.output_view.settings().set("result_real_dir", result_dir)
+
         self.output_view.settings().set("result_file_regex", file_regex)
         self.output_view.settings().set("result_line_regex", line_regex)

full_regex.py

import os
import re
import time

import sublime
import sublime_plugin

g_last_click_time = time.time()
g_last_click_buttons = None

class HackListener(sublime_plugin.EventListener):

    def replaceby(self, string, replacements):
        # print('replacements', replacements)

        for items in replacements:
            regex = items[0]
            replacement = items[1]
            string = re.sub( regex, replacement, string )
        return string

    def on_text_command(self, view, command_name, args):
        # print('command_name', command_name, 'args', args)
        result_full_regex = view.settings().get('result_full_regex')

        # print('result_full_regex', result_full_regex)
        if result_full_regex and command_name == 'drag_select' and 'event' in args:
            global g_last_click_time
            global g_last_click_buttons

            clicks_buttons = args['event']
            new_click = time.time()

            if clicks_buttons == g_last_click_buttons:
                click_time = new_click - g_last_click_time

                if click_time < 0.6:
                    view_selections = view.sel()

                    if view_selections:
                        full_line = view.substr( view.full_line( view_selections[0] ) )

                        # print('Double clicking', click_time, 'full_line', full_line )
                        full_regex_object = re.compile( result_full_regex )
                        matchobject = full_regex_object.search( full_line )

                        if matchobject:
                            groupindex = full_regex_object.groupindex

                            # https://github.com/SublimeTextIssues/Core/issues/227
                            file_name = matchobject.group('file').strip( ' ' )   if 'file'   in groupindex else None
                            line      = matchobject.group('line').strip( ' ' )   if 'line'   in groupindex else "0"
                            column    = matchobject.group('column').strip( ' ' ) if 'column' in groupindex else "0"

                            window = view.window() or sublime.active_window()
                            extract_variables = window.extract_variables()

                            # github.com/SublimeTextIssues/Core/issues/1482
                            active_view = window.active_view()
                            group, view_index = window.get_view_index( active_view )
                            window.set_view_index( active_view, group, 0 )

                            # https://github.com/SublimeTextIssues/Core/issues/938
                            result_replaceby = view.settings().get('result_replaceby', {})
                            result_real_dir = view.settings().get('result_real_dir', os.path.abspath('.') )

                            if file_name:
                                real_dir_file = os.path.join( result_real_dir, file_name )
                                real_dir_file = sublime.expand_variables( real_dir_file, extract_variables )
                                real_dir_file = self.replaceby( real_dir_file, result_replaceby )

                                if os.path.exists( real_dir_file ):
                                    file_name = real_dir_file

                                else:
                                    base_dir_file = view.settings().get('result_base_dir')
                                    file_name = os.path.join( base_dir_file, file_name )
                                    file_name = sublime.expand_variables( file_name, extract_variables )
                                    file_name = self.replaceby( file_name, result_replaceby )

                                file_name = os.path.normpath( file_name )

                            else:
                                file_name = active_view.file_name()

                            fileview = window.open_file(
                                file_name + ":" + line + ":" + column,
                                sublime.ENCODED_POSITION | sublime.FORCE_GROUP
                            )

                            # https://github.com/SublimeTextIssues/Core/issues/2506
                            restore_view( fileview, window, lambda: None )
                            window.set_view_index( active_view, group, view_index )

                            # window.focus_group( group )
                            # window.focus_view( fileview )

            g_last_click_time = new_click
            g_last_click_buttons = clicks_buttons


TIME_AFTER_FOCUS_VIEW = 30
TIME_AFTER_RESTORE_VIEW = 15

def restore_view(view, window, next_target, withfocus=True):
    """ Taken from the https://github.com/evandrocoan/FixProjectSwitchRestartBug package
    Because on Linux, set_viewport was not restoring the scroll.
    """

    if view.is_loading():
        sublime.set_timeout( lambda: restore_view( view, window, next_target, withfocus=withfocus ), 200 )

    else:
        selections = view.sel()
        file_name = view.file_name()

        if len( selections ):
            first_selection = selections[0].begin()
            original_selections = list( selections )

            def super_refocus():
                view.run_command( "move", {"by": "lines", "forward": False} )
                view.run_command( "move", {"by": "lines", "forward": True} )

                def fix_selections():
                    selections.clear()

                    for selection in original_selections:
                        selections.add( selection )

                    sublime.set_timeout( next_target, TIME_AFTER_RESTORE_VIEW )

                sublime.set_timeout( fix_selections, TIME_AFTER_RESTORE_VIEW )

            if file_name and withfocus:

                def reforce_focus():
                    # https://github.com/SublimeTextIssues/Core/issues/1482
                    group, view_index = window.get_view_index( view )
                    window.set_view_index( view, group, 0 )

                    # https://github.com/SublimeTextIssues/Core/issues/538
                    row, column = view.rowcol( first_selection )
                    window.open_file( "%s:%d:%d" % ( file_name, row + 1, column + 1 ), sublime.ENCODED_POSITION )
                    window.set_view_index( view, group, view_index )

                    # print( 'Super reforce focus focusing...' )
                    sublime.set_timeout( super_refocus, TIME_AFTER_RESTORE_VIEW )

                view.show_at_center( first_selection )
                sublime.set_timeout( reforce_focus, TIME_AFTER_FOCUS_VIEW )

            else:
                view.show_at_center( first_selection )
                sublime.set_timeout( super_refocus, TIME_AFTER_RESTORE_VIEW )

As a new alternative for file_regex, I implemented the full_regex setting which is manipulated by the replaceby list of lists. For example a valid replaceby and file full_regex settings:

{
    "cmd": [ "python", "main.py" ],
    "full_regex": "^\\d\\d:\\d\\d:\\d\\d:\\d\\d\\d.\\d\\d\\d\\d\\d\\d \\d.\\d\\de-\\d\\d - (?P<file>\\w+(?:\\.\\w+(?!\\.\\w+:))*)[^:]+:(?P<line>\\d+) - (?P<message>.*)$",

    "result_dir": "$project_path/source",
    "replaceby": [ [ "\\.", "\\\\" ], [ "(.*)", "\\1.py" ] ],
    ...
}

With these settings, after matching the full_regex expression, it performs the following algorithm:

value = view.substr( view.full_line( view_selections[0] ) )
replacements = view.settings().get('result_replaceby', {})
for items in replacements:
    source = items[0]
    replacement = items[1]
    value = re.sub( source, replacement, value)
return value

As example match:

16:14:42:849.032402 1.22e-04 - grammar.grammar.<module>:58 - Importing grammar.grammar
  1. The capture groups are 1) grammar.grammar, 2) 58 and 3) Importing grammar.grammar
  2. The first replacement [ "\\.", "\\\\" ] from replaceby will do 1) grammar.grammar become grammar/grammar and the second replacement [ "(.*)", "\\1.py" ] will do grammar/grammar become grammar/grammar.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment