PythonScript ops on selection if any, all text otherwise
-
I use PythonScript quite a bit with NPP these days, mostly for frequent regular expressions replacements, but I’m hardly fluent in Python, so generally I search the forums here for examples of scripting similar to what I hope to achieve, then try to adapt it for my own needs. I’ve been hoping for some time to modify my regex scripts to make it so they only operate on selected text IF there is any, and on all text otherwise. I just found this post from 2017, which demonstrates how to operate only on selected text, so that’s a big help for me:
@scott-sumner said in Python script to replace on selection:
here’s some code that shows how to do a replace on only the text in one or more selections:
find = 'a' replace = 'A' num_selections = editor.getSelections() for sel_nbr in range(num_selections): start_pos = editor.getSelectionNStart(sel_nbr) end_pos = editor.getSelectionNEnd(sel_nbr) editor.replace(find, replace, 0, start_pos, end_pos)
Is my selected-or-all goal possible, and can anyone give me an example of that if so?
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
Is my selected-or-all goal possible, and can anyone give me an example of that if so?
Yes. Not tested, you could see if the number of selections == 1 (there will always be one) and then test to see if that one has the same start and end position which means there is no selection, just the cursor at that position and then get the range of the entire doc and set start to 0 and end to the returned range.
if num_selections == 1 and editor.getSelectionNStart(0) == editor.getSelectionNEnd(0): start_pos = 0 end_pos = len(editor.getText()) + 1 editor.replace(find, replace, 0, start_pos, end_pos) else: ...
Cheers.
-
@michael-vincent said in PythonScript ops on selection if any, all text otherwise:
end_pos = len(editor.getText()) + 1
Works, but I’ll point out that
editor.getLength()
is available as well aseditor.getTextLength()
. I think internally they are the same, though, so who would type the longer version?Interestingly, in just evaluating my above statement that they are the same, I see that
.getLength()
“Returns the number of bytes in the document” whereas.getTextLength()
“Retrieve the number of characters in the document”. Knowing that for files I work with (UTF-8) the number of bytes and the number of “characters” can definitely be different (bytes >= chars), I tried some tests on files with a lot of multi-byte characters, and the two functions returned the same value. Some further digging showed that it is always return the number of bytes. This is a “good thing” because “position” functions return values that are byte (not char) based, and functions like.rereplace()
use position… -
Apologies but I reworked your example a bit:
(start_pos, end_pos) = (editor.getSelectionNStart(0), editor.getSelectionNEnd(0)) if num_selections == 1 and start_pos == end_pos: start_pos = 0 end_pos = len(editor.getText()) + 1 # or simpler editor.getLength() editor.replace(find, replace, 0, start_pos, end_pos)
It’s really not changed much, but I think it more fits the model the OP is truly looking for. Of course, your example paves the way for handling multi-selection or column-selection with the nebulous
else: ...
part, but that’s probably more of a complication than is needed for what the OP described. -
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
Apologies but I reworked your example a bit:
None needed - thank you! I whipped it up quick and knew there was a better way to get document length (
.getLength()
) but couldn’t quickly find it in Scintilla docs.Cheers.
–
moderator edit: fixed link -
@michael-vincent said in PythonScript ops on selection if any, all text otherwise:
if num_selections == 1 and editor.getSelectionNStart(0) == editor.getSelectionNEnd(0): start_pos = 0 end_pos = len(editor.getText()) + 1 editor.replace(find, replace, 0, start_pos, end_pos) else: …
Excellent, thanks! I came up with this for a test run, and worked just fine:
num_selections = editor.getSelections() if num_selections == 1 and editor.getSelectionNStart(0) == editor.getSelectionNEnd(0): start_pos = 0 end_pos = len(editor.getText()) + 1 editor.rereplace(r'0x0x0', ur'•×•×•', 0, start_pos, end_pos) else: for sel_nbr in range(num_selections): start_pos = editor.getSelectionNStart(sel_nbr) end_pos = editor.getSelectionNEnd(sel_nbr) editor.rereplace(r'0x0x0', ur'•×•×•', 0, start_pos, end_pos)
But most of my actual Python Regex scripts have multiple search/replace steps, up to maybe 15 or so, and most of them are at least somewhat lengthy and complicated, so it would be great if I didn’t have to repeat all those steps in both (selected and non-selected text) sections. I’m thinking the thing to do would be to move all the actual Regex code to a subroutine, and call that from both sections. In a basic internet search for Python subroutines, all the examples I’ve looked at so far deal with passing parameters to them, and receiving values from them, which I don’t think I need to worry about here.
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
I’ll point out that
editor.getLength()
is available as well aseditor.getTextLength()
.Thanks, though I’m confused. You reference
editor.getTextLength()
, allegedly in @michael-vincent ’s posted example, but I’m only seeingeditor.getText()
, which is actually shorter thaneditor.getLength()
. Typo, maybe? Anyway, good to know about the options.@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
your example paves the way for handling multi-selection or column-selection with the nebulous
else: ...
part, but that’s probably more of a complication than is needed for what the OP described.Actually, I’d prefer to keep the ability to handle multiple or columnar selections, just in case.
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
I’m confused. You reference editor.getTextLength(), allegedly in @michael-vincent ’s posted example, but I’m only seeing editor.getText(), which is actually shorter than editor.getLength(). Typo, maybe?
Michael used
len(editor.getText())
which gets a copy of the text and then calculates its length, via Python’s string length determining function.I mentioned that length is available by asking Scintilla directly for just the number, via
editor.getLength()
oreditor.getTextLength()
– no retrieving the text (since it is unnecessary to have the text).Different things, no “typo”.
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
I’d prefer to keep the ability to handle multiple or columnar selections
Well, then I’d refer you back to the code you quoted in your FIRST posting in this thread, as that code handles these types of selections.
Note that if you have “n” selections (and a column block is really “n” selections, where “n” is the number of rows in the selection), you have to do “n” separate
editor.rereplace()
calls – there’s no way to do it in one call. -
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
I mentioned that length is available by asking Scintilla directly for just the number, via
editor.getLength()
oreditor.getTextLength()
— no retrieving the text (since it is unnecessary to have the text).Ok, I see now. I missed that distinction. Thanks for the clarification.
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
most of my actual Python Regex scripts have multiple search/replace steps, up to maybe 15 or so, and most of them are at least somewhat lengthy and complicated, so it would be great if I didn’t have to repeat all those steps
Maybe a list of tuples meets the need here?
my_find_repl_tup_list = [ ( r'find_str_1', r'repl_str_1'), ( r'find_str_2', r'repl_str_2'), ( r'find_str_3', r'repl_str_3'), ]
Then you could loop over the list:
for (find, replace) in my_find_repl_tup_list: ...
-
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
Maybe a list of tuples meets the need here?
Well, it LOOKS promising, but I evidently can’t figure out how to utilize it. Based on your example as shown in your post, I adapted the find/replace code from my own previous post, consulted this page and tried to use it here, but…
thistuple = [ ( r'0x0x0', ur'•×•×•'),
…etc. gave me an invalid syntax error with their print demo example. I then tried…
( "r'0x0x0', ur'•×•×•'"),
…and that seemed to work fine with the print demo, so I tried plugging that into my test code above, as such:
findrepltuple = [ ( "r'0x0x0', ur'•×•×•'"), ( "r'this', ur'that'"), ] num_selections = editor.getSelections() if num_selections == 1 and editor.getSelectionNStart(0) == editor.getSelectionNEnd(0): start_pos = 0 end_pos = len(editor.getText()) + 1 for (find, replace) in findrepltuple: editor.rereplace(findrepltuple, 0, start_pos, end_pos) else: for sel_nbr in range(num_selections): start_pos = editor.getSelectionNStart(sel_nbr) end_pos = editor.getSelectionNEnd(sel_nbr) for (find, replace) in findrepltuple: editor.rereplace(findrepltuple, 0, start_pos, end_pos)
Also tried
editor.rereplace((find, replace), 0, start_pos, end_pos)
.Result for both was error “for (find, replace) in findrepltuple: ValueError: too many values to unpack” from PythonScript.
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
"r'0x0x0', ur'•×•×•'"
"r'this', ur'that'"
Hmm, outer quotes are not right (delete them).
I used
r
prefix on the sample strings in my demo example because typically regular-expressions contain a lot of\
and by using ther
prefix the backslashes don’t have to be doubled, leading to easier-to-read strings. If your regexes don’t use backslashes like shown here, you don’t have to use ther
prefix.So maybe try:
findrepltuple = [ ( '0x0x0', u'•×•×•'), ( 'this', u'that'), ]
-
Also, this won’t work:
editor.rereplace(findrepltuple, 0, start_pos, end_pos)
This function will require a separate find and replace expression.
So:
editor.rereplace(find, replace, 0, start_pos, end_pos)
-
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
Hmm, outer quotes are not right (delete them).
Well, they made the online print demo work, anyway.
If your regexes don’t use backslashes like shown here, you don’t have to use the
r
prefix.Oh, they’re all over the place in my regexes. My standard operating procedure in PythonScript is `editor.rereplace(r’find string’, ur’replace string’).
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
Also, this won’t work:
editor.rereplace(findrepltuple, 0, start_pos, end_pos)
Got it, and now working! Thanks much! Though I do have one gripe, which I can live with, about doing it this was: In my multi-step Regex scripts, because most steps ARE fairly long and complicated and I sometimes need to revise them, I generally precede each one with a comment giving examples of before and after text so it’s easier to zero in on when necessary, but I clearly (that I know of) can’t do that when using a tuple to store all the find/replace expressions. The next best solution that I can think of is to just have all the comments compiled together sequentially, corresponding to the order of the expression pairs, and comments such as
# Regex 1
after each pair. Unless anybody has a better suggestion. -
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
Unless anybody has a better suggestion.
findrepltuple = [] #----------------------------------------------------- findrepltuple.append(('0x0x0', u'•×•×•')) ''' as much text as you want about the above blah blah blah... blah blah blah... blah blah blah... blah blah blah... ''' #----------------------------------------------------- findrepltuple.append(('this', u'that')) ''' as much text as you want about the above blah blah blah... blah blah blah... blah blah blah... blah blah blah... ''' #----------------------------------------------------- etc.
-
@m-andre-z-eckenrode said in PythonScript ops on selection if any, all text otherwise:
I generally precede each one
Sorry, I didn’t catch that part so the sample I provided shows the explanatory text AFTER the live-code part, not before. :-(
Pretty easy to see how to modify it, though. :-) -
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
findrepltuple = [] #----------------------------------------------------- findrepltuple.append(('0x0x0', u'•×•×•')) ''' as much text as you want about the above blah blah blah... '''
Cool, that works. Thanks again.
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
Sorry, I didn’t catch that part so the sample I provided shows the explanatory text AFTER the live-code part, not before.
No problem, either way is good.
.
-
@alan-kilborn said in PythonScript ops on selection if any, all text otherwise:
.append((‘0x0x0’, u’•×•×•'))
Note that
append
is a function call to add something to a list. In this case we are adding a tuple to the list, so that’s why the opening and closing parentheses are doubled – the outer pair is for the function call, the inner pair is the tuple notation. -