/* $OpenBSD: ops.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ /* vi:set ts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. */ /* * ops.c: implementation of various operators: do_shift, do_delete, do_tilde, * do_change, do_yank, do_put, do_join */ #include "vim.h" #include "globals.h" #include "proto.h" #include "option.h" #include "ops.h" /* * Number of registers. * 0 = unnamed register, for normal yanks and puts * 1..9 = number registers, for deletes * 10..35 = named registers * 36 = delete register (-) * 37 = GUI selection register (*). Only if USE_GUI defined */ #ifdef USE_GUI # define NUM_REGISTERS 38 #else # define NUM_REGISTERS 37 #endif /* * Symbolic names for some registers. */ #define DELETION_REGISTER 36 #ifdef USE_GUI # define GUI_SELECTION_REGISTER 37 #endif /* * Each yank buffer is an array of pointers to lines. */ static struct yankbuf { char_u **y_array; /* pointer to array of line pointers */ linenr_t y_size; /* number of lines in y_array */ char_u y_type; /* MLINE, MCHAR or MBLOCK */ } y_buf[NUM_REGISTERS]; static struct yankbuf *y_current; /* ptr to current yank buffer */ static int yankappend; /* TRUE when appending */ static struct yankbuf *y_previous = NULL; /* ptr to last written yank buffr */ /* * structure used by block_prep, do_delete and do_yank for blockwise operators */ struct block_def { int startspaces; int endspaces; int textlen; char_u *textstart; colnr_t textcol; }; static void get_yank_buffer __ARGS((int)); static int stuff_yank __ARGS((int, char_u *)); static void free_yank __ARGS((long)); static void free_yank_all __ARGS((void)); static void block_prep __ARGS((struct block_def *, linenr_t, int)); static int same_leader __ARGS((int, char_u *, int, char_u *)); static int fmt_end_block __ARGS((linenr_t, int *, char_u **)); /* * do_shift - handle a shift operation */ void do_shift(op, curs_top, amount) int op; int curs_top; int amount; { register long i; int first_char; if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) return; for (i = op_line_count; --i >= 0; ) { first_char = *ml_get_curline(); if (first_char == NUL) /* empty line */ curwin->w_cursor.col = 0; /* * Don't move the line right if it starts with # and p_si is set. */ else #if defined(SMARTINDENT) || defined(CINDENT) if (first_char != '#' || ( # ifdef SMARTINDENT !curbuf->b_p_si # endif # if defined(SMARTINDENT) && defined(CINDENT) && # endif # ifdef CINDENT (!curbuf->b_p_cin || !in_cinkeys('#', ' ', TRUE)) # endif )) #endif { /* if (op_block_mode) shift the block, not the whole line else */ shift_line(op == LSHIFT, p_sr, amount); } ++curwin->w_cursor.lnum; } if (curs_top) /* put cursor on first line, for ">>" */ { curwin->w_cursor.lnum -= op_line_count; beginline(MAYBE); /* shift_line() may have changed cursor.col */ } else --curwin->w_cursor.lnum; /* put cursor on last line, for ":>" */ updateScreen(CURSUPD); if (op_line_count > p_report) smsg((char_u *)"%ld line%s %ced %d time%s", op_line_count, plural(op_line_count), (op == RSHIFT) ? '>' : '<', amount, plural((long)amount)); } /* * shift the current line one shiftwidth left (if left != 0) or right * leaves cursor on first blank in the line */ void shift_line(left, round, amount) int left; int round; int amount; { register int count; register int i, j; int p_sw = (int)curbuf->b_p_sw; count = get_indent(); /* get current indent */ if (round) /* round off indent */ { i = count / p_sw; /* number of p_sw rounded down */ j = count % p_sw; /* extra spaces */ if (j && left) /* first remove extra spaces */ --amount; if (left) { i -= amount; if (i < 0) i = 0; } else i += amount; count = i * p_sw; } else /* original vi indent */ { if (left) { count -= p_sw * amount; if (count < 0) count = 0; } else count += p_sw * amount; } set_indent(count, TRUE); /* set new indent */ } #if defined(LISPINDENT) || defined(CINDENT) /* * do_reindent - handle reindenting a block of lines for C or lisp. * * mechanism copied from do_shift, above */ void do_reindent(how) int (*how) __ARGS((void)); { register long i; char_u *l; int count; if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) return; for (i = op_line_count; --i >= 0 && !got_int; ) { /* it's a slow thing to do, so give feedback so there's no worry that * the computer's just hung. */ if ((i % 50 == 0 || i == op_line_count - 1) && op_line_count > p_report) smsg((char_u *)"%ld line%s to indent... ", i, plural(i)); /* * Be vi-compatible: For lisp indenting the first line is not * indented, unless there is only one line. */ #ifdef LISPINDENT if (i != op_line_count - 1 || op_line_count == 1 || how != get_lisp_indent) #endif { l = skipwhite(ml_get_curline()); if (*l == NUL) /* empty or blank line */ count = 0; else count = how(); /* get the indent for this line */ set_indent(count, TRUE); } ++curwin->w_cursor.lnum; } /* put cursor on first non-blank of indented line */ curwin->w_cursor.lnum -= op_line_count; beginline(MAYBE); updateScreen(CURSUPD); if (op_line_count > p_report) { i = op_line_count - (i + 1); smsg((char_u *)"%ld line%s indented ", i, plural(i)); } } #endif /* defined(LISPINDENT) || defined(CINDENT) */ /* * check if character is name of yank buffer * Note: There is no check for 0 (default register), caller should do this */ int is_yank_buffer(c, writing) int c; int writing; /* if TRUE check for writable buffers */ { if (c > '~') return FALSE; if (isalnum(c) || (!writing && vim_strchr((char_u *)".%:", c) != NULL) || c == '"' || c == '-' #ifdef USE_GUI || (gui.in_use && c == '*') #endif ) return TRUE; return FALSE; } /* * Set y_current and yankappend, according to the value of yankbuffer. * * If yankbuffer is 0 and writing, use buffer 0 * If yankbuffer is 0 and reading, use previous buffer */ static void get_yank_buffer(writing) int writing; { register int i; yankappend = FALSE; if (((yankbuffer == 0 && !writing) || yankbuffer == '"') && y_previous != NULL) { y_current = y_previous; return; } i = yankbuffer; if (isdigit(i)) i -= '0'; else if (islower(i)) i -= 'a' - 10; else if (isupper(i)) { i -= 'A' - 10; yankappend = TRUE; } else if (yankbuffer == '-') i = DELETION_REGISTER; #ifdef USE_GUI else if (gui.in_use && yankbuffer == '*') i = GUI_SELECTION_REGISTER; #endif else /* not 0-9, a-z, A-Z or '-': use buffer 0 */ i = 0; y_current = &(y_buf[i]); if (writing) /* remember the buffer we write into for do_put() */ y_previous = y_current; } /* * return TRUE if the current yank buffer has type MLINE */ int yank_buffer_mline() { if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, FALSE)) return FALSE; get_yank_buffer(FALSE); return (y_current->y_type == MLINE); } /* * start or stop recording into a yank buffer * * return FAIL for failure, OK otherwise */ int do_record(c) int c; { char_u *p; static int bufname; int retval; if (Recording == FALSE) /* start recording */ { /* registers 0-9, a-z and " are allowed */ if (c > '~' || (!isalnum(c) && c != '"')) retval = FAIL; else { Recording = TRUE; showmode(); bufname = c; retval = OK; } } else /* stop recording */ { Recording = FALSE; MSG(""); p = get_recorded(); if (p == NULL) retval = FAIL; else retval = (stuff_yank(bufname, p)); } return retval; } /* * stuff string 'p' into yank buffer 'bufname' (append if uppercase) * 'p' is assumed to be alloced. * * return FAIL for failure, OK otherwise */ static int stuff_yank(bufname, p) int bufname; char_u *p; { char_u *lp; char_u **pp; yankbuffer = bufname; /* check for read-only buffer */ if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, TRUE)) return FAIL; get_yank_buffer(TRUE); if (yankappend && y_current->y_array != NULL) { pp = &(y_current->y_array[y_current->y_size - 1]); lp = lalloc((long_u)(STRLEN(*pp) + STRLEN(p) + 1), TRUE); if (lp == NULL) { vim_free(p); return FAIL; } STRCPY(lp, *pp); STRCAT(lp, p); vim_free(p); vim_free(*pp); *pp = lp; } else { free_yank_all(); if ((y_current->y_array = (char_u **)alloc((unsigned)sizeof(char_u *))) == NULL) { vim_free(p); return FAIL; } y_current->y_array[0] = p; y_current->y_size = 1; y_current->y_type = MCHAR; /* used to be MLINE, why? */ } return OK; } /* * execute a yank buffer (register): copy it into the stuff buffer * * return FAIL for failure, OK otherwise */ int do_execbuf(c, colon, addcr) int c; int colon; /* insert ':' before each line */ int addcr; /* always add '\n' to end of line */ { static int lastc = NUL; long i; char_u *p; int truncated; int retval; if (c == '@') /* repeat previous one */ c = lastc; if (c == '%' || !is_yank_buffer(c, FALSE)) /* check for valid buffer */ return FAIL; lastc = c; if (c == ':') /* use last command line */ { if (last_cmdline == NULL) { EMSG(e_nolastcmd); return FAIL; } vim_free(new_last_cmdline); /* don't keep the cmdline containing @: */ new_last_cmdline = NULL; if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL) return FAIL; if (ins_typebuf(last_cmdline, FALSE, 0, TRUE) == FAIL) return FAIL; if (ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL) return FAIL; } else if (c == '.') /* use last inserted text */ { p = get_last_insert(); if (p == NULL) { EMSG(e_noinstext); return FAIL; } i = STRLEN(p); if (i > 0 && p[i - 1] == ESC) /* remove trailing ESC */ { p[i - 1] = NUL; truncated = TRUE; } else truncated = FALSE; retval = ins_typebuf(p, FALSE, 0, TRUE); if (truncated) p[i - 1] = ESC; return retval; } else { yankbuffer = c; get_yank_buffer(FALSE); if (y_current->y_array == NULL) return FAIL; /* * Insert lines into typeahead buffer, from last one to first one. */ for (i = y_current->y_size; --i >= 0; ) { /* insert newline between lines and after last line if type is MLINE */ if (y_current->y_type == MLINE || i < y_current->y_size - 1 || addcr) { if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL) return FAIL; } if (ins_typebuf(y_current->y_array[i], FALSE, 0, TRUE) == FAIL) return FAIL; if (colon && ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL) return FAIL; } Exec_reg = TRUE; /* disable the 'q' command */ } return OK; } /* * Insert a yank buffer: copy it into the Read buffer. * Used by CTRL-R command and middle mouse button in insert mode. * * return FAIL for failure, OK otherwise */ int insertbuf(c) int c; { long i; int retval = OK; /* * It is possible to get into an endless loop by having CTRL-R a in * register a and then, in insert mode, doing CTRL-R a. * If you hit CTRL-C, the loop will be broken here. */ mch_breakcheck(); if (got_int) return FAIL; /* check for valid buffer */ if (c != NUL && !is_yank_buffer(c, FALSE)) return FAIL; #ifdef USE_GUI if (c == '*') gui_get_selection(); /* may fill * register */ #endif if (c == '.') /* insert last inserted text */ retval = stuff_inserted(NUL, 1L, TRUE); else if (c == '%') /* insert file name */ { if (check_fname() == FAIL) return FAIL; stuffReadbuff(curbuf->b_xfilename); } else if (c == ':') /* insert last command line */ { if (last_cmdline == NULL) { EMSG(e_nolastcmd); return FAIL; } stuffReadbuff(last_cmdline); } else /* name or number register */ { yankbuffer = c; get_yank_buffer(FALSE); if (y_current->y_array == NULL) retval = FAIL; else { for (i = 0; i < y_current->y_size; ++i) { stuffReadbuff(y_current->y_array[i]); /* insert newline between lines and after last line if type is * MLINE */ if (y_current->y_type == MLINE || i < y_current->y_size - 1) stuffReadbuff((char_u *)"\n"); } } } return retval; } /* * paste a yank buffer into the command line. * used by CTRL-R command in command-line mode * insertbuf() can't be used here, because special characters from the * register contents will be interpreted as commands. * * return FAIL for failure, OK otherwise */ int cmdline_paste(c) int c; { long i; if (!is_yank_buffer(c, FALSE)) /* check for valid buffer */ return FAIL; #ifdef USE_GUI if (c == '*') gui_get_selection(); #endif if (c == '.') /* insert last inserted text */ return FAIL; /* Unimplemented */ if (c == '%') /* insert file name */ { if (check_fname() == FAIL) return FAIL; return put_on_cmdline(curbuf->b_xfilename, -1, TRUE); } if (c == ':') /* insert last command line */ { if (last_cmdline == NULL) return FAIL; return put_on_cmdline(last_cmdline, -1, TRUE); } yankbuffer = c; get_yank_buffer(FALSE); if (y_current->y_array == NULL) return FAIL; for (i = 0; i < y_current->y_size; ++i) { put_on_cmdline(y_current->y_array[i], -1, FALSE); /* insert ^M between lines and after last line if type is MLINE */ if (y_current->y_type == MLINE || i < y_current->y_size - 1) put_on_cmdline((char_u *)"\r", 1, FALSE); } return OK; } /* * do_delete - handle a delete operation */ void do_delete() { register int n; linenr_t lnum; char_u *ptr; char_u *newp, *oldp; linenr_t old_lcount = curbuf->b_ml.ml_line_count; int did_yank = FALSE; struct block_def bd; if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to do */ return; /* * Imitate the strange Vi behaviour: If the delete spans more than one line * and op_motion_type == MCHAR and the result is a blank line, make the delete * linewise. Don't do this for the change command. */ if (op_motion_type == MCHAR && op_line_count > 1 && op_type == DELETE) { ptr = ml_get(curbuf->b_op_end.lnum) + curbuf->b_op_end.col + op_inclusive; ptr = skipwhite(ptr); if (*ptr == NUL && inindent(0)) op_motion_type = MLINE; } /* * Check for trying to delete (e.g. "D") in an empty line. * Note: For change command it is ok. */ if (op_motion_type == MCHAR && op_line_count == 1 && op_type == DELETE && *ml_get(curbuf->b_op_start.lnum) == NUL) { beep_flush(); return; } /* * Do a yank of whatever we're about to delete. * If a yank buffer was specified, put the deleted text into that buffer */ if (yankbuffer != 0) { /* check for read-only buffer */ if (!is_yank_buffer(yankbuffer, TRUE)) { beep_flush(); return; } get_yank_buffer(TRUE); /* yank into specified buffer */ if (do_yank(TRUE, FALSE) == OK) /* yank without message */ did_yank = TRUE; } /* * Put deleted text into register 1 and shift number buffers if * the delete contains a line break, or when a yankbuffer has been specified! */ if (yankbuffer != 0 || op_motion_type == MLINE || op_line_count > 1) { y_current = &y_buf[9]; free_yank_all(); /* free buffer nine */ for (n = 9; n > 1; --n) y_buf[n] = y_buf[n - 1]; y_previous = y_current = &y_buf[1]; y_buf[1].y_array = NULL; /* set buffer one to empty */ yankbuffer = 0; } else if (yankbuffer == 0) /* yank into unnamed buffer */ { yankbuffer = '-'; /* use special delete buffer */ get_yank_buffer(TRUE); yankbuffer = 0; } if (yankbuffer == 0 && do_yank(TRUE, FALSE) == OK) did_yank = TRUE; /* * If there's too much stuff to fit in the yank buffer, then get a * confirmation before doing the delete. This is crude, but simple. And it * avoids doing a delete of something we can't put back if we want. */ if (!did_yank) { if (ask_yesno((char_u *)"cannot yank; delete anyway", TRUE) != 'y') { emsg(e_abort); return; } } /* * block mode delete */ if (op_block_mode) { if (u_save((linenr_t)(curbuf->b_op_start.lnum - 1), (linenr_t)(curbuf->b_op_end.lnum + 1)) == FAIL) return; for (lnum = curwin->w_cursor.lnum; curwin->w_cursor.lnum <= curbuf->b_op_end.lnum; ++curwin->w_cursor.lnum) { block_prep(&bd, curwin->w_cursor.lnum, TRUE); if (bd.textlen == 0) /* nothing to delete */ continue; /* * If we delete a TAB, it may be replaced by several characters. * Thus the number of characters may increase! */ n = bd.textlen - bd.startspaces - bd.endspaces; /* number of chars deleted */ oldp = ml_get_curline(); newp = alloc_check((unsigned)STRLEN(oldp) + 1 - n); if (newp == NULL) continue; /* copy up to deleted part */ vim_memmove(newp, oldp, (size_t)bd.textcol); /* insert spaces */ copy_spaces(newp + bd.textcol, (size_t)(bd.startspaces + bd.endspaces)); /* copy the part after the deleted part */ oldp += bd.textcol + bd.textlen; vim_memmove(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp, STRLEN(oldp) + 1); /* replace the line */ ml_replace(curwin->w_cursor.lnum, newp, FALSE); } curwin->w_cursor.lnum = lnum; CHANGED; updateScreen(VALID_TO_CURSCHAR); op_line_count = 0; /* no lines deleted */ } else if (op_motion_type == MLINE) { if (op_type == CHANGE) { /* Delete the lines except the first one. * Temporarily move the cursor to the next line. * Save the current line number, if the last line is deleted * it may be changed. */ if (op_line_count > 1) { lnum = curwin->w_cursor.lnum; ++curwin->w_cursor.lnum; dellines((long)(op_line_count - 1), TRUE, TRUE); curwin->w_cursor.lnum = lnum; } if (u_save_cursor() == FAIL) return; if (curbuf->b_p_ai) /* don't delete indent */ { beginline(TRUE); /* put cursor on first non-white */ did_ai = TRUE; /* delete the indent when ESC hit */ } truncate_line(FALSE); if (curwin->w_cursor.col > 0) --curwin->w_cursor.col; /* put cursor on last char in line */ } else { dellines(op_line_count, TRUE, TRUE); } u_clearline(); /* "U" command should not be possible after "dd" */ beginline(TRUE); } else if (op_line_count == 1) /* delete characters within one line */ { if (u_save_cursor() == FAIL) return; /* if 'cpoptions' contains '$', display '$' at end of change */ if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL && op_type == CHANGE && curbuf->b_op_end.lnum == curwin->w_cursor.lnum && !op_is_VIsual) display_dollar(curbuf->b_op_end.col - !op_inclusive); n = curbuf->b_op_end.col - curbuf->b_op_start.col + 1 - !op_inclusive; while (n-- > 0) if (delchar(TRUE) == FAIL) break; } else /* delete characters between lines */ { if (u_save_cursor() == FAIL) /* save first line for undo */ return; truncate_line(TRUE); /* delete from cursor to end of line */ curbuf->b_op_start = curwin->w_cursor; /* remember curwin->w_cursor */ ++curwin->w_cursor.lnum; /* includes save for undo */ dellines((long)(op_line_count - 2), TRUE, TRUE); if (u_save_cursor() == FAIL) /* save last line for undo */ return; n = curbuf->b_op_end.col - !op_inclusive; curwin->w_cursor.col = 0; while (n-- >= 0) /* delete from start of line until op_end */ if (delchar(TRUE) == FAIL) break; curwin->w_cursor = curbuf->b_op_start; /* restore curwin->w_cursor */ (void)do_join(FALSE, curs_rows() == OK); } if ((op_motion_type == MCHAR && op_line_count == 1) || op_type == CHANGE) { if (dollar_vcol) must_redraw = 0; /* don't want a redraw now */ cursupdate(); if (!dollar_vcol) updateline(); } else if (!global_busy) /* no need to update screen for :global */ updateScreen(CURSUPD); msgmore(curbuf->b_ml.ml_line_count - old_lcount); /* correct op_end for deleted text (for "']" command) */ if (op_block_mode) curbuf->b_op_end.col = curbuf->b_op_start.col; else curbuf->b_op_end = curbuf->b_op_start; } /* * do_tilde - handle the (non-standard vi) tilde operator */ void do_tilde() { FPOS pos; struct block_def bd; if (u_save((linenr_t)(curbuf->b_op_start.lnum - 1), (linenr_t)(curbuf->b_op_end.lnum + 1)) == FAIL) return; pos = curbuf->b_op_start; if (op_block_mode) /* Visual block mode */ { for (; pos.lnum <= curbuf->b_op_end.lnum; ++pos.lnum) { block_prep(&bd, pos.lnum, FALSE); pos.col = bd.textcol; while (--bd.textlen >= 0) { swapchar(&pos); if (inc(&pos) == -1) /* at end of file */ break; } } } else /* not block mode */ { if (op_motion_type == MLINE) { pos.col = 0; curbuf->b_op_end.col = STRLEN(ml_get(curbuf->b_op_end.lnum)); if (curbuf->b_op_end.col) --curbuf->b_op_end.col; } else if (!op_inclusive) dec(&(curbuf->b_op_end)); while (ltoreq(pos, curbuf->b_op_end)) { swapchar(&pos); if (inc(&pos) == -1) /* at end of file */ break; } } if (op_motion_type == MCHAR && op_line_count == 1 && !op_block_mode) { cursupdate(); updateline(); } else updateScreen(CURSUPD); if (op_line_count > p_report) smsg((char_u *)"%ld line%s ~ed", op_line_count, plural(op_line_count)); } /* * If op_type == UPPER: make uppercase, * if op_type == LOWER: make lowercase, * else swap case of character at 'pos' */ void swapchar(pos) FPOS *pos; { int c; c = gchar(pos); if (islower(c) && op_type != LOWER) { pchar(*pos, toupper(c)); CHANGED; } else if (isupper(c) && op_type != UPPER) { pchar(*pos, tolower(c)); CHANGED; } } /* * do_change - handle a change operation * * return TRUE if edit() returns because of a CTRL-O command */ int do_change() { register colnr_t l; l = curbuf->b_op_start.col; if (op_motion_type == MLINE) { l = 0; can_si = TRUE; /* It's like opening a new line, do si */ } if (!op_empty) do_delete(); /* delete the text and take care of undo */ if ((l > curwin->w_cursor.col) && !lineempty(curwin->w_cursor.lnum)) inc_cursor(); #ifdef LISPINDENT if (op_motion_type == MLINE) { if (curbuf->b_p_lisp && curbuf->b_p_ai) fixthisline(get_lisp_indent); # ifdef CINDENT else if (curbuf->b_p_cin) fixthisline(get_c_indent); # endif } #endif op_type = NOP; /* don't want op_type == CHANGED in Insert mode */ return edit(NUL, FALSE, (linenr_t)1); } /* * set all the yank buffers to empty (called from main()) */ void init_yank() { register int i; for (i = 0; i < NUM_REGISTERS; ++i) y_buf[i].y_array = NULL; } /* * Free "n" lines from the current yank buffer. * Called for normal freeing and in case of error. */ static void free_yank(n) long n; { if (y_current->y_array != NULL) { register long i; for (i = n; --i >= 0; ) { if ((i & 1023) == 1023) /* this may take a while */ { /* * This message should never cause a hit-return message. * Overwrite this message with any next message. */ ++no_wait_return; smsg((char_u *)"freeing %ld lines", i + 1); --no_wait_return; msg_didout = FALSE; msg_col = 0; } vim_free(y_current->y_array[i]); } vim_free(y_current->y_array); y_current->y_array = NULL; if (n >= 1000) MSG(""); } } static void free_yank_all() { free_yank(y_current->y_size); } /* * Yank the text between curwin->w_cursor and startpos into a yank buffer. * If we are to append ("uppercase), we first yank into a new yank buffer and * then concatenate the old and the new one (so we keep the old one in case * of out-of-memory). * * return FAIL for failure, OK otherwise */ int do_yank(deleting, mess) int deleting; int mess; { long i; /* index in y_array[] */ struct yankbuf *curr; /* copy of y_current */ struct yankbuf newbuf; /* new yank buffer when appending */ char_u **new_ptr; register linenr_t lnum; /* current line number */ long j; int yanktype = op_motion_type; long yanklines = op_line_count; linenr_t yankendlnum = curbuf->b_op_end.lnum; char_u *pnew; struct block_def bd; /* check for read-only buffer */ if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, TRUE)) { beep_flush(); return FAIL; } if (!deleting) /* do_delete() already set y_current */ get_yank_buffer(TRUE); curr = y_current; /* append to existing contents */ if (yankappend && y_current->y_array != NULL) y_current = &newbuf; else free_yank_all(); /* free previously yanked lines */ /* * If the cursor was in column 1 before and after the movement, and the * operator is not inclusive, the yank is always linewise. */ if (op_motion_type == MCHAR && curbuf->b_op_start.col == 0 && !op_inclusive && curbuf->b_op_end.col == 0 && yanklines > 1) { yanktype = MLINE; --yankendlnum; --yanklines; } y_current->y_size = yanklines; y_current->y_type = yanktype; /* set the yank buffer type */ y_current->y_array = (char_u **)lalloc((long_u)(sizeof(char_u *) * yanklines), TRUE); if (y_current->y_array == NULL) { y_current = curr; return FAIL; } i = 0; lnum = curbuf->b_op_start.lnum; /* * Visual block mode */ if (op_block_mode) { y_current->y_type = MBLOCK; /* set the yank buffer type */ for ( ; lnum <= yankendlnum; ++lnum) { block_prep(&bd, lnum, FALSE); if ((pnew = alloc(bd.startspaces + bd.endspaces + bd.textlen + 1)) == NULL) goto fail; y_current->y_array[i++] = pnew; copy_spaces(pnew, (size_t)bd.startspaces); pnew += bd.startspaces; vim_memmove(pnew, bd.textstart, (size_t)bd.textlen); pnew += bd.textlen; copy_spaces(pnew, (size_t)bd.endspaces); pnew += bd.endspaces; *pnew = NUL; } } else { /* * there are three parts for non-block mode: * 1. if yanktype != MLINE yank last part of the top line * 2. yank the lines between op_start and op_end, inclusive when * yanktype == MLINE * 3. if yanktype != MLINE yank first part of the bot line */ if (yanktype != MLINE) { if (yanklines == 1) /* op_start and op_end on same line */ { j = curbuf->b_op_end.col - curbuf->b_op_start.col + 1 - !op_inclusive; if ((y_current->y_array[0] = strnsave(ml_get(lnum) + curbuf->b_op_start.col, (int)j)) == NULL) { fail: free_yank(i); /* free the allocated lines */ y_current = curr; return FAIL; } goto success; } if ((y_current->y_array[0] = strsave(ml_get(lnum++) + curbuf->b_op_start.col)) == NULL) goto fail; ++i; } while (yanktype == MLINE ? (lnum <= yankendlnum) : (lnum < yankendlnum)) { if ((y_current->y_array[i] = strsave(ml_get(lnum++))) == NULL) goto fail; ++i; } if (yanktype != MLINE) { if ((y_current->y_array[i] = strnsave(ml_get(yankendlnum), curbuf->b_op_end.col + 1 - !op_inclusive)) == NULL) goto fail; } } success: if (curr != y_current) /* append the new block to the old block */ { new_ptr = (char_u **)lalloc((long_u)(sizeof(char_u *) * (curr->y_size + y_current->y_size)), TRUE); if (new_ptr == NULL) goto fail; for (j = 0; j < curr->y_size; ++j) new_ptr[j] = curr->y_array[j]; vim_free(curr->y_array); curr->y_array = new_ptr; if (yanktype == MLINE) /* MLINE overrides MCHAR and MBLOCK */ curr->y_type = MLINE; /* concatenate the last line of the old block with the first line of * the new block */ if (curr->y_type == MCHAR) { pnew = lalloc((long_u)(STRLEN(curr->y_array[curr->y_size - 1]) + STRLEN(y_current->y_array[0]) + 1), TRUE); if (pnew == NULL) { i = y_current->y_size - 1; goto fail; } STRCPY(pnew, curr->y_array[--j]); STRCAT(pnew, y_current->y_array[0]); vim_free(curr->y_array[j]); vim_free(y_current->y_array[0]); curr->y_array[j++] = pnew; i = 1; } else i = 0; while (i < y_current->y_size) curr->y_array[j++] = y_current->y_array[i++]; curr->y_size = j; vim_free(y_current->y_array); y_current = curr; } if (mess) /* Display message about yank? */ { if (yanktype == MCHAR && !op_block_mode) --yanklines; if (yanklines > p_report) { cursupdate(); /* redisplay now, so message is not deleted */ smsg((char_u *)"%ld line%s yanked", yanklines, plural(yanklines)); } } return OK; } /* * put contents of register into the text * For ":put" command count == -1. */ void do_put(dir, count, fix_indent) int dir; /* BACKWARD for 'P', FORWARD for 'p' */ long count; int fix_indent; /* make indent look nice */ { char_u *ptr; char_u *newp, *oldp; int yanklen; int oldlen; int totlen = 0; /* init for gcc */ linenr_t lnum; colnr_t col; long i; /* index in y_array[] */ int y_type; long y_size; char_u **y_array; long nr_lines = 0; colnr_t vcol; int delcount; int incr = 0; long j; FPOS new_cursor; int indent; int orig_indent = 0; /* init for gcc */ int indent_diff = 0; /* init for gcc */ int first_indent = TRUE; FPOS old_pos; struct block_def bd; char_u *insert_string = NULL; #ifdef USE_GUI if (yankbuffer == '*') gui_get_selection(); #endif if (fix_indent) orig_indent = get_indent(); curbuf->b_op_start = curwin->w_cursor; /* default for "'[" command */ if (dir == FORWARD) curbuf->b_op_start.col++; curbuf->b_op_end = curwin->w_cursor; /* default for "']" command */ /* * Using inserted text works differently, because the buffer includes * special characters (newlines, etc.). */ if (yankbuffer == '.') { (void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') : (count == -1 ? 'O' : 'i')), count, FALSE); return; } /* * For '%' (file name) and ':' (last command line) we have to create a * fake yank buffer. */ if (yankbuffer == '%') /* use file name */ { if (check_fname() == FAIL) return; insert_string = curbuf->b_xfilename; } else if (yankbuffer == ':') /* use last command line */ { if (last_cmdline == NULL) { EMSG(e_nolastcmd); return; } insert_string = last_cmdline; } if (insert_string != NULL) { y_type = MCHAR; /* use fake one-line yank buffer */ y_size = 1; y_array = &insert_string; } else { get_yank_buffer(FALSE); y_type = y_current->y_type; y_size = y_current->y_size; y_array = y_current->y_array; } if (count == -1) /* :put command */ { y_type = MLINE; count = 1; } if (y_size == 0 || y_array == NULL) { EMSG2("Nothing in register %s", transchar(yankbuffer)); return; } if (y_type == MBLOCK) { lnum = curwin->w_cursor.lnum + y_size + 1; if (lnum > curbuf->b_ml.ml_line_count) lnum = curbuf->b_ml.ml_line_count + 1; if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL) return; } else if (u_save_cursor() == FAIL) return; yanklen = STRLEN(y_array[0]); CHANGED; lnum = curwin->w_cursor.lnum; col = curwin->w_cursor.col; /* * block mode */ if (y_type == MBLOCK) { if (dir == FORWARD && gchar_cursor() != NUL) { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); ++col; ++curwin->w_cursor.col; } else getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL); for (i = 0; i < y_size; ++i) { bd.startspaces = 0; bd.endspaces = 0; bd.textcol = 0; vcol = 0; delcount = 0; /* add a new line */ if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { ml_append(curbuf->b_ml.ml_line_count, (char_u *)"", (colnr_t)1, FALSE); ++nr_lines; } oldp = ml_get_curline(); oldlen = STRLEN(oldp); for (ptr = oldp; vcol < col && *ptr; ++ptr) { /* Count a tab for what it's worth (if list mode not on) */ incr = lbr_chartabsize(ptr, (colnr_t)vcol); vcol += incr; ++bd.textcol; } if (vcol < col) /* line too short, padd with spaces */ { bd.startspaces = col - vcol; } else if (vcol > col) { bd.endspaces = vcol - col; bd.startspaces = incr - bd.endspaces; --bd.textcol; delcount = 1; } yanklen = STRLEN(y_array[i]); totlen = count * yanklen + bd.startspaces + bd.endspaces; newp = alloc_check((unsigned)totlen + oldlen + 1); if (newp == NULL) break; /* copy part up to cursor to new line */ ptr = newp; vim_memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; /* may insert some spaces before the new text */ copy_spaces(ptr, (size_t)bd.startspaces); ptr += bd.startspaces; /* insert the new text */ for (j = 0; j < count; ++j) { vim_memmove(ptr, y_array[i], (size_t)yanklen); ptr += yanklen; } /* may insert some spaces after the new text */ copy_spaces(ptr, (size_t)bd.endspaces); ptr += bd.endspaces; /* move the text after the cursor to the end of the line. */ vim_memmove(ptr, oldp + bd.textcol + delcount, (size_t)(oldlen - bd.textcol - delcount + 1)); ml_replace(curwin->w_cursor.lnum, newp, FALSE); ++curwin->w_cursor.lnum; if (i == 0) curwin->w_cursor.col += bd.startspaces; } /* for "']" command */ curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1; curbuf->b_op_end.col = bd.textcol + totlen - 1; curwin->w_cursor.lnum = lnum; cursupdate(); updateScreen(VALID_TO_CURSCHAR); } else /* not block mode */ { if (y_type == MCHAR) { /* if type is MCHAR, FORWARD is the same as BACKWARD on the next char */ if (dir == FORWARD && gchar_cursor() != NUL) { ++col; if (yanklen) { ++curwin->w_cursor.col; ++curbuf->b_op_end.col; } } new_cursor = curwin->w_cursor; } else if (dir == BACKWARD) /* if type is MLINE, BACKWARD is the same as FORWARD on the previous line */ --lnum; /* * simple case: insert into current line */ if (y_type == MCHAR && y_size == 1) { totlen = count * yanklen; if (totlen) { oldp = ml_get(lnum); newp = alloc_check((unsigned)(STRLEN(oldp) + totlen + 1)); if (newp == NULL) return; /* alloc() will give error message */ vim_memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < count; ++i) { vim_memmove(ptr, y_array[0], (size_t)yanklen); ptr += yanklen; } vim_memmove(ptr, oldp + col, STRLEN(oldp + col) + 1); ml_replace(lnum, newp, FALSE); /* put cursor on last putted char */ curwin->w_cursor.col += (colnr_t)(totlen - 1); } curbuf->b_op_end = curwin->w_cursor; updateline(); } else { while (--count >= 0) { i = 0; if (y_type == MCHAR) { /* * Split the current line in two at the insert position. * First insert y_array[size - 1] in front of second line. * Then append y_array[0] to first line. */ ptr = ml_get(lnum) + col; totlen = STRLEN(y_array[y_size - 1]); newp = alloc_check((unsigned)(STRLEN(ptr) + totlen + 1)); if (newp == NULL) goto error; STRCPY(newp, y_array[y_size - 1]); STRCAT(newp, ptr); /* insert second line */ ml_append(lnum, newp, (colnr_t)0, FALSE); vim_free(newp); oldp = ml_get(lnum); newp = alloc_check((unsigned)(col + yanklen + 1)); if (newp == NULL) goto error; /* copy first part of line */ vim_memmove(newp, oldp, (size_t)col); /* append to first line */ vim_memmove(newp + col, y_array[0], (size_t)(yanklen + 1)); ml_replace(lnum, newp, FALSE); curwin->w_cursor.lnum = lnum; i = 1; } while (i < y_size) { if ((y_type != MCHAR || i < y_size - 1) && ml_append(lnum, y_array[i], (colnr_t)0, FALSE) == FAIL) goto error; lnum++; i++; if (fix_indent) { old_pos = curwin->w_cursor; curwin->w_cursor.lnum = lnum; ptr = ml_get(lnum); #if defined(SMARTINDENT) || defined(CINDENT) if (*ptr == '#' # ifdef SMARTINDENT && curbuf->b_p_si # endif # ifdef CINDENT && curbuf->b_p_cin && in_cinkeys('#', ' ', TRUE) # endif ) indent = 0; /* Leave # lines at start */ else #endif if (*ptr == NUL) indent = 0; /* Ignore empty lines */ else if (first_indent) { indent_diff = orig_indent - get_indent(); indent = orig_indent; first_indent = FALSE; } else if ((indent = get_indent() + indent_diff) < 0) indent = 0; set_indent(indent, TRUE); curwin->w_cursor = old_pos; } ++nr_lines; } } /* put '] at last inserted character */ curbuf->b_op_end.lnum = lnum; col = STRLEN(y_array[y_size - 1]); if (col > 1) curbuf->b_op_end.col = col - 1; else curbuf->b_op_end.col = 0; if (y_type == MLINE) { curwin->w_cursor.col = 0; if (dir == FORWARD) { updateScreen(NOT_VALID); /* recomp. curwin->w_botline */ ++curwin->w_cursor.lnum; } /* put cursor on first non-blank in last inserted line */ beginline(TRUE); } else /* put cursor on first inserted character */ { curwin->w_cursor = new_cursor; } error: if (y_type == MLINE) /* for '[ */ { curbuf->b_op_start.col = 0; if (dir == FORWARD) curbuf->b_op_start.lnum++; } mark_adjust(curbuf->b_op_start.lnum + (y_type == MCHAR), MAXLNUM, nr_lines, 0L); updateScreen(CURSUPD); } } msgmore(nr_lines); curwin->w_set_curswant = TRUE; } /* Return the character name of the register with the given number */ int get_register_name(num) int num; { if (num == -1) return '"'; else if (num < 10) return num + '0'; else if (num == DELETION_REGISTER) return '-'; #ifdef USE_GUI else if (num == GUI_SELECTION_REGISTER) return '*'; #endif else return num + 'a' - 10; } /* * display the contents of the yank buffers */ void do_dis(arg) char_u *arg; { register int i, n; register long j; register char_u *p; register struct yankbuf *yb; char_u name; if (arg != NULL && *arg == NUL) arg = NULL; set_highlight('t'); /* Highlight title */ start_highlight(); MSG_OUTSTR("\n--- Registers ---"); stop_highlight(); for (i = -1; i < NUM_REGISTERS; ++i) { if (i == -1) { if (y_previous != NULL) yb = y_previous; else yb = &(y_buf[0]); } else yb = &(y_buf[i]); name = get_register_name(i); if (yb->y_array != NULL && (arg == NULL || vim_strchr(arg, name) != NULL)) { msg_outchar('\n'); msg_outchar('"'); msg_outchar(name); MSG_OUTSTR(" "); n = (int)Columns - 6; for (j = 0; j < yb->y_size && n > 1; ++j) { if (j) { MSG_OUTSTR("^J"); n -= 2; } for (p = yb->y_array[j]; *p && (n -= charsize(*p)) >= 0; ++p) msg_outtrans_len(p, 1); } flushbuf(); /* show one line at a time */ } } /* * display last inserted text */ if ((p = get_last_insert()) != NULL && (arg == NULL || vim_strchr(arg, '.') != NULL)) { MSG_OUTSTR("\n\". "); dis_msg(p, TRUE); } /* * display last command line */ if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)) { MSG_OUTSTR("\n\": "); dis_msg(last_cmdline, FALSE); } /* * display current file name */ if (curbuf->b_xfilename != NULL && (arg == NULL || vim_strchr(arg, '%') != NULL)) { MSG_OUTSTR("\n\"% "); dis_msg(curbuf->b_xfilename, FALSE); } } /* * display a string for do_dis() * truncate at end of screen line */ void dis_msg(p, skip_esc) char_u *p; int skip_esc; /* if TRUE, ignore trailing ESC */ { int n; n = (int)Columns - 6; while (*p && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= charsize(*p)) >= 0) msg_outtrans_len(p++, 1); } /* * join 'count' lines (minimal 2), including u_save() */ void do_do_join(count, insert_space, redraw) long count; int insert_space; int redraw; /* can redraw, curwin->w_col valid */ { if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), (linenr_t)(curwin->w_cursor.lnum + count)) == FAIL) return; if (count > 10) redraw = FALSE; /* don't redraw each small change */ while (--count > 0) { line_breakcheck(); if (got_int || do_join(insert_space, redraw) == FAIL) { beep_flush(); break; } } if (redraw) redraw_later(VALID_TO_CURSCHAR); else redraw_later(NOT_VALID); /* * Need to update the screen if the line where the cursor is became too * long to fit on the screen. */ cursupdate(); } /* * Join two lines at the cursor position. * * return FAIL for failure, OK ohterwise */ int do_join(insert_space, redraw) int insert_space; int redraw; /* should only be TRUE when curwin->w_row valid */ { char_u *curr; char_u *next; char_u *newp; int endcurr1, endcurr2; int currsize; /* size of the current line */ int nextsize; /* size of the next line */ int spaces; /* number of spaces to insert */ int rows_to_del = 0;/* number of rows on screen to delete */ linenr_t t; if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) return FAIL; /* can't join on last line */ if (redraw) rows_to_del = plines_m(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1); curr = ml_get_curline(); currsize = STRLEN(curr); endcurr1 = endcurr2 = NUL; if (currsize > 0) { endcurr1 = *(curr + currsize - 1); if (currsize > 1) endcurr2 = *(curr + currsize - 2); } next = ml_get((linenr_t)(curwin->w_cursor.lnum + 1)); spaces = 0; if (insert_space) { next = skipwhite(next); spaces = 1; if (*next == ')' || currsize == 0) spaces = 0; else { if (endcurr1 == ' ' || endcurr1 == TAB) { spaces = 0; if (currsize > 1) endcurr1 = endcurr2; } if (p_js && vim_strchr((char_u *)".!?", endcurr1) != NULL) spaces = 2; } } nextsize = STRLEN(next); newp = alloc_check((unsigned)(currsize + nextsize + spaces + 1)); if (newp == NULL) return FAIL; /* * Insert the next line first, because we already have that pointer. * Curr has to be obtained again, because getting next will have * invalidated it. */ vim_memmove(newp + currsize + spaces, next, (size_t)(nextsize + 1)); curr = ml_get_curline(); vim_memmove(newp, curr, (size_t)currsize); copy_spaces(newp + currsize, (size_t)spaces); ml_replace(curwin->w_cursor.lnum, newp, FALSE); /* * Delete the following line. To do this we move the cursor there * briefly, and then move it back. After dellines() the cursor may * have moved up (last line deleted), so the current lnum is kept in t. */ t = curwin->w_cursor.lnum; ++curwin->w_cursor.lnum; dellines(1L, FALSE, FALSE); curwin->w_cursor.lnum = t; /* * the number of rows on the screen is reduced by the difference * in number of rows of the two old lines and the one new line */ if (redraw) { rows_to_del -= plines(curwin->w_cursor.lnum); if (rows_to_del > 0) win_del_lines(curwin, curwin->w_cline_row + curwin->w_cline_height, rows_to_del, TRUE, TRUE); } /* * go to first character of the joined line */ if (currsize == 0) curwin->w_cursor.col = 0; else { curwin->w_cursor.col = currsize - 1; (void)oneright(); } CHANGED; return OK; } /* * Return TRUE if the two comment leaders given are the same. The cursor is * in the first line. White-space is ignored. Note that the whole of * 'leader1' must match 'leader2_len' characters from 'leader2' -- webb */ static int same_leader(leader1_len, leader1_flags, leader2_len, leader2_flags) int leader1_len; char_u *leader1_flags; int leader2_len; char_u *leader2_flags; { int idx1 = 0, idx2 = 0; char_u *p; char_u *line1; char_u *line2; if (leader1_len == 0) return (leader2_len == 0); /* * If first leader has 'f' flag, the lines can be joined only if the * second line does not have a leader. * If first leader has 'e' flag, the lines can never be joined. * If fist leader has 's' flag, the lines can only be joined if there is * some text after it and the second line has the 'm' flag. */ if (leader1_flags != NULL) { for (p = leader1_flags; *p && *p != ':'; ++p) { if (*p == COM_FIRST) return (leader2_len == 0); if (*p == COM_END) return FALSE; if (*p == COM_START) { if (*(ml_get_curline() + leader1_len) == NUL) return FALSE; if (leader2_flags == NULL || leader2_len == 0) return FALSE; for (p = leader2_flags; *p && *p != ':'; ++p) if (*p == COM_MIDDLE) return TRUE; return FALSE; } } } /* * Get current line and next line, compare the leaders. * The first line has to be saved, only one line can be locked at a time. */ line1 = strsave(ml_get_curline()); if (line1 != NULL) { for (idx1 = 0; vim_iswhite(line1[idx1]); ++idx1) ; line2 = ml_get(curwin->w_cursor.lnum + 1); for (idx2 = 0; idx2 < leader2_len; ++idx2) { if (!vim_iswhite(line2[idx2])) { if (line1[idx1++] != line2[idx2]) break; } else while (vim_iswhite(line1[idx1])) ++idx1; } vim_free(line1); } return (idx2 == leader2_len && idx1 == leader1_len); } /* * implementation of the format operator 'Q' */ void do_format() { long old_line_count = curbuf->b_ml.ml_line_count; int prev_is_blank = FALSE; int is_end_block = TRUE; int next_is_end_block; int leader_len = 0; /* init for gcc */ int next_leader_len; char_u *leader_flags = NULL; char_u *next_leader_flags; int advance = TRUE; int second_indent = -1; int do_second_indent; int first_par_line = TRUE; if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) return; /* check for 'q' and '2' in 'formatoptions' */ fo_do_comments = has_format_option(FO_Q_COMS); do_second_indent = has_format_option(FO_Q_SECOND); /* * get info about the previous and current line. */ if (curwin->w_cursor.lnum > 1) is_end_block = fmt_end_block(curwin->w_cursor.lnum - 1, &next_leader_len, &next_leader_flags); next_is_end_block = fmt_end_block(curwin->w_cursor.lnum, &next_leader_len, &next_leader_flags); curwin->w_cursor.lnum--; while (--op_line_count >= 0) { /* * Advance to next block. */ if (advance) { curwin->w_cursor.lnum++; prev_is_blank = is_end_block; is_end_block = next_is_end_block; leader_len = next_leader_len; leader_flags = next_leader_flags; } /* * The last line to be formatted. */ if (op_line_count == 0) { next_is_end_block = TRUE; next_leader_len = 0; next_leader_flags = NULL; } else next_is_end_block = fmt_end_block(curwin->w_cursor.lnum + 1, &next_leader_len, &next_leader_flags); advance = TRUE; /* * For the first line of a paragraph, check indent of second line. * Don't do this for comments and empty lines. */ if (first_par_line && do_second_indent && prev_is_blank && !is_end_block && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count && leader_len == 0 && next_leader_len == 0 && !lineempty(curwin->w_cursor.lnum + 1)) second_indent = get_indent_lnum(curwin->w_cursor.lnum + 1); /* * Skip end-of-block (blank) lines */ if (is_end_block) { } /* * If we have got to the end of a paragraph, format it. */ else if (next_is_end_block || !same_leader(leader_len, leader_flags, next_leader_len, next_leader_flags)) { /* replace indent in first line with minimal number of tabs and * spaces, according to current options */ set_indent(get_indent(), TRUE); /* put cursor on last non-space */ coladvance(MAXCOL); while (curwin->w_cursor.col && vim_isspace(gchar_cursor())) dec_cursor(); curs_columns(FALSE); /* update curwin->w_virtcol */ /* do the formatting */ State = INSERT; /* for Opencmd() */ insertchar(NUL, TRUE, second_indent); State = NORMAL; first_par_line = TRUE; second_indent = -1; } else { /* * Still in same paragraph, so join the lines together. * But first delete the comment leader from the second line. */ advance = FALSE; curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; while (next_leader_len--) delchar(FALSE); curwin->w_cursor.lnum--; if (do_join(TRUE, FALSE) == FAIL) { beep_flush(); break; } first_par_line = FALSE; } } fo_do_comments = FALSE; /* * Leave the cursor at the first non-blank of the last formatted line. * If the cursor was move one line back (e.g. with "Q}") go to the next * line, so "." will do the next lines. */ if (op_end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) ++curwin->w_cursor.lnum; beginline(TRUE); updateScreen(NOT_VALID); msgmore(curbuf->b_ml.ml_line_count - old_line_count); } /* * Blank lines, and lines containing only the comment leader, are left * untouched by the formatting. The function returns TRUE in this * case. It also returns TRUE when a line starts with the end of a comment * ('e' in comment flags), so that this line is skipped, and not joined to the * previous line. A new paragraph starts after a blank line, or when the * comment leader changes -- webb. */ static int fmt_end_block(lnum, leader_len, leader_flags) linenr_t lnum; int *leader_len; char_u **leader_flags; { char_u *flags = NULL; /* init for GCC */ char_u *ptr; ptr = ml_get(lnum); *leader_len = get_leader_len(ptr, leader_flags); if (*leader_len > 0) { /* * Search for 'e' flag in comment leader flags. */ flags = *leader_flags; while (*flags && *flags != ':' && *flags != COM_END) ++flags; } return (ptr[*leader_len] == NUL || (*leader_len > 0 && *flags == COM_END) || startPS(lnum, NUL, FALSE)); } /* * prepare a few things for block mode yank/delete/tilde * * for delete: * - textlen includes the first/last char to be (partly) deleted * - start/endspaces is the number of columns that are taken by the * first/last deleted char minus the number of columns that have to be deleted. * for yank and tilde: * - textlen includes the first/last char to be wholly yanked * - start/endspaces is the number of columns of the first/last yanked char * that are to be yanked. */ static void block_prep(bd, lnum, is_del) struct block_def *bd; linenr_t lnum; int is_del; { colnr_t vcol; int incr = 0; char_u *pend; char_u *pstart; bd->startspaces = 0; bd->endspaces = 0; bd->textlen = 0; bd->textcol = 0; vcol = 0; pstart = ml_get(lnum); while (vcol < op_start_vcol && *pstart) { /* Count a tab for what it's worth (if list mode not on) */ incr = lbr_chartabsize(pstart, (colnr_t)vcol); vcol += incr; ++pstart; ++bd->textcol; } if (vcol < op_start_vcol) /* line too short */ { if (!is_del) bd->endspaces = op_end_vcol - op_start_vcol + 1; } else /* vcol >= op_start_vcol */ { bd->startspaces = vcol - op_start_vcol; if (is_del && vcol > op_start_vcol) bd->startspaces = incr - bd->startspaces; pend = pstart; if (vcol > op_end_vcol) /* it's all in one character */ { bd->startspaces = op_end_vcol - op_start_vcol + 1; if (is_del) bd->startspaces = incr - bd->startspaces; } else { while (vcol <= op_end_vcol && *pend) { /* Count a tab for what it's worth (if list mode not on) */ incr = lbr_chartabsize(pend, (colnr_t)vcol); vcol += incr; ++pend; } if (vcol < op_end_vcol && !is_del) /* line too short */ { bd->endspaces = op_end_vcol - vcol; } else if (vcol > op_end_vcol) { bd->endspaces = vcol - op_end_vcol - 1; if (!is_del && pend != pstart && bd->endspaces) --pend; } } if (is_del && bd->startspaces) { --pstart; --bd->textcol; } bd->textlen = (int)(pend - pstart); } bd->textstart = pstart; } #define NUMBUFLEN 30 /* * add or subtract 'Prenum1' from a number in a line * 'command' is CTRL-A for add, CTRL-X for subtract * * return FAIL for failure, OK otherwise */ int do_addsub(command, Prenum1) int command; linenr_t Prenum1; { register int col; char_u buf[NUMBUFLEN]; int hex; /* 'X': hexadecimal; '0': octal */ static int hexupper = FALSE; /* 0xABC */ long n; char_u *ptr; int i; int c; int zeros = 0; /* number of leading zeros */ int digits = 0; /* number of digits in the number */ ptr = ml_get_curline(); col = curwin->w_cursor.col; /* first check if we are on a hexadecimal number */ while (col > 0 && isxdigit(ptr[col])) --col; if (col > 0 && (ptr[col] == 'X' || ptr[col] == 'x') && ptr[col - 1] == '0' && isxdigit(ptr[col + 1])) --col; /* found hexadecimal number */ else { /* first search forward and then backward for start of number */ col = curwin->w_cursor.col; while (ptr[col] != NUL && !isdigit(ptr[col])) ++col; while (col > 0 && isdigit(ptr[col - 1])) --col; } if (isdigit(ptr[col]) && u_save_cursor() == OK) { ptr = ml_get_curline(); /* get it again, because u_save may have changed it */ curwin->w_set_curswant = TRUE; hex = 0; /* default is decimal */ if (ptr[col] == '0') /* could be hex or octal */ { hex = TO_UPPER(ptr[col + 1]); /* assume hexadecimal */ if (hex != 'X' || !isxdigit(ptr[col + 2])) { if (isdigit(hex)) hex = '0'; /* octal */ else hex = 0; /* 0 by itself is decimal */ } } if (!hex && col > 0 && ptr[col - 1] == '-') --col; ptr += col; /* * we copy the number into a buffer because some versions of sscanf * cannot handle characters with the upper bit set, making some special * characters handled like digits. */ for (i = 0; *ptr && !(*ptr & 0x80) && i < NUMBUFLEN - 1; ++i) buf[i] = *ptr++; buf[i] = NUL; if (hex == '0') sscanf((char *)buf, "%lo", &n); else if (hex) sscanf((char *)buf + 2, "%lx", &n); /* "%X" doesn't work! */ else n = atol((char *)buf); if (command == Ctrl('A')) n += Prenum1; else n -= Prenum1; if (hex == 'X') /* skip the '0x' */ col += 2; else if (hex == '0') col++; /* skip the '0' */ curwin->w_cursor.col = col; c = gchar_cursor(); do /* delete the old number */ { if (digits == 0 && c == '0') ++zeros; /* count the number of leading zeros */ else ++digits; /* count the number of digits */ if (isalpha(c)) { if (isupper(c)) hexupper = TRUE; else hexupper = FALSE; } (void)delchar(FALSE); c = gchar_cursor(); } while (hex ? (hex == '0' ? c >= '0' && c <= '7' : isxdigit(c)) : isdigit(c)); if (hex == 0) sprintf((char *)buf, "%ld", n); else { if (hex == '0') sprintf((char *)buf, "%lo", n); else if (hex && hexupper) sprintf((char *)buf, "%lX", n); else if (hex) sprintf((char *)buf, "%lx", n); /* adjust number of zeros to the new number of digits, so the * total length of the number remains the same */ if (zeros) { zeros += digits - STRLEN(buf); if (zeros > 0) { vim_memmove(buf + zeros, buf, STRLEN(buf) + 1); for (col = 0; zeros > 0; --zeros) buf[col++] = '0'; } } } ins_str(buf); /* insert the new number */ --curwin->w_cursor.col; updateline(); return OK; } else { beep_flush(); return FAIL; } } #ifdef VIMINFO int read_viminfo_register(line, fp, force) char_u *line; FILE *fp; int force; { int eof; int do_it = TRUE; int size; int limit; int i; int set_prev = FALSE; char_u *str; char_u **array = NULL; /* We only get here (hopefully) if line[0] == '"' */ str = line + 1; if (*str == '"') { set_prev = TRUE; str++; } if (!isalnum(*str) && *str != '-') { EMSG2("viminfo: Illegal register name in line %s", line); do_it = FALSE; } yankbuffer = *str++; get_yank_buffer(FALSE); yankbuffer = 0; if (!force && y_current->y_array != NULL) do_it = FALSE; size = 0; limit = 100; /* Optimized for registers containing <= 100 lines */ if (do_it) { if (set_prev) y_previous = y_current; vim_free(y_current->y_array); array = y_current->y_array = (char_u **)alloc((unsigned)(limit * sizeof(char_u *))); str = skipwhite(str); if (STRNCMP(str, "CHAR", 4) == 0) y_current->y_type = MCHAR; else if (STRNCMP(str, "BLOCK", 5) == 0) y_current->y_type = MBLOCK; else y_current->y_type = MLINE; } while (!(eof = vim_fgets(line, LSIZE, fp)) && line[0] == TAB) { if (do_it) { if (size >= limit) { y_current->y_array = (char_u **) alloc((unsigned)(limit * 2 * sizeof(char_u *))); for (i = 0; i < limit; i++) y_current->y_array[i] = array[i]; vim_free(array); limit *= 2; array = y_current->y_array; } viminfo_readstring(line); str = strsave(line + 1); if (str != NULL) array[size++] = str; else do_it = FALSE; } } if (do_it) { if (size == 0) { vim_free(array); y_current->y_array = NULL; } else if (size < limit) { y_current->y_array = (char_u **)alloc((unsigned)(size * sizeof(char_u *))); for (i = 0; i < size; i++) y_current->y_array[i] = array[i]; vim_free(array); } y_current->y_size = size; } return eof; } void write_viminfo_registers(fp) FILE *fp; { int i, j; char_u *type; char_u c; int num_lines; int max_num_lines; fprintf(fp, "\n# Registers:\n"); max_num_lines = get_viminfo_parameter('"'); if (max_num_lines == 0) return; for (i = 0; i < NUM_REGISTERS; i++) { if (y_buf[i].y_array == NULL) continue; #ifdef USE_GUI /* Skip '*' register, we don't want it back next time */ if (i == GUI_SELECTION_REGISTER) continue; #endif switch (y_buf[i].y_type) { case MLINE: type = (char_u *)"LINE"; break; case MCHAR: type = (char_u *)"CHAR"; break; case MBLOCK: type = (char_u *)"BLOCK"; break; default: sprintf((char *)IObuff, "Unknown register type %d", y_buf[i].y_type); emsg(IObuff); type = (char_u *)"LINE"; break; } if (y_previous == &y_buf[i]) fprintf(fp, "\""); if (i == DELETION_REGISTER) c = '-'; else if (i < 10) c = '0' + i; else c = 'a' + i - 10; fprintf(fp, "\"%c\t%s\n", c, type); num_lines = y_buf[i].y_size; /* If max_num_lines < 0, then we save ALL the lines in the register */ if (max_num_lines > 0 && num_lines > max_num_lines) num_lines = max_num_lines; for (j = 0; j < num_lines; j++) { putc('\t', fp); viminfo_writestring(fp, y_buf[i].y_array[j]); } } } #endif /* VIMINFO */ #if defined(USE_GUI) || defined(PROTO) /* * Text selection stuff that uses the GUI selection register '*'. When using a * GUI this may be text from another window, otherwise it is the last text we * had highlighted with VIsual mode. With mouse support, clicking the middle * button performs the paste, otherwise you will need to do <"*p>. */ void gui_free_selection() { struct yankbuf *y_ptr = y_current; y_current = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ free_yank_all(); y_current->y_size = 0; y_current = y_ptr; } /* * Get the selected text and put it in the gui text register '*'. */ void gui_get_selection() { struct yankbuf *old_y_previous, *old_y_current; char_u old_yankbuffer; FPOS old_cursor, old_visual; int old_op_type; if (gui.selection.owned) { if (y_buf[GUI_SELECTION_REGISTER].y_array != NULL) return; /* Get the text between gui.selection.start & gui.selection.end */ old_y_previous = y_previous; old_y_current = y_current; old_yankbuffer = yankbuffer; old_cursor = curwin->w_cursor; old_visual = VIsual; old_op_type = op_type; yankbuffer = '*'; op_type = YANK; do_pending_operator('y', NUL, FALSE, NULL, NULL, 0, TRUE, TRUE); y_previous = old_y_previous; y_current = old_y_current; yankbuffer = old_yankbuffer; curwin->w_cursor = old_cursor; VIsual = old_visual; op_type = old_op_type; } else { gui_free_selection(); /* Try to get selected text from another window */ gui_request_selection(); } } /* Convert from the GUI selection string into the '*' register */ void gui_yank_selection(type, str, len) int type; char_u *str; long_u len; { struct yankbuf *y_ptr = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ int lnum; int start; int i; gui_free_selection(); /* Count the number of lines within the string */ y_ptr->y_size = 1; for (i = 0; i < len; i++) if (str[i] == '\n') y_ptr->y_size++; if (type != MCHAR && i > 0 && str[i - 1] == '\n') y_ptr->y_size--; y_ptr->y_array = (char_u **)lalloc(y_ptr->y_size * sizeof(char_u *), TRUE); if (y_ptr->y_array == NULL) return; y_ptr->y_type = type; lnum = 0; start = 0; for (i = 0; i < len; i++) { if (str[i] == NUL) str[i] = '\n'; else if (str[i] == '\n') { str[i] = NUL; if (type == MCHAR || i != len - 1) { if ((y_ptr->y_array[lnum] = strsave(str + start)) == NULL) { y_ptr->y_size = lnum; return; } lnum++; start = i + 1; } } } if ((y_ptr->y_array[lnum] = alloc(i - start + 1)) == NULL) return; if (i - start > 0) STRNCPY(y_ptr->y_array[lnum], str + start, i - start); y_ptr->y_array[lnum][i - start] = NUL; y_ptr->y_size = lnum + 1; } /* * Convert the '*' register into a GUI selection string returned in *str with * length *len. */ int gui_convert_selection(str, len) char_u **str; long_u *len; { struct yankbuf *y_ptr = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ char_u *p; int lnum; int i, j; *str = NULL; *len = 0; if (y_ptr->y_array == NULL) return -1; for (i = 0; i < y_ptr->y_size; i++) *len += STRLEN(y_ptr->y_array[i]) + 1; /* * Don't want newline character at end of last line if we're in MCHAR mode. */ if (y_ptr->y_type == MCHAR && *len > 1) (*len)--; p = *str = lalloc(*len, TRUE); if (p == NULL) return -1; lnum = 0; for (i = 0, j = 0; i < *len; i++, j++) { if (y_ptr->y_array[lnum][j] == '\n') p[i] = NUL; else if (y_ptr->y_array[lnum][j] == NUL) { p[i] = '\n'; lnum++; j = -1; } else p[i] = y_ptr->y_array[lnum][j]; } return y_ptr->y_type; } #endif /* USE_GUI || PROTO */