/****************************** Module Header ******************************* * * Module Name: reflowmail.e * * Copyright (c) Netlabs EPM Distribution Project 2002 * * $Id$ * * =========================================================================== * * This file is part of the Netlabs EPM Distribution package and is free * software. You can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * Netlabs EPM Distribution. This library is distributed in the hope that it * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * ****************************************************************************/ /* Todo: OK Correct wrong-wrapped lines instead of taking them as a reply level change. - Reformat tables: 2 or more spaces (or a tab) specify a new col. Every cell is aligned to the left. Optionally: indented by 2 to disable reflow automatically. OK Replace tabs with spaces in normal text. - Handle special text sections: ? par (maybe replace parindent by parskip?) OK verbatim OK lists - tables (not recognized, must be indented) OK blank lines OK multiple spaces OK trailing spaces OK tabs (currently converted to 1 space in lists, kept in verbatim parts) Mail-specific: OK quote OK cite OK signature OK macro: correct reply level of false line wraps - Add for a "abc>" region: "abc wrote:" as a new line before with the name from the signature (first 3 words of first non-blank line after '-- ' line) - Remove salutation: (Hi|Hallo|Moin|...)[ *](,|!) - Add own signature - Replace "Re: Re:" with one "Re:" - In/decrease ReplyLevel - Add 'On ... * wrote:' - Remove indent (e.g. parindent, it will be replaced by a blank line). How to recognize parindents and distinguish it from verbatim regions, where the indent is essential? */ compile if not defined( SMALL) -- If compiled separately define INCLUDING_FILE = 'REFLOWMAIL.E' include 'stdconst.e' const tryinclude 'mycnf.e' const compile if not defined( NLS_LANGUAGE) NLS_LANGUAGE = 'ENGLISH' compile endif include NLS_LANGUAGE'.e' defmain 'ReflowMail' compile endif ; --------------------------------------------------------------------------- defproc MailGetReplyLevel( LineStr, var DequotedLineStr, var ThisReplyLevel, var ThisIndent) ThisReplyLevel = 0 ThisIndent = 0 QuoteCharList = '> : %' DefaultQuoteChar = '>' -- for XyZ> quotes fLastIsQuoteChar = 0 startp = 1 col = 1 do forever if col > length( LineStr) then leave endif next = substr( LineStr, col, 1) if next == '' then leave elseif next == ' ' then -- empty line or end of line ThisIndent = ThisIndent + 1 col = col + 1 -- Remove 1 possible space after QuoteChar if fLastIsQuoteChar then -- if last char was a QuoteChar startp = col fLastIsQuoteChar = 0 endif elseif pos( next, QuoteCharList) & (ThisReplyLevel > 0 | ThisIndent < 3) then ThisReplyLevel = ThisReplyLevel + 1 ThisIndent = 0 col = col + 1 startp = col fLastIsQuoteChar = 1 else -- for WXyZ> quote marks next = substr( LineStr, col, 5) -- 5: max 4 chars for name p1 = pos( DefaultQuoteChar, next) p2 = verify( '-=', next, 'M') -- don't count arrows as quote chars if p1 & (p2 = 0) then ThisReplyLevel = ThisReplyLevel + 1 col = col + p1 + 1 startp = col else -- standard text leave endif endif enddo DequotedLineStr = substr( LineStr, startp) -- line, stripped-off leading QuoteChars return ; --------------------------------------------------------------------------- ; Reflow previous paragraph. defproc MailReflowMarkedLines( PrevParReplyLevel, PrevPrevParReplyLevel, PrevListIndent, PrevBullet, fEnableReflow, fReflowLists) KeyPath = '\NEPMD\User\Mode\TEXT\Reflow\Mail\Lists' NewListIndent = QueryConfigKey( KeyPath'\Indent') QuoteChar = '>' SpaceAmount = 1 -- Spaces after QuoteChar RightMargin = 72 - length( QuoteChar) - SpaceAmount -- Set margins to max to avoid any automatic line break .margins = 1 1599 1 -- Go to begin mark getmark FirstLine, LastLine, FirstCol, LastCol, Fid dprintf( 'REFLOWMAIL', '2.1: Starting with cursor on line '.line' after the mark') -- Remove bullet chars if PrevBullet <> '' then pReplaceLine( overlay( copies( ' ', length( PrevBullet)), textline( FirstLine)), FirstLine) endif -- Set margins for reflow, substract indent if PrevParReplyLevel > 0 then PrevIndent = PrevParReplyLevel + SpaceAmount else PrevIndent = 0 endif NewListIndent = PrevListIndent if fReflowLists then lma = NewListIndent + 1 rma = RightMargin - PrevIndent - NewListIndent pma = lma else lma = 1 rma = RightMargin - PrevIndent -- Indent the bullet line to the length of the bullet chars plus -- following spaces. Also the original indent is kept. pma = NewListIndent + 1 endif .margins = lma rma pma if fEnableReflow then -- Reflow marked lines, starting at cursored line dprintf( 'REFLOWMAIL', '2.2: Reflowing lines no 'FirstLine' to 'LastLine', Indent = 'PrevIndent', ListIndent = 'PrevListIndent', Bullet = ['PrevBullet']') reflow else -- Don't reflow marked lines, starting at cursored line dprintf( 'REFLOWMAIL', '2.3: Not reflowing lines no 'FirstLine' to 'LastLine) endif -- Set margins to max to avoid any automatic line break .margins = 1 1599 1 -- Mark after reflow getmark FirstLine, LastLine, FirstCol, LastCol, Fid -- Re-add bullet chars if PrevBullet <> '' then pReplaceLine( overlay( PrevBullet, textline( FirstLine)), FirstLine) endif -- Insert quote chars in every marked line -- Start with prev line if it is a blank line to calc reply level -- and add quote chars StartLine = FirstLine if FirstLine > 1 then getline LineStr, FirstLine - 1 if strip( LineStr) = '' then StartLine = FirstLine - 1 endif endif do l = StartLine to LastLine getline LineStr, l if strip( LineStr) = '' then dprintf( 'REFLOWMAIL', '2.4: * ReplyLevel PrevPar/PrevPrevPar = 'PrevParReplyLevel'/'PrevPrevParReplyLevel) BlankLineReplyLevel = Min( PrevParReplyLevel, PrevPrevParReplyLevel) if BlankLineReplyLevel > 0 then pReplaceLine( copies( QuoteChar, BlankLineReplyLevel)''copies( ' ', SpaceAmount)''LineStr, l) dprintf( 'REFLOWMAIL', '2.5: * LineStr = ['textline( l)']') endif elseif PrevParReplyLevel > 0 then -- Prepend quote chars and space pReplaceLine( copies( QuoteChar, PrevParReplyLevel)''copies( ' ', SpaceAmount)''LineStr, l) dprintf( 'REFLOWMAIL', '2.6: * LineStr = ['textline( l)']') endif enddo dprintf( 'REFLOWMAIL', '2.7: Cursor is now on line no '.line': ['textline( .line)']') return ; --------------------------------------------------------------------------- ; Check if current line is verbatim. defproc MailIsVerbatim( DequotedLineStr) StrippedDequotedLineStr = strip( DequotedLineStr, 'l') FirstChar = substr( StrippedDequotedLineStr, 1, 1) fIndentedIsVerbatim = (arg( 2) = 1) -- Flag; if 1: recognize every indented line as verbatim fIsVerbatim = 0 -- Empty line if StrippedDequotedLineStr = '' then -- not verbatim -- External quotes elseif FirstChar = '|' then fIsVerbatim = 1 -- Indented line with indent >= 1 elseif (substr( DequotedLineStr, 1, 1) = ' ' | substr( DequotedLineStr, 1, 1) = \9) & fIndentedIsVerbatim then fIsVerbatim = 1 endif return fIsVerbatim ; --------------------------------------------------------------------------- ; Check if current line starts a list item and set vars for it. defproc MailIsListItem( DequotedLineStr, var ThisListIndent, var ThisBullet) StrippedDequotedLineStr = strip( DequotedLineStr, 'l') fIsListItem = 0 ThisBullet = '' SavedListIndent = ThisListIndent Blanks = ' '\9 KeyPath = '\NEPMD\User\Mode\TEXT\Reflow\Mail\Lists' BulletWords = QueryConfigKey( KeyPath'\BulletWords') NumericSepWords = QueryConfigKey( KeyPath'\NumericSepWords') NumericEndWords = QueryConfigKey( KeyPath'\NumericEndWords') /* BulletWords = '- o * +' NumericSepWords = '. -' NumericEndWords = '. ) - /' */ BulletWords = EscapeSearchChars( BulletWords) NumericSepWords = EscapeSearchChars( NumericSepWords) NumericEndWords = EscapeSearchChars( NumericEndWords) if not fIsListItem then -- Unordered lists -- ^(-|o|\*|\+):w -- ~~~~~~~~~~~ -- BulletStr -- :w = whitespace BulletStr = '' do w = 1 to words( BulletWords) Bullet = strip( word( BulletWords, w)) if length( BulletStr) > 0 then BulletStr = BulletStr'|' endif BulletStr = BulletStr''Bullet enddo BulletStr = '('BulletStr')' SearchStr = '^'BulletStr':w' ret = pos( SearchStr, StrippedDequotedLineStr, 1, 'XM') ThisListIndent = 0 do once = 1 to 1 if rc then leave endif parse value ret with p1 p2 . if not p1 = 1 then leave endif if not p2 then leave endif ThisListIndent = p2 ThisBullet = strip( translate( leftstr( StrippedDequotedLineStr, p2), ' ', \9)) fIsListItem = 1 enddo endif if not fIsListItem then -- Ordered lists -- This finds also words followed by '.': -- ^:n+((\.|-)*:n)*(\.|\)|-):w -- ~~~~~~ ~~~~~~~~~ -- NumericSepStr NumericEndStr -- Can't use ':n+' or ':n*'. -- :n = [a-zA-Z0-9] -- :d = [0-9] -- :i = [0-9]+ -- :a = [a-zA-Z] -- :w = whitespace -- -- This works: -- ^(:a|:i|:i:a)(:i|((\.|-):a|:a|(\.|-)):i|((\.|-):i|:i|(\.|-)):a)*(\.|\)|-):w -- In parts: -- ^ -- (:a|:i|:i:a) -- ( -- :i -- | -- ((\.|-):a|:a|(\.|-)):i -- | -- ((\.|-):i|:i|(\.|-)):a -- )* -- (\.|\)|-) -- :w /* Examples) not found, OK 1 not found, OK 1. 1. found, OK previous line not found, because no whitespace following, OK 23- found, OK A) found, OK A22) found, OK A-22) found, OK A-22b) found, OK A-22.3b-c) found, OK 5b. found, OK 5b-c1.a- found, OK 6-77-8. found, OK abc. not found, OK A-abc. not found, OK A-a5. found, OK A-5a. found, OK 3AB-C7. not found, OK */ NumericSepStr = '' do w = 1 to words( NumericSepWords) NumericSep = strip( word( NumericSepWords, w)) if length( NumericSepStr) > 0 then NumericSepStr = NumericSepStr'|' endif NumericSepStr = NumericSepStr''NumericSep enddo NumericSepStr = '('NumericSepStr')' NumericEndStr = '' do w = 1 to words( NumericEndWords) NumericEnd = strip( word( NumericEndWords, w)) if length( NumericEndStr) > 0 then NumericEndStr = NumericEndStr'|' endif NumericEndStr = NumericEndStr''NumericEnd enddo NumericEndStr = '('NumericEndStr')' SearchStr = '^(:a|:i|:i:a)(:i|('NumericSepStr':a|:a|'NumericSepStr'):i|('NumericSepStr':i|:i|'NumericSepStr'):a)*'NumericEndStr':w' ret = pos( SearchStr, StrippedDequotedLineStr, 1, 'XM') ThisListIndent = 0 do once = 1 to 1 if rc then leave endif parse value ret with p1 p2 . if not p1 = 1 then leave endif if not p2 then leave endif ThisListIndent = p2 ThisBullet = strip( translate( leftstr( StrippedDequotedLineStr, p2), ' ', \9)) fIsListItem = 1 enddo endif if fIsListItem then -- Prepend spaces from from DequotedLineStr -- (above is StrippedDequotedLineStr processed) SpacesBeforeBullet = Max( verify( DequotedLineStr, Blanks) - 1, 0) ThisBullet = copies( ' ', SpacesBeforeBullet)''ThisBullet ThisListIndent = SpacesBeforeBullet + ThisListIndent else ThisListIndent = SavedListIndent endif return fIsListItem ; --------------------------------------------------------------------------- defproc MailAskToFixFalseReply QuoteChar = '>' SpaceAmount = 1 Title = 'Fix false reply level' FixNum = arg( 1) if FixNum then Title = Title FixNum':' endif Title = Title .line - 1' -> '.line Title = Title''copies( ' ', 20) Text = 'Lines '.line - 1' and '.line' may be eranously split:' PrevReplyLevel = arg( 2) if PrevReplyLevel = '' then PrevReplyLevel = 0 endif ThisReplyLevel = arg( 3) if ThisReplyLevel = '' then ThisReplyLevel = 0 endif TabWidth = word( .tabs, 1) LineNumLen = length( .last) getmark FirstLine, LastLine, FirstCol, LastCol, Fid Entries = '' -- Add previous par (marked lines) ReplyLevel = PrevReplyLevel do l = FirstLine to LastLine -- Get line and convert tabs to spaces LineStr = TabExpandStr( textline( l), TabWidth, fChanged) -- Prepend quote chars and space LineStr = copies( QuoteChar, ReplyLevel)''copies( ' ', SpaceAmount)''LineStr -- Append to Entries Entries = Entries''\1''rightstr( l, LineNumLen)': 'LineStr enddo -- Add this line ReplyLevel = ThisReplyLevel l = .line -- Get line and convert tabs to spaces LineStr = TabExpandStr( textline( l), TabWidth, fChanged) -- Prepend quote chars and space LineStr = copies( QuoteChar, ReplyLevel)''copies( ' ', SpaceAmount)''LineStr -- Append to Entries Entries = Entries''\1''rightstr( l, LineNumLen)': 'LineStr -- Add following 3 lines -- Quote char are not stripped do l = .line + 1 to .line + 3 -- Get line and convert tabs to spaces LineStr = TabExpandStr( textline( l), TabWidth, fChanged) -- Append to Entries Entries = Entries''\1''rightstr( l, LineNumLen)': 'LineStr enddo -- Better mark the current line like in the list box unmark markline DefaultItem = LastLine - FirstLine + 2 HelpId = 0 DefaultButton = 1 Buttons = '/~Append (fix)/~Reply (add empty line)/'CANCEL__MSG refresh Button = 0 Result = ListBox( Title, Entries, Buttons, 0, 0, -- top, left in chars, (0,0) = center, (C,C) = cursor 4, 120, -- height, width in chars GethWndC( APP_HANDLE) || atoi( DefaultItem) || atoi( DefaultButton) || atoi( HelpId) || Text\0) refresh -- Check result parse value Result with Button 2 Selected \0 -- Note: Selected line is ignored Button = asc( Button) -- Restore previous mark setmark FirstLine, LastLine, FirstCol, LastCol, 0, Fid return Button ; --------------------------------------------------------------------------- ; Get quote level for current line, ignore all spaces in between. ; Get quote level for next line... ; Stop at a line with a different quote level. Don't count blank lines. Get ; new level. ; Set margins to 1 1599 1. ; Go back to prev section and remove all quote chars for that section. ; Reformat that section. ; Add quote chars in every line of this section. ; Insert a new line. ; Strip blank lines. ; Handle parindents: Add a blank line if one line has a different indent ; or just keep the indent (and remove only 1 optional ; space after the prev '>'). defc ReflowMail universal InfolineRefresh universal vepm_pointer mouse_setpointer WAIT_POINTER saved_autosave = .autosave .autosave = 0 saved_modify = .modify call NextCmdAltersText() InfolineRefresh = 0 fPrevLineIsBlank = 0 fPrevLineIsVerbatim = 0 PrevReplyLevel = 0 PrevPrevReplyLevel = 0 PrevParReplyLevel = 0 PrevPrevParReplyLevel = 0 fNoReflow = 0 fNewPar = 0 SigReplyLevel = 0 ThisIndent = 0 PrevIndent = 0 ThisReplyLevel = 0 fReflow = 0 fSig = 0 ThisBullet = '' PrevBullet = '' ThisListIndent = 0 PrevListIndent = 0 AppendChangeReplyLevel = 0 AppendPrevReplyLevel = 0 fCancel = 0 FixNum = 0 KeyPath = '\NEPMD\User\Mode\TEXT\Reflow' Indented = QueryConfigKey( KeyPath'\Mail\Indented') fKeepIndented = (upcase( leftstr( Indented, 1)) = 'K') fReflowLists = QueryConfigKey( KeyPath'\Mail\Lists') fIndentedIsVerbatim = fKeepIndented .line = 1 .col = 1 unmark TabWidth = word( .tabs, 1) -- Add a blank line after last to make reflow of the last par easy. -- Blank lines will be removed from the end. if strip( textline( .last)) <> '' then insertline '', .last + 1 endif do i = 1 to 1000 -- Just an upper limit, instead of forever fThisLineIsBlank = 0 fThisLineIsVerbatim = 0 getline LineStr LineStr = TabExpandStr( LineStr, TabWidth, fChanged) -- GetReplyLevel() and strip quote chars from line call MailGetReplyLevel( LineStr, DequotedLineStr, ThisReplyLevel, ThisIndent) -- Fix signature mark: append missing space if DequotedLineStr == '--' then DequotedLineStr = '-- ' pReplaceLine( DequotedLineStr) endif -- Recognize signature mark if DequotedLineStr == '-- ' then fSig = 1 -- Get ReplyLevel for signature mark SigReplyLevel = ThisReplyLevel endif if fSig then if SigReplyLevel = 0 then -- Keep signature until end if .line = .last then leave else iterate endif elseif DequotedLineStr = '' | ThisReplyLevel < SigReplyLevel then -- End quoted signature at blank line or at reply fSig = 0 else -- Remove quoted signatures until blank line deleteline if .line = .last then leave else iterate endif endif endif -- Strip off trailing spaces DequotedLineStr = strip( DequotedLineStr, 'T', ' ') -- Temp. write the line without the stripped quote chars pReplaceLine( DequotedLineStr) dprintf( 'REFLOWMAIL', '1.1: Line no '.line': # fPrevLineIsBlank = 'fPrevLineIsBlank', ReplyLevel = 'ThisReplyLevel', LineStr = ['LineStr']') -- Reset appended lines on blnk line or on reply level change if AppendPrevReplyLevel then if DequotedLineStr = '' | ThisReplyLevel <> AppendChangeReplyLevel then AppendChangeReplyLevel = 0 AppendPrevReplyLevel = 0 endif endif -- Append this line if AppendPrevReplyLevel then ThisReplyLevel = AppendPrevReplyLevel -- If a blank line elseif DequotedLineStr = '' then -- If previous line is blank if fPrevLineIsBlank then deleteline if .line = .last then leave else iterate endif -- If previous line is not blank else fThisLineIsBlank = 1 fNewPar = 1 -- reflow prev par -- Keep previous list indent level to enable pars in list items endif -- If not a blank line else if ThisReplyLevel = PrevReplyLevel then -- If ReplyLevel hasn't changed fNewPar = 0 elseif not fNoReflow then -- Reflow prev par if ReplyLevel has changed if fPrevLineIsBlank then fNewPar = 0 elseif ThisReplyLevel > PrevReplyLevel then -- Change back from DequotedLineStr to LineStr, because this -- is processed again by MailGetReplyLevel. The blank line -- then sets fNewPar to reflow the last par. After this line -- is processed again, the blank line is reflowed (quote chars -- prpended). pReplaceLine( LineStr) -- Insert a blank line and move cursor up insertline '', .line dprintf( 'REFLOWMAIL', '1.2: Line no '.line': blank line inserted before current line') .line = .line - 1 iterate elseif ThisReplyLevel < PrevReplyLevel then -- Ask to fix false wrap or if this is a reply if QueryConfigKey( KeyPath'\Mail\FixFalseReplyLevels') then FixNum = FixNum + 1 Button = MailAskToFixFalseReply( FixNum, PrevReplyLevel, ThisReplyLevel) else Button = 2 endif if Button = 1 then -- Append -- Save reply levels AppendChangeReplyLevel = ThisReplyLevel AppendPrevReplyLevel = PrevReplyLevel -- Append this line ThisReplyLevel = AppendPrevReplyLevel elseif Button = 2 then -- Reply -- Change back from DequotedLineStr to LineStr, because this -- is processed again by MailGetReplyLevel. The blank line -- then sets fNewPar to reflow the last par. After this line -- is processed again, the blank line is reflowed (quote chars -- prepended). pReplaceLine( LineStr) -- Insert a blank line and move cursor up insertline '', .line .line = .line - 1 ThisReplyLevel = PrevReplyLevel iterate else fCancel = 1 leave endif endif endif -- Check if this line is a list item or if verbatim SavedBullet = ThisBullet fIsNewListItem = MailIsListItem( DequotedLineStr, ThisListIndent, ThisBullet) fIsContinuedListItem = (ThisListIndent > 0 & -- indent for current line is last list indent ((ThisIndent = ThisListIndent) | -- allow 1 optional space after the quote chars (ThisIndent = ThisListIndent + 1 & ThisReplyLevel > 0))) fIsVerbatim = MailIsVerbatim( DequotedLineStr, fIndentedIsVerbatim) dprintf( 'REFLOWMAIL', '1.4: Line no '.line': fIsNewListItem = 'fIsNewListItem', fIsContinuedListItem = 'fIsContinuedListItem', fIsVerbatim = 'fIsVerbatim) if fNoReflow then -- Ignore elseif fIsNewListItem then fNewPar = 1 dprintf( 'REFLOWMAIL', '1.5: Line no '.line': New list item: ThisListIndent = 'ThisListIndent) elseif fIsContinuedListItem then -- Continuing list item -- The bullet from the new item line must be restored, because the -- check for it has just reset it. The bullet char will be handled -- specially for the first marked line only and if it's not empty. ThisBullet = SavedBullet dprintf( 'REFLOWMAIL', '1.6: Line no '.line': Continued list item: ThisListIndent = 'ThisListIndent) elseif fIsVerbatim then fThisLineIsVerbatim = 1 ThisListIndent = 0 dprintf( 'REFLOWMAIL', '1.7: Line no '.line': Verbatim line: fThisLineIsVerbatim = 'fThisLineIsVerbatim) elseif ThisIndent <> PrevIndent | (ThisIndent <> PrevListIndent & PrevListIndent > 0) then -- Different indent ends list item and starts new par ThisListIndent = 0 fNewPar = 1 dprintf( 'REFLOWMAIL', '1.8: Line no '.line': Different indent: This/Prev/PrevList = 'ThisIndent'/'PrevIndent'/'PrevListIndent) endif if not fPrevLineIsBlank then -- Reflow last par if this or prev line is a verbatim line if fThisLineIsVerbatim | fPrevLineIsVerbatim then fNewPar = 1 endif endif endif dprintf( 'REFLOWMAIL', '1.9: Line no '.line': ReplyLevel this/prev/prevprev = 'ThisReplyLevel'/'PrevReplyLevel'/'PrevPrevReplyLevel', fNewPar = 'fNewPar) -- Reflow previous par if fNewPar then PrevPrevParReplyLevel = PrevParReplyLevel -- quote level for prevprev par PrevParReplyLevel = PrevReplyLevel -- quote level for prev par if FileIsMarked() then -- if marked dprintf( 'REFLOWMAIL', '1.10: Line no '.line': fPrevLineIsVerbatim = 'fPrevLineIsVerbatim', fNoReflow = 'fNoReflow) --fReflow = (not fPrevLineIsVerbatim) & (not fNoReflow) fReflow = (not fPrevLineIsVerbatim) call MailReflowMarkedLines( PrevParReplyLevel, PrevPrevParReplyLevel, PrevListIndent, PrevBullet, fReflow, fReflowLists) unmark endif endif if not fThisLineIsBlank then markline endif if .line = .last then leave else .line = .line + 1 .col = 1 endif -- Save vars for current line. In case of a reflow if a new par was -- indented, these values are submitted to MailReflowMarkedLines. fPrevLineIsBlank = fThisLineIsBlank fPrevLineIsVerbatim = fThisLineIsVerbatim PrevIndent = ThisIndent PrevListIndent = ThisListIndent PrevBullet = ThisBullet if fThisLineIsBlank = 0 then PrevPrevReplyLevel = PrevReplyLevel PrevReplyLevel = ThisReplyLevel endif enddo -- i if not fCancel then -- Remove last blank lines do while textline( .last) = '' deleteline .last enddo endif InfolineRefresh = 1 .autosave = saved_autosave if .modify > saved_modify then .modify = saved_modify + 1 endif mouse_setpointer vepm_pointer return