[comp.unix.questions] Paragraph reform in vi

jim@bilpin.UUCP (JimG) (06/20/89)

    #{ v_unix.3 }
    [ Long - about 280 lines ]

    IN ARTICLE <19890@adm.BRL.MIL>, thoyt@ddn-wms.arpa (Thomas Hoyt)
    WRITES:
>   Can vi reformat a paragraph, justifying it properly(left, right, or 
>   centered)?

    IN ARTICLE <13816@dartvax.Dartmouth.EDU>, andyb@coat.com (Andy Behrens)
    WRITES:
>   If you put the following line in your .exrc file, you can type "V" when
>   the cursor is anywhere in a paragraph, and the entire paragraph will be
>   rejustified by being piped through "fmt".
>   	map V   0}!{fmt^M}

    For those, like me, who don't have fmt on their system, here are three
    Bourne shell scripts which use awk to effect the required reformatting.
    Use the usual vi ! operator to execute them, preceded by an optional
    count, and succeeded by a context marker, ) for sentences, } for
    paragraphs, and G for lines ( remember that vi requires two spaces after
    a full stop to delimit a sentence ). The reformat scripts preserve blank
    lines, so multiple paragraphs can be altered at once; lines can also be
    protected by appending a CTRL M character, so a chunk of text can be
    reformatted while leaving certain lines within it unchanged; optional
    line width and left margin size can be specified, otherwise defaults are
    taken from specified system variables, or assumed. See the detailed
    comments at the start of each script for more information.

    The following scripts are delimited by lines of +'s.

#+ START OF centre + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 

#{ centre 1.2 }				Last update: 20-Apr-89 11:44
#  Author:   Jim Grimwood, Hatfield, England
#  CENTRE TEXT TO SPECIFIED MARGIN
#  Syntax  : centre  [arg1] [arg2]
#  Optional arguments are :
#	arg1	line size	( range 10 - 136 )
#	arg2	margin size	( range 0 - 40 )
#  Line/margin arguments on the command line are given priority; if not
#  defined, shell environment variables PSIZE/PMARG are used; if not defined,
#  defaults of 80/0 are used. Errors in any arguments supplied are trapped
#  silently and defaults substituted.
#  NOTE:     Assumes tabstep to be 8.
#  WARNING!: Avoid embedded apostrophes in awk comments!

PSIZE=${1:-$PSIZE}
PMARG=${2:-$PMARG}
if [ "$PSIZE" -lt 10 -o "$PSIZE" -gt 136 ]
then 
  PSIZE=80
fi
# The next line *shouldn't* be -lt 0 ( rather than -lt 1 ), because then
# invalid alphanumeric arguments wouldn't be trapped
if [ "$PMARG" -lt 1 -o "$PMARG" -gt 40 ]
then 
  PMARG=0
fi

#  Convert embedded tabs to spaces first, to get 'length' char count correct
pr -e -t | \
awk '
BEGIN	{ SPACES = "                                        " 	# 40 spaces
	  TABS   = "					"	#  5 tabs
	}
	{ TEXT 	 = substr( $0, index( $0, $1 ) )  # Omit leading white space
	  OFFSET = PMARG + int( ( PSIZE - length( TEXT ) ) / 2 )
	  CT	 = int( OFFSET / 8 )
	  CB	 = OFFSET - 8 * CT
	  print substr( TABS, 1, CT ) substr( SPACES, 1, CB ) TEXT
	} ' PSIZE=$PSIZE PMARG=$PMARG

#+ END OF centre + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 

#+ START OF reform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 

