Sublime Forum

Easiest way to input a number in a multi-selection

#1

I very commonly want to insert a number when using multiple selections. Many times, I’d like that number to increase between selections.

For example, I have 3 lines which I’d like to number:
First Line
Second Line
Third Line

I’d like to add a numbering in front of each, to get:

  1. First Line
  2. Second Line
  3. Third Line

So my question is, after multi-selecting the three lines, how can I add a number in front of each?
And how do I control the details of the numbers? (i.e., the starting number, the amount of increase between the numbers, etc.).

Most editors have some kind of way of doing this when in “column-select” mode, I think that Sublime should have something even better because of the unique way it works with multiple selections.

0 Likes

#2

Well, you don’t need to select all 3 lines. Just use ctrl+alt+ up or down to select up and down. But, if you do select multiple lines, just hit ctrl+shift+l and it’ll break your selection up into multiple lines. Then hit home/end and you can type. Ctrl+click will allow you to select multiple points as well.

Once you have multiple selections going though, you can do whatever you want in there. Type, navigate w/ keys like up, down, left, right, home, end, page up, page down, whatever you want.

0 Likes

#3

Don’t think he has a problem with multiple selections. I find myself running into similar problems but it doesn’t really have a clear answer. It’s most likely best suited to be tackled by a plugin. Bind a popup that takes a starting number and step and have it insert the proper numbers.

From what I understand he wants to have a few selections and he wants to add an incrementing number in each. For example if you have 3 selections there is no easy to add a 1 at the beginning of the first, 2 at the beginning of the second, etc… If you hit 1 there’s going to be a 1 at the beginning of every selection.

0 Likes

#4

I don’t think there’s currently a built-in way to do this, but I believe there was a way to do it with either the MiniPy or PowerUser plugin.

0 Likes

#5

I decided to finally mess around with plugins and I made one that does this. It feels really ugly though. I don’t have any Python experience so this is just what I pieced together while quickly skipping through the Python docs. I was wondering if anyone with more experienced could answer a few questions about it for me.

[code]import sublime, sublimeplugin

class insertNumbers(sublimeplugin.TextCommand):
def run(self, view, args):
self.view = view;
window = view.window()

	window.showInputPanel('Enter a starting number and step.', '1 1', self._onDone, None, None)
	
def _onDone(self, input):
	input = input.split(' ')
	
	for region in self.view.sel():
		if region.empty():
			self.view.insert(region.a, input[0])
		else:
			self.view.replace(region, input[0])
			
		input[0] = int(input[0]) + int(input[1])
		input[0] = str(input[0])[/code]

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)

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?

If there’s anything else that can be done cleaner please say so. My first time ever messing with Python and Sublime plugins so I’d like to start right. Thanks.

Oh and if anyone wants to mess with this plugin, bind insertNumbers to a key, it will open an input box at the bottom of the screen. The first number is the number to start with, and the second number is the step. For example, 1 1 will result in 1, 2, 3, 4…, 1 2 will result in 1, 3, 5, 7…, etc…

0 Likes

#6

I was actually planning on creating my own plugin to add this functionality, but I didn’t know if there was something already available.

@Anomareh - from a first glance, it looks good. I’m not on my dev computer so I don’t even have Sublime here, I’ll check it out when I get.

Here’s the thinking I had for the plugin:
I’d like an input panel, like you have, but which lets you enter lots of different types of input.
One thing for example, should be a range (e.g. 0-5, etc.).
One other input type would be a comma-separater list (“blue, green, brown”). This would make sense when you’ve got multiple selections which you don’t want to break up, but do want to input something else.

I’ll probably write this plugin sometime next week.
Does anyone have any other good ideas for what kinds of input it should accept?

0 Likes

#7

Really? There isn’t a cleaner solution to the type casting? I can’t imagine it’s something Python, but why are so many commands restricted to strings? Just being able to use integers would make it a lot cleaner.

Most of the time I’m just editing something or prefixing / suffixing something so what I made pretty much covers anything I’d ever need. The other situations where you need to paste words is already in Sublime enough for me with column pasting. When I need to paste a column of words it’s usually in a case where I’m copying variable names or something of the like. You can just make a selection of each word and it will paste into a column just fine.

0 Likes

#8

You can number a sequence of regions with the PowershellUtils plugin too:

code One
(|) Two
(|) Three

Note: | denotes the caret

open “filter through PoSh”

type: ++$a

you should get:

(1) One
(2) Two
(3) Three [/code]

You can also do stuff like this:

0..6|%{ "$($_+1) $([dayofweek]$_)" }

Beware of unintentional operations on your filesystem, though! Powershell is pretty powerful.

0 Likes

#9

Another variant:

"one two three" -split " "|%{++$a; "($a) $_"}
0 Likes

#10

It’s worth noting that execution of user scripts is disabled by default in Powershell for security reasons.

Do this from a terminal with admin privileges if you haven’t yet:

set-executionpolicy remotesigned
0 Likes

#11

[quote=“Anomareh”]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)
[/quote]

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.

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

[code]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

[/code]

0 Likes

#12

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.

0 Likes

#13

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]
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
1 2
01
0x5
0x05

  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:
  hello
  this
  is
  hello
  this

  
  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
  10
  11
  
  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:
     try:
        modifier = int(input.split(" ")[1])
     except:
        raise RuntimeError, "Invalid modifier"
  else:
     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):
        res.append(curr)
        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))
     else:
        lastnum_len = len(str(int(initial)))
        print lastnum_len


     curr = int(initial)
     if leading_zeros:
        append_string = "%0" + str(lastnum_len) + "d"
     else:
        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
     else:
        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*)
  else:
     to_insert = self.interpretInput(input, num_selections)

  for i, region in enumerate(selections):
     self.view.replace(region, to_insert*)

[/code]**

0 Likes