Copy entire code group?
-
Is there a way to copy grouped text of code? An example of what I mean is the + and - that allows collapsing a group of code such as when we have an html table
<table>tons of code</table>
can be collapsed so it’s hidden. That’s what I’m referring to as a group, I don’t know what the technical term for it is if group isn’t the term for it.Is there a way to easily select an entire tag group from
<open> to </close>
? Maybe a key combination after clicking on the + or - ?With all the helpful features in notepad++ I assume this is possible but I can’t figure out how to do it if it is possible.
I have found that we can:
- Place the cursor on the line we want to start the selection at.
- Right-click and select Begin/End Selection.
- Click on the <tag> so it highlights where that tag closes.
- Scroll to the highlighted </tag> and repeat step 2.
Is there a faster and easier way of selecting the whole tag than doing it this way?
-
Hello @arel3 andAll,
One easy possibility is to grab all the text with a regular expression !
For instance, in order to select all the text
<table>tons of code</table>
-
Open the
Mark dialog
-
SEARCH
(?s-i)^\h*<table>.+?</table>
-
Untick all options
-
Tick the
Purge for each search
andWrap around
options -
Select the
Regular expression
search mode -
Click on the
Mark All
button -
Click on the
Copy Marked text
button -
Open a new tab and Paste the clipboard contents
=> You should get all text between the
<table>
and</table>
tags, including the tags themselvesBest Regards,
guy038
-
-
Thank you very much for the reply, @guy038!
Is there a faster way to do it than the 4-step way I figured out and shared?
Something like holding a key and clicking on a
<tag>
? -
@arel3 ,
Is there a faster way to do it than the 4-step way I figured out and shared?
Guy’s method is faster than what you described.(edit: oh, except that it’s limited to a single hardcoded tag type, whereas your problem statement could be interpreted as “any random tag I click on”)Notepad++ has a built-in Search > Select All Between Matching Braces, which will select everything from
(
to)
or{
to}
. But it does not have a built in equivalent Select All Between Matching Tags – which is mildly surprising, since the logic is basically identical.Such a feature might already exist in one of the HTML or XML plugins, because there are some of those that add some pretty cool features for people who are regularly editing markup-language files (I am not included in that set, so I don’t know the features and pluses/minuses of those plugins).
If it’s not there, one could write a script for the PythonScript plugin that did that… I might have a think about that implementation, if I find the time today.
-
@peterjones said in Copy entire code group?:
I might have a think about that implementation, if I find the time today.
It was interesting enough to me that I took a script that had similar logic and expanded it.
- Install PythonScript plugin if not already installed (Plugins > Plugins Admin, check
Python Script
then click Install - Create a new script in PythonScript (Plugins > PythonScript > New script, name =
select-current-tag.py
) - Paste the contents from below and save
- Run Plugins > PythonScript > Scripts >
select-current-tag
to run the script. - If you want to assign a keyboard shortcut:
- Plugins > PythonScript > Configuration…
- In User Scripts, click on
select-current-tag
and then the left Add button (to add it to the Menu Items list on the left) - OK
- Exit and Restart Notepad++
- Use Settings > Shortcut Mapper, Plugins tab; filter on
Python
; the Modify the shortcut forselect-current-tag
You only have to do step#4 once: after that, every time you start Notepad++, that script will be available through your shortcut.
To use it, click on the
<tagname
or</tagname
then run Plugins > PythonScript > Scripts >select-current-tag
or the keystroke you chose in #4. This will select from<tagname
thru</tagname>
, and then you can easily copy that with Ctrl+C. Caveats:- If you click after a space (for example, on
class
in<tagname class="...">
), it will not find the tag properly - If you click after the
>
(for example, after>
in<tagname>
), it will not find the tag properly - Currently, the script assumes that you want to find the closing tag immediately after the active open tag, or the opening tag immediately before the active close tag. It does not find balanced tags, because that is hard. Thus, if you have nested
div
s, it might not do what you desire. But Guy made the same assumption, and most HTML tags don’t nest anyway, so except fordiv
and a few others, it’s probably not a big deal. If it is a big deal to your use case, then let us know, and maybe someone (maybe even me) will be interested enough to modify my script to require balanced tags. - If you have lots of confusing things, like
>
inside attributes or using<!-- </div> -->
to comment out tags that you want to skip, this won’t handle it. I am not going to write a whole HTML/XML parser just to satisfy a complicated sequence like that. If you need something that complicated, a full-blown HTML or XML plugin will be required, and you will need to investigate whether any of the existing plugins already have the select-tag-and-contents feature for you.
# encoding=utf-8 """ selectCurrentTag: in response to https://community.notepad-plus-plus.org/topic/22965/copy-entire-code-group Starts at the current position; looks to see if it's `<open`, `</close`, or neither. If not neither, then search for the first pair it finds. TODO: change from "first pair it finds" to "first balanced pair it finds" """ from Npp import editor, notepad, console #console.show() class SelectCurrentTag(object): class TAGTYPE(object): OPEN = 'OPEN' CLOSE = 'CLOSE' OTHER = None #def __init__(self): # self.oldPos = editor.getCurrentPos() # console.write("SelectCurrentTag: initialized\n") def getTagWord(self): self.oldPos = editor.getCurrentPos() self.tagword = editor.getCurrentWord() self.tagtype = self.TAGTYPE.OTHER self.wordtagpos = [None,None] direction = 0 searchpos = self.oldPos #console.write(__file__ + "::" + __name__ + "::{}..{}".format(searchpos, self.oldPos) + "\n") while searchpos > 0 and searchpos < editor.getLength(): c = editor.getCharAt(searchpos) if c<0: # utf8: first byte&0xC0 is 0xC0; subsequent bytes in the char are 0x80 # thus, step backword a byte while those aren't start-byte while searchpos>0 and c & 0xc0 != 0xc0: searchpos -= 1 c = editor.getCharAt(searchpos) q = editor.positionAfter(searchpos) #console.write("\t{}: {}..{}\n".format("searching", searchpos, q)) #console.write("\t{}: {}..{}\n".format("found", searchpos, q)) s = editor.getTextRange(searchpos,q).decode('utf-8') if len(s)==1: c = ord(s) elif len(s)==2: c = 0x10000 + (ord(s[0]) - 0xD800) * 0x400 + (ord(s[1]) - 0xDC00) else: c = ord(s) # will probably give an exception elif c>255: console.writeError("unknown character {} while searching for \\".format(c)) s = unichr(c) # should probably create an exception else: s = unichr(c) #info = "#{0:5}# '{2}' = HEX:0x{1:04X} = DEC:{1} ".format(searchpos, c, s.encode('utf-8') if c not in [13, 10, 0] else 'LINE-ENDING' if c != 0 else 'END-OF-FILE') #console.write(info + "\n") if c in [0, 10, 13, 8, 32] and direction==0: # nul, newline, horizontal whitespace not allowed when searching for start of the current open/close tag self.wordtagpos[direction] = None self.tagtype = self.TAGTYPE.OTHER break if s == '<' and direction==0: self.wordtagpos[direction] = searchpos if editor.getTextRange(searchpos+1,searchpos+2) == '/': self.tagtype = self.TAGTYPE.CLOSE else: self.tagtype = self.TAGTYPE.OPEN # since I found it, start from original and change direction searchpos = self.oldPos - 1 direction = 1 # was: break if s == '>' and direction==1: self.wordtagpos[direction] = searchpos+1 # include the '>' if self.tagword == '': def assn(m): self.tagword = m.group(1) #console.write("assn called: 0:'{}' 1:'{}'\n".format(m.group(0), m.group(1))) editor.research(r'</?(\w+).*?>', lambda m: assn(m), 0, self.wordtagpos[0], self.wordtagpos[1], 1) pass break # if not found yet, go back a character and loop searchpos += (2*direction-1) # so direction==0 will subtract 1, direction==1 will add 1 #console.write("SelectCurrentTag: {:d}..{:d}..\n".format(foundstartpos, self.oldPos)) return self.tagword, self.tagtype def selectFromThisOpen(self): """selects from the beginning of the current-position-open tag to the first close tag found""" # TODO: require balanced tags inside self.startNewSelection = self.wordtagpos[0] def cb(m): #console.write("->selectFromThisOpen: 0:'{}' at ({}..{})\n".format(m.group(0), m.start(), m.end())) self.endNewSelection = m.end() editor.research(r'</{}.*?>'.format(self.tagword), cb, 0, self.wordtagpos[0], editor.getLength(), 1) # find the first #console.write("->selectFromThisOpen => {} .. {}\n".format(self.startNewSelection, self.endNewSelection)) editor.setSelection(self.startNewSelection, self.endNewSelection) def selectToThisClose(self): """selects from the last open-tag found to the close tag known at the current location""" self.endNewSelection = self.wordtagpos[1] self.startNewSelection = 0 # TODO: actually find the previous open tag (or eventually, matching open tag) def cb(m): #console.write("->selectToThisClose: 0:'{}' at ({}..{})\n".format(m.group(0), m.start(), m.end())) self.startNewSelection = m.start() editor.research(r'<{}.*?>'.format(self.tagword), cb, 0, 0, self.wordtagpos[1]) # find the last, so don't put a maxCount parameter #console.write("->selectToThisClose => {} .. {}\n".format(self.startNewSelection, self.endNewSelection)) editor.setSelection(self.startNewSelection, self.endNewSelection) def go(self): self.getTagWord() #console.write("SelectCurrentTag: tagword='{:s}' tagtype={:s} oldPos={:d} wordtagpos={}\n".format(self.tagword, self.tagtype, self.oldPos, self.wordtagpos)) if self.tagtype == self.TAGTYPE.OPEN: self.selectFromThisOpen() pass elif self.tagtype == self.TAGTYPE.CLOSE: self.selectToThisClose() pass else: # no matching tag found, so don't select anything new editor.setSelection( self.oldPos, self.oldPos ) pass SelectCurrentTag().go()
- Install PythonScript plugin if not already installed (Plugins > Plugins Admin, check
-
-
@arel3 said in Copy entire code group?:
An example of what I mean is the + and - that allows collapsing a group of code…
Maybe I’m missing something, but isn’t what the OP is asking about something in the spirit of the following example?:
- Open
langs.xml
- Collapse line 4 to get a
+
before it showing in its margin:
- Start selecting on line 4, and go downward, making sure your caret is on line 408 when you stop selecting (but it is only the next visible line downward, so really easy to do):
- Press Ctrl+c
- Move to a new tab
- Press Ctrl+v to see that 400-some lines were copied/pasted, between the
<Languages>
tag and the</Languages>
tag.
- Open
-
@alan-kilborn said in Copy entire code group?:
Maybe I’m missing something,
Nope, I think I was missing something. That’s much more obvious, and macro recordable: Fold,
Alt+Home
,Alt+Home
(*), select-line-down, copy, arrow up, unfold.<Macro name="CopyCurrentTagWithContents" Ctrl="yes" Alt="yes" Shift="yes" Key="67"> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 44030 = IDM_VIEW_FOLD_CURRENT" /> <Action type="2" message="0" wParam="44030" lParam="0" sParam="" /> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 2345:HomeDisplay x2 to make sure at the first line of the collapsed level" /> <Action type="0" message="2345" wParam="0" lParam="0" sParam="" /> <Action type="0" message="2345" wParam="0" lParam="0" sParam="" /> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 2301:LineDownExtend: select the whole block" /> <Action type="0" message="2301" wParam="0" lParam="0" sParam="" /> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 2178:Copy" /> <Action type="0" message="2178" wParam="0" lParam="0" sParam="" /> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 2302:LineUp: back to the start of the block" /> <Action type="0" message="2302" wParam="0" lParam="0" sParam="" /> <Action type="0" message="2172" wParam="0" lParam="0" sParam="COMMENT: 44031 = IDM_VIEW_UNFOLD_CURRENT" /> <Action type="2" message="0" wParam="44031" lParam="0" sParam="" /> </Macro>
After recording the macro and assigning it to
Ctrl+Alt+Shift+C
, and restarting Notepad++, I added in comments to the macro inshortcuts.xml
to help it be understandable what’s going on, then I used that macro to grab the macro source out ofshortcuts.xml
, which made it really easy to prove that it was truly working after restart. ;-)So, instead of installing PythonScript and my almost-good-enough script, you could instead just record that macro yourself. Or, if you want, follow the sequence:
- Exit all Notepad++ and restart one instance
- Open
%AppData%\Notepad++\shortcuts.xml
(or your portable or cloud or alternate-location equivalentshortcuts.xml
) - In the
<Macros>
section of theshortcuts.xml
file, add the macro code from this post - Save, Exit Notepad++, and restart.
- From now on,
Ctrl+Alt+Shift+C
will run that macro to copy the current tag with its contents.- You can use Macro > Modify Shortcut/Delete Macro to change the shortcut if you don’t like mine.
That command might become part of my usage when copying macro source XML into the forum. ;-) So thanks, @Alan-Kilborn , for the simplified procedure, and to @arel3 for asking the question which prompted it.
–
*: Why twoAlt+Home
while recording the macro (aka, two 2345=SCI_HOMEDISPLAY in the macro source)? Because if your cursor happens to be inside of a folded section of text, the first SCI_HOMEDISPLAY takes you the end of the first (visible) line of the folded section, then the secondAlt+Home
takes you to the very beginning of the physical line.Alt+Home
will work if you have the default keyboard shortcuts; if you’ve modified it like I have, SCI_HOMEDISPLAY might be a different keystroke for you. -
@Alan-Kilborn , @arel3 , et alia,
The other benefit of the macro sequence, compared to Search > Select All Between Matching Braces or my earlier script, is that it will work for any foldable chunk of text, whether it’s the
{ ... }
code block in C/C++ and Perl, or the indented code block in Python, or the<tag>...</endtag>
in HTML/XML. It will even work in the simple UDL code-folding, if you have properly defined that in your UDL.So that’s graduated to being something that I think I might be able to incorporate into my workflow.
-
Wow, thank you @peterjones!
I do mainly use the program for markup languages. I had more expectation that being able to do this was already possible, not that someone would put the time in to write a script for it if it didn’t. Thank you!!!
I’m sure others can make use of this too since you’ve made it capable of working with any foldable text.Yes, that’s what I meant; foldable text seems to be the term I didn’t know.
-
@arel3,
Have you heard about the HTML Tag plugin? -
@rdipardo said in Copy entire code group?:
@arel3,
Have you heard about the HTML Tag plugin?This is the solution, thank you!