Sublime Forum

Bug or Curious Error? (Or newbie mistake?) Message_Dialog focus on newly opened tab

#1

Hi there,
My first post on the forum, so I hope the formatting is acceptable!

I have a curious (to me) issue with a Plug In that I’m toying with. I’ve had success with Plug In development previously, but prior functionality has always been related to working on the self object…

This time, my intention is to create a command that will edit a large number of differing source types.
The command will read from a text file which lists the path of each applicable source file ( I’ll create this filelist in advance ), and then process each individual source file in turn using regex etc to identify the required snippets of code for amendment. ( I can’t just use regex replacements, as it needs to be more context-sensitive than that (and readable!) ).

In the stripped-back code below, you can see that I create the class StepThroughFilesCommand(sublime_plugin.TextCommand) which then opens the filelist and loops through it, calling ProcessSingleFile(self, view) for each entry in the filelist. I invoke the command from a key-mapped macro.

Within ProcessSingleFile() there is a sublime.message_dialog(displaytext)

With the message_dialog in place, the contents of each source file are captured as expected. Great!
BUT
if I comment out the message_dialog (as would be required for bulk processing of files), it doesn’t work! :frowning:

The message_dialog call seems to make the new ProcessSingleFile() view active/focussed, and without this call the contents end up showing the details of the buffer/view that I had open when I invoked the macro.

Can anyone tell me what/why this happens? Am I missing something obvious?

Thanks in advance!
Darren

##################################################
import sublime
import sublime_plugin
import re
import time

##################################
# StepThroughFilesCommand
##################################
class StepThroughFilesCommand(sublime_plugin.TextCommand):
    def run(self, edit, **keywordedargs):
        userpath = 'C:\\Users\\someone\\AppData\\Roaming\\Sublime Text 3\\Packages\\User\\'

        target_filelist  = userpath + keywordedargs['filelist']

        sublime.message_dialog( 'StepThroughFiles          target_filelist:' + target_filelist )

        with open( target_filelist ) as filelist:

            for singlefile in filelist.read().splitlines():
                sublime.message_dialog( 'singlefile : ' + singlefile )

                # open the file in a new tab, and pass it as a View
                self.ProcessSingleFile(sublime.active_window().open_file(singlefile))

                sublime.active_window().run_command("close_file")

        sublime.message_dialog('StepThroughFiles end')

#----------------------------------------------------------------------------------

    def ProcessSingleFile(self, view):
        displaytext = 'PBIF() view .id() :' + str( view.id() ) + ' .buffer_id() :' + str( view.buffer_id() ) + ' .file_name : ' + view.file_name() + ' .size : ' + str( view.size() )  # size is 0!

        # if this message_dialog is not executed before reference to size(), contents are empty and it doesnt work!!?
        # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        # sublime.message_dialog(displaytext)
        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        print(displaytext)

        contents = view.substr(sublime.Region(0, view.size()))
        sublime.message_dialog('after opening.... contents :' + contents )

        # The intention is to step through the view for context-sensitive adjustments to source files :
        #
        # r_source = sublime.Region(0, view.size())
        # r_source_split = view.split_by_newlines(r_source)
        #
        # for vr_source_line in r_source_split:
        #     vs_source_string = view.substr(vr_source_line)
        #     vs_source_string_len = len(vs_source_string)
        #     sublime.message_dialog('Source: '+str(vs_source_string)+'       source_line_len:'+str(vs_source_string_len))

        #if (sublime.active_window().active_view().change_count() > 0):
        #    sublime.active_window().run_command("save")
0 Likes

#2

at a guess I would say that when the file is first opened, the view could still be loading when you are trying to read it’s contents…

1 Like

#3

Hi kingkeith

I suspected it was a sequencing/timing issue, but didn’t know how to move forward… I’ll have a good look at that linked article.

Thanks for your help.

0 Likes

#4

To add some closure to my question, in case this helps anyone else…

I have got the basic flow of process that I’m after, by adding the EventListener as suggested.

Within ProcessSingleFile() I can now process each line of each file (vr_source_line) by adding requisite regex processing.

Thanks.

################### Event Listener ########################
#to load the eventlistener, uncomment and save. 
#to remove it, comment out and reload Sublime to refresh plugins

class EventListener(sublime_plugin.EventListener):

    def on_load(self,view):
        StepThroughFilesCommand.ProcessSingleFile(self,view)

##################################

