Sublime Text 2 plugin: capture the selected text - python

I am trying to write my first ST2 plugin (I'm also new to Python). What I want to do is capture the currently selected text. This is what I have thus far. I thought this would save all the selected text into the text variable but it looks like I'm only capturing the beginning and ending indices of the selection. Thus, if I select the first character in the buffer, my plugin callback prints "01". What I want is the text between index 0 and index 1.
import sublime, sublime_plugin
class CopyOnSelectListener(sublime_plugin.EventListener):
def on_selection_modified(self, view):
selections = view.sel()
text = ""
for s in selections:
text += str(s.begin())
if not s.empty():
text += str(s.end())
print(text)

The ST2 API reference is here. view.sel() returns a RegionSet, an object containing the Region of each selection. Region.a and Region.b are the integers referring to the beginning and ending, respectively, of the region. So, if your view contains
This is some text.
and you have selected text, Region.a would be 13 and Region.b would be 17. To actually get the contents of a Region, you need to use view.substr(region). The following code will print the contents of each selection to the console:
import sublime_plugin
class PrintSelectionTextCommand(sublime_plugin.TextCommand):
def run(self, edit):
view = self.view
for region in view.sel():
print(view.substr(region))
You can run it by opening the console with Ctrl`, making one or more selections in the open file, then running
view.run_command("print_selection_text")
from the console (assuming you've saved it as Packages/User/print_selection_text.py).

Related

How can I accurately set the new cursor positions after text replacements have been made

I am trying to adapt a plugin for automated text replacement in a Sublime Text 3 Plugin. What I want it to do is paste in text from the clipboard and make some automatic text substitutions
import sublime
import sublime_plugin
import re
class PasteAndEscapeCommand(sublime_plugin.TextCommand):
def run(self, edit):
# Position of cursor for all selections
before_selections = [sel for sel in self.view.sel()]
# Paste from clipboard
self.view.run_command('paste')
# Postion of cursor for all selections after paste
after_selections = [sel for sel in self.view.sel()]
# Define a new region based on pre and post paste cursor positions
new_selections = list()
delta = 0
for before, after in zip(before_selections, after_selections):
new = sublime.Region(before.begin() + delta, after.end())
delta = after.end() - before.end()
new_selections.append(new)
# Clear any existing selections
self.view.sel().clear()
# Select the saved region
self.view.sel().add_all(new_selections)
# Replace text accordingly
for region in self.view.sel():
# Get the text from the selected region
text = self.view.substr(region)
# Make the required edits on the text
text = text.replace("\\","\\\\")
text = text.replace("_","\\_")
text = text.replace("*","\\*")
# Paste the text back to the saved region
self.view.replace(edit, region, text)
# Clear selections and set cursor position
self.view.sel().clear()
self.view.sel().add_all(after_selections)
This works for the most part except I need to get the new region for the edited text. The cursor will be placed to the location of the end of the pasted text. However since I am making replacements which always make the text larger the final position will be inaccurate.
I know very little about Python for Sublime and like most others this is my first plugin.
How do I set the cursor position to account for the size changes in the text. I know I need to do something with the after_selections list as I am not sure how to create new regions as they were created from selections which are cleared in an earlier step.
I feel that I am getting close with
# Add the updated region to the selection
self.view.sel().subtract(region)
self.view.sel().add(sublime.Region(region.begin()+len(text)))
This, for some yet unknown to me reason, places the cursor at the beginning and end of the replaced text. A guess would be that I am removing the regions one by one but forgetting some "initial" region that also exists.
Note
I am pretty sure the double loop in the code in the question here is redundant. but that is outside the scope of the question.
I think your own answer to your question is a good one and probably the way I would go if I was to do something like this in this manner.
In particular, since the plugin is modifying the text on the fly and making it longer, the first way that immediately presents itself as a solution other than what your own answer is doing would be to track the length change of the text after the replacements so you can adjust the selections accordingly.
Since I can't really provide a better answer to your question than the one you already came up with, here's an alternative solution to this instead:
import sublime
import sublime_plugin
class PasteAndEscapeCommand(sublime_plugin.TextCommand):
def run(self, edit):
org_text = sublime.get_clipboard()
text = org_text.replace("\\","\\\\")
text = text.replace("_","\\_")
text = text.replace("*","\\*")
sublime.set_clipboard(text)
self.view.run_command("paste")
sublime.set_clipboard(org_text)
This modifies the text on the clipboard to be quoted the way you want it to be quoted so that it can just use the built in paste command to perform the paste.
The last part puts the original clipboard text back on the clipboard, which for your purposes may or may not be needed.
So, one approach for this would be to make new regions as the replaced text is created using their respective lengths as starting positions. Then once the loop is complete clear all existing selections and set the new one we created in the replacement loop.
# Replace text accordingly
new_replacedselections = list()
for region in self.view.sel():
# Get the text from the selected region
text = self.view.substr(region)
# Make the required edits on the text
text = text.replace("\\","\\\\") # Double up slashes
text = text.replace("*","\\*") # Escape *
text = text.replace("_","\\_") # Escape _
# Paste the text back to the saved region
self.view.replace(edit, region, text)
# Add the updated region to the collection
new_replacedselections.append(sublime.Region(region.begin()+len(text)))
# Set the selection positions after the new insertions.
self.view.sel().clear()
self.view.sel().add_all(new_replacedselections)

how to regain the original font properties and its associated properties like bold, italics using python-docx while text replacement

I am using python-docx for a automation tool. I have a issue like once after I run the code for replacement of certain words in one list with corresponding in another list it is removing all the properties (like font size, font name, part of a text in bold or italics, bookmarks in the paragraphs or table) of the text in the paragraph and table and its coming with a plain text in "Calibri" with a font size of '12'.
The code that I used is:
wrongWord = "xyz"
correctWord = "abcd"
def iter_block_items(parent):
if isinstance(parent, _Document):
parent_elm = parent.element.body
elif isinstance(parent, _Cell):
parent_elm = parent._tc
else:
raise ValueError("something's not right")
for child in parent_elm.iterchildren():
if isinstance(child, CT_P):
yield Paragraph(child, parent)
elif isinstance(child, CT_Tbl):
yield Table(child, parent)
document = Document(r"F:\python\documentSample.docx")
for block in iter_block_items(document):
if isinstance(block, Paragraph):
if wrongWord in block.text:
block.text = block.text.replace(wrongWord, correctWord)
else:
for row in block.rows:
for cell in row.cells:
if wrongWord in cell.text:
cell.text = cell.text.replace(wrongWord, correctWord)
document.save(r"F:\python\documentSampleAfterChanges.docx")
Could you help me to get the same font size, font name and other associated properties to be copied from the original file after the text replacement.
Search and replace is a hard problem in the general case, which is the main reason that feature hasn't been added yet.
What's happening here is that assigning to the .text attribute on the cell is removing all the existing runs and the font-related attributes are removed with those runs.
Font information (e.g. bold, italic, typeface, size) is stored at the run level (a paragraph is composed of zero or more runs). Assigning to the .text attribute removes all the runs and replaces them with a single new run containing the assigned text.
So the challenge is to find the text within the multiple runs somewhere, and preserve as much of the font formatting settings as possible.
This is a hard problem because Word breaks paragraph text into separate runs for many reasons, and runs tend to proliferate. There's no guarantee at all that your search term will be completely enclosed in a single run or start at a run boundary. So perhaps you start to see the challenge of a general-case solution.
One thing you can do that might work in your case is something like this:
# ---replace text of first run with new cell value---
runs = table_cell.paragraphs[0].runs
runs[0].text = replacement_text
# ---delete all remaining runs---
for run in runs[1:]:
r = run._element
r.getparent().remove(r)
Basically this replaces the text of the first run and deletes any remaining runs. Since the first run often contains the formatting you want, this can often work. If the first word is formatted differently though, say bold, then all the replacement text will be bold too. You'll have to see how this approach works in your specific case.

How can I set the text widget contents to the value of a variable in Python/Tkinter?

I am writing a program to assist with a trivial part of my job that can be automated. My purpose here is to:
Copy and paste a chunk of plain text into a Tkinter text widget
Use that pasted chunk of text as the value of a variable so that the variable can have certain characters pulled and returned down the line.
I have a functioning little bit of code. For example, here is my text widget and the lines of code I use to get and print its contents:
textBox = Text(root)
textBox.focus_set()
def get_input():
print textBox.get(1.0, 'end-1c')
Then I use a button that uses the command get_input. It works when it comes to printing the contents of the widget.
Now that I know how to properly call on the contents and 'get' them, I would like to learn how I can assign those contents (a string) to the value of a variable.
I think what you want is this. It will delete all the text and then insert a variable.
def set_input(value):
text.delete(1.0, "END")
text.insert("END", value)
It is Python 2.x only. Python 3 requires that "END" be END from the Tk namespace.
def set_value():
text.insert("end-1c", value)

How do I set a variable to a regex string in Python Script for notepad++?

I am trying to set a variable (x) to a string in a text file using regular expression.
Within the file I am searching there exists several lines of code and always one of those lines is a ticket number WS########. Looks like this
~File~
out.test
WS12345678
something here
pineapple joe
etc.
~Code~
def foundSomething(m):
console.write('{0}\n'.format(m.group(0), str(m.span(0))))
editor1.research('([W][S]\d\d\d\d\d\d\d\d)', foundSomething)
Through my research i've managed to get the above code to work, it outputs WS12345678 to the console when the cooresponding text exists within a file.
How do I put WS12345678 to a variable so I can save that file with the corresponding number?
EDIT
To put it in pseudo code I am trying to
x = find "WS\d\d\d\d\d\d\d\d"
file.save(x)
Solution
Thank you #Kasra AD for the solution. I was able to create a workaround.
import re #import regular expression
test = editor1.getText() #variable = all the text in an editor window
savefilename = re.search(r"WS\d{8}",test).group(0) #setting the savefile variable
console.write(savefilename) #checking the variable
To find a specific string within a file in notepad++ using the PythonScript plugin you can pull everything from 1 editor into a string and run a regex search on that.
You need to return the result in your function then simply assign to a variable :
def foundSomething(m):
return console.write('{0}\n'.format(m.group(0), str(m.span(0))))
my_var=foundSomething(input_arg)
and also for extract your desire string you can use the following regex :
>>> s="""out.test
... WS12345678
... something here
... pineapple joe"""
>>> import re
>>> re.search(r'WS\d{8}',s).group(0)
'WS12345678'

Finding the currently selected tab of Ttk Notebook

I have a Ttk Notebook widget containing 8 Frames - so, 8 tabs. Each frame contains a Text widget. I have a button outside the Notebook widget, and I want to insert text into the current tabs Text widget when this button is pressed.
This would seem to require working out which widget in the Notebook is currently selected, but I can't seem to find how to do this. How would I find the currently selected tab?
Alternatively, how can I implement what I want to?
If it helps, here's the code for my notebook:
self.nb = Notebook(master)
self.nb.pack(fill='both', expand='yes', padx=10, pady=10)
self.frames = []
self.texts = []
for i in xrange(8):
self.frames.append(Frame())
self.nb.add(self.frames[i])
self.texts.append(Text(self.frames[i]))
self.texts[i].pack(fill='both')
You can retrieve the selected tab through select method. However, this method returns a tab_id which is not much useful as is. index convert it to the number of the selected tab.
>>> nb.select()
'.4299842480.4300630784'
>>> nb.index(nb.select())
2
Note that you coud also get more information about the selected tab using tab
>>> nb.tab(nb.select(), "text")
'mytab2'
You might look at Notebook reference documentation : http://docs.python.org/3/library/tkinter.ttk.html#notebook
You can get currently selected tab using the "current" keyword:
noteBook.index("current")
Check this website:
https://docs.python.org/2/library/ttk.html#tab-identifiers
24.2.5.3. Tab Identifiers
There are two simple ways to see which tab is selected:
nb.select() # returns the Tab NAME (string) of the current selection
and
nb.index('current') # returns the Tab INDEX (number) of the current selection
The .select() method can also be used to select which tab is currently active, via nb.select(tabId). Without the arg, it returns the tabId (in "name" form) of the current selection.
The .index(tabId) converts a tabId into a numerical index. It also can take the string "end" which will return the number of tabs. So, nb.index(tkinter.END) is like a len() method for a notebook widget.
When there are no tabs, .select() returns an empty string, but .index('current') throws an exception. So, if you want the index, I would say
if nb.select():
idx = nb.index('current')
is the best way to go.
In your particular case, you would probably want to grab the current notebook tab name and then convert that name into the actual child text widget, via the nametowidget() method, for manipulation. So...
tabName = notebook.select()
if tabName:
textWidget = notebook.nametowidget(tabName) # here, 'notebook' could be any widget
textWidget.insert(pos, text, tags)
The nametowidget(name) method maps a Tkinter name to the actual widget. It is a method callable by any actual widget.
I am not a expert at all but hope i can help with some "fresh eyes".
I imagine it could be something involving
def buttonclick():
somevariablename = focus_get()
#Print your text into the somevariable notebook could be
#something like(not sure about the syntax):
focusednotebook = somevariablename
focusednotebook.insert('1.0', 'your text here')
yourbutton = Button(parent, text = "button name", command = buttonclick)
yourbutton.pack()
Hope it works or get you in the right direction.
Please feel free to edit as I am fairly new here amd with python :-)
Getting the tageted tab in tk.Notebook it's easy all you have to do is to use the notebook object and target the index of the current tab. This can be done as follows
# creating a notebook object
notebook = ttk.Notebook(root, height=height, width=width, padding=20)
# Adding tabs
notebook.add(bin_tab, text="Binary Conversion")
notebook.add(oct_tab, text="Octal Conversion")
notebook.add(hex_tab, text="Hexadecimal Conversion")
print(notebook.index("current")) # returns 0, 1, 2depending on how many tabs you have in my case i have 3 which means index from 0 to 2

Categories