Sublime Forum

How to replace dots (.) with slashes (/) when using `file_regex`?

#1

I am implementing a goto file/function with file_regex on my Python Projects build systems. For example, when I am using the following build system:

{
	"folders":
	[
		{
			"path": ".",
		}
	],
    "build_systems":
    [
        {
            "name": "Run Project",
            "file_regex": "^\\d\\d:\\d\\d:\\d\\d:\\d\\d\\d.\\d\\d\\d\\d\\d\\d \\d.\\d\\de-\\d\\d - (\\w+(?:\\.\\w+(?!\\.\\w+:))*)[^:]+:(\\d+) - (.*)$",
            "shell": true,
            "env": {"PYTHONIOENCODING": "utf8"},
            "syntax": "Packages/Text/Plain text.tmLanguage",
            "working_dir": "$project_path/source",
            "shell_cmd": "python unit_tests.py -v TestChomskyGrammar.test_grammarInvalidNonTerminalException -f",
        },
    ],
}

And some python log mine on the output.exec build panel has:

23:32:13:530.091047 1.22e-04 - grammar.grammar.<module>:58 - Importing grammar.grammar
23:32:13:657.607079 1.54e-04 - source.<module>:60 - Importing __main__

And I double click on the first line, I got this on the Sublime Text console:

found result file, line, col of [grammar.grammar], [58], [Importing grammar.grammar] full path: /D/grammar.grammar
Unable to open /D/grammar.grammar

It is not interpreting the dot (.) as a file path separator. How can I intercept the file_regex results before Sublime Text tries to open the file and replace the dots (.) with slashes (/)? So /D/grammar.grammar becomes /D/grammar/grammar?

One way I can think of, is to implement my own system of file_regex, like my_file_regex and when I double click on the output build panel, I perform my custom go to file and open the file by myself, without relying on Sublime Text internals result_file_regex voodoo. (by creating a mouse bind I can detect a double click by timing the differences between the clicks, get the current line, apply my my_file_regex, extract the capturing groups and finally open the file replacing dots (.) with slashes (/))

Related threads:


0 Likes

#2

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 - (\\w+(?:\\.\\w+(?!\\.\\w+:))*)[^:]+:(\\d+) - (.*)$",
            "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, value, replacements):
        # print('replacements', replacements)

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

    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 )
                        matchobject = re.search( result_full_regex, full_line )

                        if matchobject:
                            row = 0
                            column = 0
                            file_name = matchobject.group(0)
                            matchgroups = matchobject.groups()

                            # print( 'matchgroups', matchgroups )
                            for index, value in enumerate( matchgroups ):

                                if index == 0:
                                    file_name = value

                                elif index == 1:
                                    row = value

                                elif index == 2:

                                    try:
                                        column = int( value )
                                    except:
                                        print('Ignoring capture group', index, value)

                                else:
                                    print('Ignoring extra capture groups', index, value)

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

                            result_replaceby = view.settings().get('result_replaceby', {})
                            result_real_dir = view.settings().get('result_real_dir', os.path.abspath('.') )

                            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 )
                            window.open_file(
                                file_name + ":" + str(row) + ":" + str(column),
                                sublime.ENCODED_POSITION | sublime.FORCE_GROUP
                            )

            g_last_click_time = new_click
            g_last_click_buttons = clicks_buttons

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 - (\\w+(?:\\.\\w+(?!\\.\\w+:))*)[^:]+:(\\d+) - (.*)$",

    "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

For more related threads on this topic:

0 Likes

#3

Try this,
Replace /'/'/g with /[/]/g

  • / - start of the regex
  • [/] match characters inside square brackets
  • /g end of the regex
0 Likes