#{ reform 1.2 }				Last update: 20-Apr-89 11:55
#  Author:   Jim Grimwood, Hatfield, England
#  REFORMAT TEXT TO SPECIFIED LINE LENGTH / MARGIN - RAGGED RIGHT
#  Syntax  : reform  [arg1] [arg2]
#  Optional arguments are :
#	arg1	line size	( range 10 - 136 )
#	arg2	margin size	( range 0 - 40 )
#  Line/margin arguments on the command line are given priority; if not
#  defined, shell environment variables PSIZE/PMARG are used; if not defined,
#  defaults of 80/0 are used. Errors in any arguments supplied are trapped
#  silently and defaults substituted.
#  Multiple paragraphs in the context area are re-formatted individually and
#  kept separate; if you always want the context area to be merged into a
#  single paragraph, then remove the clause 'NF == 0 ||' on the line marked 
#  '# <<<<' in the main awk program below.
#  Lines marked with a CTRL M as the last character will print unchanged
#  (useful for headings, embedded tables, and so on). 
#  NOTE:     Assumes tabstep to be 8.
#  WARNING!: Avoid embedded apostrophes in awk comments!

PSIZE=${1:-$PSIZE}
PMARG=${2:-$PMARG}
if [ "$PSIZE" -lt 10 -o "$PSIZE" -gt 136 ]
then 
  PSIZE=80
fi
# The next line *shouldn't* be -lt 0 ( rather than -lt 1 ), because then
# invalid alphanumeric arguments wouldn't be trapped
if [ "$PMARG" -lt 1 -o "$PMARG" -gt 40 ]
then 
  PMARG=0
fi

awk '

BEGIN	{ SPACES = "                                        "	# 40 spaces	
	  TABS   = "					"	#  5 tabs
	}

