There are a few issues with this code. The first is that as presented above, the code doesn’t compile because the self.replace()
call in handle_thread()
isn’t indented properly, though I’m assuming that should line up with the erase_status()
call.
With that change in mind, if you follow the logic of the code as it’s laid out the reason that you’re seeing this error is because the call to self.handle_thread()
in the lambda
call is broken. Notice that the argument list for handle_thread()
is:
def handle_thread(self, edit, threads, offset = 0, i = 0, dir = 1):
However at the call point:
lambda: self.handle_thread(threads, offset, i, dir)
That is, you’ve skipped the edit
argument in the call. Thus the result of this call is that every argument you provide is interpreted differently than you expect; edit
becomes the value threads
, threads
becomes the value offset
, offset
becomes the value i
, i
becomes the value dir
, and dir
is set to the default of 1
because it’s not provided.
With that in mind, once the first call to set_timeout()
happens and handle_thread()
is called again, the value of threads
is 0; the if
test concludes that it doesn’t need to do anything further, and the code falls through to the self.replace()
call, where it provides the value 0
for the argument of threads.
Now it’s easy to see that in replace()
, the line in question gets mad because it’s expecting that value to be an instance of JsonPlaceHolder
/Thread
but it’s actually a number, and kabooom.
The fix in that case would be to include the edit
argument as the first argument in the lambda
call, so that all of the arguments line up the way you expect them to.
If you make that change, you’ll find that the command never actually completes. That’s because of this line:
if threads:
It looks like the intention is that this should return True
while the thread is running and False
when it’s not, so that once the request completes the results can be handled. The Thread
class doesn’t (AFAIK but I’m no guru) work that way; what this is actually testing is whether or not the value is not None
, which it always is.
if you were to include a line like print(threads)
at the top of of handle_thread()
you can see that the state of the thread changes from started
to stopped
when the request finishes, but that’s not the state that you’re checking.
So that line should look more like:
if threads.is_alive():
Now the command will correctly drop out of the loop when the result comes back, but there’s a new issue instead:
Traceback (most recent call last):
File "/home/tmartin/.config/sublime-text-3/Packages/User/test_panel_thing.py", line 28, in <lambda>
sublime.set_timeout(lambda: self.handle_thread(edit, threads, offset, i, dir), 100)
File "/home/tmartin/.config/sublime-text-3/Packages/User/test_panel_thing.py", line 33, in handle_thread
self.replace(edit, threads)
File "/home/tmartin/.config/sublime-text-3/Packages/User/test_panel_thing.py", line 37, in replace
for todo in thread.result():
TypeError: 'list' object is not callable
There are a couple of bugs here; the first is that if there was an error collecting the JSON in the thread, it sets the value to False
to indicate that, but this code isn’t checking for it. The other is that result
isn’t a function, it’s the actual result of the call (which in this case is a list).
Based on that, the code could perhaps be expressed better as the following:
def replace(self, edit, thread):
if thread.result:
todos = []
for todo in thread.result:
todos.append(todo["title"])
self.view.window().show_quick_panel(todos, self.on_done)
else:
sublime.status_message("Error during request")
Now a test is done to see if there is a result or not; if not a message is displayed in the status bar, but otherwise the result of the command is that a quick panel opens as you might expect:
One last thing to note is that in Sublime Text 2 (which is the version the tutorial you’re referencing is from) a plugin was free to create it’s own edit
object as desired. However in Sublime Text 3 this is strictly controlled and the only thing that can provide an edit
object is Sublime itself, which it passes to the run()
method of a TextCommand
.
The value of the edit
object is only valid from inside of the run()
call that it was given to. If you try to keep it and use it later, you’ll get an error as a result.
That doesn’t matter for the code as you have it laid out here because it’s not actually trying to change any text. However if your ultimate goal is to use the result of the call to modify the text, you can no longer do it this way.
Instead, you would need to gather the text and then pass it as an argument to a TextCommand
using run_command()
so that Sublime will execute that command and it can then make the change for you. Depending on your use case you can use the insert
command that already exists to do this, or you can create one of your own.
This is outlined a bit in the plugin porting guide.