Sublime Forum

Kick-start for plug-in

#1

Can someone push me in the right-direction with studying/developing a plug-in please?

The second to last line of the following code is my sticking point at the moment.

[code]import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
# self.view.insert(edit, 0, “Hello, World!”)
the_sels = self.view.sel()
for a_sel in the_sels:
# the_text = self.view.substr(a_sel)
# self.view.insert(edit, 0, the_text)
# self.view.replace(edit,a_sel,“howdy”)
the_text = self.view.line(a_sel) # the current line of text?
self.view.insert(edit, 0, the_text)[/code]
I want to read (store) the current line of text. How do I achieve this please?

  1. Also, do I need to loop through all selections, or is it possible just to specify the current line/ cursor location?
  2. How do I read/store the current ‘point’? That is, the cursor position (on the current line)?

Andy.

0 Likes

#2
  1. view.substr()
  2. view.sel() => list of sels
  3. view.sel()[0].begin() or .end() or .a or .b
0 Likes

#3

I can help :smile:

When you first start creating a plugin, don’t worry about multiple cursors. It will just get your code messy and you’ll get confused. The best way to start it looking through the premade plugins in Packages/Default or at someone else’s plugin. Also, when working on a plugin, always keep your console open (control + `). “Print” is your friend, especially when learning the difference between a Region, a RegionSet, a String, and a Point. For example, on your second to last line, line() returns a region while insert() is expecting a string. Try printing out a Region to your console, it is essentially a tuple of points. To retrieve the actual text that corresponds to that Region, use self.view.substr().

  1. If you must deal with multiple cursors, your only way is through looping, yes.
  2. It’s important to know how to convert what you have to what you need. self.view.sel() returns a list of cursor positions. If you look at any of my plugins, I usually start with sel = self.view.sel()[0]
    because more often than not, I’m only using one cursor. However, sel is a Region here, meaning that if you have something selected, it will look like this: (Start of Selection, End of Selection) Note: Start of Selection does not mean beginning. If you select text from right to left, the Region will be reversed. So to retrieve a point: you have a few options.

sel.a --> the start of the selection (aka what the user selects first)
sel.b --> the end of the selection (aka where the user stopped the selection)
sel.begin() --> the minimum of a and b (If you need to know the leftmost/topmost point of the selection no matter how the user selects the text)
sel.end() --> the maximum of a and b

Each of these 4 methods returns an int, which, as far as the API is concern, is equivalent to a point.

If there is no text selected, sel.b and sel.end() will return the same thing it’s your preference as to which you use to retrieve the current cursor position. So your code would be something like: sel = self.view.sel()[0] line = self.view.line(sel.b)

Another great resource: Will Bond wrote an excellent piece on Nettuts+ on how to create a plugin (here: net.tutsplus.com/tutorials/pytho … -2-plugin/)

0 Likes

#4

This is great, thanks both!

@COD312 I had that web page already open :wink:. It’s good, but I needed to ignore bits of it (such as threading), and the multi-select is distracting for a beginner.

I did manage to grab the current line and replace it, woo-hoo! But it replaces the initial tabs as well.

line = self.view.line(the_region) self.view.replace(edit, line, "Hello there")
How would I skip the tabs at the beginning of the line please? Regards, Andy.

0 Likes

#5

Short anwer: regex + python’s string.strip() method

Long answer: I just had to do this for a plugin of mine. I was working with PHP and wanted to prepend certain lines with $. Unfortunately, the spacing in the beginning of the line would get screwed up. So here’s my workaround: github.com/BoundInCode/PHP-Sani … eatevar.py I used view.find() to get the actual start of the line.

0 Likes

#6

@COD312 Thanks again. I’ve a bit of studying to do!

I’m not yet familiar with Python, but I tested
print line_contents.find(’\t’);

to find the first tab position (if any). But if find() uses regex then I suppose it could find the first non-whitespace character. I’ll admit I haven’t studied your example yet (it’s a bit late!) but I’m assuming I could store all the (beginning) non-ws characters in a variable, then pre-pend them to my new string before replacing the line.

I’ll have a look again tomorrow. Regards, Andy.

0 Likes

#7

I got this to work :smiley:. That is, to replace the current line of text, but keep the same indenting:

sel = self.view.sel()[0] cur_line = self.view.line(sel) print cur_line # tuple line_text = self.view.substr(cur_line) print '#' + line_text + '#' # includes tabs/ spaces mtc = re.search('\S',line_text) pos_ltr = mtc.start(0) # the posn of the 1st character print pos_ltr white_sp = line_text[0:pos_ltr] self.view.replace(edit, cur_line, white_sp + "Hello there")
It stores the white-space at the beginning of the line (hopefully!) and then prepends this to the replacement text. I could then add it to any further lines.

It’s not bullet-proof yet. In particular, if begin() = end() is there an ‘Exit Function’ or ‘End’ equivalent in Python? Andy.

0 Likes

#8

if sel.empty(): return

0 Likes

#9

[quote=“C0D312”]if sel.empty(): return[/quote]

Thank you. But, oops! My mistake. I meant if the current line is empty.

I suppose, given that I’m reading past all the white-space characters, I should check if this either ‘fails’, or is at, or beyond, the end-point of the line…

Andy.

0 Likes

#10

No worries :wink:

if not mtc: print 'nothing there' return

0 Likes

#11

I’m making good progress and I’m using the console to run/feed a Snippet. At the moment I grab the console text, insert it into the view, run the Snippet, and then delete the line that was added.

The Snippet uses TM_CURRENT_LINE for its input. But want I *really *want to do is feed the console text directly to the Snippet. Is this possible please?

[code]import sublime, sublime_plugin

class AndyOutput(sublime_plugin.WindowCommand):

def run(self):
    self.window.show_input_panel('Andy>','', self.on_done, self.on_change, self.on_cancel)
    pass        # empty statement

def on_done(self, text):
    # if self.window.active_view(): # why?
    self.window.active_view().run_command("use_main_view", {"the_text": text})

def on_change(self, text):
    pass

def on_cancel(self):
    pass

class UseMainView(sublime_plugin.TextCommand):
def run(self, edit, the_text):
# self.view.insert(edit, 0, the_text)
sel = self.view.sel()[0]
pt = sel.end()
self.view.insert(edit, pt, the_text)
self.view.run_command(“insert_snippet”,{“name”: “Packages/CSS/Andypropxs.sublime-snippet”})
cur_linef = self.view.full_line(sel)
self.view.erase(edit, cur_linef)[/code]

0 Likes

#12

I’m getting somewhere :smiley: If they type a space in the input panel, I show the quick panel. If they choose an item its text is added to the main window, and the focus is given back to the input panel, whe’hay.

But I’m still struggling to discover whether it’s possible to feed text directly to a Snippet? Andy.

0 Likes

#13

Hello. I’m assuming it’s not possible to feed text directly to a snippet, so I’m moving on :smile: (although I could probably copy the entire snippet as ‘contents’ and insert the text into this string…).

I can track someone’s editing of a css file, but I’m unable to use ‘view.insert(edit, pt, ‘ght’)’ to add additional text to the current view/region - because ‘edit’ is not defined within an event listener (code below).

This may be intentional, in that it’s not possible to insert text within the modified event? I can see how this may be an issue (circular reference…). But perhaps it can be done by appending data to an internal buffer? Or by creating, and appending, a new region? Any suggestion is welcome :smiley:

[code]class EditorTracking(sublime_plugin.EventListener):
def init(self):
pass

def on_modified(self, view):
    sel = view.sel()[0]
    pt = sel.end()
    if not view.match_selector(pt, ('source.css meta.property-list.css')):
        # print 'not in css'
        return
    else:
        # print 'yes, in css property list'
        cur_line = view.line(sel)
        line_text = view.substr(cur_line)
        if line_text-2:] == 'ri':
            view.insert(edit, pt, 'ght')[/code]

Added: ‘view.is_read_only()’ returns False, so maybe it is possible to add text??

0 Likes

#14

No worries :smiley:

[code]class EditorTracking(sublime_plugin.EventListener):
def init(self):
self.ignore = False

def on_modified(self, view):
    if self.ignore:
        return
    sel = view.sel()[0]
    pt = sel.end()
    if not view.match_selector(pt, ('source.css meta.property-list.css')):
        # print 'not in css'
        return
    else:
        # print 'yes, in css property list'
        # print view.is_read_only()
        cur_line = view.line(sel)
        line_text = view.substr(cur_line)
        edit = view.begin_edit()
        try:
            if line_text-2:] == 'ri':
                view.insert(edit, pt, 'ght')
            pass
        finally:
            self.ignore = True
            view.end_edit(edit)
            self.ignore = False[/code]

Next task is to disable the auto-complete list (while my code is running) and to find some way to remove my event listener… Andy.

0 Likes

#15

Hello. Is it possible to disable/detach my on_modified event-listener within my event’s code? Or do I need to use a second key-binding that sets on_modified back to None?

0 Likes