Home Download Buy Blog Forum Support

Pass a function to a TextCommand plugin?

Pass a function to a TextCommand plugin?

Postby kefka0 on Mon Oct 24, 2011 11:30 pm

Hey guys,

I'm trying to write a generic plugin that performs arbitrary operations on your current selection in sublimetext. The usage I had in mind was that youd have a globals alias to the command, which is a function that accepts a function as a parameter -- that function itself accepting and returning a single string. Example:
Code: Select all
selmod(string.upper)

or
Code: Select all
selmod(lambda s: str(len(s)))


Writing the plugin itself was simple, but for some reason when I try to pass a lambda or other function as a keyword argument, it claims its None. Heres my code:
Code: Select all
class Selmod(sublime_plugin.TextCommand):
    def run(self, edit, func=None):
        for region in self.view.sel():
            text = self.view.substr(region)
            self.view.replace(edit, region, func(text))


But when I try to invoke it from the console, i get this:
Code: Select all
>>> view.run_command("selmod", {"func": lambda s: 'test'})
Traceback (most recent call last):
  File "./sublime_plugin.py", line 276, in run_
  File "./selrep.py", line 9, in run
    self.view.replace(edit, region, func(text))
TypeError: 'NoneType' object is not callable


Any ideas?
kefka0
 
Posts: 11
Joined: Wed Aug 17, 2011 11:20 pm

Re: Pass a function to a TextCommand plugin?

Postby adzenith on Tue Oct 25, 2011 12:23 am

I believe that all the data you send to a command gets transmuted and must be representable as JSON. In such a case a lambda would probably turn into a None... :(
adzenith
 
Posts: 1213
Joined: Mon Oct 19, 2009 9:12 pm

Re: Pass a function to a TextCommand plugin?

Postby facelessuser on Tue Oct 25, 2011 5:03 pm

As far as I know adzenith is right. I actually run different arbitrary functions in the BracketHighlighter plugin.

If I want BracketHighlighter to do something extra, I just send in the command with the parameters in a json structure.
The plugin parameter is where I contain the info to run the arbitrary function or class (specifically the "command" and "args" items). You can see the command is broken up: filename.classname
Code: Select all
   {
      "caption": "Bracket Highlighter: Show Left Bracket",
      "command": "bracket_highlighter_key",
      "args":
      {
         "lines" : true,
         "plugin":
         {
            "type": ["quote","bracket","tag"],
            "command": "bracketselect.select_bracket",
            "args": {"select": "left"}
         }
      }
   },


In my case I just created my own class to be run from my plugin
Code: Select all
import bracket_plugin
import sublime


# Move cursor to left or right bracket if specified, else select the content between
class select_bracket(bracket_plugin.BracketPluginCommand):
    def run(self, bracket, content, selection, select=''):
        (first, last) = (content.a, content.b)
        if select == 'left':
            last = content.a
        elif select == 'right':
            first = content.b
        self.attr.set_selection([sublime.Region(first, last)])


The only thing else I needed was a simple layer to process the parameters, dynamically import the file and function, and execute it. I had to decide what the main code would generally supply the external functions, but that was it. Modularizing things like this allows me to quickly add in new stuff that doesn't really need to be in the core code or even distributed with the core code; it also helps with maintainability.

I am sure there may be more eloquent ways to possibly do this; I have very little Python experience. But basically, what your trying to do is certainly doable.
facelessuser
 
Posts: 1550
Joined: Tue Apr 05, 2011 7:38 pm

Re: Pass a function to a TextCommand plugin?

Postby kefka0 on Tue Oct 25, 2011 9:57 pm

I think I've got a solution, although its very hacky. I pass my function to the command as a string, using s as the text selection variable. The command looks like this:
Code: Select all
class Selmod(sublime_plugin.TextCommand):
    def run(self, edit, func=None):
        exec('def f(s): %s;' % func)
        for region in self.view.sel():
            newtext = f(self.view.substr(region))
            self.view.replace(edit, region, newtext)


And it works by running it from the command line using view.run_command(). Success!

Now the next step is to figure out the best way to pass this function to the command. Is it possible to use sublimetext's nice popup menu to accept user input and use that as a parameter for a command?
kefka0
 
Posts: 11
Joined: Wed Aug 17, 2011 11:20 pm

Re: Pass a function to a TextCommand plugin?

Postby facelessuser on Tue Oct 25, 2011 10:32 pm

Oh, I see what you are doing now. You are trying to allow dynamic input from the user.

Glad you got it working. I haven't personally looked into accepting input directly from the user, but I am sure there has to be a way though that somewhere here knows.
facelessuser
 
Posts: 1550
Joined: Tue Apr 05, 2011 7:38 pm

Re: Pass a function to a TextCommand plugin?

Postby kefka0 on Tue Oct 25, 2011 10:35 pm

facelessuser wrote:Oh, I see what you are doing now. You are trying to allow dynamic input from the user.


Honestly, even if i can somehow register a global in the python environment so i could just open up a console, and type mod('return s.upper().strip()'); would suffice, doesnt need to be pretty. But i cant even figure out how to do that. At least my command works, though
kefka0
 
Posts: 11
Joined: Wed Aug 17, 2011 11:20 pm

Re: Pass a function to a TextCommand plugin?

Postby facelessuser on Tue Oct 25, 2011 11:14 pm

You can create a simple file called helloworld.py

and put the simple function inside.

Code: Select all
def test(value):
   print "hello"
   print value


and then from command line you can say ST2 command line say

Code: Select all
from helloworld import test


and then you can issue the command
Code: Select all
test("world")


and you will get

Code: Select all
hello
world


for the rest of your session that function will be accessible from command line. I am just not sure yet how to make it accessible without manually issuing the from X import Y command.
facelessuser
 
Posts: 1550
Joined: Tue Apr 05, 2011 7:38 pm

Re: Pass a function to a TextCommand plugin?

Postby kefka0 on Tue Oct 25, 2011 11:35 pm

Hmm, right, but how do I access the view object in helloworld.py? The issue is this separation of two layers
kefka0
 
Posts: 11
Joined: Wed Aug 17, 2011 11:20 pm

Re: Pass a function to a TextCommand plugin?

Postby facelessuser on Tue Oct 25, 2011 11:44 pm

Just make sure helloworld.py imports sublime

And use

sublime.active_window().active_view()
facelessuser
 
Posts: 1550
Joined: Tue Apr 05, 2011 7:38 pm

Re: Pass a function to a TextCommand plugin?

Postby facelessuser on Wed Oct 26, 2011 4:24 am

Spent about 15 minutes doing a little research. This opens an input panel and takes a string. It then displays says hello to whatever input you give it in the status bar.

Code: Select all
import sublime
import sublime_plugin


def display_hello(value):
    sublime.active_window().active_view().set_status("hello", "Hello " + value + "!")


class HelloWorldCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.window().show_input_panel(
            "Hello:",
            "World",
            display_hello,
            None,
            None
        )
facelessuser
 
Posts: 1550
Joined: Tue Apr 05, 2011 7:38 pm

Next

Return to Plugin Development

Who is online

Users browsing this forum: No registered users and 3 guests