text: #if	!defined(lint) && !defined(DOS)
  static char rcsid[] = "$Id: random.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
  #endif
  
  /*
   * ========================================================================
   * Copyright 2006-2007 University of Washington
   * Copyright 2013 Eduardo Chappa
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * ========================================================================
   */
  
  /*
   * Program:	Random routines
   *
   * This file contains the command processing functions for a number of random
   * commands. There is no functional grouping here, for sure.
   */
  
  #include	"headers.h"
  
  #include "osdep/terminal.h"
  
  int	worthit(int *);
  
  int     tabsize;                        /* Tab size (0: use real tabs)  */
  
  
  /*
   * Display the current position of the cursor, in origin 1 X-Y coordinates,
   * the character that is under the cursor (in octal), and the fraction of the
   * text that is before the cursor. The displayed column is not the current
   * column, but the column that would be used on an infinite width display.
   * Normally this is bound to "C-X =".
   */
  int
  showcpos(int f, int n)
  {
      register LINE   *clp;
      register long   nch;
      register int    cbo;
      register long   nbc;
      register int    lines;
      register int    thisline;
      char     buffer[80];
  
      clp = lforw(curbp->b_linep);            /* Grovel the data.     */
      cbo = 0;
      nch = 0L;
      lines = 0;
      for (;;) {
  	if (clp==curwp->w_dotp && cbo==curwp->w_doto) {
  	    thisline = lines;
  	    nbc = nch;
  	}
  	if (cbo == llength(clp)) {
  	    if (clp == curbp->b_linep)
  	      break;
  	    clp = lforw(clp);
  	    cbo = 0;
  	    lines++;
  	} else
  	  ++cbo;
  	++nch;
      }
  
      snprintf(buffer,sizeof(buffer),"line %d of %d (%d%%%%), character %ld of %ld (%d%%%%)",
  	    thisline+1, lines+1, (int)((100L*(thisline+1))/(lines+1)),
  	    nbc, nch, (nch) ? (int)((100L*nbc)/nch) : 0);
  
      emlwrite(buffer, NULL);
      return (TRUE);
  }
  
  
  /*
   * Return current column.  Stop at first non-blank given TRUE argument.
   */
  int
  getccol(int bflg)
  {
      UCS c;
      int i, col;
  
      col = 0;
      for (i=0; i<curwp->w_doto; ++i) {
  	c = lgetc(curwp->w_dotp, i).c;
  	if (c!=' ' && c!='\t' && bflg)
  	  break;
  
  	if (c == '\t'){
  	    col |= 0x07;
  	    ++col;
  	}
  	else if (ISCONTROL(c)){
  	    col += 2;
  	}
  	else{
  	    int ww;
  
  	    ww = wcellwidth(c);
  	    col += (ww >= 0 ? ww : 1);
  	}
      }
  
      return(col);
  }
  
  
  
  /*
   * Set tab size if given non-default argument (n <> 1).  Otherwise, insert a
   * tab into file.  If given argument, n, of zero, change to true tabs.
   * If n > 1, simulate tab stop every n-characters using spaces. This has to be
   * done in this slightly funny way because the tab (in ASCII) has been turned
   * into "C-I" (in 10 bit code) already. Bound to "C-I".
   */
  int
  tab(int f, int n)
  {
      if (n < 0)
        return (FALSE);
  
      if (n == 0 || n > 1) {
  	tabsize = n;
  	return(TRUE);
      }
  
      if (! tabsize)
        return(linsert(1, '\t'));
  
      return(linsert(tabsize - (getccol(FALSE) % tabsize), ' '));
  }
  
  
  /*
   * Insert a newline. Bound to "C-M".
   */
  int
  newline(int f, int n)
  {
      register int    s;
  
      if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
        return(rdonly());	/* we are in read only mode	*/
  
      if (n < 0)
        return (FALSE);
  
      if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){
  	int l;
  
  	if(worthit(&l)){
  	    if(curwp->w_doto != 0)
  	      l++;
  	    scrolldown(curwp, l, n);
  	}
      }
  
      /* if we are in C mode and this is a default <NL> */
      /* pico's never in C mode */
  
      if(Pmaster && Pmaster->allow_flowed_text && curwp->w_doto
         && ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto - 1).c)
         && !(curwp->w_doto == 3
  	    && lgetc(curwp->w_dotp, 0).c == '-'
  	    && lgetc(curwp->w_dotp, 1).c == '-'
  	    && lgetc(curwp->w_dotp, 2).c == ' ')){
  	/*
  	 * flowed mode, make the newline a hard one by
  	 * stripping trailing space.
  	 */
  	int i, dellen;
  	for(i = curwp->w_doto - 1;
  	    i && ucs4_isspace(lgetc(curwp->w_dotp, i - 1).c);
  	    i--);
  	dellen = curwp->w_doto - i;
  	curwp->w_doto = i;
  	ldelete(dellen, NULL);
      }
      /* insert some lines */
      while (n--) {
  	if ((s=lnewline()) != TRUE)
  	  return (s);
      }
      return (TRUE);
  }
  
  
  
  /*
   * Delete forward. This is real easy, because the basic delete routine does
   * all of the work. Watches for negative arguments, and does the right thing.
   * If any argument is present, it kills rather than deletes, to prevent loss
   * of text if typed with a big argument. Normally bound to "C-D".
   */
  int
  forwdel(int f, int n)
  {
      if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
        return(rdonly());	/* we are in read only mode	*/
  
      if (n < 0)
        return (backdel(f, -n));
  
      if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){
  	int l;
  
  	if(worthit(&l) && curwp->w_doto == llength(curwp->w_dotp))
  	  scrollup(curwp, l+1, 1);
      }
  
      if (f != FALSE) {                       /* Really a kill.       */
  	if ((lastflag&CFKILL) == 0)
  	  kdelete();
  	thisflag |= CFKILL;
      }
  
      return (ldelete((long) n, f ? kinsert : NULL));
  }
  
  
  
  /*
   * Delete backwards. This is quite easy too, because it's all done with other
   * functions. Just move the cursor back, and delete forwards. Like delete
   * forward, this actually does a kill if presented with an argument. Bound to
   * both "RUBOUT" and "C-H".
   */
  int
  backdel(int f, int n)
  {
      register int    s;
  
      if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
        return(rdonly());	/* we are in read only mode	*/
  
      if (n < 0)
        return (forwdel(f, -n));
  
      if(TERM_OPTIMIZE && curwp->w_dotp != curwp->w_bufp->b_linep){
  	int l;
  	
  	if(worthit(&l) && curwp->w_doto == 0 &&
  	   lback(curwp->w_dotp) != curwp->w_bufp->b_linep){
  	    if(l == curwp->w_toprow)
  	      scrollup(curwp, l+1, 1);
  	    else if(llength(lback(curwp->w_dotp)) == 0)
  	      scrollup(curwp, l-1, 1);
  	    else
  	      scrollup(curwp, l, 1);
  	}
      }
  
      if (f != FALSE) {                       /* Really a kill.       */
  	if ((lastflag&CFKILL) == 0)
  	  kdelete();
  
  	thisflag |= CFKILL;
      }
  
      if ((s=backchar(f, n)) == TRUE)
        s = ldelete((long) n, f ? kinsert : NULL);
  
      return (s);
  }
  
  
  
  /*
   * killtext - delete the line that the cursor is currently in.
   *	      a greatly pared down version of its former self.
   */
  int
  killtext(int f, int n)
  {
      register int chunk;
      int		 opt_scroll = 0;
  
      if (curbp->b_mode&MDVIEW)		/* don't allow this command if  */
        return(rdonly());			/* we are in read only mode     */
  
      if ((lastflag&CFKILL) == 0)		/* Clear kill buffer if */
        kdelete();			/* last wasn't a kill.  */
  
      if(gmode & MDDTKILL){		/*  */
  	if((chunk = llength(curwp->w_dotp) - curwp->w_doto) == 0){
  	    chunk = 1;
  	    if(TERM_OPTIMIZE)
  	      opt_scroll = 1;
  	}
      }
      else{
  	gotobol(FALSE, 1);		/* wack from bol past newline */
  	chunk = llength(curwp->w_dotp) + 1;
  	if(TERM_OPTIMIZE)
  	  opt_scroll = 1;
      }
  
      /* optimize what motion we can */
      if(opt_scroll && (curwp->w_dotp != curwp->w_bufp->b_linep)){
  	int l;
  
  	if(worthit(&l))
  	  scrollup(curwp, l, 1);
      }
  
      thisflag |= CFKILL;
      return(ldelete((long) chunk, kinsert));
  }
  
  
  /*
   * Yank text back from the kill buffer. This is really easy. All of the work
   * is done by the standard insert routines. All you do is run the loop, and
   * check for errors. Bound to "C-Y".
   */
  int
  yank(int f, int n)
  {
      int    c, i;
      REGION region, *added_region;
      LINE  *dotp;
  
      if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
        return(rdonly());	/* we are in read only mode	*/
  
      if (n < 0)
        return (FALSE);
  
      if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){
  	int l;
  
  	if(worthit(&l) && !(lastflag&CFFILL)){
  	    register int  t = 0; 
  	    register int  i = 0;
  	    register int  ch;
  
  	    while((ch=fremove(i++)) >= 0)
  	      if(ch == '\n')
  		t++;
  	    if(t+l < curwp->w_toprow+curwp->w_ntrows)
  	      scrolldown(curwp, l, t);
  	}
      }
  
      if(lastflag & CFFILL){		/* if last command was fillpara() */
  	if(lastflag & CFFLBF){
  	    gotoeob(FALSE, 1);
  	    dotp = curwp->w_dotp;
  	    gotobob(FALSE, 1);
  	    curwp->w_doto = 0;
  	    getregion(&region, dotp, llength(dotp));
  	}
  	else{
  	    added_region = get_last_region_added();
  	    if(added_region){
  		curwp->w_dotp = added_region->r_linep;
  		curwp->w_doto = added_region->r_offset;
  		region = (*added_region);
  	    }
  	    else
  	      return(FALSE);
  	}
  
  	if(!ldelete(region.r_size, NULL))
  	  return(FALSE);
      }					/* then splat out the saved buffer */
  
      while (n--) {
  	i = 0;
  	while ((c = ((lastflag&CFFILL)
  		     ? ((lastflag & CFFLBF) ? kremove(i) : fremove(i))
  		     : kremove(i))) >= 0) {
  	    if (c == '\n') {
  		if (lnewline() == FALSE)
  		  return (FALSE);
  	    } else {
  		if (linsert(1, c) == FALSE)
  		  return (FALSE);
  	    }
  
  	    ++i;
  	}
      }
  
      if(lastflag&CFFLPA){            /* if last command was fill paragraph */
  	curwp->w_dotp = lforw(curwp->w_dotp);
  	curwp->w_doto = 0;
  
  	curwp->w_flag |= WFMODE;
  	
  	if(!Pmaster){
  	    sgarbk = TRUE;
  	    emlwrite("", NULL);
  	}
      }
  
      return (TRUE);
  }
  
  
  
  /*
   * worthit - generic sort of test to roughly gage usefulness of using 
   *           optimized scrolling.
   *
   * note:
   *	returns the line on the screen, l, that the dot is currently on
   */
  int
  worthit(int *l)
  {
      int i;			/* l is current line */
      unsigned below;		/* below is avg # of ch/line under . */
  
      *l = doton(&i, &below);
      below = (i > 0) ? below/(unsigned)i : 0;
  
      return(below > 3);
  }
  