smol/src/cut.c

470 lines
13 KiB
C
Raw Normal View History

/* $Id$ */
/**************************************************************************
* cut.c *
* *
* Copyright (C) 1999-2004 Chris Allegretta *
* This program 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. *
* *
* This program 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 this program; if not, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
* *
**************************************************************************/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "proto.h"
#include "nano.h"
static bool keep_cutbuffer = FALSE;
/* Should we keep the contents of the cutbuffer? */
static cut_type marked_cut = CUT_LINE;
/* What type of cut is in the cutbuffer? */
#ifndef NANO_SMALL
static bool concatenate_cut = FALSE;
/* Should we add this cut string to the end of the last one? */
#endif
static filestruct *cutbottom = NULL;
/* Pointer to end of cutbuffer. */
void cutbuffer_reset(void)
{
keep_cutbuffer = FALSE;
}
filestruct *get_cutbottom(void)
{
return cutbottom;
}
void add_to_cutbuffer(filestruct *inptr, bool allow_concat)
{
#ifdef DEBUG
fprintf(stderr, "add_to_cutbuffer(): inptr->data = %s\n", inptr->data);
#endif
if (cutbuffer == NULL)
cutbuffer = inptr;
#ifndef NANO_SMALL
else if (allow_concat && concatenate_cut) {
/* Just tack the text in inptr onto the text in cutbottom,
* unless allow_concat is FALSE. */
cutbottom->data = charealloc(cutbottom->data,
strlen(cutbottom->data) + strlen(inptr->data) + 1);
strcat(cutbottom->data, inptr->data);
return;
}
#endif
else {
cutbottom->next = inptr;
inptr->prev = cutbottom;
}
cutbottom = inptr;
cutbottom->next = NULL;
}
#ifndef NANO_SMALL
/* Cut a marked segment instead of a whole line.
*
* The first cut character is top->data[top_x]. Unless top == bot, the
* last cut line has length bot_x. That is, if bot_x > 0 then we cut to
* bot->data[bot_x - 1].
*
* We maintain totsize, totlines, filebot, the magicline, and line
* numbers. Also, we set current and current_x so the cursor will be on
* the first character after what was cut. We do not do any screen
* updates.
*
* Note cutbuffer might not be NULL if cut to end is used. */
void cut_marked_segment(void)
{
filestruct *top;
filestruct *bot;
filestruct *tmp;
size_t top_x;
size_t bot_x;
size_t newsize;
/* If the mark doesn't cover any text, get out. */
if (current == mark_beginbuf && current_x == mark_beginx)
return;
assert(current != NULL && mark_beginbuf != NULL);
/* Set up the top and bottom lines and coordinates of the marked
* text. */
mark_order((const filestruct **)&top, &top_x,
(const filestruct **)&bot, &bot_x);
/* Make the first cut line manually. Move the cut part of the top
* line into tmp, and set newsize to that partial line's length. */
tmp = copy_node(top);
newsize = (top == bot ? bot_x - top_x : strlen(top->data + top_x));
charmove(tmp->data, tmp->data + top_x, newsize);
null_at(&tmp->data, newsize);
/* Add the contents of tmp to the cutbuffer. Note that cutbuffer
* might be non-NULL if we have cut to end enabled. */
if (cutbuffer == NULL) {
cutbuffer = tmp;
cutbottom = tmp;
} else {
cutbottom->next = tmp;
tmp->prev = cutbottom;
cutbottom = tmp;
}
/* And make the top remainder line manually too. Update current_x
* and totlines to account for all the cut text, and update totsize
* to account for the length of the cut part of the first line. */
current_x = top_x;
totsize -= newsize;
totlines -= bot->lineno - top->lineno;
/* Now set newsize to be the length of the top remainder line plus
* the bottom remainder line, plus one for the null terminator. */
newsize = top_x + strlen(bot->data + bot_x) + 1;
if (top == bot) {
/* In this case, we're only cutting one line or part of one
* line, so the remainder line is shorter. This means that we
* must move text from the end forward first. */
charmove(top->data + top_x, bot->data + bot_x, newsize - top_x);
top->data = charealloc(top->data, newsize);
cutbottom->next = NULL;
#ifdef DEBUG
dump_buffer(cutbuffer);
#endif
return;
}
/* Update totsize to account for the cut part of the last line. */
totsize -= bot_x + 1;
/* Here, the top remainder line might get longer (if the bottom
* remainder line is added to the end of it), so we realloc() it
* first. */
top->data = charealloc(top->data, newsize);
charmove(top->data + top_x, bot->data + bot_x, newsize - top_x);
assert(cutbottom != NULL && cutbottom->next != NULL);
/* We're cutting multiple lines, so in particular the next line is
* cut too. */
cutbottom->next->prev = cutbottom;
/* Update totsize to account for all the complete lines that have
* been cut. After this, totsize is fully up to date. */
for (tmp = top->next; tmp != bot; tmp = tmp->next)
totsize -= strlen(tmp->data) + 1;
/* Make the last cut line manually. */
null_at(&bot->data, bot_x);
/* Move the rest of the cut text (other than the cut part of the top
* line) from the buffer to the end of the cutbuffer, and fix the
* edit buffer to account for the cut text. */
top->next = bot->next;
cutbottom = bot;
cutbottom->next = NULL;
if (top->next != NULL)
top->next->prev = top;
renumber(top);
current = top;
/* If the bottom line of the cut was the magicline, set filebot
* properly, and add a new magicline if the top remainder line
* (which is now the new bottom line) is non-blank. */
if (bot == filebot) {
filebot = top;
assert(bot_x == 0);
if (top_x > 0)
new_magicline();
}
#ifdef DEBUG
dump_buffer(cutbuffer);
#endif
}
#endif
void do_cut_text(void)
{
filestruct *fileptr;
assert(current != NULL && current->data != NULL);
check_statusblank();
if (!keep_cutbuffer) {
free_filestruct(cutbuffer);
cutbuffer = NULL;
marked_cut = CUT_LINE;
#ifndef NANO_SMALL
concatenate_cut = FALSE;
#endif
#ifdef DEBUG
fprintf(stderr, "Blew away cutbuffer =)\n");
#endif
}
/* You can't cut the magicline except with the mark. But trying
* does clear the cutbuffer if keep_cutbuffer is FALSE. */
if (current == filebot
#ifndef NANO_SMALL
&& !ISSET(MARK_ISSET)
#endif
)
return;
keep_cutbuffer = TRUE;
#ifndef NANO_SMALL
if (ISSET(CUT_TO_END) && !ISSET(MARK_ISSET)) {
assert(current_x <= strlen(current->data));
if (current->data[current_x] == '\0') {
/* If the line is empty and we didn't just cut a non-blank
* line, create a dummy blank line and add it to the
* cutbuffer. */
if (marked_cut != CUT_MARKED && current->next != filebot) {
filestruct *junk = make_new_node(current);
junk->data = charalloc(1);
junk->data[0] = '\0';
add_to_cutbuffer(junk, TRUE);
#ifdef DEBUG
dump_buffer(cutbuffer);
#endif
}
do_delete();
marked_cut = CUT_TO_END;
return;
} else {
SET(MARK_ISSET);
mark_beginx = strlen(current->data);
mark_beginbuf = current;
}
}
if (ISSET(MARK_ISSET)) {
cut_marked_segment();
placewewant = xplustabs();
UNSET(MARK_ISSET);
/* If we just did a marked cut of part of a line, we should add
* the first line of any cut done immediately afterward to the
* end of this cut, as Pico does. */
if (current == mark_beginbuf && current_x <
strlen(current->data))
concatenate_cut = TRUE;
marked_cut = CUT_MARKED;
edit_refresh();
set_modified();
return;
}
#endif /* !NANO_SMALL */
totlines--;
totsize -= strlen(current->data) + 1;
fileptr = current;
current = current->next;
current->prev = fileptr->prev;
add_to_cutbuffer(fileptr, TRUE);
#ifdef DEBUG
dump_buffer(cutbuffer);
#endif
if (fileptr == fileage)
fileage = current;
else
current->prev->next = current;
if (fileptr == edittop)
edittop = current;
renumber(current);
current_x = 0;
edit_refresh();
set_modified();
marked_cut = CUT_LINE;
#ifndef NANO_SMALL
concatenate_cut = FALSE;
#endif
}
void do_uncut_text(void)
{
filestruct *tmp = current;
filestruct *newbuf = NULL;
filestruct *newend = NULL;
#ifndef DISABLE_WRAPPING
wrap_reset();
#endif
check_statusblank();
if (cutbuffer == NULL || current == NULL)
return; /* AIEEEEEEEEEEEE */
/* If we're uncutting a previously non-marked block, uncut to end if
* we're not at the beginning of the line. If we are at the
* beginning of the line, set placewewant to 0. Pico does both of
* these. */
if (marked_cut == CUT_LINE) {
if (current_x > 0)
marked_cut = CUT_TO_END;
else
placewewant = 0;
}
/* If we're going to uncut on the magicline, always make a new
* magicline in advance, as Pico does. */
if (current->next == NULL)
new_magicline();
if (marked_cut == CUT_LINE || cutbuffer->next != NULL) {
newbuf = copy_filestruct(cutbuffer);
for (newend = newbuf; newend->next != NULL && newend != NULL;
newend = newend->next)
totlines++;
}
/* Hook newbuf in at current. */
if (marked_cut != CUT_LINE) {
filestruct *hold = current;
/* If there's only one line in the cutbuffer... */
if (cutbuffer->next == NULL) {
size_t buf_len = strlen(cutbuffer->data);
size_t cur_len = strlen(current->data);
current->data = charealloc(current->data, cur_len +
buf_len + 1);
charmove(current->data + current_x + buf_len,
current->data + current_x, cur_len - current_x + 1);
strncpy(current->data + current_x, cutbuffer->data,
buf_len);
/* Use strncpy() to not copy the null terminator. */
current_x += buf_len;
totsize += buf_len;
placewewant = xplustabs();
} else { /* Yuck -- no kidding! */
char *tmpstr, *tmpstr2;
tmp = current->next;
/* New beginning. */
tmpstr = charalloc(current_x + strlen(newbuf->data) + 1);
strncpy(tmpstr, current->data, current_x);
strcpy(&tmpstr[current_x], newbuf->data);
totsize += strlen(newbuf->data) + strlen(newend->data) + 1;
/* New end. */
tmpstr2 = charalloc(strlen(newend->data) +
strlen(&current->data[current_x]) + 1);
strcpy(tmpstr2, newend->data);
strcat(tmpstr2, &current->data[current_x]);
free(current->data);
current->data = tmpstr;
current->next = newbuf->next;
newbuf->next->prev = current;
delete_node(newbuf);
current_x = strlen(newend->data);
placewewant = xplustabs();
free(newend->data);
newend->data = tmpstr2;
newend->next = tmp;
/* If tmp isn't NULL, we're in the middle: update the
* prev pointer. If it IS NULL, we're at the end; update
* the filebot pointer. */
if (tmp != NULL)
tmp->prev = newend;
else {
filebot = newend;
new_magicline();
}
/* Now why don't we update the totsize also? */
for (tmp = current->next; tmp != newend; tmp = tmp->next)
totsize += strlen(tmp->data) + 1;
current = newend;
}
/* If we're doing a cut to end, we don't want anything else on
* the line, so we have to screw up all the work we just did and
* separate the line. */
if (marked_cut == CUT_TO_END) {
tmp = make_new_node(current);
tmp->data = mallocstrcpy(NULL, current->data + current_x);
splice_node(current, tmp, current->next);
null_at(&current->data, current_x);
current = current->next;
current_x = 0;
placewewant = 0;
/* Extra line added; update stuff. */
totlines++;
totsize++;
}
/* Renumber from BEFORE where we pasted ;) */
renumber(hold);
#ifdef DEBUG
dump_buffer(fileage);
dump_buffer(cutbuffer);
#endif
set_modified();
edit_refresh();
return;
}
if (current != fileage) {
tmp = current->prev;
tmp->next = newbuf;
newbuf->prev = tmp;
} else
fileage = newbuf;
totlines++; /* Unmarked uncuts don't split lines. */
/* This is so uncutting at the top of the buffer will work => */
if (current_y == 0)
edittop = newbuf;
/* Connect the end of the buffer to the filestruct. */
newend->next = current;
current->prev = newend;
/* Recalculate size *sigh* */
for (tmp = newbuf; tmp != current; tmp = tmp->next)
totsize += strlen(tmp->data) + 1;
renumber(newbuf);
edit_refresh();
#ifdef DEBUG
dump_buffer_reverse();
#endif
set_modified();
}