mirror of
https://github.com/rfivet/uemacs.git
synced 2024-12-25 02:26:22 -05:00
572 lines
16 KiB
C
572 lines
16 KiB
C
/* buffer.c -- implements buffer.h */
|
|
#include "buffer.h"
|
|
|
|
/* Buffer management. Some of the functions are internal, and some are
|
|
actually attached to user keys. Like everyone else, they set hints for
|
|
the display system.
|
|
|
|
modified by Petri Kutvonen
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "defines.h"
|
|
#include "file.h"
|
|
#include "input.h"
|
|
#include "list.h"
|
|
#include "mlout.h"
|
|
#include "utf8.h"
|
|
#include "util.h"
|
|
#include "window.h"
|
|
|
|
|
|
buffer_p curbp ; /* Current buffer */
|
|
buffer_p bheadp ; /* Head of list of buffers */
|
|
buffer_p blistp ; /* Buffer for C-X C-B */
|
|
|
|
const char *modename[ NUMMODES] = { /* name of modes */
|
|
"Wrap", "Cmode", "Exact", "View", "Over",
|
|
"Magic",
|
|
"Asave", "Utf-8", "Dos"
|
|
} ;
|
|
|
|
int gmode = 0 ; /* global editor mode */
|
|
|
|
static int makelist( int iflag) ;
|
|
static int addline( char *text) ;
|
|
static void l_to_a( char *buf, int width, long num) ;
|
|
|
|
|
|
/* Attach a buffer to a window. The values of dot and mark come from the
|
|
buffer if the use count is 0. Otherwise, they come from some other
|
|
window.
|
|
*/
|
|
BINDABLE( usebuffer) {
|
|
char *bufn ;
|
|
|
|
/* Get buffer name */
|
|
int status = newmlarg( &bufn, "select-buffer: ", sizeof( bname_t)) ;
|
|
if( status != TRUE)
|
|
return status ;
|
|
|
|
/* Find buffer in list */
|
|
buffer_p bp = bfind( bufn, TRUE, 0) ;
|
|
free( bufn) ;
|
|
if( bp == NULL)
|
|
return FALSE ;
|
|
|
|
/* Switch to buffer */
|
|
return swbuffer( bp) ;
|
|
}
|
|
|
|
|
|
/* switch to the next buffer in the buffer list
|
|
*
|
|
* int f, n; default flag, numeric argument
|
|
*/
|
|
BINDABLE( nextbuffer) {
|
|
buffer_p bp = NULL ; /* eligible buffer to switch to */
|
|
|
|
/* make sure the arg is legit */
|
|
if( f == FALSE)
|
|
n = 1 ;
|
|
|
|
if( n < 1)
|
|
return FALSE ;
|
|
|
|
buffer_p bbp = curbp ; /* eligible buffer to switch to */
|
|
while( n-- > 0) {
|
|
/* advance to the next buffer */
|
|
bp = bbp->b_bufp ;
|
|
|
|
/* cycle through the buffers to find an eligible one */
|
|
while( bp == NULL || bp->b_flag & BFINVS) {
|
|
if (bp == NULL)
|
|
bp = bheadp ;
|
|
else
|
|
bp = bp->b_bufp ;
|
|
|
|
/* don't get caught in an infinite loop! */
|
|
if( bp == bbp)
|
|
return FALSE ;
|
|
}
|
|
|
|
bbp = bp ;
|
|
}
|
|
|
|
return swbuffer( bp) ;
|
|
}
|
|
|
|
|
|
/* make buffer BP current
|
|
*/
|
|
int swbuffer( buffer_p bp) {
|
|
window_p wp ;
|
|
|
|
if (--curbp->b_nwnd == 0) { /* Last use. */
|
|
curbp->b_dotp = curwp->w_dotp;
|
|
curbp->b_doto = curwp->w_doto;
|
|
curbp->b_markp = curwp->w_markp;
|
|
curbp->b_marko = curwp->w_marko;
|
|
}
|
|
curbp = bp; /* Switch. */
|
|
if (curbp->b_active != TRUE) { /* buffer not active yet */
|
|
/* read it in and activate it */
|
|
readin(curbp->b_fname, TRUE);
|
|
curbp->b_dotp = lforw(curbp->b_linep);
|
|
curbp->b_doto = 0;
|
|
curbp->b_active = TRUE;
|
|
curbp->b_mode |= gmode; /* P.K. */
|
|
}
|
|
curwp->w_bufp = bp;
|
|
curwp->w_linep = bp->b_linep; /* For macros, ignored. */
|
|
curwp->w_flag |= WFMODE | WFFORCE | WFHARD; /* Quite nasty. */
|
|
if (bp->b_nwnd++ == 0) { /* First use. */
|
|
curwp->w_dotp = bp->b_dotp;
|
|
curwp->w_doto = bp->b_doto;
|
|
curwp->w_markp = bp->b_markp;
|
|
curwp->w_marko = bp->b_marko;
|
|
cknewwindow();
|
|
return TRUE;
|
|
}
|
|
wp = wheadp; /* Look for old. */
|
|
while (wp != NULL) {
|
|
if (wp != curwp && wp->w_bufp == bp) {
|
|
curwp->w_dotp = wp->w_dotp;
|
|
curwp->w_doto = wp->w_doto;
|
|
curwp->w_markp = wp->w_markp;
|
|
curwp->w_marko = wp->w_marko;
|
|
break;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
cknewwindow();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Dispose of a buffer, by name. Ask for the name. Look it up (don't get
|
|
too upset if it isn't there at all!). Get quite upset if the buffer is
|
|
being displayed. Clear the buffer (ask if the buffer has been changed).
|
|
Then free the header line and the buffer header. Bound to "C-X K".
|
|
*/
|
|
BINDABLE( killbuffer) {
|
|
buffer_p bp ;
|
|
int status ;
|
|
char *bufn ;
|
|
|
|
/* Get buffer name */
|
|
status = newmlarg( &bufn, "delete-buffer: ", sizeof( bname_t)) ;
|
|
if( status != TRUE)
|
|
return status ;
|
|
|
|
/* Find buffer in list */
|
|
bp = bfind( bufn, FALSE, 0) ;
|
|
free( bufn) ;
|
|
if( bp == NULL) /* Easy if unknown. */
|
|
return TRUE ;
|
|
|
|
if( bp->b_flag & BFINVS) /* Deal with special buffers */
|
|
return TRUE ; /* by doing nothing. */
|
|
|
|
return zotbuf( bp) ;
|
|
}
|
|
|
|
|
|
/* kill the buffer pointed to by bp
|
|
*/
|
|
int zotbuf( buffer_p bp) {
|
|
if( bp->b_nwnd != 0) /* Error if on screen. */
|
|
return mloutfail( "Buffer is being displayed") ;
|
|
|
|
int s = bclear( bp) ; /* Blow text away. */
|
|
if( s != TRUE)
|
|
return s ;
|
|
|
|
free( bp->b_linep) ; /* Release header line. */
|
|
|
|
/* unlink buffer from buffer chain */
|
|
if( bheadp == bp)
|
|
bheadp = bp->b_bufp ;
|
|
else for( buffer_p prev = bheadp ; prev != NULL ; prev = prev->b_bufp)
|
|
if( prev->b_bufp == bp) {
|
|
prev->b_bufp = bp->b_bufp ;
|
|
break ;
|
|
}
|
|
|
|
free( bp) ; /* Release buffer block */
|
|
return TRUE ;
|
|
}
|
|
|
|
|
|
/* Rename the current buffer
|
|
*
|
|
* int f, n; default Flag & Numeric arg
|
|
*/
|
|
BINDABLE( namebuffer) {
|
|
char *bufn ; /* buffer to hold buffer name */
|
|
int status ;
|
|
|
|
/* iterate until it gets a unique new buffer name */
|
|
do {
|
|
/* prompt for it */
|
|
status = newmlarg( &bufn, "name-buffer: ", sizeof( bname_t)) ;
|
|
if( status != TRUE)
|
|
return status ;
|
|
|
|
/* and check for duplicates */
|
|
for( buffer_p bp = bheadp ; bp != NULL ; bp = bp->b_bufp) {
|
|
if( bp != curbp) { /* it's ok to rename buffer to same, so skip */
|
|
/* retry if the names are the same */
|
|
if( strcmp( bufn, bp->b_bname) == 0) {
|
|
free( bufn) ;
|
|
status = FALSE ; /* try again */
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
} while( !status) ;
|
|
|
|
/* copy buffer name to structure */
|
|
mystrscpy( curbp->b_bname, bufn, sizeof( bname_t)) ;
|
|
free( bufn) ;
|
|
|
|
curwp->w_flag |= WFMODE ; /* make mode line replot */
|
|
mloutstr( "") ; /* erase message line */
|
|
return TRUE ;
|
|
}
|
|
|
|
|
|
/* List all of the active buffers. First update the special buffer that
|
|
holds the list. Next make sure at least 1 window is displaying the
|
|
buffer list, splitting the screen if this is what it takes. Lastly,
|
|
repaint all of the windows that are displaying the list. Bound to "C-X
|
|
C-B".
|
|
|
|
A numeric argument forces it to list invisible buffers as well.
|
|
*/
|
|
BINDABLE( listbuffers) {
|
|
window_p wp ;
|
|
|
|
int s = makelist( f) ;
|
|
if( s != TRUE)
|
|
return s ;
|
|
|
|
if( blistp->b_nwnd == 0) { /* Not on screen yet. */
|
|
buffer_p bp ;
|
|
|
|
if( (wp = wpopup()) == NULL)
|
|
return FALSE ;
|
|
|
|
bp = wp->w_bufp ;
|
|
if( --bp->b_nwnd == 0) {
|
|
bp->b_dotp = wp->w_dotp ;
|
|
bp->b_doto = wp->w_doto ;
|
|
bp->b_markp = wp->w_markp ;
|
|
bp->b_marko = wp->w_marko ;
|
|
}
|
|
|
|
wp->w_bufp = blistp ;
|
|
++blistp->b_nwnd ;
|
|
}
|
|
|
|
for( wp = wheadp ; wp != NULL ; wp = wp->w_wndp) {
|
|
if( wp->w_bufp == blistp) {
|
|
wp->w_linep = lforw( blistp->b_linep) ;
|
|
wp->w_dotp = lforw( blistp->b_linep) ;
|
|
wp->w_doto = 0 ;
|
|
wp->w_markp = NULL ;
|
|
wp->w_marko = 0 ;
|
|
wp->w_flag |= WFMODE | WFHARD ;
|
|
}
|
|
}
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
|
|
/* This routine rebuilds the text in the special secret buffer that holds
|
|
the buffer list. It is called by the list buffers command. Return TRUE
|
|
if everything works. Return FALSE if there is an error (if there is no
|
|
memory). Iflag indicates wether to list hidden buffers.
|
|
|
|
int iflag; list hidden buffer flag
|
|
*/
|
|
/* Layout: "ACT MODES Size Buffer File"
|
|
AAA MMMMMMMMMSSSSSSSSSS BBBBBBBBBBBBBBB FFF...
|
|
FNAMSTART ---------------------------------------^
|
|
*/
|
|
#define FNAMSTART (3 + 1 + NUMMODES + 10 + 1 + (sizeof( bname_t) - 1) + 1)
|
|
|
|
static void do_layout( char *line, int mode) {
|
|
int i ;
|
|
|
|
/* build line to report global mode settings */
|
|
strcpy( line, " WCEVOMAUD Global Modes") ;
|
|
|
|
/* output the mode codes */
|
|
for( i = 0 ; i < NUMMODES ; i++)
|
|
if( 0 == (mode & (1 << i)))
|
|
line[ 4 + i] = '.' ;
|
|
}
|
|
|
|
|
|
static unsigned int utf8_disp_len( const char *s) {
|
|
unsigned int len = 0 ;
|
|
|
|
while( *s) {
|
|
unicode_t c ;
|
|
|
|
s += utf8_to_unicode( s, 0, 4, &c) ;
|
|
len += utf8_width( c) ;
|
|
}
|
|
|
|
return len ;
|
|
}
|
|
|
|
|
|
static int makelist( int iflag) {
|
|
char line[ FNAMSTART + sizeof( fname_t)] ;
|
|
|
|
blistp->b_flag &= ~BFCHG; /* Don't complain! Mute bclear() */
|
|
int s = bclear( blistp) ; /* Blow old text away */
|
|
if( s != TRUE)
|
|
return s ;
|
|
|
|
blistp->b_fname[ 0] = 0 ; /* in case of user override */
|
|
|
|
if( addline("ACT MODES Size Buffer File") == FALSE
|
|
|| addline("‾‾‾ ‾‾‾‾‾ ‾‾‾‾ ‾‾‾‾‾‾ ‾‾‾‾") == FALSE)
|
|
return FALSE ;
|
|
|
|
/* report global mode settings */
|
|
do_layout( line, gmode) ;
|
|
if( addline( line) == FALSE)
|
|
return FALSE ;
|
|
|
|
/* output the list of buffers */
|
|
for( buffer_p bp = bheadp ; bp != NULL ; bp = bp->b_bufp) {
|
|
int c ;
|
|
|
|
/* skip invisible buffers if iflag is false */
|
|
if (((bp->b_flag & BFINVS) != 0) && (iflag != TRUE))
|
|
continue;
|
|
|
|
do_layout( line, bp->b_mode) ;
|
|
char *cp1 = line ; /* Start at left edge */
|
|
|
|
/* output status of ACTIVE flag ('@' when the file has been read in) */
|
|
*cp1++ = (bp->b_active == TRUE) ? '@' : ' ' ;
|
|
|
|
/* report if the file is truncated */
|
|
*cp1++ = ((bp->b_flag & BFTRUNC) != 0) ? '#' : ' ' ;
|
|
|
|
/* output status of changed flag ('*' when the buffer is changed) */
|
|
*cp1 = ((bp->b_flag & BFCHG) != 0) ? '*' : ' ' ;
|
|
|
|
/* Buffer size */
|
|
long nbytes = 0L; /* Count bytes in buf. */
|
|
long nlines = 0 ;
|
|
for( line_p lp = lforw( bp->b_linep) ; lp != bp->b_linep ;
|
|
lp = lforw( lp)) {
|
|
nbytes += (long) llength(lp) + 1L;
|
|
nlines += 1 ;
|
|
}
|
|
|
|
if( bp->b_mode & MDDOS)
|
|
nbytes += nlines ;
|
|
|
|
l_to_a( &line[ 13], 10 + 1, nbytes) ; /* "%10d" formatted numbers */
|
|
cp1 = &line[ 23] ;
|
|
*cp1++ = ' ' ;
|
|
|
|
/* Display buffer name */
|
|
char *cp2 = &bp->b_bname[ 0] ;
|
|
while( (c = *cp2++) != 0)
|
|
*cp1++ = c;
|
|
|
|
/* Pad with spaces to max buffer name length */
|
|
int len = sizeof bp->b_bname ;
|
|
len -= utf8_disp_len( bp->b_bname) ;
|
|
while( len--)
|
|
*cp1++ = ' ' ;
|
|
|
|
/* Display filename if any */
|
|
if( bp->b_fname[ 0] != 0)
|
|
mystrscpy( cp1, bp->b_fname, &line[ sizeof line] - cp1) ;
|
|
else
|
|
*cp1 = 0 ; /* Terminate string */
|
|
|
|
if( addline( line) == FALSE) /* Add to the buffer. */
|
|
return FALSE ;
|
|
}
|
|
|
|
return TRUE ; /* All done */
|
|
}
|
|
|
|
|
|
static void l_to_a(char *buf, int width, long num) {
|
|
buf[ --width] = 0 ; /* End of string. */
|
|
while (num >= 10) { /* Conditional digits. */
|
|
buf[--width] = (int) (num % 10L) + '0';
|
|
num /= 10L;
|
|
}
|
|
buf[--width] = (int) num + '0'; /* Always 1 digit. */
|
|
while( width > 0) /* Pad with blanks. */
|
|
buf[--width] = ' ';
|
|
}
|
|
|
|
|
|
/* The argument "text" points to a string. Append this line to the buffer
|
|
list buffer. Handcraft the EOL on the end. Return TRUE if it worked
|
|
and FALSE if you ran out of room.
|
|
*/
|
|
static int addline( char *text) {
|
|
int ntext = strlen( text) ;
|
|
line_p lp = lalloc( ntext) ;
|
|
if( lp == NULL)
|
|
return FALSE ;
|
|
|
|
for( int i = 0 ; i < ntext ; ++i)
|
|
lputc( lp, i, text[ i]) ;
|
|
|
|
blistp->b_linep->l_bp->l_fp = lp ; /* Hook onto the end */
|
|
lp->l_bp = blistp->b_linep->l_bp ;
|
|
blistp->b_linep->l_bp = lp ;
|
|
lp->l_fp = blistp->b_linep ;
|
|
if( blistp->b_dotp == blistp->b_linep) /* If "." is at the end */
|
|
blistp->b_dotp = lp ; /* move it to new line */
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
|
|
/* Look through the list of buffers. Return TRUE if there are any changed
|
|
buffers. Buffers that hold magic internal stuff are not considered; who
|
|
cares if the list of buffer names is hacked. Return FALSE if no buffers
|
|
have been changed.
|
|
*/
|
|
boolean anycb( void) {
|
|
for( buffer_p bp = bheadp ; bp != NULL ; bp = bp->b_bufp) {
|
|
if( (bp->b_flag & (BFINVS | BFCHG)) == BFCHG)
|
|
return TRUE ;
|
|
}
|
|
|
|
return FALSE ;
|
|
}
|
|
|
|
|
|
/* Find a buffer, by name. Return a pointer to the buffer structure
|
|
associated with it. If the buffer is not found and the "create_f" is
|
|
TRUE, create it. The "flags" is the settings for the buffer flags.
|
|
*/
|
|
buffer_p bfind( const char *bname, boolean create_f, int flags) {
|
|
buffer_p bp ;
|
|
|
|
for( bp = bheadp ; bp != NULL ; bp = bp->b_bufp)
|
|
if( strcmp( bname, bp->b_bname) == 0)
|
|
return bp ;
|
|
|
|
if( create_f != FALSE) {
|
|
/* allocate empty buffer */
|
|
bp = malloc( sizeof *bp) ;
|
|
if( bp == NULL)
|
|
return NULL ;
|
|
|
|
line_p lp = lalloc( 0) ;
|
|
if( lp == NULL) {
|
|
free( bp) ;
|
|
return NULL ;
|
|
}
|
|
|
|
lp->l_fp = lp ;
|
|
lp->l_bp = lp ;
|
|
bp->b_linep = lp ;
|
|
bp->b_dotp = lp ;
|
|
bp->b_doto = 0 ;
|
|
|
|
/* find the place in the list to insert this buffer */
|
|
if( bheadp == NULL || strcmp( bheadp->b_bname, bname) > 0) {
|
|
/* insert at the beginning */
|
|
bp->b_bufp = bheadp ;
|
|
bheadp = bp ;
|
|
} else {
|
|
buffer_p sb ; /* buffer to insert after */
|
|
|
|
for( sb = bheadp ; sb->b_bufp != NULL ; sb = sb->b_bufp)
|
|
if( strcmp( sb->b_bufp->b_bname, bname) > 0)
|
|
break ;
|
|
|
|
/* and insert it */
|
|
bp->b_bufp = sb->b_bufp ;
|
|
sb->b_bufp = bp ;
|
|
}
|
|
|
|
/* and set up the other buffer fields */
|
|
bp->b_active = TRUE ;
|
|
bp->b_markp = NULL ;
|
|
bp->b_marko = 0 ;
|
|
bp->b_flag = flags ;
|
|
bp->b_mode = gmode ;
|
|
bp->b_nwnd = 0 ;
|
|
bp->b_fname[ 0] = '\0' ;
|
|
mystrscpy( bp->b_bname, bname, sizeof( bname_t)) ;
|
|
}
|
|
|
|
return bp ;
|
|
}
|
|
|
|
|
|
/* This routine blows away all of the text in a buffer. If the buffer is
|
|
marked as changed then we ask if it is ok to blow it away; this is to
|
|
save the user the grief of losing text. The window chain is nearly
|
|
always wrong if this gets called; the caller must arrange for the
|
|
updates that are required. Return TRUE if everything looks good.
|
|
*/
|
|
int bclear( buffer_p bp) {
|
|
int s ;
|
|
|
|
if( (bp->b_flag & (BFINVS | BFCHG)) == BFCHG /* regular and changed */
|
|
&& (s = mlyesno( "Discard changes")) != TRUE)
|
|
return s ;
|
|
|
|
bp->b_flag &= ~BFCHG ; /* Not changed */
|
|
line_p lp = bp->b_linep ;
|
|
if( lp->l_fp != lp) { /* non empty buffer */
|
|
/* turn line ring into a list and delete it */
|
|
lp->l_bp->l_fp = NULL ;
|
|
freelist( (list_p) lp->l_fp) ;
|
|
lp->l_fp = lp->l_bp = lp ;
|
|
|
|
/* Fix dots and marks */
|
|
bp->b_dotp = bp->b_linep ; /* Fix "." */
|
|
bp->b_doto = 0 ;
|
|
bp->b_markp = NULL ; /* Invalidate "mark" */
|
|
bp->b_marko = 0 ;
|
|
if( bp->b_nwnd) /* buffer is displayed */
|
|
for( window_p wp = wheadp ; wp ; wp = wp->w_wndp)
|
|
if( wp->w_bufp == bp) {
|
|
wp->w_linep = wp->w_dotp = lp ;
|
|
wp->w_doto = 0 ;
|
|
wp->w_markp = NULL ;
|
|
wp->w_marko = 0 ;
|
|
}
|
|
}
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
|
|
/* unmark the current buffers change flag
|
|
*
|
|
* int f, n; unused command arguments
|
|
*/
|
|
BINDABLE( unmark) {
|
|
curbp->b_flag &= ~BFCHG ;
|
|
curwp->w_flag |= WFMODE ;
|
|
return TRUE ;
|
|
}
|
|
|
|
/* end of buffer.c */
|