Maintain Indent While Pasting Multiple Lines
-
Sorry all… earlier in this thread I promised a script… and I haven’t delivered.
It IS more difficult to do a comprehensive solution on this than one would think – and sometimes that can keep one from finishing something to their own satisfaction.
Let me see what I can do about dumbing it down and at least getting something posted… -
Quite fresh code that uses PythonScript. Got the
getClipboardText()
idea from a post by @Alan-Kilborn .from Npp import editor, notepad import re def main(): def getClipboardText(): editor = notepad.createScintilla() editor.paste() editor.convertEOLs(eolMode) text = editor.getText() notepad.destroyScintilla(editor) return text def repl(m): if useTabs: result = m.group(1).replace(' ' * tabWidth, '\t') size = result.count('\t') else: result = m.group(1).replace('\t', ' ' * tabWidth) size = result.count(' ' * tabWidth) if not minSize[0] or size < minSize[0]: minSize[0] = size return result pos = editor.getCurrentPos() tabWidth = editor.getTabWidth() useTabs = editor.getUseTabs() eolMode = editor.getEOLMode() eol = ['\r\n', '\r', '\n'][eolMode] minSize = [0] text = getClipboardText() if not text: return parts = re.split(r'\r?\n', text) # Normalize indents. re_indents = re.compile(r'^([\t ]+)') for index, line in enumerate(parts): modified = re_indents.sub(repl, line) parts[index] = modified # Remove indents to flatten. tabLiteral = '\t' if useTabs else ' ' * tabWidth while minSize[0]: for index, line in enumerate(parts): parts[index] = line.replace(tabLiteral, '', 1) minSize[0] -= 1 # Add indents to finalize. line = editor.lineFromPosition(pos) indentSize = editor.getLineIndentation(line) while indentSize > 0: for index, line in enumerate(parts): if index > 0: parts[index] = tabLiteral + line indentSize -= tabWidth # Join lines and insert into editor. text = eol.join(parts) editor.insertText(pos, text) main()
It should get the text from the clipboard, normalize and flatten the indenting, add indents from position from the current editor line and then insert into the editor.
-
@Alan-Kilborn said in Maintain Indent While Pasting Multiple Lines:
Let me see what I can do about dumbing it down and at least getting something posted…
Here’s my entry; I call the script
PasteSpecialAtCurrentIndentColumn.py
.# -*- coding: utf-8 -*- from __future__ import print_function ######################################### # # PasteSpecialAtCurrentIndentColumn (PSACIC) # ######################################### # references: # https://community.notepad-plus-plus.org/topic/25295 # for newbie info on PythonScripts, see https://community.notepad-plus-plus.org/topic/23039/faq-desk-how-to-install-and-run-a-script-in-pythonscript #------------------------------------------------------------------------------- from Npp import * import os, re # script modified by moderator; see May 11 9:27p posting (by mpheath) below #------------------------------------------------------------------------------- try: editor3h # third editor, hidden except NameError: editor3h = notepad.createScintilla() #------------------------------------------------------------------------------- class PSACIC(object): def __init__(self): if not editor.canPaste() or not editor3h.canPaste(): return rect_sel_mode = editor.getSelectionMode() in [ SELECTIONMODE.RECTANGLE, SELECTIONMODE.THIN ] if rect_sel_mode: self.mb('Cannot use this paste-special when in column-block mode.') return if editor.getSelections() > 1: self.mb('Cannot use this paste-special when more than a single caret is active.') return if len(editor.getSelText()) > 0: self.mb('Cannot use this paste-special when text is selected.') return curr_pos = editor.getCurrentPos() curr_line_starting_pos = editor.positionFromLine(editor.lineFromPosition(curr_pos)) if curr_pos == curr_line_starting_pos: # caret at start of line; just do normal/simple paste editor.paste() return curr_line_text_before_caret = editor.getTextRange(curr_line_starting_pos, curr_pos) if re.search(r'\S', curr_line_text_before_caret): self.mb('Cannot use this paste-special at an insertion point with non-whitespace to its left.') return clipboard_text = self.clipboard_get_text() eol = [ '\r\n', '\r', '\n' ][editor.getEOLMode()] if eol not in clipboard_text: # no 2nd+ line(s) in data to paste, so nothing to adjust; just do normal/simple paste editor.paste() return clipboard_lines_list = clipboard_text.splitlines() # see if there's a common all-whitespace prefix on the clipboard lines m = re.search(r'^\s+', os.path.commonprefix(clipboard_lines_list)) common_ws_prefix_on_clipboard_lines = m.group(0) if m else '' # remove any common all-whitespace prefix on the clipboard lines clipboard_lines_list = [ re.sub('^' + common_ws_prefix_on_clipboard_lines, '', s) for s in clipboard_lines_list ] # prepend the current line's indentation whitespace to all of the clipboard lines: first_clipboard_line_text = clipboard_lines_list[0] # remember the first clipboard line before indenting all the rest # add new indentation to all of the modified clipboard lines except the first line: clipboard_lines_list = [ re.sub('^', curr_line_text_before_caret, s) for s in clipboard_lines_list[1:] ] clipboard_lines_list.insert(0, first_clipboard_line_text) # put the first line back into the list at its old position # do our modified "paste" and set new caret appropriately: old_doc_len = editor.getLength() editor.insertText(curr_pos, eol.join(clipboard_lines_list) + eol) # "paste" in the appropriately indented new text new_caret_pos = curr_pos + editor.getLength() - old_doc_len editor.setSel(new_caret_pos, new_caret_pos) # set caret at end of "pasted" text; scroll viewport if needed def clipboard_get_text(self, eol_mode=None): # if eol_mode arg is not-specified, use the current editor's eol mode: editor3h.setEOLMode(editor.getEOLMode() if eol_mode is None else eol_mode) editor3h.clearAll() editor3h.paste() retval = editor3h.getText() return retval def mb(self, msg, flags=0, title=''): # a message-box function return notepad.messageBox(msg, title if title else 'PSACIC', flags) #------------------------------------------------------------------------------- if __name__ == '__main__': PSACIC()
-
-
@Alan-Kilborn You probably have the
os
andre
modules imported in a previous script as your posted script does not import those modules and can produceNameError
tracebacks.Imports:
from Npp import * import os, re
Added near the top of the script fixes it.
The script looks interesting. The comparison between both scripts may suggest some ideas for improvement. ;)
-
@mpheath said in Maintain Indent While Pasting Multiple Lines:
You probably have the os and re modules imported
Ah, yea, sorry. :-(
That’s what I get for “hurrying” to catch up with you in getting something posted (but again, that’s an artifact of me not posting a script much earlier). I’d be surprised if there aren’t further errors in the script I posted. :-)
My
startup.py
does a lot of things, and imports os and re out of necessity there. I usually test scripts I’m going to post with a vanilla setup which would have discovered the error, but this time I didn’t. :-(
The comparison between both scripts may suggest some ideas for improvement
Yes, for sure!
Your script futzes with tab characters vs. space characters; in my script I chose to handle it this way (or, largely not-handle it):
- require any common leading whitespace on all lines in the clipboard to be identical for it to be removed
- use the leading whitespace on the line where the paste-special is invoked to be used “as is” when prepended to the clipboard lines
If users can’t get their tab vs. space indentation consistent (i.e., one way or the other)…I consider this “not my problem”.
-
-