Sublime Forum

[Questions] Help Me Implement My Plugin Idea

#1

##Hello SublimeText community,

I’m new here and also new to plugins. The past three days I’ve read some of the documentation (both official one and also the other) and learned a bunch but clearly not enough.

I have two plugin ideas I’d like to implement just to learn and improve my skills, but I have a lot of questions, and since the documentation isn’t enough I’d like to ask for some help from the community.

tl;dr
I’m new. I have some ideas. I need your help.


##Idea

One of my plugin Ideas is “Python Test File”. For those who use python, you know that it’s really easy to open a console and test something quickly but sometimes I want to test it in a file because it’s easier to read and change code. The problem is that we got to save that file somewhere and delete it when we’re done.

With this plugin I want to:

version 1

  • have a file called test.py that is always there for me
  • every time I open it it is empty
  • I can open it using the command pallet or a shortcut

version 2

  • create a history log of test files that I can navigate through

##So far I’ve done this:

###Root folder:
AppData\Roaming\Sublime Text 3\Packages\PythonTestFile

###Folder structure:

PythonTestFile
|-- test
    |-- test.py
|-- Default.sublime-commands
|-- PythonTestFile.py

###PythonTestFile.py

import sublime
import sublime_plugin

class PythonTestFileCommand(sublime_plugin.WindowCommand):
  def run(self):
    test_file = 'test\\test.py'
    test_view = self.window.open_file(test_file)

###Default.sublime-commands

[
	{"caption": "Python Test File: Open", "command": "python_test_file"}
]

not much I know


##Questions

  1. Am I doing something wrong already?
  2. What’s the difference between creating the plugin folder inside and outside “User” directory?
  3. Should I create the “test.py” inside my plugin folder? (I have more questions depending on the answer)
  4. Deleting the contents of the file should be with sublime.View Class from what I understand. Should I create another Class that extends from sublime_plugin.TextCommand? How do I connect it with the test_view?
  5. How should I go about creating a file history log?

Tips, suggestions, recommendations and useful information would the much appreciated

0 Likes

#2

Ok, that’s a pretty good idea!

You’re going to need two “type” of commands, right? One to open a test file, and another to browse the previous files, OK?

Here’s what I’d do:

The test file would be test1.py, test2.py, etc…

in the open-a-test-file command:

  • get the last test[number].py file
  • create a test[number+1].py file
  • open it up in Sublime Text

in the browse command:

  • simply see which file is currently open
  • take the previous/next one (depending on the arguments)

Did you understand? If not, just let me (us) know, I’ll be happy to help!


Using “actions”:

I’m starting to build my plugin like this because this system allows you to publish only one command. The idea is to call your only command with an with an action argument, and then it’ll execute the requested action.

Here’s what it looks like:

Every action you want to create, you just have to create a method that ends with _action, for example, open_action, and you’ll be able to run it like this:

{
    "command": "my_package_command",
    "args": {
         "action": "open",
         "forward": false
    }
}

Of course, you can change the run method to fit your needs, but I think that for your plugin it should be good… :smile:

Matt

0 Likes

#3

Hello and welcome to the wonderful world of Sublime Text!

This is an interesting idea for a plugin, actually. I have a similar (non-sublime) related setup for writing small test C programs, tied together with a shell script that does the same sort of archiving and file manipulation. Now I have the urge to turn it into a Sublime plugin instead…

I’ve been using Sublime for just a bit less than a year at this point, so I am by no means an expert in this sort of thing. So basically, take everything I say with a grain of salt. :wink:

The only obvious thing I can see from what you have so far is that your file name for opening test.py is not correct; as stated it’s relative to wherever directory happens to be the current one. In order to always hit the file that you want, you should build the path based on sublime.packages_path() along with the name of your package and the rest of the file name.

If you create the folder inside of the User folder, you’re adding something to the User package, but if it’s outside of the User folder (i.e. has the same parent) you’re creating a brand new package instead. From a terminology/logical perspective, one is creating a new package and one is just adding content to an existing package. (note also that a plugin is technically the .py file you created; the structure as a whole is a Package).

That said, Sublime will not automatically load plugin .py files that are not directly rooted at the base of a package file so as to allow you more control.

In your case that means that although it will consider PythonTestFile.py as a plugin for your package, it will completely ignore test/test.py even though it’s a py file (which for you is exactly what you want).

Also, Packages are loaded in a defined order with User always being loaded last. So anything you do inside of the User package “wins” as far as overriding things are concerned. For your case that doesn’t matter, but if you were trying to e.g. change a key binding it would be possible for another plugin to “steal” that key from you if it gets loaded after your plugin does.

I would think that’s something of an ideological thing for the most part. Since you’ve put it inside a sub-folder of your plugin, Sublime will ignore it. You could make the argument that as not a part of your package it should be created in e.g. your home directory or in the temporary files location, or you could make the argument that as a file you don’t want laying around, it’s best kept inside of a location that is out of the way, and the location of your package is already well known.

A TextCommand has an implicit self.view the same way that WindowCommand has self.window. So when your command gets invoked, it will be told what view is the current view. You need to do your own check to determine if that view is one you want to do something with, though (more on this below).

Based on your design idea, I would say that you should have an EventListener listen for on_close or on_close_async to detect when your test file is being closed, and at that point you can copy/move it to a history folder inside of your plugin.

Since you want the file to always be empty when you open it, you probably want to move the file on close and create it new every time you tell it to open. Something like the following might work for you as a jumping off point.

class PythonTestFileCommand(sublime_plugin.WindowCommand):
  def run(self):
    test_file = os.path.join(sublime.packages_path(), "PythonTestFile",
                             "test", "test.py")

    test_view = self.window.new_file()
    test_view.settings().set("is_python_test", True)
    test_view.retarget(test_file)

    test_view.assign_syntax("Packages/Python/Python.sublime-syntax")
    test_view.run_command("insert_snippet", {
        "name": "Packages/PythonTestFile/boilerplate.sublime-snippet"
    })
    test_view.run_command("save")

class PythonTestFileListener(sublime_plugin.EventListener):
    def on_close(self, view):
        if view.settings().get("is_python_test", False) is True:
            print("The python test file just closed")

Here the command to open the test file creates a new file, sets a custom setting on it so that it can be detected as a test file later on when it’s closed, and then sets the file name to be the one that you want it to be. It then makes sure that it’s a python file, executes a snippet to populate some boiler plate code in there (e.g. maybe some imports you always use) and then saves the file. You could skip the snippet part if you literally want an always empty file, though.

The event listener can detect when the file is closed and then physically move it to the history location (e.g. a folder named history inside of your plugin), making sure to make the file name unique (say with a date and time or a numeric suffix or something).

Then you would need to implement a WindowCommand that collects the list of files inside of the history location and presents them to you in a quick panel so you can select an old test to open.

2 Likes

#4

As an aside, note that if you always use the same file name for your test file in progress (e.g. test.py) you will run into issues if you run the command while the test file is already open. The code I posted above will clobber the existing file with a new one, for example.

For robustness you could select a unique name every time you create a file, or modify the command so that when you invoke it, it iterates over all views in all windows to see if the test file is open or not. In the latter case it could switch to the already open file, archive it and then create a new one, or something else.

Arguably since your command is named “Open test” and not “Create test” I would probably go with switching to an existing test instead of creating a new one. That’s the thought I had in my head when I posted the above, but I realized I didn’t mention it, so I thought it would be worth pointing out.

2 Likes

#5

Thanks a lot for the wonderful answers, comments and suggestions. You’ve been really helpful. :facepunch:

I will try to give it some shape and keep this thread up to date with my achievements and questions.

2 Likes