Home Download Buy Blog Forum Support

Easiest way to input a number in a multi-selection

Re: Easiest way to input a number in a multi-selection

Postby gpfsmurf on Fri May 07, 2010 7:06 pm

Anomareh wrote:The first issue I had was passing integers as arguments to commands. I'm assuming commands can only take strings for some reason? That then led to the really ugly typecasting I ended up having to do. Is there a better way of doing what I did? (I hope there is :s)

I'm no expert in Python, but see the code below for my take on it. Basically, the 'split' function returns strings, I map them to the int type. It still feels a bit cumbersome though.
Anomareh wrote:Secondly, is there a better way to pass reference to the view the command was called on? I just stored it in the class and that seemed to work, but I was thinking there might be a better way of doing this?

You can use functools (or maybe lambda) to pass parameters to a callback function.

Code: Select all
import sublime, sublimeplugin
import functools

class insertNumbers(sublimeplugin.TextCommand):
   def run(self, view, args):
      view.window().showInputPanel('Enter a starting number and step.', '1 1', functools.partial(self._onDone, view), None, None)
   def _onDone(self, view, input):
      (current, step) = map(int, input.split())
      for region in view.sel():
         view.replace(region, str(current))      
         current += step
Posts: 211
Joined: Mon Jun 23, 2008 6:31 pm

Re: Easiest way to input a number in a multi-selection

Postby Anomareh on Sat May 08, 2010 5:29 pm

Thanks for the response.

The problem I was having with ints was inserting or replacing text via the API. It only takes strings so you have to keep converting and it felt silly.

Your implementation of _onDone is a lot cleaner.
Posts: 222
Joined: Thu Oct 01, 2009 7:32 pm

Re: Easiest way to input a number in a multi-selection

Postby edanm on Sat May 08, 2010 8:34 pm

I've written my own version, which includes a few smart options for the input. I'll add the code at the end (it's a lot of code). I'll probably bundle it up and put it on BitBucket in a little while (I'm working on a few more plugins).
I'd be happy if you could try it out and tell me what you think right now, so I can get in some final fixes before putting it on BitBucket.

Summary of what this does
You can read the comments to find out how it works and see examples, but the idea is, it tries to do the "right" thing based on your input.

    It supports comma separated lists of input.
    It also supports numbers with any modifier you want.
    With numbers, it supports automatically adding leading zeros, if it makes sense to do so.
    It also supports hex output (input 0x1 and the output will be hex numbers), both "regular" hex output and 6-character hex output.

OK Here's the code, let me know what you think. I bind the command to ctrl+i (i for insert), because I don't use incremental find, I just use the normal find.
Code: Select all
class insertText(sublimeplugin.TextCommand):
   def __init__(self):
      self.last_string = "1"

   def run(self, view, args):
      Asks for text to be inserted in multiple selections.
      Quick way to see all the features:
      Select ten different lines, then try the following inputs and see what you get:
      "green, blue, orange"
      1 2
      Full explanation of the input modes:
      If you put in a comma separated list, it will copy each value in the list into each selection (it will keep repeating the list if necessary).
      E.g., with 5 lines multi-selected, the input: "hello, this, is" will produce:

      If you put in a number (and optionally a modifier to add/subtract, default is 1):
      If it's a regular number, it will be added as expected.
      If it has a 0 at the beginning, it will add leading zeros. For example, the input: "08" with 4 selections will give:
      08             <----------- Note leading zero
      09             <----------- Note leading zero
      If you put in a number that starts with 0x, it will output hex numbers. If it starts with 0x0 (a leading zero), it will make it a full, 6 character hex number (and will wraparound to 0x000000 after 0xffffff).
      self.view = view

      window = view.window()
      window.showInputPanel('Enter value to insert:', self.last_string, self.onDone, None, None)

   def interpretInput(self, input, num_selections):
      Returns a list of results, based on the input and the number of selections (i.e., the result list).
      This is called in the case that the input is *not* a comma-seperated list.
      initial = input.split(" ")[0]
      res = [] # The result array.

      # Check if there is a modifier, if not use 1.
      if len(input.split(" ")) > 1:
            modifier = int(input.split(" ")[1])
            raise RuntimeError, "Invalid modifier"
         modifier = 1

      # The following code attempts to understand the input.
      # It tries to match it against various patterns which we accept.
      # If it fails, an exception is raised.

      # If it's a single char (i.e. a letter)
      if initial.isalpha() and len(initial) == 1:
         curr = initial
         for i in range(num_selections):
            curr = chr(ord(curr) + modifier)

         return res

      # If it's only a number (not hex or binary, but might have a leading zero).
      if initial.isdigit():
         # Set some settings.
         leading_zeros = False

         if initial.startswith("0") and len(initial) > 1:
            leading_zeros = True

         # How many leading zeros to add?
         if modifier > 0:
            lastnum_len = len(str(int(initial) + modifier * num_selections))
            lastnum_len = len(str(int(initial)))
            print lastnum_len

         curr = int(initial)
         if leading_zeros:
            append_string = "%0" + str(lastnum_len) + "d"
            append_string = "%d"
         for i in range(num_selections):
            res.append(append_string % curr)
            curr = curr + modifier

         return res

      # If it's a "full" hex number.
      if initial.startswith("0x0") and len(initial) > 3:
         hexnum = initial[3:]
         intnum = int(hexnum, 16)

         # Different code for full and non-full hex strings.
         for i in range(num_selections):
            # Since it's a full hex string, we'll make sure it's always 6 chars.
            res.append("0x%06x" % (intnum % 0xffffff))
            intnum = intnum + modifier

         return res

      # If it's a hex number (starts with an "0x").
      if initial.startswith("0x") and len(initial) > 2:
         hexnum = initial[2:]
         intnum = int(hexnum, 16)

         # Different code for full and non-full hex strings.
         if len(hexnum) == 6:
            for i in range(num_selections):
               # Since it's a full hex string, we'll make sure it's always 6 chars.
               res.append("0x%06x" % (intnum % 0xffffff))
               intnum = intnum + modifier
            for i in range(num_selections):
               res.append("0x%x" % intnum)
               intnum = intnum + modifier

         return res

      # We couldn't recognize what it was, so raise an error.
      raise RuntimeError, "Invalid input"

   def onDone(self, input):
      # Make a list of things to insert. We'll fill this list up, then insert at the end.
      self.last_string = input
      selections = self.view.sel()
      to_insert = []

      num_selections = len(selections)

      # Input is either a comma seperated list, or a range.
      if "," in input:
         sp = [l.lstrip() for l in input.split(",")]
         for i in range(num_selections):
            to_insert.append(sp[i % len(sp)])
         to_insert = self.interpretInput(input, num_selections)

      for i, region in enumerate(selections):
         self.view.replace(region, to_insert[i])
Posts: 131
Joined: Thu Mar 04, 2010 11:05 pm


Return to Technical Support

Who is online

Users browsing this forum: No registered users and 13 guests