Community
    • Login

    Copy entire code group?

    Scheduled Pinned Locked Moved Help wanted · · · – – – · · ·
    11 Posts 5 Posters 4.8k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • arel3A
      arel3
      last edited by

      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:

      1. Place the cursor on the line we want to start the selection at.
      2. Right-click and select Begin/End Selection.
      3. Click on the <tag> so it highlights where that tag closes.
      4. 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?

      1 Reply Last reply Reply Quote 0
      • guy038G
        guy038
        last edited by

        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 and Wrap 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 themselves

        Best Regards,

        guy038

        arel3A 1 Reply Last reply Reply Quote 2
        • arel3A
          arel3 @guy038
          last edited by

          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>?

          PeterJonesP 1 Reply Last reply Reply Quote 0
          • PeterJonesP
            PeterJones @arel3
            last edited by PeterJones

            @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.

            PeterJonesP 1 Reply Last reply Reply Quote 1
            • PeterJonesP
              PeterJones @PeterJones
              last edited by

              @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.

              1. Install PythonScript plugin if not already installed (Plugins > Plugins Admin, check Python Script then click Install
              2. Create a new script in PythonScript (Plugins > PythonScript > New script, name = select-current-tag.py)
              3. Paste the contents from below and save
              4. Run Plugins > PythonScript > Scripts > select-current-tag to run the script.
              5. If you want to assign a keyboard shortcut:
                1. Plugins > PythonScript > Configuration…
                2. 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)
                3. OK
                4. Exit and Restart Notepad++
                5. Use Settings > Shortcut Mapper, Plugins tab; filter on Python; the Modify the shortcut for select-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 divs, it might not do what you desire. But Guy made the same assumption, and most HTML tags don’t nest anyway, so except for div 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()
              
              1 Reply Last reply Reply Quote 0
              • PeterJonesP PeterJones referenced this topic on
              • Alan KilbornA
                Alan Kilborn
                last edited by

                @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:
                  76f507b8-3d72-45e7-96f4-94f290f93218-image.png
                • 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):
                  5e71ac98-5bf2-42cc-af27-bf84a2954377-image.png
                • 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.
                PeterJonesP 1 Reply Last reply Reply Quote 2
                • PeterJonesP
                  PeterJones @Alan Kilborn
                  last edited by PeterJones

                  @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 in shortcuts.xml to help it be understandable what’s going on, then I used that macro to grab the macro source out of shortcuts.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:

                  1. Exit all Notepad++ and restart one instance
                  2. Open %AppData%\Notepad++\shortcuts.xml (or your portable or cloud or alternate-location equivalent shortcuts.xml)
                  3. In the <Macros> section of the shortcuts.xml file, add the macro code from this post
                  4. Save, Exit Notepad++, and restart.
                  5. 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 two Alt+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 second Alt+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.

                  PeterJonesP 1 Reply Last reply Reply Quote 2
                  • PeterJonesP
                    PeterJones @PeterJones
                    last edited by

                    @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.

                    arel3A 1 Reply Last reply Reply Quote 2
                    • arel3A
                      arel3 @PeterJones
                      last edited by

                      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.

                      rdipardoR 1 Reply Last reply Reply Quote 1
                      • rdipardoR
                        rdipardo @arel3
                        last edited by

                        @arel3,
                        Have you heard about the HTML Tag plugin?

                        npp_841_x64_htmltag_v123.gif

                        arel3A 1 Reply Last reply Reply Quote 2
                        • arel3A
                          arel3 @rdipardo
                          last edited by

                          @rdipardo said in Copy entire code group?:

                          @arel3,
                          Have you heard about the HTML Tag plugin?

                          npp_841_x64_htmltag_v123.gif

                          This is the solution, thank you!

                          1 Reply Last reply Reply Quote 0
                          • First post
                            Last post
                          The Community of users of the Notepad++ text editor.
                          Powered by NodeBB | Contributors