Hi,
I am a recent convert to pn; I used to use Nedit, but I could not stand the bugs it seemed to have on Cygwin anymore.
In order to try out the Python script plug-in, I implemented the following three Nedit features which I use hundreds of times daily:
* indent current selection (Ctrl-0 in Nedit)
- moves all lines in the current selection one step to the right
* unindent current selection (Ctrl-9 in Nedit)
- moves all lines in the current selection one step to the left
* format paragraph (Ctrl-J in Nedit)
- spread out all words in the current paragraph using maximum line length, and as
little lines as possible
Some approximation of the first two exists already, namely right click on the selection and say Indent/Unindent, but (1) this goes by tab-distance, and (2) no keyboard shortcut can be added.
In the implementation below, I have also added a function for formatting the current selection. Formatting the current paragraph first selects the current paragraph and then formats it. Since I use LaTeX a lot, the format paragraph implementation tries to identify obvious paragraph separators (such as \begin, \end and so forth).
The reason for posting these is three-fold:
1. I wanted to share these scripts with all of you, sort of a pay-back for being allowed to use this great editor :-)
2. Please provide feedback on my implementation, I am not a Python programmer nor a Scintilla programmer. For example, I am not sure if this code actually works with all possible different newline encodings. (Is there one standard encoding that PN uses internally? That would make this kind of code a lot easier to write...)
3. I seem not to be able to use the functions GetLine and GetCurLine, so I implemented my own. Is this a bug?
/Koen
----
import pn
import scintilla
import string
from pypn.decorators import script
def ExpandSelectionToLines():
""" Expand the first and last lines of a possibly
partial selection to the full lines (by Koen) """
editor = scintilla.Scintilla(pn.CurrentDoc())
selStart = editor.SelectionStart
selEnd = editor.SelectionEnd
mx = editor.Length
# find first character of first line
while selStart > 0 and editor.GetText(selStart-1,selStart) != "\n":
selStart = selStart-1
# find last character of last line
while selEnd < mx and (editor.GetText(selEnd-1,selEnd) != "\n" or selStart == selEnd ) and editor.GetText(selEnd-1,selEnd) != "\r":
selEnd = selEnd+1
editor.SetSel(selStart,selEnd)
@script("Indent", "Text")
def Indent():
""" Indent the current selection with 1 space (by Koen) """
editor = scintilla.Scintilla(pn.CurrentDoc())
editor.BeginUndoAction()
ExpandSelectionToLines()
selStart = editor.SelectionStart
selEnd = editor.SelectionEnd
# find all lines
lsSelection = editor.GetText(selStart, selEnd)
laLines = lsSelection.splitlines(0)
# add a space to the beginning of each line
for i in range(len(laLines)):
laLines[i] = " " + laLines[i] + "\n"
lsReplace = string.join(laLines, "")
# replace and augment the selection
editor.ReplaceSel(lsReplace)
editor.SetSel(selStart,selEnd+len(laLines))
editor.EndUndoAction()
@script("Unindent", "Text")
def Unindent():
""" Unindent the current selection with 1 space (by Koen) """
editor = scintilla.Scintilla(pn.CurrentDoc())
editor.BeginUndoAction()
ExpandSelectionToLines()
selStart = editor.SelectionStart
selEnd = editor.SelectionEnd
# find all lines
lsSelection = editor.GetText(selStart, selEnd)
laLines = lsSelection.splitlines(0)
# remove a space from the beginning of each line
deleted = 0
for i in range(len(laLines)):
if len(laLines[i]) > 0 and laLines[i][0].expandtabs(1).isspace():
deleted = deleted+1
laLines[i] = laLines[i][1:len(laLines[i])]
laLines[i] = laLines[i] + "\n"
lsReplace = string.join(laLines, "")
# replace and shrink the selection
editor.ReplaceSel(lsReplace)
editor.SetSel(selStart,selEnd-deleted)
editor.EndUndoAction()
@script("Format Selection", "Text")
def FormatSelection(m = 78):
""" Format current selection (by Koen) """
editor = scintilla.Scintilla(pn.CurrentDoc())
editor.BeginUndoAction()
ExpandSelectionToLines()
selStart = editor.SelectionStart
selEnd = editor.SelectionEnd
# find all words
lsSelection = editor.GetText(selStart, selEnd)
laWords = lsSelection.rsplit()
# spread the words out over the lines
l=0
laLines = [""]
for w in range(len(laWords)):
if len(laLines[l]) + len(laWords[w]) + 1 > m and len(laLines[l]) > 0:
laLines[l] = laLines[l][0:-1] + "\n"
l = l+1
laLines = laLines + [""]
laLines[l] = laLines[l] + laWords[w] + " "
laLines[l] = laLines[l][0:-1] + "\n"
# replace the selection with the new lines
lsReplace = string.join(laLines, "")
editor.ReplaceSel(lsReplace)
editor.EndUndoAction()
def IsSeparator(s):
""" Check if s forms a separator between two paragraphs (by Koen) """
if s == "":
return True
if s.startswith("\\begin{"):
return True
if s.startswith("\\end{"):
return True
if s.startswith("$$"):
return True
if s.startswith("\\["):
return True
if s.startswith("\\]"):
return True
if s.startswith("%"):
return True
if s.expandtabs(1).isspace():
return True
return False
def Line(editor,line):
""" Implementation of editor.GetLine (which seems broken) (by Koen) """
pos = editor.PositionFromLine(line)
end = pos
mx = editor.Length
while end < mx and editor.GetText(end,end+1) != "\n" and editor.GetText(end,end+1) != "\r":
end = end+1
return editor.GetText(pos,end)
def SelectCurrentParagraph():
""" Select current paragraph (By Koen) """
editor = scintilla.Scintilla(pn.CurrentDoc())
editor.BeginUndoAction()
mx = editor.LineCount
here = editor.LineFromPosition(editor.CurrentPos)
if not IsSeparator(Line(editor,here)):
# find start of paragraph
while here > 0 and not IsSeparator(Line(editor,here-1)):
here = here-1
selStart = editor.PositionFromLine(here)
# find end of paragraph
while here < mx and not IsSeparator(Line(editor,here)):
here = here+1
selEnd = editor.PositionFromLine(here)
editor.SetSel(selStart, selEnd)
@script("Format Paragraph (width 78)", "Text")
def FormatParagraph78():
""" Format current paragraph (width 78) (By Koen) """
SelectCurrentParagraph()
FormatSelection(78)