/************************************************************************** * cut.c -- This file is part of GNU nano. * * * * Copyright (C) 1999-2011, 2013-2021 Free Software Foundation, Inc. * * Copyright (C) 2014 Mark Majeres * * Copyright (C) 2016, 2018-2020 Benno Schulenberg * * * * GNU nano 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 3 of the License, * * or (at your option) any later version. * * * * GNU nano 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, see http://www.gnu.org/licenses/. * * * **************************************************************************/ #include "prototypes.h" #include /* Delete the character under the cursor. */ void do_deletion(undo_type action) { openfile->placewewant = xplustabs(); /* When in the middle of a line, delete the current character. */ if (openfile->current->data[openfile->current_x] != '\0') { int charlen = char_length(openfile->current->data + openfile->current_x); size_t line_len = strlen(openfile->current->data + openfile->current_x); #ifndef NANO_TINY size_t old_amount = ISSET(SOFTWRAP) ? extra_chunks_in(openfile->current) : 0; /* If the type of action changed or the cursor moved to a different * line, create a new undo item, otherwise update the existing item. */ if (action != openfile->last_action || openfile->current->lineno != openfile->current_undo->head_lineno) add_undo(action, NULL); else update_undo(action); #endif /* Move the remainder of the line "in", over the current character. */ memmove(&openfile->current->data[openfile->current_x], &openfile->current->data[openfile->current_x + charlen], line_len - charlen + 1); #ifndef NANO_TINY /* When softwrapping, a changed number of chunks requires a refresh. */ if (ISSET(SOFTWRAP) && extra_chunks_in(openfile->current) != old_amount) refresh_needed = TRUE; /* Adjust the mark if it is after the cursor on the current line. */ if (openfile->mark == openfile->current && openfile->mark_x > openfile->current_x) openfile->mark_x -= charlen; #endif /* Otherwise, when not at end of buffer, join this line with the next. */ } else if (openfile->current != openfile->filebot) { linestruct *joining = openfile->current->next; /* If there is a magic line, and we're before it: don't eat it. */ if (joining == openfile->filebot && openfile->current_x != 0 && !ISSET(NO_NEWLINES)) { #ifndef NANO_TINY if (action == BACK) add_undo(BACK, NULL); #endif return; } #ifndef NANO_TINY add_undo(action, NULL); /* Adjust the mark if it is on the line that will be "eaten". */ if (openfile->mark == joining) { openfile->mark = openfile->current; openfile->mark_x += openfile->current_x; } openfile->current->has_anchor |= joining->has_anchor; #endif /* Add the content of the next line to that of the current one. */ openfile->current->data = nrealloc(openfile->current->data, strlen(openfile->current->data) + strlen(joining->data) + 1); strcat(openfile->current->data, joining->data); unlink_node(joining); /* Two lines were joined, so do a renumbering and refresh the screen. */ renumber_from(openfile->current); refresh_needed = TRUE; } else /* We're at the end-of-file: nothing to do. */ return; /* Adjust the file size, and remember it for a possible redo. */ openfile->totsize--; #ifndef NANO_TINY openfile->current_undo->newsize = openfile->totsize; #endif set_modified(); } /* Delete the character under the cursor. */ void do_delete(void) { #ifndef NANO_TINY if (openfile->mark && ISSET(LET_THEM_ZAP)) zap_text(); else #endif { do_deletion(DEL); #ifdef ENABLE_UTF8 while (openfile->current->data[openfile->current_x] != '\0' && is_zerowidth(openfile->current->data + openfile->current_x)) do_deletion(DEL); #endif } } /* Backspace over one character. That is, move the cursor left one * character, and then delete the character under the cursor. */ void do_backspace(void) { #ifndef NANO_TINY if (openfile->mark && ISSET(LET_THEM_ZAP)) zap_text(); else #endif if (openfile->current_x > 0) { openfile->current_x = step_left(openfile->current->data, openfile->current_x); do_deletion(BACK); } else if (openfile->current != openfile->filetop) { do_left(); do_deletion(BACK); } } /* Return FALSE when a cut command would not actually cut anything: when * on an empty line at EOF, or when the mark covers zero characters, or * (when test_cliff is TRUE) when the magic line would be cut. */ bool is_cuttable(bool test_cliff) { size_t from = (test_cliff) ? openfile->current_x : 0; if ((openfile->current->next == NULL && openfile->current->data[from] == '\0' #ifndef NANO_TINY && openfile->mark == NULL) || (openfile->mark == openfile->current && openfile->mark_x == openfile->current_x) || (from > 0 && !ISSET(NO_NEWLINES) && openfile->current->data[from] == '\0' && openfile->current->next == openfile->filebot #endif )) { #ifndef NANO_TINY statusbar(_("Nothing was cut")); openfile->mark = NULL; #endif return FALSE; } else return TRUE; } #ifndef NANO_TINY /* Delete text from the cursor until the first start of a word to * the left, or to the right when forward is TRUE. */ void chop_word(bool forward) { /* Remember the current cursor position. */ linestruct *is_current = openfile->current; size_t is_current_x = openfile->current_x; /* Remember where the cutbuffer is, then make it seem blank. */ linestruct *is_cutbuffer = cutbuffer; cutbuffer = NULL; /* Move the cursor to a word start, to the left or to the right. * If that word is on another line and the cursor was not already * on the edge of the original line, then put the cursor on that * edge instead, so that lines will not be joined unexpectedly. */ if (!forward) { do_prev_word(); if (openfile->current != is_current) { if (is_current_x > 0) { openfile->current = is_current; openfile->current_x = 0; } else openfile->current_x = strlen(openfile->current->data); } } else { do_next_word(ISSET(AFTER_ENDS)); if (openfile->current != is_current && is_current->data[is_current_x] != '\0') { openfile->current = is_current; openfile->current_x = strlen(is_current->data); } } /* Set the mark at the start of that word. */ openfile->mark = openfile->current; openfile->mark_x = openfile->current_x; /* Put the cursor back where it was, so an undo will put it there too. */ openfile->current = is_current; openfile->current_x = is_current_x; /* Now kill the marked region and a word is gone. */ add_undo(CUT, NULL); do_snip(TRUE, FALSE, FALSE); update_undo(CUT); /* Discard the cut word and restore the cutbuffer. */ free_lines(cutbuffer); cutbuffer = is_cutbuffer; } /* Delete a word leftward. */ void chop_previous_word(void) { if (openfile->current->prev == NULL && openfile->current_x == 0) statusbar(_("Nothing was cut")); else chop_word(BACKWARD); } /* Delete a word rightward. */ void chop_next_word(void) { openfile->mark = NULL; if (is_cuttable(TRUE)) chop_word(FORWARD); } #endif /* !NANO_TINY */ /* Excise the text between the given two points and add it to the cutbuffer. */ void extract_segment(linestruct *top, size_t top_x, linestruct *bot, size_t bot_x) { linestruct *taken, *last; bool edittop_inside = (openfile->edittop->lineno >= top->lineno && openfile->edittop->lineno <= bot->lineno); #ifndef NANO_TINY bool same_line = (openfile->mark == top); bool post_marked = (openfile->mark && (openfile->mark->lineno > top->lineno || (same_line && openfile->mark_x > top_x))); static bool inherited_anchor = FALSE; bool had_anchor = top->has_anchor; if (top == bot && top_x == bot_x) return; if (top != bot) for (linestruct *line = top->next; line != bot->next; line = line->next) had_anchor |= line->has_anchor; #endif if (top == bot) { taken = make_new_node(NULL); taken->data = measured_copy(top->data + top_x, bot_x - top_x); memmove(top->data + top_x, top->data + bot_x, strlen(top->data + bot_x) + 1); last = taken; } else if (top_x == 0 && bot_x == 0) { taken = top; last = make_new_node(NULL); last->data = copy_of(""); #ifndef NANO_TINY last->has_anchor = bot->has_anchor; #endif last->prev = bot->prev; bot->prev->next = last; last->next = NULL; bot->prev = top->prev; if (top->prev) top->prev->next = bot; else openfile->filetop = bot; openfile->current = bot; } else { taken = make_new_node(NULL); taken->data = copy_of(top->data + top_x); taken->next = top->next; top->next->prev = taken; top->next = bot->next; if (bot->next) bot->next->prev = top; top->data = nrealloc(top->data, top_x + strlen(bot->data + bot_x) + 1); strcpy(top->data + top_x, bot->data + bot_x); last = bot; last->data[bot_x] = '\0'; last->next = NULL; openfile->current = top; } /* Subtract the size of the excised text from the buffer size. */ openfile->totsize -= number_of_characters_in(taken, last); /* If the cutbuffer is currently empty, just move all the text directly * into it; otherwise, append the text to what is already there. */ if (cutbuffer == NULL) { cutbuffer = taken; cutbottom = last; #ifndef NANO_TINY inherited_anchor = taken->has_anchor; #endif } else { cutbottom->data = nrealloc(cutbottom->data, strlen(cutbottom->data) + strlen(taken->data) + 1); strcat(cutbottom->data, taken->data); #ifndef NANO_TINY cutbottom->has_anchor = taken->has_anchor && !inherited_anchor; inherited_anchor |= taken->has_anchor; #endif cutbottom->next = taken->next; delete_node(taken); if (cutbottom->next != NULL) { cutbottom->next->prev = cutbottom; cutbottom = last; } } openfile->current_x = top_x; #ifndef NANO_TINY openfile->current->has_anchor = had_anchor; if (post_marked || same_line) openfile->mark = openfile->current; if (post_marked) openfile->mark_x = openfile->current_x; #endif if (openfile->filebot == bot) openfile->filebot = openfile->current; renumber_from(openfile->current); /* When the beginning of the viewport was inside the excision, adjust. */ if (edittop_inside) { adjust_viewport(STATIONARY); refresh_needed = TRUE; } /* If the text doesn't end with a newline, and it should, add one. */ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') new_magicline(); } /* Meld the buffer that starts at topline into the current file buffer * at the current cursor position. */ void ingraft_buffer(linestruct *topline) { linestruct *line = openfile->current; size_t length = strlen(line->data); size_t extralen = strlen(topline->data); size_t xpos = openfile->current_x; char *tailtext = copy_of(line->data + xpos); #ifndef NANO_TINY bool mark_follows = (openfile->mark == line && !mark_is_before_cursor()); #endif linestruct *botline = topline; while (botline->next != NULL) botline = botline->next; /* Add the size of the text to be grafted to the buffer size. */ openfile->totsize += number_of_characters_in(topline, botline); if (topline != botline) length = xpos; if (extralen > 0) { /* Insert the text of topline at the current cursor position. */ line->data = nrealloc(line->data, length + extralen + 1); memmove(line->data + xpos + extralen, line->data + xpos, length - xpos + 1); strncpy(line->data + xpos, topline->data, extralen); } if (topline != botline) { /* When inserting at end-of-buffer, update the relevant pointer. */ if (line->next == NULL) openfile->filebot = botline; line->data[xpos + extralen] = '\0'; /* Hook the grafted lines in after the current one. */ botline->next = openfile->current->next; if (botline->next) botline->next->prev = botline; openfile->current->next = topline->next; topline->next->prev = openfile->current; /* Add the text after the cursor position at the end of botline. */ length = strlen(botline->data); extralen = strlen(tailtext); botline->data = nrealloc(botline->data, length + extralen + 1); strcpy(botline->data + length, tailtext); /* Put the cursor at the end of the grafted text. */ openfile->current = botline; openfile->current_x = length; } else openfile->current_x += extralen; #ifndef NANO_TINY /* When needed, update the mark's pointer and position. */ if (mark_follows && topline != botline) { openfile->mark = botline; openfile->mark_x += length - xpos; } else if (mark_follows) openfile->mark_x += extralen; #endif delete_node(topline); free(tailtext); renumber_from(line); /* If the text doesn't end with a newline, and it should, add one. */ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') new_magicline(); } /* Meld a copy of the given buffer into the current file buffer. */ void copy_from_buffer(linestruct *somebuffer) { linestruct *the_copy = copy_buffer(somebuffer); ingraft_buffer(the_copy); } #ifndef NANO_TINY /* Move all marked text from the current buffer into the cutbuffer. */ void cut_marked_region(void) { linestruct *top, *bot; size_t top_x, bot_x; get_region(&top, &top_x, &bot, &bot_x); extract_segment(top, top_x, bot, bot_x); openfile->placewewant = xplustabs(); } #endif /* Move text from the current buffer into the cutbuffer. * If until_eof is TRUE, move all text from the current cursor * position to the end of the file into the cutbuffer. If append * is TRUE (when zapping), always append the cut to the cutbuffer. */ void do_snip(bool marked, bool until_eof, bool append) { linestruct *line = openfile->current; #ifndef NANO_TINY keep_cutbuffer &= (openfile->last_action != COPY); #endif /* If cuts were not continuous, or when cutting a region, clear the slate. */ if ((marked || until_eof || !keep_cutbuffer) && !append) { free_lines(cutbuffer); cutbuffer = NULL; } #ifndef NANO_TINY /* Now move the relevant piece of text into the cutbuffer. */ if (until_eof) extract_segment(openfile->current, openfile->current_x, openfile->filebot, strlen(openfile->filebot->data)); else if (openfile->mark) { cut_marked_region(); openfile->mark = NULL; } else if (ISSET(CUT_FROM_CURSOR)) { /* When not at the end of a line, move the rest of this line into * the cutbuffer. Otherwise, when not at the end of the buffer, * move just the "line separator" into the cutbuffer. */ if (openfile->current_x < strlen(openfile->current->data)) extract_segment(line, openfile->current_x, line, strlen(line->data)); else if (openfile->current != openfile->filebot) { extract_segment(line, openfile->current_x, line->next, 0); openfile->placewewant = xplustabs(); } } else #endif { /* When not at end-of-buffer, move one full line into the cutbuffer; * otherwise, move all text until end-of-line into the cutbuffer. */ if (openfile->current != openfile->filebot) extract_segment(line, 0, line->next, 0); else extract_segment(line, 0, line, strlen(line->data)); openfile->placewewant = 0; } /* After a line operation, future ones should add to the cutbuffer. */ keep_cutbuffer = !marked && !until_eof; set_modified(); refresh_needed = TRUE; } /* Move text from the current buffer into the cutbuffer. */ void cut_text(void) { #ifndef NANO_TINY if (!is_cuttable(ISSET(CUT_FROM_CURSOR) && openfile->mark == NULL)) return; /* Only add a new undo item when the current item is not a CUT or when * the current cut is not contiguous with the previous cutting. */ if (openfile->last_action != CUT || !keep_cutbuffer) { keep_cutbuffer = FALSE; add_undo(CUT, NULL); } do_snip(openfile->mark != NULL, FALSE, FALSE); update_undo(CUT); #else if (is_cuttable(FALSE)) do_snip(FALSE, FALSE, FALSE); #endif wipe_statusbar(); } #ifndef NANO_TINY /* Cut from the current cursor position to the end of the file. */ void cut_till_eof(void) { ran_a_tool = TRUE; if (openfile->current->data[openfile->current_x] == '\0' && (openfile->current->next == NULL || (!ISSET(NO_NEWLINES) && openfile->current_x > 0 && openfile->current->next == openfile->filebot))) { statusbar(_("Nothing was cut")); return; } add_undo(CUT_TO_EOF, NULL); do_snip(FALSE, TRUE, FALSE); update_undo(CUT_TO_EOF); wipe_statusbar(); } /* Erase text (current line or marked region), sending it into oblivion. */ void zap_text(void) { /* Remember the current cutbuffer so it can be restored after the zap. */ linestruct *was_cutbuffer = cutbuffer; if (!is_cuttable(ISSET(CUT_FROM_CURSOR) && openfile->mark == NULL)) return; /* Add a new undo item only when the current item is not a ZAP or when * the current zap is not contiguous with the previous zapping. */ if (openfile->last_action != ZAP || !keep_cutbuffer) add_undo(ZAP, NULL); /* Use the cutbuffer from the ZAP undo item, so the cut can be undone. */ cutbuffer = openfile->current_undo->cutbuffer; do_snip(openfile->mark != NULL, FALSE, TRUE); update_undo(ZAP); wipe_statusbar(); cutbuffer = was_cutbuffer; } /* Make a copy of the marked region, putting it in the cutbuffer. */ void copy_marked_region(void) { linestruct *topline, *botline, *afterline; char *was_datastart, saved_byte; size_t top_x, bot_x; get_region(&topline, &top_x, &botline, &bot_x); openfile->last_action = OTHER; keep_cutbuffer = FALSE; openfile->mark = NULL; refresh_needed = TRUE; if (topline == botline && top_x == bot_x) { statusbar(_("Copied nothing")); return; } /* Make the area that was marked look like a separate buffer. */ afterline = botline->next; botline->next = NULL; saved_byte = botline->data[bot_x]; botline->data[bot_x] = '\0'; was_datastart = topline->data; topline->data += top_x; cutbuffer = copy_buffer(topline); /* Restore the proper state of the buffer. */ topline->data = was_datastart; botline->data[bot_x] = saved_byte; botline->next = afterline; } /* Copy text from the current buffer into the cutbuffer. The text is either * the marked region, the whole line, the text from cursor to end-of-line, * just the line break, or nothing, depending on mode and cursor position. */ void copy_text(void) { bool at_eol = (openfile->current->data[openfile->current_x] == '\0'); bool sans_newline = (ISSET(NO_NEWLINES) && openfile->current->next == NULL); size_t from_x = (ISSET(CUT_FROM_CURSOR)) ? openfile->current_x : 0; linestruct *was_current = openfile->current; linestruct *addition; if (openfile->mark || openfile->last_action != COPY || !keep_cutbuffer) { free_lines(cutbuffer); cutbuffer = NULL; } wipe_statusbar(); if (openfile->mark) { copy_marked_region(); return; } /* When at the very end of the buffer, there is nothing to do. */ if (openfile->current->next == NULL && at_eol && (ISSET(CUT_FROM_CURSOR) || openfile->current_x == 0 || cutbuffer)) { statusbar(_("Copied nothing")); return; } addition = make_new_node(NULL); addition->data = copy_of(openfile->current->data + from_x); if (ISSET(CUT_FROM_CURSOR)) sans_newline = !at_eol; /* Create the cutbuffer OR add to it, depending on the mode, the position * of the cursor, and whether or not the cutbuffer is currently empty. */ if (cutbuffer == NULL && sans_newline) { cutbuffer = addition; cutbottom = addition; } else if (cutbuffer == NULL) { cutbuffer = addition; cutbottom = make_new_node(cutbuffer); cutbottom->data = copy_of(""); cutbuffer->next = cutbottom; } else if (sans_newline) { addition->prev = cutbottom->prev; addition->prev->next = addition; delete_node(cutbottom); cutbottom = addition; } else if (ISSET(CUT_FROM_CURSOR)) { addition->prev = cutbottom; cutbottom->next = addition; cutbottom = addition; } else { addition->prev = cutbottom->prev; addition->prev->next = addition; addition->next = cutbottom; cutbottom->prev = addition; } /* When needed and possible, move the cursor to the next line. */ if ((!ISSET(CUT_FROM_CURSOR) || at_eol) && openfile->current->next) { openfile->current = openfile->current->next; openfile->current_x = 0; } else openfile->current_x = strlen(openfile->current->data); edit_redraw(was_current, FLOWING); openfile->last_action = COPY; keep_cutbuffer = TRUE; } #endif /* !NANO_TINY */ /* Copy text from the cutbuffer into the current buffer. */ void paste_text(void) { #if defined(ENABLE_WRAPPING) || !defined(NANO_TINY) /* Remember where the paste started. */ linestruct *was_current = openfile->current; #endif #ifndef NANO_TINY bool had_anchor = was_current->has_anchor; #endif ssize_t was_lineno = openfile->current->lineno; size_t was_leftedge = 0; if (cutbuffer == NULL) { statusline(AHEM, _("Cutbuffer is empty")); return; } #ifndef NANO_TINY add_undo(PASTE, NULL); if (ISSET(SOFTWRAP)) was_leftedge = leftedge_for(xplustabs(), openfile->current); #endif /* Add a copy of the text in the cutbuffer to the current buffer * at the current cursor position. */ copy_from_buffer(cutbuffer); #ifndef NANO_TINY /* Wipe any anchors in the pasted text, so that they don't proliferate. */ for (linestruct *line = was_current; line != openfile->current->next; line = line->next) line->has_anchor = FALSE; was_current->has_anchor = had_anchor; update_undo(PASTE); #endif #ifdef ENABLE_WRAPPING /* When still on the same line and doing hard-wrapping, limit the width. */ if (openfile->current == was_current && ISSET(BREAK_LONG_LINES)) do_wrap(); #endif /* If we pasted less than a screenful, don't center the cursor. */ if (less_than_a_screenful(was_lineno, was_leftedge)) focusing = FALSE; #ifdef ENABLE_COLOR else precalc_multicolorinfo(); #endif /* Set the desired x position to where the pasted text ends. */ openfile->placewewant = xplustabs(); set_modified(); wipe_statusbar(); refresh_needed = TRUE; }