home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fresh Fish 8
/
FreshFishVol8-CD1.bin
/
new
/
util
/
edit
/
jade
/
src
/
undo.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-07-12
|
11KB
|
404 lines
/* undo.c -- Recording and use of undo information
Copyright (C) 1994 John Harper <jsh@ukc.ac.uk>
This file is part of Jade.
Jade 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; either version 2, or (at your option)
any later version.
Jade 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.
You should have received a copy of the GNU General Public License
along with Jade; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "jade.h"
#include "jade_protos.h"
#include <string.h>
_PR void undo_record_deletion(TX *, POS *, POS *);
_PR VALUE undo_push_deletion(TX *, POS *, POS *);
_PR void undo_record_insertion(TX *, POS *, POS *);
_PR void undo_record_modification(TX *, POS *, POS *);
_PR void undo_distinct(void);
_PR void undo_new_group(void);
_PR void undo_trim(void);
_PR void undo_init(void);
/* Maximum number of bytes that *each buffer* may devote to undo
information. */
static max_undo_size = 10000;
/* Lets us use the string which undo_record_deletion() creates for
other uses. */
static VALUE pending_deletion_string;
static POS pending_deletion_start, pending_deletion_end;
static TX *pending_deletion_tx;
/* A new command has been invoked but the group-separator hasn't been
added yet. */
static bool pending_group;
/* While we're in cmd_undo() this is set. This is also tested by
undo_distinct(); if FALSE it will call coalesce_undo() if necessary.
undo_distinct() always sets this to FALSE. */
static bool in_undo;
static TX *last_undid_tx;
static VALUE sym_undo;
/* If not in an undo, this will re-combine the waiting_undo and
tx_UndoList. */
static void
coalesce_undo(TX *tx)
{
if(!in_undo
&& (tx->tx_ToUndoList != NULL))
{
VALUE tmp = cmd_nreverse(tx->tx_UndoneList);
if(tmp)
{
tx->tx_UndoList = cmd_nconc(list_3(tx->tx_UndoList,
tmp,
tx->tx_ToUndoList));
}
tx->tx_UndoneList = sym_nil;
tx->tx_ToUndoList = NULL;
last_undid_tx = NULL;
}
}
/* Called *after* recording an undo command, checks if this is the
first change to the buffer. Should be called before the operation. */
static INLINE void
check_first_mod(TX *tx)
{
if((tx->tx_Changes == tx->tx_ProperSaveChanges)
&& ((tx->tx_Flags & TXFF_NO_UNDO) == 0))
{
/* First modification, record this. */
tx->tx_UndoList = cmd_cons(sym_t, tx->tx_UndoList);
}
}
/* If pending_group is set a group-separator is added to the buffer's
undo list. */
static void
check_group(TX *tx)
{
if(pending_group
#if 0
&& !NILP(tx->tx_UndoList)
#endif
&& ((tx->tx_Flags & TXFF_NO_UNDO) == 0))
{
tx->tx_UndoList = cmd_cons(sym_nil, tx->tx_UndoList);
}
pending_group = FALSE;
}
/* Grabs the string between START and END in buffer TX and adds it to
the buffer's undo-list. This has to be done *before* the text is
actually deleted from the buffer (for obvious reasons). */
void
undo_record_deletion(TX *tx, POS *start, POS *end)
{
if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
{
VALUE string;
VALUE lstart = make_lpos(start);
if((pending_deletion_string != NULL)
&& (pending_deletion_tx = tx)
&& (POS_EQUAL_P(&pending_deletion_start, start))
&& (POS_EQUAL_P(&pending_deletion_end, end)))
{
/* A saved deletion; use it. */
string = pending_deletion_string;
}
else
{
long len = section_length(tx, start, end);
if(len == 1)
{
/* A deletion of 1 character is recorded as a character. */
string = cmd_get_char(lstart, VAL(tx));
if(!string || !NUMBERP(string))
return;
}
else
{
string = make_string(len + 1);
copy_section(tx, start, end, VSTR(string));
VSTR(string)[len] = 0;
}
}
coalesce_undo(tx);
check_group(tx);
check_first_mod(tx);
tx->tx_UndoList = cmd_cons(cmd_cons(lstart, string),
tx->tx_UndoList);
}
pending_deletion_string = NULL;
}
/* Lets the saved deletion be used for more than the undo list. Call
this *before* doing anything else. It will be copy the string and
return it. The next call to undo_record_deletion() will use the
*same* copy (unless the parameters don't match). */
VALUE
undo_push_deletion(TX *tx, POS *start, POS *end)
{
long len = section_length(tx, start, end);
VALUE string = make_string(len + 1);
copy_section(tx, start, end, VSTR(string));
VSTR(string)[len] = 0;
pending_deletion_string = string;
pending_deletion_start = *start;
pending_deletion_end = *end;
pending_deletion_tx = tx;
return(string);
}
/* Adds an insertion between START and END to the TX buffer's undo-list.
Doesn't copy anything, just records START and END. */
void
undo_record_insertion(TX *tx, POS *start, POS *end)
{
if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
{
VALUE item;
coalesce_undo(tx);
check_group(tx);
check_first_mod(tx);
item = tx->tx_UndoList;
if(CONSP(item) && CONSP(VCAR(item)))
{
item = VCAR(item);
if(POSP(VCDR(item)) && POS_EQUAL_P(start, &VPOS(VCDR(item))))
{
/* This insertion is directly after the end of the
previous insertion; extend the previous one to cover
this one. */
VPOS(VCDR(item)) = *end;
return;
}
}
tx->tx_UndoList = cmd_cons(cmd_cons(make_lpos(start), make_lpos(end)),
tx->tx_UndoList);
}
}
/* Record that the text between START and END has been modified. This
must be done *before* the modification is actually done. */
void
undo_record_modification(TX *tx, POS *start, POS *end)
{
if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
{
undo_record_deletion(tx, start, end);
undo_record_insertion(tx, start, end);
}
}
/* Signal the end of this command. */
void
undo_distinct(void)
{
if((!last_command || !NILP(last_command))
&& (last_command != sym_undo)
&& last_undid_tx
&& last_undid_tx->tx_ToUndoList)
{
coalesce_undo(last_undid_tx);
}
in_undo = FALSE;
}
/* A new command is started. */
void
undo_new_group(void)
{
pending_group = TRUE;
}
_PR VALUE cmd_undo(VALUE tx);
DEFUN_INT("undo", cmd_undo, subr_undo, (VALUE tx), V_Subr1, DOC_undo, "") /*
::doc:undo::
undo [BUFFER]
In the buffer BUFFER, undo everything back to the start of the previous
command. Consecutive undo commands work backwards through the BUFFER's
history.
::end:: */
{
if(!BUFFERP(tx))
tx = VAL(curr_vw->vw_Tx);
if(VTX(tx)->tx_ToUndoList == NULL)
{
/* First call. */
VTX(tx)->tx_ToUndoList = VTX(tx)->tx_UndoList;
VTX(tx)->tx_UndoList = sym_nil;
}
if(NILP(VTX(tx)->tx_ToUndoList))
return(cmd_signal(sym_error, LIST_1(MKSTR("Nothing to undo!"))));
in_undo = TRUE;
last_undid_tx = VTX(tx);
while(CONSP(VTX(tx)->tx_ToUndoList))
{
VALUE item = VCAR(VTX(tx)->tx_ToUndoList);
VTX(tx)->tx_ToUndoList = VCDR(VTX(tx)->tx_ToUndoList);
VTX(tx)->tx_UndoneList = cmd_cons(item, VTX(tx)->tx_UndoneList);
if(NILP(item))
break; /* Group separator; break the loop. */
else if(CONSP(item) && POSP(VCAR(item)))
{
if(STRINGP(VCDR(item)))
{
/* A deleted string */
VALUE new = cmd_insert(VCDR(item), VCAR(item), tx);
if(new && POSP(new))
cmd_goto_char(new);
}
else if(NUMBERP(VCDR(item)))
{
/* A deleted character */
VALUE tmp = make_string(2);
VSTR(tmp)[0] = (u_char)VNUM(VCDR(item));
VSTR(tmp)[1] = 0;
tmp = cmd_insert(tmp, VCAR(item), tx);
if(tmp && POSP(tmp))
cmd_goto_char(tmp);
}
else if(POSP(VCDR(item)))
{
cmd_delete_area(VCAR(item), VCDR(item), tx); /* insert */
cmd_goto_char(VCAR(item));
}
}
else if(POSP(item))
{
cmd_goto_char(item);
}
else if(item == sym_t)
{
/* clear modification flag. */
cmd_set_buffer_modified(tx, sym_nil);
}
}
this_command = sym_undo;
return(sym_t);
}
_PR VALUE var_max_undo_size(VALUE val);
DEFUN("max-undo-size", var_max_undo_size, subr