Sublime Forum

How to run part of my plugin in the second thread properly?

#7

So, I change the plugin code like you say. Now it is:

# This command automatically called from EventListener
class MainClanglinterCommand(sublime_plugin.TextCommand):
	def run(self, edit):
		view = sublime.active_window().active_view()
		sublime.set_timeout_async(view.run_command('AsyncClanglinter'), 0)

# This is a main plugin logic class
class AsyncClanglinterCommand(sublime_plugin.TextCommand):
	def run(self, edit):
		...
		...

After this changing, plugin doesn’t work at all. What I am doing wrong?

0 Likes

#8

the parameter to the run command has to be a lower case version of the TextCommand class name, with underscores before the upper case chars - i.e. view.run_command('async_clanglinter')

0 Likes

#9

I change
sublime.set_timeout_async(view.run_command('AsyncClanglinter'), 0)
to
sublime.set_timeout_async(view.run_command('async_clanglinter'), 0)
Now plugin works but still freeze UI like before :disappointed:

0 Likes

#10

Your set_timeout_async call is wrong, the first parameter is a callback.
In your example, the run_command is executed directly and the set_timeout_async does nothing.
Try to replace the 0 with 10000 and you will see that it doesn’t change anything.
You must use a lambda (or something similar):

sublime.set_timeout_async(lambda: view.run_command('async_clanglinter'), 0)

However, I’m not sure it really works as you expect.

IMHO, your example in your first post is correct, except that you cannot use the edit token.
Replace the call that use edit:

ui.output_panel_clear(self.edit)
ui.output_panel_insert_lines(self.edit, parser_output)

With your own TextCommand commands, something like::

self.windows.run_command('output_panel_clear')
self.windows.run_command('output_panel_insert_lines', {'chars' : parser_output})
1 Like

#11

Could you please explain how to use this part of code: , {'chars' : parser_output}) ?
How can I pass and receive this argument?

class OutputPanelInsertLinesCommand(sublime_plugin.TextCommand):
	def run(self, edit, chars):

Or what?

0 Likes

#12
class OutputPanelInsertLinesCommand(sublime_plugin.TextCommand):
def run(self, edit, **kwargs):
        chars = kwargs['chars']
0 Likes

#13

Now I have the error:

..\sublimetext\Data\Packages\ClangLinter\ClangLinter.py", line 22, in run
    clanglinter_thread = ClanglinterThread(self, edit)
TypeError: __init__() takes 2 positional arguments but 3 were given

This is how I start the tread:

# This command automatically called from EventListener
class MainClanglinterCommand(sublime_plugin.TextCommand):
	def run(self, edit):
		clanglinter_thread = ClanglinterThread(self, edit)
		clanglinter_thread.start()

This is top part of thread:

class ClanglinterThread(threading.Thread):
	def __init__(self, edit):
		self.edit = edit
		threading.Thread.__init__(self)
	
	def run(self):
		...
		...

How can I solve this problem?

0 Likes

#14

Please post your entire source code; I will correct it for you.

0 Likes

#15

This is the plugin code
This is archive with all plugin sources

I think that you are interested in lines 21 and 56.

For this plugin you need to add path to the clang.exe into your PATH variable. When you open any file with .c or .h (you can change in settings) plugin will analyze this file.

0 Likes

#16

Try this: http://pastebin.com/AEEE6ANv

I did not test it, but I did eliminate all edit objects being passed around and instead used “synchronous view.run_command calls”, which ST will create edit objects for. I also changed commands to window commands where applicable (no edit object pitfall).

The way you utilize implcit global state with your ClanglinterContext instance is very error prone and may easily bite you in form of a race condition when multiple threads are running at the same time while relying on that global state.
The proper way would be to always pass context information along to whatever processes it, but I did not want to refactor that much. (Note, again, that Edit objects can not be passed because they are destructed at the end of the run method.

1 Like

#17

Now it works asynchronously! Thank you.
But I have noticed kind of strange behavior. When ST starting, in console I get this error:

sublimetext\Data\Packages\ClangLinter\ClangLinter.py", line 15, in plugin_loaded
    start_thread()
TypeError: start_thread() missing 1 required positional argument: 'view'

When I try to change code like this:

def plugin_loaded():
    settings.update()
    start_thread(view)

I get different error:

..sublimetext\Data\Packages\ClangLinter\ClangLinter.py", line 15, in plugin_loaded
    start_thread(view)
NameError: global name 'view' is not defined

P.S.: at this moment I haven’t enough time to test your modification of plugin, so I will write you after 4 hour (I am at work now).

0 Likes

#18

Ah yeah … just remove line 15, it doesn’t serve any real purpose anyway.

0 Likes

#19

I spent some time to understand how it work. Interesting.
I have also notice strange and unplanned plugin behaviour. I think that the main trouble now is a EventListener triggering during plugin thread operation :smiley:
For example: when on_activated_async() triggered by user (common case), the plugin thread strats run and cause triggering on_selection_modified_async(). So in analized tab cursor always jump to the position which ST get from analyzer output :smiley: (It behave like user click on first like in output panel, but plugin does it automatically)
My guess is that happens because the command below

sublime.active_window().run_command('output_panel_insert_lines', {'chars': parser_output})

has diferent behaviour from

view.insert(edit, view.size(), parser_output)

Because of this ST think that user ckick on first line of output panel.
Maybe do you know how can I avoid it?

0 Likes

#20

I solve it (add dirty hack):

	view.set_read_only(False)
	view.run_command('append', {'characters': parser_output})
	
	# Set cursor on the last line, so on_selection_modified_async() will
	# not be called when we don't want
	view.run_command('goto_line', {"line": 0})
	
	# Scroll output panel to first line without staying cursor on it
	view.show(0)
	
	view.set_read_only(True)

Can you also explain what did you mean by this [quote=“FichteFoll, post:18, topic:18962, full:true”]
The proper way would be to always pass context information along to whatever processes it
[/quote]

Do you mean that I need to compare context before thread and after?
P.S.: You were right. class ClanglinterContext() causes BIG problems when working with different files!!

0 Likes

#21

I’m afraid I do not have the time to teach you “good” programming habits, but I will say this: Avoid global and shared state. You can do that by passing relevant information as parameters to other functions (or classes) instead of setting the state on a global instance or something.

0 Likes

#22

Thank you for your help.
Maybe I didn’t do everything right in python because it is my first experience with this language.
All the rest time I am using C for embedded devices (and also create this plugin for linting C).

Now the plugin looks like this:

In a short time I’m going to share code in githab and publish plugin on packagecontrol :slightly_smiling:

Thank you @FichteFoll

0 Likes

#23

Well, you could also have used this clang linter based on SublimeLinter, but it’s okay to have a stand-alone linter package in the package list alongside SublimeLinter-based ones, imo.

0 Likes

#24

I have long been used by these plugins, and I know how they work.

Sublime​Linter-contrib-clang doesn’t show any error summary (like output_panel in my plugin). It make using this plugin very uncomfortable with files which have more than 100 lines :slightly_smiling: It was the biggest disadvantage of this plugin, that forced me to write own.

Sublime​Clang is the second C linter for Sublime Text, but it doesn’t support ST3. There are several ways how to force it to work with ST3 (recompile Clang dll), but all this action can be difficult for majority of people.

So I hope soon to improve the situation with the C Linters.

0 Likes

#25

FYI, you can make SublimeLinter show a quick panel with all errors it encountered in the current file using Ctrl+K, A.

0 Likes

#26

Oops! I will check it.

0 Likes