This is ex_cmds.c in view mode; [Download] [Up]
/* 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. */ /* * ex_cmds.c: functions for command line commands */ #include "vim.h" #include "globals.h" #include "proto.h" #include "option.h" #include "ex_cmds.h" static int linelen __ARGS((int *has_tab)); static void do_filter __ARGS((linenr_t line1, linenr_t line2, char_u *buff, int do_in, int do_out)); #ifdef VIMINFO static char_u *viminfo_filename __ARGS((char_u *)); static void do_viminfo __ARGS((FILE *fp_in, FILE *fp_out, int want_info, int want_marks, int force_read)); static int read_viminfo_up_to_marks __ARGS((char_u *line, FILE *fp, int forceit)); #endif /* VIMINFO */ static int do_sub_msg __ARGS((void)); static int help_compare __ARGS((const void *s1, const void *s2)); void do_ascii() { int c; char buf1[20]; char buf2[20]; char_u buf3[3]; c = gchar_cursor(); if (c == NUL) { MSG("empty line"); return; } if (c == NL) /* NUL is stored as NL */ c = NUL; if (isprintchar(c) && (c < ' ' || c > '~')) { transchar_nonprint(buf3, c); sprintf(buf1, " <%s>", (char *)buf3); } else buf1[0] = NUL; if (c >= 0x80) sprintf(buf2, " <M-%s>", transchar(c & 0x7f)); else buf2[0] = NUL; sprintf((char *)IObuff, "<%s>%s%s %d, Hex %02x, Octal %03o", transchar(c), buf1, buf2, c, c, c); msg(IObuff); } /* * Handle ":left", ":center" and ":right" commands: align text. */ void do_align(eap) EXARG *eap; { FPOS save_curpos; int len; int indent = 0; int new_indent = 0; /* init for GCC */ int has_tab; int width; #ifdef RIGHTLEFT if (curwin->w_p_rl) { /* switch left and right aligning */ if (eap->cmdidx == CMD_right) eap->cmdidx = CMD_left; else if (eap->cmdidx == CMD_left) eap->cmdidx = CMD_right; } #endif width = atoi((char *)eap->arg); save_curpos = curwin->w_cursor; if (eap->cmdidx == CMD_left) /* width is used for new indent */ { if (width >= 0) indent = width; } else { /* * if 'textwidth' set, use it * else if 'wrapmargin' set, use it * if invalid value, use 80 */ if (width <= 0) width = curbuf->b_p_tw; if (width == 0 && curbuf->b_p_wm > 0) width = Columns - curbuf->b_p_wm; if (width <= 0) width = 80; } if (u_save((linenr_t)(eap->line1 - 1), (linenr_t)(eap->line2 + 1)) == FAIL) return; for (curwin->w_cursor.lnum = eap->line1; curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum) { if (eap->cmdidx == CMD_left) /* left align */ new_indent = indent; else { len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent(); if (len <= 0) /* skip blank lines */ continue; if (eap->cmdidx == CMD_center) new_indent = (width - len) / 2; else { new_indent = width - len; /* right align */ /* * Make sure that embedded TABs don't make the text go too far * to the right. */ if (has_tab) while (new_indent > 0) { set_indent(new_indent, TRUE); /* set indent */ if (linelen(NULL) <= width) { /* * Now try to move the line as much as possible to * the right. Stop when it moves too far. */ do set_indent(++new_indent, TRUE); /* set indent */ while (linelen(NULL) <= width); --new_indent; break; } --new_indent; } } } if (new_indent < 0) new_indent = 0; set_indent(new_indent, TRUE); /* set indent */ } curwin->w_cursor = save_curpos; beginline(TRUE); /* * If the cursor is after the first changed line, its position needs to be * updated. */ if (curwin->w_cursor.lnum > eap->line1) { changed_line_abv_curs(); invalidate_botline(); } else if (curwin->w_cursor.lnum == eap->line1) changed_cline_bef_curs(); /* * If the start of the aligned lines is before botline, it may have become * approximated (lines got longer or shorter). */ if (botline_approximated() && eap->line1 < curwin->w_botline) approximate_botline(); update_screen(NOT_VALID); } /* * Get the length of the current line, excluding trailing white space. */ static int linelen(has_tab) int *has_tab; { char_u *line; char_u *first; char_u *last; int save; int len; /* find the first non-blank character */ line = ml_get_curline(); first = skipwhite(line); /* find the character after the last non-blank character */ for (last = first + STRLEN(first); last > first && vim_iswhite(last[-1]); --last) ; save = *last; *last = NUL; len = linetabsize(line); /* get line length */ if (has_tab != NULL) /* check for embedded TAB */ *has_tab = (vim_strrchr(first, TAB) != NULL); *last = save; return len; } /* * Handle ":retab" command. */ void do_retab(eap) EXARG *eap; { linenr_t lnum; int got_tab = FALSE; long num_spaces = 0; long num_tabs; long len; long col; long vcol; long start_col = 0; /* For start of white-space string */ long start_vcol = 0; /* For start of white-space string */ int temp; long old_len; char_u *ptr; char_u *new_line = (char_u *)1; /* init to non-NULL */ int did_something = FALSE; int did_undo; /* called u_save for current line */ int new_ts; new_ts = getdigits(&(eap->arg)); if (new_ts == 0) new_ts = curbuf->b_p_ts; for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) { ptr = ml_get(lnum); col = 0; vcol = 0; did_undo = FALSE; for (;;) { if (vim_iswhite(ptr[col])) { if (!got_tab && num_spaces == 0) { /* First consecutive white-space */ start_vcol = vcol; start_col = col; } if (ptr[col] == ' ') num_spaces++; else got_tab = TRUE; } else { if (got_tab || (eap->forceit && num_spaces > 1)) { /* Retabulate this string of white-space */ /* len is virtual length of white string */ len = num_spaces = vcol - start_vcol; num_tabs = 0; if (!curbuf->b_p_et) { temp = new_ts - (start_vcol % new_ts); if (num_spaces >= temp) { num_spaces -= temp; num_tabs++; } num_tabs += num_spaces / new_ts; num_spaces -= (num_spaces / new_ts) * new_ts; } if (curbuf->b_p_et || got_tab || (num_spaces + num_tabs < len)) { if (did_undo == FALSE) { did_undo = TRUE; if (u_save((linenr_t)(lnum - 1), (linenr_t)(lnum + 1)) == FAIL) { new_line = NULL; /* flag out-of-memory */ break; } } /* len is actual number of white characters used */ len = num_spaces + num_tabs; old_len = STRLEN(ptr); new_line = lalloc(old_len - col + start_col + len + 1, TRUE); if (new_line == NULL) break; if (start_col > 0) vim_memmove(new_line, ptr, (size_t)start_col); vim_memmove(new_line + start_col + len, ptr + col, (size_t)(old_len - col + 1)); ptr = new_line + start_col; for (col = 0; col < len; col++) ptr[col] = (col < num_tabs) ? '\t' : ' '; ml_replace(lnum, new_line, FALSE); did_something = TRUE; ptr = new_line; col = start_col + len; } } got_tab = FALSE; num_spaces = 0; } if (ptr[col] == NUL) break; vcol += chartabsize(ptr[col++], (colnr_t)vcol); } if (new_line == NULL) /* out of memory */ break; line_breakcheck(); } if (got_int) emsg(e_interr); if (did_something) CHANGED; if (curbuf->b_p_ts != new_ts || did_something) { /* * Cursor may need updating when change is before or at the cursor * line. w_botline may be wrong a bit now. */ if (curbuf->b_p_ts != new_ts || eap->line1 < curwin->w_cursor.lnum) changed_line_abv_curs(); /* recompute cursor pos compl. */ else if (eap->line1 == curwin->w_cursor.lnum) changed_cline_bef_curs(); /* recompute curosr pos partly */ approximate_botline(); } curbuf->b_p_ts = new_ts; coladvance(curwin->w_curswant); u_clearline(); update_screen(NOT_VALID); } /* * :move command - move lines line1-line2 to line dest * * return FAIL for failure, OK otherwise */ int do_move(line1, line2, dest) linenr_t line1; linenr_t line2; linenr_t dest; { char_u *str; linenr_t l; linenr_t extra; /* Num lines added before line1 */ linenr_t num_lines; /* Num lines moved */ linenr_t last_line; /* Last line in file after adding new text */ int has_mark; if (dest >= line1 && dest < line2) { EMSG("Move lines into themselves"); return FAIL; } num_lines = line2 - line1 + 1; /* * First we copy the old text to its new location -- webb * Also copy the flag that ":global" command uses. */ if (u_save(dest, dest + 1) == FAIL) return FAIL; for (extra = 0, l = line1; l <= line2; l++) { str = vim_strsave(ml_get(l + extra)); if (str != NULL) { has_mark = ml_has_mark(l + extra); ml_append(dest + l - line1, str, (colnr_t)0, FALSE); vim_free(str); if (has_mark) ml_setmarked(dest + l - line1 + 1); if (dest < line1) extra++; } } /* * Now we must be careful adjusting our marks so that we don't overlap our * mark_adjust() calls. * * We adjust the marks within the old text so that they refer to the * last lines of the file (temporarily), because we know no other marks * will be set there since these line numbers did not exist until we added * our new lines. * * Then we adjust the marks on lines between the old and new text positions * (either forwards or backwards). * * And Finally we adjust the marks we put at the end of the file back to * their final destination at the new text position -- webb */ last_line = curbuf->b_ml.ml_line_count; mark_adjust(line1, line2, last_line - line2, 0L); if (dest >= line2) mark_adjust(line2 + 1, dest, -num_lines, 0L); else mark_adjust(dest + 1, line1 - 1, num_lines, 0L); mark_adjust(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L); /* * Now we delete the original text -- webb */ if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) return FAIL; for (l = line1; l <= line2; l++) ml_delete(line1 + extra, TRUE); CHANGED; if (!global_busy && num_lines > p_report) smsg((char_u *)"%ld line%s moved", num_lines, plural(num_lines)); /* * Leave the cursor on the last of the moved lines. */ if (dest >= line1) curwin->w_cursor.lnum = dest; else curwin->w_cursor.lnum = dest + (line2 - line1) + 1; changed_line_abv_curs(); /* * TODO: should recompute w_botline for simple situations. */ invalidate_botline(); return OK; } /* * :copy command - copy lines line1-line2 to line n */ void do_copy(line1, line2, n) linenr_t line1; linenr_t line2; linenr_t n; { linenr_t lnum; char_u *p; mark_adjust(n + 1, MAXLNUM, line2 - line1 + 1, 0L); /* * there are three situations: * 1. destination is above line1 * 2. destination is between line1 and line2 * 3. destination is below line2 * * n = destination (when starting) * curwin->w_cursor.lnum = destination (while copying) * line1 = start of source (while copying) * line2 = end of source (while copying) */ if (u_save(n, n + 1) == FAIL) return; curwin->w_cursor.lnum = n; lnum = line2 - line1 + 1; while (line1 <= line2) { /* need to use vim_strsave() because the line will be unlocked within ml_append */ p = vim_strsave(ml_get(line1)); if (p != NULL) { ml_append(curwin->w_cursor.lnum, p, (colnr_t)0, FALSE); vim_free(p); } /* situation 2: skip already copied lines */ if (line1 == n) line1 = curwin->w_cursor.lnum; ++line1; if (curwin->w_cursor.lnum < line1) ++line1; if (curwin->w_cursor.lnum < line2) ++line2; ++curwin->w_cursor.lnum; } CHANGED; changed_line_abv_curs(); /* * TODO: should recompute w_botline for simple situations. */ invalidate_botline(); msgmore((long)lnum); } /* * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" * Bangs in the argument are replaced with the previously entered command. * Remember the argument. */ void do_bang(addr_count, line1, line2, forceit, arg, do_in, do_out) int addr_count; linenr_t line1, line2; int forceit; char_u *arg; int do_in, do_out; { static char_u *prevcmd = NULL; /* the previous command */ char_u *newcmd = NULL; /* the new command */ int ins_prevcmd; char_u *t; char_u *p; char_u *trailarg; int len; int scroll_save = msg_scroll; /* * Disallow shell commands for "rvim". * Disallow shell commands from .exrc and .vimrc in current directory for * security reasons. */ if (check_restricted() || check_secure()) return; if (addr_count == 0) /* :! */ { msg_scroll = FALSE; /* don't scroll here */ autowrite_all(); msg_scroll = scroll_save; } /* * Try to find an embedded bang, like in :!<cmd> ! [args] * (:!! is indicated by the 'forceit' variable) */ ins_prevcmd = forceit; trailarg = arg; do { len = STRLEN(trailarg) + 1; if (newcmd != NULL) len += STRLEN(newcmd); if (ins_prevcmd) { if (prevcmd == NULL) { emsg(e_noprev); vim_free(newcmd); return; } len += STRLEN(prevcmd); } if ((t = alloc(len)) == NULL) { vim_free(newcmd); return; } *t = NUL; if (newcmd != NULL) STRCAT(t, newcmd); if (ins_prevcmd) STRCAT(t, prevcmd); p = t + STRLEN(t); STRCAT(t, trailarg); vim_free(newcmd); newcmd = t; /* * Scan the rest of the argument for '!', which is replaced by the * previous command. "\!" is replaced by "!" (this is vi compatible). */ trailarg = NULL; while (*p) { if (*p == '!') { if (p > newcmd && p[-1] == '\\') vim_memmove(p - 1, p, (size_t)(STRLEN(p) + 1)); else { trailarg = p; *trailarg++ = NUL; ins_prevcmd = TRUE; break; } } ++p; } } while (trailarg != NULL); vim_free(prevcmd); prevcmd = newcmd; if (bangredo) /* put cmd in redo buffer for ! command */ { AppendToRedobuff(prevcmd); AppendToRedobuff((char_u *)"\n"); bangredo = FALSE; } /* * Add quotes around the command, for shells that need them. */ if (*p_shq != NUL) { newcmd = alloc((unsigned)(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1)); if (newcmd == NULL) return; STRCPY(newcmd, p_shq); STRCAT(newcmd, prevcmd); STRCAT(newcmd, p_shq); } if (addr_count == 0) /* :! */ { /* echo the command */ msg_start(); msg_putchar(':'); msg_putchar('!'); msg_outtrans(newcmd); msg_clr_eos(); windgoto(msg_row, msg_col); do_shell(newcmd); } else /* :range! */ do_filter(line1, line2, newcmd, do_in, do_out); if (newcmd != prevcmd) vim_free(newcmd); } /* * call a shell to execute a command */ void do_shell(cmd) char_u *cmd; { BUF *buf; int save_nwr; /* * Disallow shell commands for "rvim". * Disallow shell commands from .exrc and .vimrc in current directory for * security reasons. */ if (check_restricted() || check_secure()) { msg_end(); return; } #ifdef WIN32 /* * Check if external commands are allowed now. */ if (can_end_termcap_mode(TRUE) == FALSE) return; #endif /* * For autocommands we want to get the output on the current screen, to * avoid having to type return below. */ msg_putchar('\r'); /* put cursor at start of line */ #ifdef AUTOCMD if (!autocmd_busy) #endif stoptermcap(); msg_putchar('\n'); /* may shift screen one line up */ /* warning message before calling the shell */ if (p_warn #ifdef AUTOCMD && !autocmd_busy #endif ) for (buf = firstbuf; buf; buf = buf->b_next) if (buf->b_changed) { MSG_PUTS("[No write since last change]\n"); break; } /* This windgoto is required for when the '\n' resulted in a "delete line 1" * command to the terminal. */ windgoto(msg_row, msg_col); cursor_on(); (void)mch_call_shell(cmd, SHELL_COOKED); need_check_timestamps = TRUE; /* * put the message cursor at the end of the screen, avoids wait_return() to * overwrite the text that the external command showed */ msg_pos((int)Rows - 1, 0); #ifdef AUTOCMD if (autocmd_busy) must_redraw = CLEAR; else #endif { /* * For ":sh" there is no need to call wait_return(), just redraw. * Otherwise there is probably text on the screen that the user wants * to read before redrawing, so call wait_return(). */ if (cmd == NULL) { must_redraw = CLEAR; need_wait_return = FALSE; dont_wait_return = TRUE; } else { /* * If K_TI is defined, we assume that we switch screens when * starttermcap() is called. In that case we really want to wait * for "hit return to continue". */ save_nwr = no_wait_return; if (*T_TI != NUL) no_wait_return = FALSE; #ifdef AMIGA wait_return(term_console ? -1 : TRUE); /* see below */ #else wait_return(TRUE); #endif no_wait_return = save_nwr; } starttermcap(); /* start termcap if not done by wait_return() */ /* * In an Amiga window redrawing is caused by asking the window size. * If we got an interrupt this will not work. The chance that the * window size is wrong is very small, but we need to redraw the * screen. Don't do this if ':' hit in wait_return(). THIS IS UGLY * but it saves an extra redraw. */ #ifdef AMIGA if (skip_redraw) /* ':' hit in wait_return() */ must_redraw = CLEAR; else if (term_console) { OUTSTR("\033[0 q"); /* get window size */ if (got_int) must_redraw = CLEAR; /* if got_int is TRUE, redraw needed */ else must_redraw = 0; /* no extra redraw needed */ } #endif /* AMIGA */ } } /* * do_filter: filter lines through a command given by the user * * We use temp files and the mch_call_shell() routine here. This would normally * be done using pipes on a UNIX machine, but this is more portable to * non-unix machines. The mch_call_shell() routine needs to be able * to deal with redirection somehow, and should handle things like looking * at the PATH env. variable, and adding reasonable extensions to the * command name given by the user. All reasonable versions of mch_call_shell() * do this. * We use input redirection if do_in is TRUE. * We use output redirection if do_out is TRUE. */ static void do_filter(line1, line2, buff, do_in, do_out) linenr_t line1, line2; char_u *buff; int do_in, do_out; { char_u *itmp = NULL; char_u *otmp = NULL; linenr_t linecount; FPOS cursor_save; #ifdef AUTOCMD BUF *old_curbuf = curbuf; #endif if (*buff == NUL) /* no filter command */ return; #ifdef WIN32 /* * Check if external commands are allowed now. */ if (can_end_termcap_mode(TRUE) == FALSE) return; #endif cursor_save = curwin->w_cursor; linecount = line2 - line1 + 1; curwin->w_cursor.lnum = line1; curwin->w_cursor.col = 0; changed_line_abv_curs(); invalidate_botline(); /* * 1. Form temp file names * 2. Write the lines to a temp file * 3. Run the filter command on the temp file * 4. Read the output of the command into the buffer * 5. Delete the original lines to be filtered * 6. Remove the temp files */ if ((do_in && (itmp = vim_tempname('i')) == NULL) || (do_out && (otmp = vim_tempname('o')) == NULL)) { emsg(e_notmp); goto filterend; } /* * The writing and reading of temp files will not be shown. * Vi also doesn't do this and the messages are not very informative. */ ++no_wait_return; /* don't call wait_return() while busy */ if (do_in && buf_write(curbuf, itmp, NULL, line1, line2, FALSE, FALSE, FALSE, TRUE) == FAIL) { msg_putchar('\n'); /* keep message from buf_write() */ --no_wait_return; (void)emsg2(e_notcreate, itmp); /* will call wait_return */ goto filterend; } #ifdef AUTOCMD if (curbuf != old_curbuf) goto filterend; #endif if (!do_out) msg_putchar('\n'); #if (defined(UNIX) && !defined(ARCHIE)) || defined(OS2) /* * put braces around the command (for concatenated commands) */ sprintf((char *)IObuff, "(%s)", (char *)buff); if (do_in) { STRCAT(IObuff, " < "); STRCAT(IObuff, itmp); } #else /* * for shells that don't understand braces around commands, at least allow * the use of commands in a pipe. */ STRCPY(IObuff, buff); if (do_in) { char_u *p; /* * If there is a pipe, we have to put the '<' in front of it. * Don't do this when 'shellquote' is not empty, otherwise the redirection * would be inside the quotes. */ p = vim_strchr(IObuff, '|'); if (p && *p_shq == NUL) *p = NUL; STRCAT(IObuff, " < "); STRCAT(IObuff, itmp); p = vim_strchr(buff, '|'); if (p && *p_shq == NUL) STRCAT(IObuff, p); } #endif if (do_out) { char_u *p; if ((p = vim_strchr(p_srr, '%')) != NULL && p[1] == 's') { p = IObuff + STRLEN(IObuff); *p++ = ' '; /* not really needed? Not with sh, ksh or bash */ sprintf((char *)p, (char *)p_srr, (char *)otmp); } else sprintf((char *)IObuff + STRLEN(IObuff), " %s %s", (char *)p_srr, (char *)otmp); } windgoto((int)Rows - 1, 0); cursor_on(); /* * When not redirecting the output the command can write anything to the * screen. If 'shellredir' is equal to ">", screen may be messed up by * stderr output of external command. Clear the screen later. * If do_in is FALSE, this could be something like ":r !cat", which may * also mess up the screen, clear it later. */ if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in) must_redraw = CLEAR; else redraw_later(NOT_VALID); /* * When mch_call_shell() fails wait_return() is called to give the user a * chance to read the error messages. Otherwise errors are ignored, so you * can see the error messages from the command that appear on stdout; use * 'u' to fix the text * Switch to cooked mode when not redirecting stdin, avoids that something * like ":r !cat" hangs. */ if (mch_call_shell(IObuff, SHELL_FILTER | SHELL_COOKED) == FAIL) { must_redraw = CLEAR; wait_return(FALSE); } need_check_timestamps = TRUE; if (do_out) { if (u_save((linenr_t)(line2), (linenr_t)(line2 + 1)) == FAIL) { goto error; } if (readfile(otmp, NULL, line2, (linenr_t)0, MAXLNUM, READ_FILTER) == FAIL) { msg_putchar('\n'); emsg2(e_notread, otmp); goto error; } #ifdef AUTOCMD if (curbuf != old_curbuf) goto filterend; #endif if (do_in) { /* * Put cursor on first filtered line for ":range!cmd". * Adjust '[ and '] (set by buf_write()). */ curwin->w_cursor.lnum = line1; del_lines(linecount, TRUE, TRUE); curbuf->b_op_start.lnum -= linecount; /* adjust '[ */ curbuf->b_op_end.lnum -= linecount; /* adjust '] */ write_lnum_adjust(-linecount); /* adjust last line for next write */ } else { /* * Put cursor on last new line for ":r !cmd". */ curwin->w_cursor.lnum = curbuf->b_op_end.lnum; linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; } beginline(TRUE); /* cursor on first non-blank */ --no_wait_return; if (linecount > p_report) { if (do_in) { sprintf((char *)msg_buf, "%ld lines filtered", (long)linecount); if (msg(msg_buf) && !msg_scroll) { keep_msg = msg_buf; /* display message after redraw */ keep_msg_attr = 0; } } else msgmore((long)linecount); } } else { error: /* put cursor back in same position for ":w !cmd" */ curwin->w_cursor = cursor_save; --no_wait_return; wait_return(FALSE); } filterend: #ifdef AUTOCMD if (curbuf != old_curbuf) { --no_wait_return; EMSG("*Filter* Autocommands must not change current buffer"); } #endif if (itmp != NULL) vim_remove(itmp); if (otmp != NULL) vim_remove(otmp); vim_free(itmp); vim_free(otmp); } #ifdef VIMINFO static int no_viminfo __ARGS((void)); static int viminfo_errcnt; static int no_viminfo() { /* "vim -i NONE" does not read or write a viminfo file */ return (use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0); } /* * Report an error for reading a viminfo file. * Count the number of errors. When there are more than 10, return TRUE. */ int viminfo_error(message, line) char *message; char_u *line; { sprintf((char *)IObuff, "viminfo: %s in line: ", message); STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff)); emsg(IObuff); if (++viminfo_errcnt >= 10) { EMSG("viminfo: Too many errors, skipping rest of file"); return TRUE; } return FALSE; } /* * read_viminfo() -- Read the viminfo file. Registers etc. which are already * set are not over-written unless force is TRUE. -- webb */ int read_viminfo(file, want_info, want_marks, forceit) char_u *file; int want_info; int want_marks; int forceit; { FILE *fp; if (no_viminfo()) return FAIL; file = viminfo_filename(file); /* may set to default if NULL */ if ((fp = fopen((char *)file, READBIN)) == NULL) return FAIL; viminfo_errcnt = 0; do_viminfo(fp, NULL, want_info, want_marks, forceit); fclose(fp); return OK; } /* * write_viminfo() -- Write the viminfo file. The old one is read in first so * that effectively a merge of current info and old info is done. This allows * multiple vims to run simultaneously, without losing any marks etc. If * forceit is TRUE, then the old file is not read in, and only internal info is * written to the file. -- webb */ void write_viminfo(file, forceit) char_u *file; int forceit; { FILE *fp_in = NULL; /* input viminfo file, if any */ FILE *fp_out = NULL; /* output viminfo file */ char_u *tempname = NULL; /* name of temp viminfo file */ struct stat st_new; /* stat() of potential new file */ char_u *wp; #ifdef UNIX int shortname = FALSE; /* use 8.3 filename */ mode_t umask_save; struct stat st_old; /* stat() of existing viminfo file */ #endif if (no_viminfo()) return; file = viminfo_filename(file); /* may set to default if NULL */ file = vim_strsave(file); /* make a copy, don't want NameBuff */ if (file != NULL) { fp_in = fopen((char *)file, READBIN); if (fp_in == NULL) { #ifdef UNIX /* * For Unix we create the .viminfo non-accessible for others, * because it may contain text from non-accessible documents. */ umask_save = umask(077); #endif fp_out = fopen((char *)file, WRITEBIN); #ifdef UNIX (void)umask(umask_save); #endif } else { /* * There is an existing viminfo file. Create a temporary file to * write the new viminfo into, in the same directory as the * existing viminfo file, which will be renamed later. */ #ifdef UNIX /* * For Unix we check the owner of the file. It's not very nice to * overwrite a user's viminfo file after a "su root", with a * viminfo file that the user can't read. */ st_old.st_dev = st_old.st_ino = 0; st_old.st_mode = 0600; if (stat((char *)file, &st_old) == 0 && !(st_old.st_uid == getuid() ? (st_old.st_mode & 0200) : (st_old.st_gid == getgid() ? (st_old.st_mode & 0020) : (st_old.st_mode & 0002)))) { EMSG2("Viminfo file is not writable: %s", file); goto end; } #endif /* * Make tempfile name. * May try twice: Once normal and once with shortname set, just in * case somebody puts his viminfo file in an 8.3 filesystem. */ for (;;) { tempname = buf_modname( #ifdef UNIX shortname, #else # ifdef SHORT_FNAME TRUE, # else FALSE, # endif #endif file, (char_u *)".tmp"); if (tempname == NULL) /* out of memory */ break; /* * Check if tempfile already exists. Never overwrite an * existing file! */ if (stat((char *)tempname, &st_new) == 0) { #ifdef UNIX /* * Check if tempfile is same as original file. May happen * when modname gave the same file back. E.g. silly * link, or filename-length reached. Try again with * shortname set. */ if (!shortname && st_new.st_dev == st_old.st_dev && st_new.st_ino == st_old.st_ino) { vim_free(tempname); tempname = NULL; shortname = TRUE; continue; } #endif /* * Try another name. Change one character, just before * the extension. This should also work for an 8.3 * filename, when after adding the extension it still is * the same file as the original. */ wp = tempname + STRLEN(tempname) - 5; if (wp < gettail(tempname)) /* empty file name? */ wp = gettail(tempname); for (*wp = 'z'; stat((char *)tempname, &st_new) == 0; --*wp) { /* * They all exist? Must be something wrong! Don't * write the viminfo file then. */ if (*wp == 'a') { vim_free(tempname); tempname = NULL; break; } } } break; } if (tempname != NULL) { fp_out = fopen((char *)tempname, WRITEBIN); /* * If we can't create in the same directory, try creating a * "normal" temp file. */ if (fp_out == NULL) { vim_free(tempname); if ((tempname = vim_tempname('o')) != NULL) fp_out = fopen((char *)tempname, WRITEBIN); } #ifdef UNIX /* * Set file protection same as original file, but strip s-bit * and make sure the owner can read/write it. */ if (fp_out != NULL) (void)setperm(tempname, (st_old.st_mode & 0777) | 0600); #endif } } } /* * Check if the new viminfo file can be written to. */ if (file == NULL || fp_out == NULL) { EMSG2("Can't write viminfo file %s!", file == NULL ? (char_u *)"" : fp_in == NULL ? file : tempname); if (fp_in != NULL) fclose(fp_in); goto end; } viminfo_errcnt = 0; do_viminfo(fp_in, fp_out, !forceit, !forceit, FALSE); fclose(fp_out); /* errors are ignored !? */ if (fp_in != NULL) { fclose(fp_in); /* * In case of an error, don't overwrite the original viminfo file. */ if (viminfo_errcnt || vim_rename(tempname, file) == -1) vim_remove(tempname); } end: vim_free(file); vim_free(tempname); } /* * Get the viminfo filename to use. * If "file" is given and not empty, use it (has already been expanded by * cmdline functions). * Otherwise use "-i filename", value from 'viminfo' or the default, and * expand environment variables. */ static char_u * viminfo_filename(file) char_u *file; { if (file == NULL || *file == NUL) { if (use_viminfo != NULL) file = use_viminfo; else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) file = (char_u *)VIMINFO_FILE; expand_env(file, NameBuff, MAXPATHL); return NameBuff; } return file; } /* * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). */ static void do_viminfo(fp_in, fp_out, want_info, want_marks, force_read) FILE *fp_in; FILE *fp_out; int want_info; int want_marks; int force_read; { int count = 0; int eof = FALSE; char_u *line; if ((line = alloc(LSIZE)) == NULL) return; if (fp_in != NULL) { if (want_info) eof = read_viminfo_up_to_marks(line, fp_in, force_read); else /* Skip info, find start of marks */ while (!(eof = vim_fgets(line, LSIZE, fp_in)) && line[0] != '>') ; } if (fp_out != NULL) { /* Write the info: */ fprintf(fp_out, "# This viminfo file was generated by vim\n"); fprintf(fp_out, "# You may edit it if you're careful!\n\n"); write_viminfo_search_pattern(fp_out); write_viminfo_sub_string(fp_out); write_viminfo_history(fp_out); write_viminfo_registers(fp_out); write_viminfo_filemarks(fp_out); count = write_viminfo_marks(fp_out); } if (fp_in != NULL && want_marks) copy_viminfo_marks(line, fp_in, fp_out, count, eof); vim_free(line); } /* * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the * first part of the viminfo file which contains everything but the marks that * are local to a file. Returns TRUE when end-of-file is reached. -- webb */ static int read_viminfo_up_to_marks(line, fp, forceit) char_u *line; FILE *fp; int forceit; { int eof; prepare_viminfo_history(forceit ? 9999 : 0); eof = vim_fgets(line, LSIZE, fp); while (!eof && line[0] != '>') { switch (line[0]) { /* Characters reserved for future expansion, ignored now */ case '+': /* "+40 /path/dir file", for running vim without args */ case '=': /* to be defined */ case '-': /* to be defined */ case '!': /* to be defined */ case '@': /* to be defined */ case '%': /* to be defined */ case '^': /* to be defined */ case '*': /* to be defined */ /* A comment */ case NUL: case '\r': case '\n': case '#': eof = vim_fgets(line, LSIZE, fp); break; case '"': eof = read_viminfo_register(line, fp, forceit); break; case '/': /* Search string */ case '&': /* Substitute search string */ case '~': /* Last search string, followed by '/' or '&' */ eof = read_viminfo_search_pattern(line, fp, forceit); break; case '$': eof = read_viminfo_sub_string(line, fp, forceit); break; case ':': case '?': case '|': eof = read_viminfo_history(line, fp); break; case '\'': /* How do we have a file mark when the file is not in the * buffer list? */ eof = read_viminfo_filemark(line, fp, forceit); break; default: if (viminfo_error("Illegal starting char", line)) eof = TRUE; else eof = vim_fgets(line, LSIZE, fp); break; } } finish_viminfo_history(); return eof; } /* * check string read from viminfo file * remove '\n' at the end of the line * - replace CTRL-V CTRL-V with CTRL-V * - replace CTRL-V 'n' with '\n' */ void viminfo_readstring(p) char_u *p; { while (*p != NUL && *p != '\n') { if (*p == Ctrl('V')) { if (p[1] == 'n') p[0] = '\n'; vim_memmove(p + 1, p + 2, STRLEN(p)); } ++p; } *p = NUL; } /* * write string to viminfo file * - replace CTRL-V with CTRL-V CTRL-V * - replace '\n' with CTRL-V 'n' * - add a '\n' at the end */ void viminfo_writestring(fd, p) FILE *fd; char_u *p; { int c; while ((c = *p++) != NUL) { if (c == Ctrl('V') || c == '\n') { putc(Ctrl('V'), fd); if (c == '\n') c = 'n'; } putc(c, fd); } putc('\n', fd); } #endif /* VIMINFO */ /* * Implementation of ":fixdel", also used by get_stty(). * <BS> resulting <Del> * ^? ^H * not ^? ^? */ void do_fixdel() { char_u *p; p = find_termcode((char_u *)"kb"); add_termcode((char_u *)"kD", p != NULL && *p == 0x7f ? (char_u *)"\010" : (char_u *)"\177"); } void print_line_no_prefix(lnum, use_number) linenr_t lnum; int use_number; { char_u numbuf[20]; if (curwin->w_p_nu || use_number) { sprintf((char *)numbuf, "%7ld ", (long)lnum); msg_puts_attr(numbuf, highlight_attr[HLF_N]); /* Highlight line nrs */ } msg_prt_line(ml_get(lnum)); } void print_line(lnum, use_number) linenr_t lnum; int use_number; { msg_start(); print_line_no_prefix(lnum, use_number); } /* * Implementation of ":file[!] [fname]". */ void do_file(arg, forceit) char_u *arg; int forceit; { char_u *fname, *sfname, *xfname; BUF *buf; if (*arg != NUL) { /* * The name of the current buffer will be changed. * A new buffer entry needs to be made to hold the old * file name, which will become the alternate file name. */ fname = curbuf->b_ffname; sfname = curbuf->b_sfname; xfname = curbuf->b_fname; curbuf->b_ffname = NULL; curbuf->b_sfname = NULL; if (setfname(arg, NULL, TRUE) == FAIL) { curbuf->b_ffname = fname; curbuf->b_sfname = sfname; return; } curbuf->b_notedited = TRUE; buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, FALSE); if (buf != NULL) curwin->w_alt_fnum = buf->b_fnum; vim_free(fname); vim_free(sfname); } /* print full filename if :cd used */ fileinfo(FALSE, FALSE, forceit); } /* * do the Ex mode :insert and :append commands */ void do_append(lnum, getline, cookie) linenr_t lnum; char_u *(*getline) __ARGS((int, void *, int)); void *cookie; /* argument for getline() */ { char_u *theline; int did_undo = FALSE; State = INSERT; /* behave like in Insert mode */ while (1) { msg_scroll = TRUE; need_wait_return = FALSE; theline = getline(NUL, cookie, 0); lines_left = Rows - 1; if (theline == NULL || (theline[0] == '.' && theline[1] == NUL)) break; if (!did_undo && u_save(lnum, lnum + 1) == FAIL) break; did_undo = TRUE; mark_adjust(lnum + 1, MAXLNUM, 1L, 0L); ml_append(lnum, theline, (colnr_t)0, FALSE); CHANGED; vim_free(theline); ++lnum; } State = NORMAL; curwin->w_cursor.lnum = lnum; check_cursor_lnum(); beginline(MAYBE); changed_line_abv_curs(); invalidate_botline(); dont_wait_return = TRUE; /* don't use wait_return() now */ need_wait_return = FALSE; update_screen(NOT_VALID); } /* * do the Ex mode :change command */ void do_change(start, end, getline, cookie) linenr_t start; linenr_t end; char_u *(*getline) __ARGS((int, void *, int)); void *cookie; /* argument for getline() */ { if (end >= start && u_save(start - 1, end + 1) == FAIL) return; while (end >= start) { if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ break; ml_delete(start, FALSE); CHANGED; end--; } do_append(start - 1, getline, cookie); } void do_z(line, arg) linenr_t line; char_u *arg; { char_u *x; int bigness = curwin->w_height - 3; char_u kind; int minus = 0; linenr_t start, end, curs, i; if (bigness < 1) bigness = 1; x = arg; if (*x == '-' || *x == '+' || *x == '=' || *x == '^' || *x == '.') x++; if (*x != 0) { if (!isdigit(*x)) { EMSG("non-numeric argument to :z"); return; } else bigness = atoi((char *)x); } kind = *arg; switch (kind) { case '-': start = line - bigness; end = line; curs = line; break; case '=': start = line - bigness / 2 + 1; end = line + bigness / 2 - 1; curs = line; minus = 1; break; case '^': start = line - bigness * 2; end = line - bigness; curs = line - bigness; break; case '.': start = line - bigness / 2; end = line + bigness / 2; curs = end; break; default: /* '+' */ start = line; end = line + bigness; curs = end; break; } if (start < 1) start = 1; if (end > curbuf->b_ml.ml_line_count) end = curbuf->b_ml.ml_line_count; if (curs > curbuf->b_ml.ml_line_count) curs = curbuf->b_ml.ml_line_count; for (i = start; i <= end; i++) { int j; if (minus && (i == line)) { msg_putchar('\n'); for (j = 1; j < Columns; j++) msg_putchar('-'); } print_line(i, FALSE); if (minus && (i == line)) { msg_putchar('\n'); for (j = 1; j < Columns; j++) msg_putchar('-'); } } curwin->w_cursor.lnum = curs; } /* * Check if the restricted flag is set. * If so, give an error message and return TRUE. * Otherwise, return FALSE. */ int check_restricted() { if (restricted) { EMSG("Shell commands not allowed in rvim"); return TRUE; } return FALSE; } /* * Check if the secure flag is set (.exrc or .vimrc in current directory). * If so, give an error message and return TRUE. * Otherwise, return FALSE. */ int check_secure() { if (secure) { secure = 2; emsg(e_curdir); return TRUE; } return FALSE; } static char_u *old_sub = NULL; /* previous substitute pattern */ /* * When ":global" is used to number of substitutions and changed lines is * accumulated until it's finished. */ static long sub_nsubs; /* total number of substitutions */ static linenr_t sub_nlines; /* total number of lines changed */ /* do_sub() * * Perform a substitution from line eap->line1 to line eap->line2 using the * command pointed to by eap->arg which should be of the form: * * /pattern/substitution/gc * * The trailing 'g' is optional and, if present, indicates that multiple * substitutions should be performed on each line, if applicable. * The trailing 'c' is optional and, if present, indicates that a confirmation * will be asked for each replacement. * The usual escapes are supported as described in the regexp docs. */ void do_sub(eap) EXARG *eap; { linenr_t lnum; long i; char_u *ptr; char_u *old_line; vim_regexp *prog; static int do_all = FALSE; /* do multiple substitutions per line */ static int do_ask = FALSE; /* ask for confirmation */ int do_print = FALSE; /* print last line with subst. */ char_u *pat, *sub; int delimiter; int sublen; int got_quit = FALSE; int got_match = FALSE; int temp; int which_pat; char_u *cmd; cmd = eap->arg; if (!global_busy) { sub_nsubs = 0; sub_nlines = 0; } if (eap->cmdidx == CMD_tilde) which_pat = RE_LAST; /* use last used regexp */ else which_pat = RE_SUBST; /* use last substitute regexp */ /* new pattern and substitution */ if (eap->cmdidx == CMD_substitute && *cmd != NUL && !vim_iswhite(*cmd) && vim_strchr((char_u *)"0123456789gcr|\"", *cmd) == NULL) { /* don't accept alphanumeric for separator */ if (isalpha(*cmd)) { EMSG("Regular expressions can't be delimited by letters"); return; } /* * undocumented vi feature: * "\/sub/" and "\?sub?" use last used search pattern (almost like * //sub/r). "\&sub&" use last substitute pattern (like //sub/). */ if (*cmd == '\\') { ++cmd; if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { emsg(e_backslash); return; } if (*cmd != '&') which_pat = RE_SEARCH; /* use last '/' pattern */ pat = (char_u *)""; /* empty search pattern */ delimiter = *cmd++; /* remember delimiter character */ } else /* find the end of the regexp */ { which_pat = RE_LAST; /* use last used regexp */ delimiter = *cmd++; /* remember delimiter character */ pat = cmd; /* remember start of search pat */ cmd = skip_regexp(cmd, delimiter, (int)p_magic); if (cmd[0] == delimiter) /* end delimiter found */ *cmd++ = NUL; /* replace it with a NUL */ } /* * Small incompatibility: vi sees '\n' as end of the command, but in * Vim we want to use '\n' to find/substitute a NUL. */ sub = cmd; /* remember the start of the substitution */ while (cmd[0]) { if (cmd[0] == delimiter) /* end delimiter found */ { *cmd++ = NUL; /* replace it with a NUL */ break; } if (cmd[0] == '\\' && cmd[1] != 0) /* skip escaped characters */ ++cmd; ++cmd; } vim_free(old_sub); old_sub = vim_strsave(sub); } else /* use previous pattern and substitution */ { if (old_sub == NULL) /* there is no previous command */ { emsg(e_nopresub); return; } pat = NULL; /* search_regcomp() will use previous pattern */ sub = old_sub; } /* * find trailing options */ if (!p_ed) { if (p_gd) /* default is global on */ do_all = TRUE; else do_all = FALSE; do_ask = FALSE; } while (*cmd) { /* * Note that 'g' and 'c' are always inverted, also when p_ed is off * 'r' is never inverted. */ if (*cmd == 'g') do_all = !do_all; else if (*cmd == 'c') do_ask = !do_ask; else if (*cmd == 'r') /* use last used regexp */ which_pat = RE_LAST; else if (*cmd == 'p') do_print = TRUE; else break; ++cmd; } /* * check for a trailing count */ cmd = skipwhite(cmd); if (isdigit(*cmd)) { i = getdigits(&cmd); if (i <= 0) { emsg(e_zerocount); return; } eap->line1 = eap->line2; eap->line2 += i - 1; } /* * check for trailing command or garbage */ cmd = skipwhite(cmd); if (*cmd && *cmd != '\"') /* if not end-of-line or comment */ { eap->nextcomm = check_nextcomm(cmd); if (eap->nextcomm == NULL) { emsg(e_trailing); return; } } if ((prog = search_regcomp(pat, RE_SUBST, which_pat, SEARCH_HIS)) == NULL) { emsg(e_invcmd); return; } /* * ~ in the substitute pattern is replaced with the old pattern. * We do it here once to avoid it to be replaced over and over again. */ sub = regtilde(sub, (int)p_magic); old_line = NULL; for (lnum = eap->line1; lnum <= eap->line2 && !(got_int || got_quit); ++lnum) { ptr = ml_get(lnum); if (vim_regexec(prog, ptr, TRUE)) /* a match on this line */ { char_u *new_end, *new_start = NULL; char_u *old_match, *old_copy; char_u *prev_old_match = NULL; char_u *p1; int did_sub = FALSE; int match, lastone; unsigned len, needed_len; unsigned new_start_len = 0; /* make a copy of the line, so it won't be taken away when updating the screen */ if ((old_line = vim_strsave(ptr)) == NULL) continue; vim_regexec(prog, old_line, TRUE); /* match again on this line to * update the pointers. TODO: * remove extra vim_regexec() */ if (!got_match) { setpcmark(); got_match = TRUE; } old_copy = old_match = old_line; for (;;) /* loop until nothing more to replace */ { /* * Save the position of the last change for the final cursor * position (just like the real vi). */ curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (int)(prog->startp[0] - old_line); changed_cline_bef_curs(); /* * Match empty string does not count, except for first match. * This reproduces the strange vi behaviour. * This also catches endless loops. */ if (old_match == prev_old_match && old_match == prog->endp[0]) { ++old_match; goto skip; } old_match = prog->endp[0]; prev_old_match = old_match; while (do_ask) /* loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed */ { temp = RedrawingDisabled; RedrawingDisabled = FALSE; search_match_len = prog->endp[0] - prog->startp[0]; /* invert the matched string * remove the inversion afterwards */ if (search_match_len == 0) search_match_len = 1; /* show something! */ highlight_match = TRUE; update_topline(); update_screen(NOT_VALID); highlight_match = FALSE; redraw_later(NOT_VALID); /* same highlighting as for wait_return */ smsg_attr(highlight_attr[HLF_R], (char_u *)"replace with %s (y/n/a/q/^E/^Y)?", sub); showruler(TRUE); RedrawingDisabled = temp; ++no_mapping; /* don't map this key */ i = vgetc(); --no_mapping; /* clear the question */ msg_didout = FALSE; /* don't scroll up */ msg_col = 0; gotocmdline(TRUE); if (i == 'q' || i == ESC || i == Ctrl('C')) { got_quit = TRUE; break; } else if (i == 'n') goto skip; else if (i == 'y') break; else if (i == 'a') { do_ask = FALSE; break; } else if (i == Ctrl('E')) scrollup_clamp(); else if (i == Ctrl('Y')) scrolldown_clamp(); } if (got_quit) break; /* get length of substitution part */ sublen = vim_regsub(prog, sub, old_line, FALSE, (int)p_magic); if (new_start == NULL) { /* * Get some space for a temporary buffer to do the * substitution into (and some extra space to avoid * too many calls to alloc()/free()). */ new_start_len = STRLEN(old_copy) + sublen + 25; if ((new_start = alloc_check(new_start_len)) == NULL) goto outofmem; *new_start = NUL; new_end = new_start; } else { /* * Extend the temporary buffer to do the substitution into. * Avoid an alloc()/free(), it takes a lot of time. */ len = STRLEN(new_start); needed_len = len + STRLEN(old_copy) + sublen + 1; if (needed_len > new_start_len) { needed_len += 20; /* get some extra */ if ((p1 = alloc_check(needed_len)) == NULL) goto outofmem; STRCPY(p1, new_start); vim_free(new_start); new_start = p1; new_start_len = needed_len; } new_end = new_start + len; } /* * copy the text up to the part that matched */ i = prog->startp[0] - old_copy; vim_memmove(new_end, old_copy, (size_t)i); new_end += i; vim_regsub(prog, sub, new_end, TRUE, (int)p_magic); sub_nsubs++; did_sub = TRUE; /* * Now the trick is to replace CTRL-Ms with a real line break. * This would make it impossible to insert CTRL-Ms in the text. * That is the way vi works. In Vim the line break can be * avoided by preceding the CTRL-M with a CTRL-V. Now you can't * precede a line break with a CTRL-V, big deal. */ while ((p1 = vim_strchr(new_end, CR)) != NULL) { if (p1 == new_end || p1[-1] != Ctrl('V')) { if (u_inssub(lnum) == OK) /* prepare for undo */ { *p1 = NUL; /* truncate up to the CR */ mark_adjust(lnum, MAXLNUM, 1L, 0L); ml_append(lnum - 1, new_start, (colnr_t)(p1 - new_start + 1), FALSE); ++lnum; ++eap->line2; /* number of lines increases */ STRCPY(new_start, p1 + 1); /* copy the rest */ new_end = new_start; } } else /* remove CTRL-V */ { STRCPY(p1 - 1, p1); new_end = p1; } } old_copy = prog->endp[0]; /* remember next character to be copied */ /* * continue searching after the match * prevent endless loop with patterns that match empty strings, * e.g. :s/$/pat/g or :s/[a-z]* /(&)/g */ skip: match = -1; lastone = (*old_match == NUL || got_int || got_quit || !do_all); if (lastone || do_ask || (match = vim_regexec(prog, old_match, (int)FALSE)) == 0) { if (new_start) { /* * Copy the rest of the line, that didn't match. * Old_match has to be adjusted, we use the end of the * line as reference, because the substitute may have * changed the number of characters. */ STRCAT(new_start, old_copy); i = old_line + STRLEN(old_line) - old_match; if (u_savesub(lnum) == OK) ml_replace(lnum, new_start, TRUE); vim_free(old_line); /* free the temp buffer */ old_line = new_start; new_start = NULL; old_match = old_line + STRLEN(old_line) - i; if (old_match < old_line) /* safety check */ { EMSG("do_sub internal error: old_match < old_line"); old_match = old_line; } old_copy = old_line; } if (match == -1 && !lastone) match = vim_regexec(prog, old_match, (int)FALSE); if (match <= 0) /* quit loop if there is no more match */ break; } line_breakcheck(); } if (did_sub) ++sub_nlines; vim_free(old_line); /* free the copy of the original line */ old_line = NULL; } line_breakcheck(); } outofmem: vim_free(old_line); /* may have to free an allocated copy of the line */ if (sub_nsubs) { CHANGED; approximate_botline(); if (!global_busy) { update_topline(); beginline(TRUE); update_screen(NOT_VALID); /* need this to update LineSizes */ if (!do_sub_msg() && do_ask) MSG(""); } if (do_print) print_line(curwin->w_cursor.lnum, FALSE); } else if (!global_busy) { if (got_int) /* interrupted */ emsg(e_interr); else if (got_match) /* did find something but nothing substituted */ MSG(""); else /* nothing found */ emsg(e_nomatch); } vim_free(prog); } /* * Give message for number of substitutions. * Can also be used after a ":global" command. * Return TRUE if a message was given. */ static int do_sub_msg() { /* * Only report substitutions when: * - more than 'report' substitutions * - command was typed by user, or number of changed lines > 'report' * - giving messages is not disabled by 'lazyredraw' */ if (sub_nsubs > p_report && (KeyTyped || sub_nlines > 1 || p_report < 1) && messaging()) { sprintf((char *)msg_buf, "%s%ld substitution%s on %ld line%s", got_int ? "(Interrupted) " : "", sub_nsubs, plural(sub_nsubs), (long)sub_nlines, plural((long)sub_nlines)); if (msg(msg_buf)) { keep_msg = msg_buf; keep_msg_attr = 0; } return TRUE; } if (got_int) { emsg(e_interr); return TRUE; } return FALSE; } /* * do_glob(cmd) * * Execute a global command of the form: * * g/pattern/X : execute X on all lines where pattern matches * v/pattern/X : execute X on all lines where pattern does not match * * where 'X' is an EX command * * The command character (as well as the trailing slash) is optional, and * is assumed to be 'p' if missing. * * This is implemented in two passes: first we scan the file for the pattern and * set a mark for each line that (not) matches. secondly we execute the command * for each line that has a mark. This is required because after deleting * lines we do not know where to search for the next match. */ void do_glob(eap) EXARG *eap; { linenr_t lnum; /* line number according to old situation */ linenr_t old_lcount; /* b_ml.ml_line_count before the command */ int ndone; int type; /* first char of cmd: 'v' or 'g' */ char_u *cmd; /* command argument */ char_u delim; /* delimiter, normally '/' */ char_u *pat; vim_regexp *prog; int match; int which_pat; if (global_busy) { EMSG("Cannot do :global recursive"); /* will increment global_busy */ return; } type = *eap->cmd; cmd = eap->arg; which_pat = RE_LAST; /* default: use last used regexp */ sub_nsubs = 0; sub_nlines = 0; /* * undocumented vi feature: * "\/" and "\?": use previous search pattern. * "\&": use previous substitute pattern. */ if (*cmd == '\\') { ++cmd; if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { emsg(e_backslash); return; } if (*cmd == '&') which_pat = RE_SUBST; /* use previous substitute pattern */ else which_pat = RE_SEARCH; /* use previous search pattern */ ++cmd; pat = (char_u *)""; } else if (*cmd == NUL) { EMSG("Regular expression missing from global"); return; } else { delim = *cmd; /* get the delimiter */ if (delim) ++cmd; /* skip delimiter if there is one */ pat = cmd; /* remember start of pattern */ cmd = skip_regexp(cmd, delim, (int)p_magic); if (cmd[0] == delim) /* end delimiter found */ *cmd++ = NUL; /* replace it with a NUL */ } if ((prog = search_regcomp(pat, RE_BOTH, which_pat, SEARCH_HIS)) == NULL) { emsg(e_invcmd); return; } /* * pass 1: set marks for each (not) matching line */ ndone = 0; for (lnum = eap->line1; lnum <= eap->line2 && !got_int; ++lnum) { /* a match on this line? */ match = vim_regexec(prog, ml_get(lnum), (int)TRUE); if ((type == 'g' && match) || (type == 'v' && !match)) { ml_setmarked(lnum); ndone++; } line_breakcheck(); } /* * pass 2: execute the command for each line that has been marked */ if (got_int) MSG("Interrupted"); else if (ndone == 0) msg(e_nomatch); else { /* * Set current position only once for a global command. * If global_busy is set, setpcmark() will not do anything. * If there is an error, global_busy will be incremented. */ setpcmark(); global_busy = 1; old_lcount = curbuf->b_ml.ml_line_count; while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) { curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; if (*cmd == NUL || *cmd == '\n') do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT); else do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); ui_breakcheck(); } global_busy = 0; adjust_cursor(); /* cursor may be beyond the end of the line */ /* * Redraw everything. Could use CLEAR, which is faster in some * situations, but when there are few changes this makes the display * flicker. */ redraw_later(NOT_VALID); /* If subsitutes done, report number of substitues, otherwise report * number of extra or deleted lines. */ if (!do_sub_msg()) msgmore(curbuf->b_ml.ml_line_count - old_lcount); } ml_clearmarked(); /* clear rest of the marks */ vim_free(prog); } #ifdef VIMINFO int read_viminfo_sub_string(line, fp, force) char_u *line; FILE *fp; int force; { if (old_sub != NULL && force) vim_free(old_sub); if (force || old_sub == NULL) { viminfo_readstring(line); old_sub = vim_strsave(line + 1); } return vim_fgets(line, LSIZE, fp); } void write_viminfo_sub_string(fp) FILE *fp; { if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { fprintf(fp, "\n# Last Substitute String:\n$"); viminfo_writestring(fp, old_sub); } } #endif /* VIMINFO */ /************************************************************************* * functions for ":help": open a read-only window on the help.txt file */ void do_help(arg) char_u *arg; { FILE *helpfd; /* file descriptor of help file */ int n; WIN *wp; int num_matches; char_u **matches; int need_free = FALSE; /* * If an argument is given, check if there is a match for it. */ if (*arg != NUL) { n = find_help_tags(arg, &num_matches, &matches); if (num_matches == 0 || n == FAIL) { EMSG2("Sorry, no help for %s", arg); return; } /* The first match is the best match */ arg = vim_strsave(matches[0]); need_free = TRUE; FreeWild(num_matches, matches); } /* * If there is already a help window open, use that one. */ if (!curwin->w_buffer->b_help) { for (wp = firstwin; wp != NULL; wp = wp->w_next) if (wp->w_buffer != NULL && wp->w_buffer->b_help) break; if (wp != NULL && wp->w_buffer->b_nwindows > 0) win_enter(wp, TRUE); else { /* * There is no help buffer yet. * Try to open the file specified by the "helpfile" option. */ if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL) { smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf); goto erret; } fclose(helpfd); if (win_split(0, FALSE) == FAIL) goto erret; if (curwin->w_height < p_hh) win_setheight((int)p_hh); #ifdef RIGHTLEFT curwin->w_p_rl = 0; /* help window is left-to-right */ #endif curwin->w_p_nu = 0; /* no line numbers */ /* * open help file (do_ecmd() will set b_help flag, readfile() will * set b_p_ro flag) */ (void)do_ecmd(0, p_hf, NULL, NULL, (linenr_t)0, ECMD_HIDE + ECMD_SET_HELP); /* save the values of the options we change */ vim_free(help_save_isk); help_save_isk = vim_strsave(curbuf->b_p_isk); help_save_ts = curbuf->b_p_ts; /* accept all chars for keywords, except ' ', '*', '"', '|' */ set_string_option((char_u *)"isk", -1, (char_u *)"!-~,^*,^|,^\"", TRUE); curbuf->b_p_ts = 8; check_buf_options(curbuf); (void)init_chartab(); /* needed because 'isk' changed */ } } restart_edit = 0; /* don't want insert mode in help file */ stuffReadbuff((char_u *)":ta "); if (arg != NULL && *arg != NUL) stuffReadbuff(arg); else stuffReadbuff((char_u *)"help.txt"); /* go to the index */ stuffcharReadbuff('\n'); erret: if (need_free) vim_free(arg); } /* * Return a heuristic indicating how well the given string matches. The * smaller the number, the better the match. This is the order of priorities, * from best match to worst match: * - Match with least alpha-numeric characters is better. * - Match with least total characters is better. * - Match towards the start is better. * Assumption is made that the matched_string passed has already been found to * match some string for which help is requested. webb. */ int help_heuristic(matched_string, offset) char_u *matched_string; int offset; /* offset for match */ { int num_letters; char_u *p; num_letters = 0; for (p = matched_string; *p; p++) if (isalnum(*p)) num_letters++; /* * Multiply the number of letters by 100 to give it a much bigger * weighting than the number of characters. * If the match starts in the middle of a word, add 10000 to put it * somewhere in the last half. * If the match is more than 2 chars from the start, multiply by 200 to * put it after matches at the start. */ if (isalnum(matched_string[offset]) && offset > 0 && isalnum(matched_string[offset - 1])) offset += 10000; else if (offset > 2) offset *= 200; return (int)(100 * num_letters + STRLEN(matched_string) + offset); } /* * Compare functions for qsort() below, that checks the help heuristics number * that has been put after the tagname by find_tags(). */ static int help_compare(s1, s2) const void *s1; const void *s2; { char *p1; char *p2; p1 = *(char **)s1 + strlen(*(char **)s1) + 1; p2 = *(char **)s2 + strlen(*(char **)s2) + 1; return strcmp(p1, p2); } /* * Find all help tags matching "arg", sort them and return in matches[], with * the number of matches in num_matches. * We try first with case, and then ignoring case. Then we try to choose the * "best" match from the ones found. */ int find_help_tags(arg, num_matches, matches) char_u *arg; int *num_matches; char_u ***matches; { char_u *s, *d; vim_regexp *prog; int attempt; int retval = FAIL; int i; static char *(mtable[]) = {"*", "g*", "[*", "]*", "/*", "/\\*", "/\\(\\)", "?", ":?", "?<CR>"}; static char *(rtable[]) = {"star", "gstar", "[star", "]star", "/star", "/\\\\star", "/\\\\(\\\\)", "?", ":?", "?<CR>"}; d = IObuff; /* assume IObuff is long enough! */ /* * Recognize a few exceptions to the rule. Some strings that contain '*' * with "star". Otherwise '*' is recognized as a wildcard. */ for (i = sizeof(mtable) / sizeof(char *); --i >= 0; ) { if (STRCMP(arg, mtable[i]) == 0) { STRCPY(d, rtable[i]); break; } } if (i < 0) /* no match in table, replace single characters */ { for (s = arg; *s; ++s) { /* * Replace "|" with "bar" and '"' with "quote" to match the name of * the tags for these commands. * Replace "*" with ".*" and "?" with "." to match command line * completion. * Insert a backslash before '~', '$' and '.' to avoid their * special meaning. */ if (d - IObuff > IOSIZE - 10) /* getting too long!? */ break; switch (*s) { case '|': STRCPY(d, "bar"); d += 3; continue; case '\"': STRCPY(d, "quote"); d += 5; continue; case '*': *d++ = '.'; break; case '?': *d++ = '.'; continue; case '$': case '.': case '~': *d++ = '\\'; break; } /* * Replace "^x" by "CTRL-X". Don't do this for "^_" to make * ":help i_^_CTRL-D" work. */ if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_')) /* ^x */ { STRCPY(d, "CTRL-"); d += 5; if (*s < ' ') { *d++ = *s + '@'; continue; } ++s; } else if (*s == '^') /* "^" or "CTRL-^" or "^_" */ *d++ = '\\'; /* * Insert a backslash before a backslash after a slash, for search * pattern tags: "/\|" --> "/\\|". */ else if (s[0] == '\\' && s[1] != '\\' && *arg == '/' && s == arg + 1) *d++ = '\\'; *d++ = *s; /* * If tag starts with ', toss everything after a second '. Fixes * CTRL-] on 'option'. (would include the trailing '.'). */ if (*s == '\'' && s > arg && *arg == '\'') break; } *d = NUL; } reg_ic = FALSE; prog = vim_regcomp(IObuff, (int)p_magic); if (prog == NULL) return FAIL; /* First try to match with case, then without */ for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE) { *matches = (char_u **)""; *num_matches = 0; retval = find_tags(NULL, prog, num_matches, matches, TRUE, FALSE); if (retval == FAIL || *num_matches) break; } vim_free(prog); #ifdef HAVE_QSORT /* * Sort the matches found on the heuristic number that is after the * tag name. If there is no qsort, the output will be messy! */ qsort((void *)*matches, (size_t)*num_matches, sizeof(char_u *), help_compare); #endif return OK; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.