# I`d like to be able to do the next bit in the BEGIN, but awk pretends not
# to know about the command line arguments at that time
NR == 1	{ PSIZE  = int( PSIZE )			# coerce to type int
	  CT	 = int( PMARG / 8 )
	  CB	 = PMARG - 8 * CT
	  MARGIN = substr( TABS,1,CT ) substr( SPACES,1,CB ) # set left margin
	}

	# The next un-commented line may display partially overprinted on
	# your terminal, except in vi; the line should read (minus the #):
	# { if( NF == 0 || substr( $0, length ) == "^M" )
	# where the character in quotes is a single-byte CTRL M
	{ if( NF == 0 || substr( $0, length ) == "
	  { if( TEXT > "" )
	    { print MARGIN TEXT
	      TEXT = ""
	    }
	    print 
	    next
	  }
	  $1 = $1				# compress white space
	  # discard leading white space and append to previous text, if any
	  if( length( TEXT ) == 0 )
	    TEXT = substr( $0, index( $0, $1 ) )
	  else
	    TEXT = TEXT " " substr( $0, index( $0, $1 ) )
	  while( length( TEXT ) > PSIZE )
	  { for( L = PSIZE+1; L > 0; L-- )	# find last word which fits
	      if( substr( TEXT, L, 1 ) == " " )
	        break
	    print MARGIN substr( TEXT, 1, L-1 )
	    TEXT = substr( TEXT, L+1 )
	  }
	}

END	{ print MARGIN TEXT } 

' PSIZE=$PSIZE PMARG=$PMARG

#+ END OF reform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 

#+ START OF rjust + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 

#{ rjust 1.2 }				Last update: 20-Apr-89 11:55
#  Author:   Jim Grimwood, Hatfield, England
#  REFORMAT TEXT TO SPECIFIED LINE LENGTH / MARGIN - ALIGNED TO BOTH MARGINS
#  Syntax  : rjust  [arg1] [arg2]
#  Optional arguments are :
#	arg1	line size	( range 10 - 136 )
#	arg2	margin size	( range 0 - 40 )
#  Line/margin arguments on the command line are given priority; if not
#  defined, shell environment variables PSIZE/PMARG are used; if not defined,
#  defaults of 80/0 are used. Errors in any arguments supplied are trapped
#  silently and defaults substituted.
#  Multiple paragraphs in the context area are re-formatted individually and
#  kept separate; if you always want the context area to be merged into a
#  single paragraph, then remove the clause 'NF == 0 ||' on the line marked 
#  '# <<<<' in the main awk program below.
#  Lines marked with a CTRL M as the last character will print unchanged
#  (useful for headings, embedded tables, and so on).
#  NOTE:     Assumes tabstep to be 8.
#  WARNING!: Avoid embedded apostrophes in awk comments!

PSIZE=${1:-$PSIZE}
PMARG=${2:-$PMARG}
if [ "$PSIZE" -lt 10 -o "$PSIZE" -gt 136 ]
then 
  PSIZE=80
fi
# The next line *shouldn't* be -lt 0 ( rather than -lt 1 ), because then
# invalid alphanumeric arguments wouldn't be trapped
if [ "$PMARG" -lt 1 -o "$PMARG" -gt 40 ]
then 
  PMARG=0
fi

awk '

BEGIN	{ SPACES = "                                        "	# 40 spaces	
	  TABS   = "					"	#  5 tabs
	}

# I`d like to be able to do the next bit in the BEGIN, but awk pretends not
# to know about the command line arguments at that time
NR == 1	{ PSIZE  = int( PSIZE )			# coerce to type int
	  CT	 = int( PMARG / 8 )
	  CB	 = PMARG - 8 * CT
	  MARGIN = substr( TABS,1,CT ) substr( SPACES,1,CB ) # set left margin
	}

	# The next un-commented line may display partially overprinted on
	# your terminal, except in vi; the line should read (minus the #):
	# { if( NF == 0 || substr( $0, length ) == "^M" )
	# where the character in quotes is a single-byte CTRL M
	{ if( NF == 0 || substr( $0, length ) == "
	  { if( TEXT > "" )
	    { print MARGIN TEXT
	      TEXT = ""
	    }
	    print 
	    next
	  }
	  $1 = $1				# compress white space
	  # discard leading white space and append to previous text, if any
	  if( length( TEXT ) == 0 )
	    TEXT = substr( $0, index( $0, $1 ) )
	  else
	    TEXT = TEXT " " substr( $0, index( $0, $1 ) )
	  while( length( TEXT ) > PSIZE )
	  { for( L = PSIZE+1; L > 0; L-- )	# find last word which fits
	      if( substr( TEXT, L, 1 ) == " " )
	        break
	    if( L-1 < PSIZE )		# if it`s a short line, spread it out
	    { SPACE = 0
	      for( I = 1; I <= L-1; I++ )		# count spaces
	        if( substr( TEXT, I, 1 ) == " " )
		  SPACE++
	      CHARS = L - 1 - SPACE			# no of non-space chars
	      WSEP  = int( ( PSIZE - CHARS ) / SPACE )	# avg separation requ`d
	      WSEPC = substr( SPACES, 1, WSEP )		# avg separation spaces
	      LSEP  = PSIZE - CHARS - WSEP * ( SPACE - 1 ) # remainder sep`tion
	      WTEXT = ""; WORDL=0; LASTW=0
	      for( I = 1; I <= L-1; I++ )
	      { TCHAR = substr( TEXT, I, 1 ) 
		if( TCHAR == " " )
		{ if( LSEP > 2 )		# even out the separation
		  { WTEXT = WTEXT WSEPC " "
		    LASTW += WORDL + WSEP + 1
		    LSEP--
		  }
		  else
		  { WTEXT = WTEXT WSEPC
		    LASTW += WORDL + WSEP
		  }
		  WORDL = 0
		}
		else
		{ WTEXT = WTEXT TCHAR
		  WORDL++			# length of current word
		}
	      }
	      print MARGIN substr( WTEXT,1,LASTW-WSEP ) \
		    	   substr( SPACES,1,LSEP ) \
		    	   substr( WTEXT,LASTW+1 )	# phew! finally done it!
	    }
	    else
	      print MARGIN substr( TEXT, 1, L-1 )
	    TEXT = substr( TEXT, L+1 )
	  }
	}

END	{ print MARGIN TEXT } 

' PSIZE=$PSIZE PMARG=$PMARG

#+ END OF rjust + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 
-- 
	   <Path: mcvax!ukc!icdoc!bilpin!jim> <UUCP: jim@bilpin.uucp>
			  {JimG : Hatfield, England}
		  This line has been intentionally left blank.