class StepThroughFilesCommand(sublime_plugin.TextCommand):

    def run(self, edit, **keywordedargs):
        userpath = 'C:\\Users\\someone\\AppData\\Roaming\\Sublime Text 3\\Packages\\User\\'
        target_filelist  = userpath + keywordedargs['filelist']

        sublime.message_dialog( 'StepThroughFiles          target_filelist:' + target_filelist )

        with open( target_filelist ) as filelist:

            for singlefile in filelist.read().splitlines():
                sublime.message_dialog( 'singlefile : ' + singlefile )

                # open the file in a new tab, and pass it as a View, and let the Event fire
                sublime.active_window().open_file(singlefile)

        sublime.message_dialog('StepThroughFiles end')

    #----------------------------------------------------------------------------------

    def ProcessSingleFile(self, view):
        displaytext = 'PBIF() view .id() :' + str( view.id() ) + ' .buffer_id() :' + str( view.buffer_id() ) + ' .file_name : ' + view.file_name() + ' .size : ' + str( view.size() )  # size is 0!

        # sublime.message_dialog(displaytext)
        print(displaytext)

        # The intention is to step through the view for context-sensitive adjustments to source files :
        r_source = sublime.Region(0, view.size())
        r_source_split = view.split_by_newlines(r_source)
        
        for vr_source_line in r_source_split:
            vs_source_string = view.substr(vr_source_line)
            vs_source_string_len = len(vs_source_string)
            sublime.message_dialog('Source: '+str(vs_source_string)+'      source_line_len:' + str(vs_source_string_len))

        if (sublime.active_window().active_view().change_count() > 0):
           sublime.active_window().run_command("save")

        sublime.active_window().run_command("close_file")
0 Likes

#5

Another way of switching the OnLoad event on/off, using class level switching value of 0 or 1, saves reloading Sublime when you don’t want it to fire! :slight_smile:

class EventListener(sublime_plugin.EventListener):
    def on_load(self,view):
        if StepThroughFilesCommand.OnLoadSwitch == 1 :
            StepThroughFilesCommand.ProcessSingleFile(self,view)

#########################################
class StepThroughFilesCommand(sublime_plugin.TextCommand):
    OnLoadSwitch = 0

    def run(self, edit, **keywordedargs):
        # switch it on at start of command (when want OnLoad to fire)
        StepThroughFilesCommand.OnLoadSwitch = 1

        <code...>

        # switch it off at the end, so that it's not active once this command has finished
        StepThroughFilesCommand.OnLoadSwitch = 0
0 Likes

#6

If you want an EventListener to only fire sometimes, you can use a ViewEventListener instead, which associates itself with specific views based on settings inside of those views; when the settings change, the listener is detached and the event no longer fires.

import sublime
import sublime_plugin

import os


class MyEventListener(sublime_plugin.ViewEventListener):
    """
    Event listener that handles on_load(), but only for files which have the
    _my_setting setting set to True
    """
    @classmethod
    def is_applicable(cls, settings):
        return settings.get('_my_setting', False)

    def on_load(self):
        print('File %s loaded' % self.view.file_name())


class StepThroughFilesCommand(sublime_plugin.TextCommand):
    def run(self, edit, filelist):
        target_filelist = os.path.join(sublime.packages_path(), 'User', filelist)

        # open file, then set a setting in the returned view that indicates we
        # want the event listener for fire for it
        view = self.view.window().open_file(target_filelist)
        view.settings().set("_my_setting", True)

For what it’s worth, your ProcessSingleFile method doesn’t seem to use self in it anywhere, which means that it doesn’t need to be tied to your command at all. So in all likelyhood you could just put its code directly in the on_load() handler and not have to do weird tricks like trying to call it.

This is of particular interest because at the site where you call it from the event listener, self is the EventListener instance and not the TextCommand instance, so you’re effectively executing an EventListener method which is confusingly stored in a TextCommand class.

You could just inline that code directly in the event handler, or make it a regular function that gets called instead.

1 Like

#7

Hi OdatNurd

Thanks for your thoughts and suggestions, I can see that I could refactor the code.

With regards to :

As currently implemented, I found that if I referred to self.view within ProcessSingleFile() I was getting the view that was active at the time I invoked the Event, rather than the new view for the opened file. Hence the reason for omitting self.view in favour of view.

This is very much a work ( and learning ) in progress!
Thanks

0 Likes