diff options
author | Jason Downs <downsj@cvs.openbsd.org> | 1996-09-07 21:40:33 +0000 |
---|---|---|
committer | Jason Downs <downsj@cvs.openbsd.org> | 1996-09-07 21:40:33 +0000 |
commit | c224fc199c25dd257673c273eb344786b9bf532c (patch) | |
tree | 8f8ed1297120c537480d9e5d46bfe7452bd8505b /usr.bin/vim/misccmds.c | |
parent | d0d91e2d3d6569e4defdd5178241f28fa678d753 (diff) |
Initial import of vim 4.2.
This is meant to replace nvi in the tree. Vim, in general, works better,
provides more features, and does not suffer from the license problems
being imposed upon nvi.
On the other hand, vim lacks a non-visual ex mode, in addition to open mode.
This includes the GUI (X11) code, but doesn't try to compile it.
Diffstat (limited to 'usr.bin/vim/misccmds.c')
-rw-r--r-- | usr.bin/vim/misccmds.c | 3906 |
1 files changed, 3906 insertions, 0 deletions
diff --git a/usr.bin/vim/misccmds.c b/usr.bin/vim/misccmds.c new file mode 100644 index 00000000000..ff08529ab07 --- /dev/null +++ b/usr.bin/vim/misccmds.c @@ -0,0 +1,3906 @@ +/* $OpenBSD: misccmds.c,v 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. + */ + +/* + * misccmds.c: functions that didn't seem to fit elsewhere + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#ifdef HAVE_FCNTL_H +# include <fcntl.h> /* for chdir() */ +#endif + +static int get_indent_str __ARGS((char_u *ptr)); +static void check_status __ARGS((BUF *)); + +/* + * count the size of the indent in the current line + */ + int +get_indent() +{ + return get_indent_str(ml_get_curline()); +} + +/* + * count the size of the indent in line "lnum" + */ + int +get_indent_lnum(lnum) + linenr_t lnum; +{ + return get_indent_str(ml_get(lnum)); +} + +/* + * count the size of the indent in line "ptr" + */ + static int +get_indent_str(ptr) + register char_u *ptr; +{ + register int count = 0; + + for ( ; *ptr; ++ptr) + { + if (*ptr == TAB) /* count a tab for what it is worth */ + count += (int)curbuf->b_p_ts - (count % (int)curbuf->b_p_ts); + else if (*ptr == ' ') + ++count; /* count a space for one */ + else + break; + } + return (count); +} + +/* + * set the indent of the current line + * leaves the cursor on the first non-blank in the line + */ + void +set_indent(size, del_first) + register int size; + int del_first; +{ + int oldstate = State; + register int c; + + State = INSERT; /* don't want REPLACE for State */ + curwin->w_cursor.col = 0; + if (del_first) /* delete old indent */ + { + /* vim_iswhite() is a define! */ + while ((c = gchar_cursor()), vim_iswhite(c)) + (void)delchar(FALSE); + } + if (!curbuf->b_p_et) /* if 'expandtab' is set, don't use TABs */ + while (size >= (int)curbuf->b_p_ts) + { + ins_char(TAB); + size -= (int)curbuf->b_p_ts; + } + while (size) + { + ins_char(' '); + --size; + } + State = oldstate; +} + +#if defined(CINDENT) || defined(SMARTINDENT) + +static int is_cinword __ARGS((char_u *line)); + +/* + * Return TRUE if the string "line" starts with a word from 'cinwords'. + */ + static int +is_cinword(line) + char_u *line; +{ + char_u *cinw; + char_u *cinw_buf; + int cinw_len; + int retval = FALSE; + int len; + + cinw_len = STRLEN(curbuf->b_p_cinw) + 1; + cinw_buf = alloc((unsigned)cinw_len); + if (cinw_buf != NULL) + { + line = skipwhite(line); + for (cinw = curbuf->b_p_cinw; *cinw; ) + { + len = copy_option_part(&cinw, cinw_buf, cinw_len, ","); + if (STRNCMP(line, cinw_buf, len) == 0 && + (!iswordchar(line[len]) || !iswordchar(line[len - 1]))) + { + retval = TRUE; + break; + } + } + vim_free(cinw_buf); + } + return retval; +} +#endif + +/* + * Opencmd + * + * Add a new line below or above the current line. + * Caller must take care of undo. + * + * Return TRUE for success, FALSE for failure + */ + + int +Opencmd(dir, redraw, del_spaces) + int dir; /* FORWARD or BACKWARD */ + int redraw; /* redraw afterwards */ + int del_spaces; /* delete spaces after cursor */ +{ + char_u *saved_line; /* copy of the original line */ + char_u *p_extra = NULL; /* what goes to next line */ + int extra_len = 0; /* length of p_extra string */ + FPOS old_cursor; /* old cursor position */ + int newcol = 0; /* new cursor column */ + int newindent = 0; /* auto-indent of the new line */ + int n; + int trunc_line = FALSE; /* truncate current line afterwards */ + int retval = FALSE; /* return value, default is FAIL */ + int lead_len; /* length of comment leader */ + char_u *lead_flags; /* position in 'comments' for comment leader */ + char_u *leader = NULL; /* copy of comment leader */ + char_u *allocated = NULL; /* allocated memory */ + char_u *p; + int saved_char = NUL; /* init for GCC */ + FPOS *pos; + int old_plines = 0; /* init for GCC */ + int new_plines = 0; /* init for GCC */ +#ifdef SMARTINDENT + int no_si = FALSE; /* reset did_si afterwards */ + int first_char = NUL; /* init for GCC */ +#endif + + /* + * make a copy of the current line so we can mess with it + */ + saved_line = strsave(ml_get_curline()); + if (saved_line == NULL) /* out of memory! */ + return FALSE; + + if (State == INSERT || State == REPLACE) + { + p_extra = saved_line + curwin->w_cursor.col; +#ifdef SMARTINDENT + if (curbuf->b_p_si) /* need first char after new line break */ + { + p = skipwhite(p_extra); + first_char = *p; + } +#endif + extra_len = STRLEN(p_extra); + saved_char = *p_extra; + *p_extra = NUL; + } + + u_clearline(); /* cannot do "U" command when adding lines */ +#ifdef SMARTINDENT + did_si = FALSE; +#endif + + /* + * If 'autoindent' and/or 'smartindent' is set, try to figure out what + * indent to use for the new line. + */ + if (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + ) + { + /* + * count white space on current line + */ + newindent = get_indent(); + if (newindent == 0) + newindent = old_indent; /* for ^^D command in insert mode */ + old_indent = 0; + + /* + * If we just did an auto-indent, then we didn't type anything on + * the prior line, and it should be truncated. + */ + if (dir == FORWARD && did_ai) + trunc_line = TRUE; + +#ifdef SMARTINDENT + /* + * Do smart indenting. + * In insert/replace mode (only when dir == FORWARD) + * we may move some text to the next line. If it starts with '{' + * don't add an indent. Fixes inserting a NL before '{' in line + * "if (condition) {" + */ + else if (curbuf->b_p_si && *saved_line != NUL && + (p_extra == NULL || first_char != '{')) + { + char_u *ptr; + char_u last_char; + + old_cursor = curwin->w_cursor; + ptr = saved_line; + lead_len = get_leader_len(ptr, NULL); + if (dir == FORWARD) + { + /* + * Skip preprocessor directives, unless they are + * recognised as comments. + */ + if (lead_len == 0 && ptr[0] == '#') + { + while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) + ptr = ml_get(--curwin->w_cursor.lnum); + newindent = get_indent(); + } + lead_len = get_leader_len(ptr, NULL); + if (lead_len > 0) + { + /* + * This case gets the following right: + * \* + * * A comment (read "\" as "/"). + * *\ + * #define IN_THE_WAY + * This should line up here; + */ + p = skipwhite(ptr); + if (p[0] == '/' && p[1] == '*') + p++; + if (p[0] == '*') + { + for (p++; *p; p++) + { + if (p[0] == '/' && p[-1] == '*') + { + /* + * End of C comment, indent should line up + * with the line containing the start of + * the comment + */ + curwin->w_cursor.col = p - ptr; + if ((pos = findmatch(NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + } + } + } + } + else /* Not a comment line */ + { + /* Find last non-blank in line */ + p = ptr + STRLEN(ptr) - 1; + while (p > ptr && vim_iswhite(*p)) + --p; + last_char = *p; + + /* + * find the character just before the '{' or ';' + */ + if (last_char == '{' || last_char == ';') + { + if (p > ptr) + --p; + while (p > ptr && vim_iswhite(*p)) + --p; + } + /* + * Try to catch lines that are split over multiple + * lines. eg: + * if (condition && + * condition) { + * Should line up here! + * } + */ + if (*p == ')') + { + curwin->w_cursor.col = p - ptr; + if ((pos = findmatch('(')) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + ptr = ml_get_curline(); + } + } + /* + * If last character is '{' do indent, without + * checking for "if" and the like. + */ + if (last_char == '{') + { + did_si = TRUE; /* do indent */ + no_si = TRUE; /* don't delete it when '{' typed */ + } + /* + * Look for "if" and the like, use 'cinwords'. + * Don't do this if the previous line ended in ';' or + * '}'. + */ + else if (last_char != ';' && last_char != '}' && + is_cinword(ptr)) + did_si = TRUE; + } + } + else /* dir == BACKWARD */ + { + /* + * Skip preprocessor directives, unless they are + * recognised as comments. + */ + if (lead_len == 0 && ptr[0] == '#') + { + int was_backslashed = FALSE; + + while ((ptr[0] == '#' || was_backslashed) && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') + was_backslashed = TRUE; + else + was_backslashed = FALSE; + ptr = ml_get(++curwin->w_cursor.lnum); + } + if (was_backslashed) + newindent = 0; /* Got to end of file */ + else + newindent = get_indent(); + } + p = skipwhite(ptr); + if (*p == '}') /* if line starts with '}': do indent */ + did_si = TRUE; + else /* can delete indent when '{' typed */ + can_si_back = TRUE; + } + curwin->w_cursor = old_cursor; + } + if (curbuf->b_p_si) + can_si = TRUE; +#endif /* SMARTINDENT */ + + did_ai = TRUE; + } + + /* + * Find out if the current line starts with a comment leader. + * This may then be inserted in front of the new line. + */ + lead_len = get_leader_len(saved_line, &lead_flags); + if (lead_len > 0) + { + char_u *lead_repl = NULL; /* replaces comment leader */ + int lead_repl_len = 0; /* length of *lead_repl */ + char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */ + char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + char_u *comment_end = NULL; /* where lead_end has been found */ + int extra_space = FALSE; /* append extra space */ + int current_flag; + + /* + * If the comment leader has the start, middle or end flag, it may not + * be used or may be replaced with the middle leader. + */ + for (p = lead_flags; *p && *p != ':'; ++p) + { + if (*p == COM_START || *p == COM_MIDDLE) + { + current_flag = *p; + if (*p == COM_START) + { + /* + * Doing "O" on a start of comment does not insert leader. + */ + if (dir == BACKWARD) + { + lead_len = 0; + break; + } + + /* find start of middle part */ + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + } + + /* + * Isolate the strings of the middle and end leader. + */ + while (*p && p[-1] != ':') /* find end of middle flags */ + ++p; + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + while (*p && p[-1] != ':') /* find end of end flags */ + ++p; + (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + /* + * If the end of the comment is in the same line, don't use + * the comment leader. + */ + if (dir == FORWARD) + { + n = STRLEN(lead_end); + for (p = saved_line + lead_len; *p; ++p) + if (STRNCMP(p, lead_end, n) == 0) + { + comment_end = p; + lead_len = 0; + break; + } + } + + /* + * Doing "o" on a start of comment inserts the middle leader. + */ + if (lead_len) + { + if (current_flag == COM_START) + { + lead_repl = lead_middle; + lead_repl_len = STRLEN(lead_middle); + } + + /* + * If we have hit RETURN immediately after the start + * comment leader, then put a space after the middle + * comment leader on the next line. + */ + if (!vim_iswhite(saved_line[lead_len - 1]) && + ((p_extra != NULL && + (int)curwin->w_cursor.col == lead_len) || + (p_extra == NULL && saved_line[lead_len] == NUL))) + extra_space = TRUE; + } + break; + } + if (*p == COM_END) + { + /* + * Doing "o" on the end of a comment does not insert leader. + * Remember where the end is, might want to use it to find the + * start (for C-comments). + */ + if (dir == FORWARD) + { + comment_end = skipwhite(saved_line); + lead_len = 0; + break; + } + + /* + * Doing "O" on the end of a comment inserts the middle leader. + * Find the string for the middle leader, searching backwards. + */ + while (p > curbuf->b_p_com && *p != ',') + --p; + for (lead_repl = p; lead_repl > curbuf->b_p_com && + lead_repl[-1] != ':'; --lead_repl) + ; + lead_repl_len = p - lead_repl; + break; + } + if (*p == COM_FIRST) + { + /* + * Comment leader for first line only: Don't repeat leader + * when using "O", blank out leader when using "o". + */ + if (dir == BACKWARD) + lead_len = 0; + else + { + lead_repl = (char_u *)""; + lead_repl_len = 0; + } + break; + } + } + if (lead_len) + { + /* allocate buffer (may concatenate p_exta later) */ + leader = alloc(lead_len + lead_repl_len + extra_space + + extra_len + 1); + allocated = leader; /* remember to free it later */ + + if (leader == NULL) + lead_len = 0; + else + { + STRNCPY(leader, saved_line, lead_len); + leader[lead_len] = NUL; + + /* + * Replace leader with lead_repl, right or left adjusted + */ + if (lead_repl != NULL) + { + for (p = lead_flags; *p && *p != ':'; ++p) + if (*p == COM_RIGHT || *p == COM_LEFT) + break; + if (*p == COM_RIGHT) /* right adjusted leader */ + { + /* find last non-white in the leader to line up with */ + for (p = leader + lead_len - 1; p > leader && + vim_iswhite(*p); --p) + ; + + ++p; + if (p < leader + lead_repl_len) + p = leader; + else + p -= lead_repl_len; + vim_memmove(p, lead_repl, (size_t)lead_repl_len); + if (p + lead_repl_len > leader + lead_len) + p[lead_repl_len] = NUL; + + /* blank-out any other chars from the old leader. */ + while (--p >= leader) + if (!vim_iswhite(*p)) + *p = ' '; + } + else /* left adjusted leader */ + { + p = skipwhite(leader); + vim_memmove(p, lead_repl, (size_t)lead_repl_len); + + /* blank-out any other chars from the old leader. */ + for (p += lead_repl_len; p < leader + lead_len; ++p) + if (!vim_iswhite(*p)) + *p = ' '; + *p = NUL; + } + + /* Recompute the indent, it may have changed. */ + if (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + ) + newindent = get_indent_str(leader); + } + + lead_len = STRLEN(leader); + if (extra_space) + { + leader[lead_len++] = ' '; + leader[lead_len] = NUL; + } + + newcol = lead_len; + + /* + * if a new indent will be set below, remove the indent that + * is in the comment leader + */ + if (newindent +#ifdef SMARTINDENT + || did_si +#endif + ) + { + while (lead_len && vim_iswhite(*leader)) + { + --lead_len; + --newcol; + ++leader; + } + } + + } +#ifdef SMARTINDENT + did_si = can_si = FALSE; +#endif + } + else if (comment_end != NULL) + { + /* + * We have finished a comment, so we don't use the leader. + * If this was a C-comment and 'ai' or 'si' is set do a normal + * indent to align with the line containing the start of the + * comment. + */ + if (comment_end[0] == '*' && comment_end[1] == '/' && + (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + )) + { + old_cursor = curwin->w_cursor; + curwin->w_cursor.col = comment_end - saved_line; + if ((pos = findmatch(NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + curwin->w_cursor = old_cursor; + } + } + } + + /* (State == INSERT || State == REPLACE), only when dir == FORWARD */ + if (p_extra != NULL) + { + *p_extra = saved_char; /* restore char that NUL replaced */ + + /* + * When 'ai' set or "del_spaces" TRUE, skip to the first non-blank. + * + * When in REPLACE mode, put the deleted blanks on the replace + * stack, followed by a NUL, so they can be put back when + * a BS is entered. + */ + if (State == REPLACE) + replace_push(NUL); /* end of extra blanks */ + if (curbuf->b_p_ai || del_spaces) + { + while (*p_extra == ' ' || *p_extra == '\t') + { + if (State == REPLACE) + replace_push(*p_extra); + ++p_extra; + } + } + if (*p_extra != NUL) + did_ai = FALSE; /* append some text, don't trucate now */ + } + + if (p_extra == NULL) + p_extra = (char_u *)""; /* append empty line */ + + /* concatenate leader and p_extra, if there is a leader */ + if (lead_len) + { + STRCAT(leader, p_extra); + p_extra = leader; + } + + old_cursor = curwin->w_cursor; + if (dir == BACKWARD) + --curwin->w_cursor.lnum; + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_t)0, FALSE) == FAIL) + goto theend; + mark_adjust(curwin->w_cursor.lnum + 1, MAXLNUM, 1L, 0L); + if (newindent +#ifdef SMARTINDENT + || did_si +#endif + ) + { + ++curwin->w_cursor.lnum; +#ifdef SMARTINDENT + if (did_si) + { + if (p_sr) + newindent -= newindent % (int)curbuf->b_p_sw; + newindent += (int)curbuf->b_p_sw; + } +#endif + set_indent(newindent, FALSE); + /* + * In REPLACE mode the new indent must be put on + * the replace stack for when it is deleted with BS + */ + if (State == REPLACE) + for (n = 0; n < (int)curwin->w_cursor.col; ++n) + replace_push(NUL); + newcol += curwin->w_cursor.col; +#ifdef SMARTINDENT + if (no_si) + did_si = FALSE; +#endif + } + /* + * In REPLACE mode the extra leader must be put on the replace stack for + * when it is deleted with BS. + */ + if (State == REPLACE) + while (lead_len-- > 0) + replace_push(NUL); + + curwin->w_cursor = old_cursor; + + if (dir == FORWARD) + { + if (redraw) /* want to know the old number of screen lines */ + { + old_plines = plines(curwin->w_cursor.lnum); + new_plines = old_plines; + } + if (trunc_line || State == INSERT || State == REPLACE) + { + if (trunc_line) + { + /* find start of trailing white space */ + for (n = STRLEN(saved_line); n > 0 && + vim_iswhite(saved_line[n - 1]); --n) + ; + saved_line[n] = NUL; + } + else /* truncate current line at cursor */ + *(saved_line + curwin->w_cursor.col) = NUL; + ml_replace(curwin->w_cursor.lnum, saved_line, FALSE); + saved_line = NULL; + new_plines = plines(curwin->w_cursor.lnum); + } + + /* + * Get the cursor to the start of the line, so that 'curwin->w_row' + * gets set to the right physical line number for the stuff that + * follows... + */ + curwin->w_cursor.col = 0; + + if (redraw) + { + /* + * Call cursupdate() to compute w_row. + * But we don't want it to update the srceen. + */ + ++RedrawingDisabled; + cursupdate(); + --RedrawingDisabled; + + /* + * If we're doing an open on the last logical line, then go ahead + * and scroll the screen up. Otherwise, just insert a blank line + * at the right place if the number of screen lines changed. + * We use calls to plines() in case the cursor is resting on a + * long line, we want to know the row below the line. + */ + n = curwin->w_row + new_plines; + if (n == curwin->w_winpos + curwin->w_height) + scrollup(1L); + else + win_ins_lines(curwin, n, + plines(curwin->w_cursor.lnum + 1) + new_plines - old_plines, + TRUE, TRUE); + } + + /* + * Put the cursor on the new line. Careful: the cursupdate() and + * scrollup() above may have moved w_cursor, we must use old_cursor. + */ + curwin->w_cursor.lnum = old_cursor.lnum + 1; + } + else if (redraw) /* insert physical line above current line */ + win_ins_lines(curwin, curwin->w_row, 1, TRUE, TRUE); + + curwin->w_cursor.col = newcol; + +#ifdef LISPINDENT + /* + * May do lisp indenting. + */ + if (leader == NULL && curbuf->b_p_lisp && curbuf->b_p_ai) + fixthisline(get_lisp_indent); +#endif +#ifdef CINDENT + /* + * May do indenting after opening a new line. + */ + if (leader == NULL && curbuf->b_p_cin && + in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : + KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) + fixthisline(get_c_indent); +#endif + + if (redraw) + { + updateScreen(VALID_TO_CURSCHAR); + cursupdate(); /* update curwin->w_row */ + } + CHANGED; + + retval = TRUE; /* success! */ +theend: + vim_free(saved_line); + vim_free(allocated); + return retval; +} + +/* + * get_leader_len() returns the length of the prefix of the given string + * which introduces a comment. If this string is not a comment then 0 is + * returned. + * When "flags" is non-zero, it is set to point to the flags of the recognized + * comment leader. + */ + int +get_leader_len(line, flags) + char_u *line; + char_u **flags; +{ + int i, j; + int got_com = FALSE; + int found_one; + char_u part_buf[COM_MAX_LEN]; /* buffer for one option part */ + char_u *string; /* pointer to comment string */ + char_u *list; + + if (!fo_do_comments) /* don't format comments at all */ + return 0; + + i = 0; + while (vim_iswhite(line[i])) /* leading white space is ignored */ + ++i; + + /* + * Repeat to match several nested comment strings. + */ + while (line[i]) + { + /* + * scan through the 'comments' option for a match + */ + found_one = FALSE; + for (list = curbuf->b_p_com; *list; ) + { + /* + * Get one option part into part_buf[]. Advance list to next one. + * put string at start of string. + */ + if (!got_com && flags != NULL) /* remember where flags started */ + *flags = list; + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) /* missing ':', ignore this part */ + continue; + *string++ = NUL; /* isolate flags from string */ + + /* + * When already found a nested comment, only accept further + * nested comments. + */ + if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) + continue; + + /* + * Line contents and string must match. + */ + for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) + ; + if (string[j] != NUL) + continue; + + /* + * When 'b' flag used, there must be white space or an + * end-of-line after the string in the line. + */ + if (vim_strchr(part_buf, COM_BLANK) != NULL && + !vim_iswhite(line[i + j]) && line[i + j] != NUL) + continue; + + /* + * We have found a match, stop searching. + */ + i += j; + got_com = TRUE; + found_one = TRUE; + break; + } + + /* + * No match found, stop scanning. + */ + if (!found_one) + break; + + /* + * Include any trailing white space. + */ + while (vim_iswhite(line[i])) + ++i; + + /* + * If this comment doesn't nest, stop here. + */ + if (vim_strchr(part_buf, COM_NEST) == NULL) + break; + } + return (got_com ? i : 0); +} + +/* + * plines(p) - return the number of physical screen lines taken by line 'p' + */ + int +plines(p) + linenr_t p; +{ + return plines_win(curwin, p); +} + + int +plines_win(wp, p) + WIN *wp; + linenr_t p; +{ + register long col; + register char_u *s; + register int lines; + + if (!wp->w_p_wrap) + return 1; + + s = ml_get_buf(wp->w_buffer, p, FALSE); + if (*s == NUL) /* empty line */ + return 1; + + col = linetabsize(s); + + /* + * If list mode is on, then the '$' at the end of the line takes up one + * extra column. + */ + if (wp->w_p_list) + col += 1; + + /* + * If 'number' mode is on, add another 8. + */ + if (wp->w_p_nu) + col += 8; + + lines = (col + (Columns - 1)) / Columns; + if (lines <= wp->w_height) + return lines; + return (int)(wp->w_height); /* maximum length */ +} + +/* + * Count the physical lines (rows) for the lines "first" to "last" inclusive. + */ + int +plines_m(first, last) + linenr_t first, last; +{ + return plines_m_win(curwin, first, last); +} + + int +plines_m_win(wp, first, last) + WIN *wp; + linenr_t first, last; +{ + int count = 0; + + while (first <= last) + count += plines_win(wp, first++); + return (count); +} + +/* + * Insert or replace a single character at the cursor position. + * When in REPLACE mode, replace any existing character. + */ + void +ins_char(c) + int c; +{ + register char_u *p; + char_u *newp; + char_u *oldp; + int oldlen; + int extra; + colnr_t col = curwin->w_cursor.col; + linenr_t lnum = curwin->w_cursor.lnum; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp) + 1; + + if (State != REPLACE || *(oldp + col) == NUL) + extra = 1; + else + extra = 0; + + /* + * a character has to be put on the replace stack if there is a + * character that is replaced, so it can be put back when BS is used. + * Otherwise a 0 is put on the stack, indicating that a new character + * was inserted, which can be deleted when BS is used. + */ + if (State == REPLACE) + replace_push(!extra ? *(oldp + col) : 0); + newp = alloc_check((unsigned)(oldlen + extra)); + if (newp == NULL) + return; + vim_memmove(newp, oldp, (size_t)col); + p = newp + col; + vim_memmove(p + extra, oldp + col, (size_t)(oldlen - col)); + *p = c; + ml_replace(lnum, newp, FALSE); + + /* + * If we're in insert or replace mode and 'showmatch' is set, then check for + * right parens and braces. If there isn't a match, then beep. If there + * is a match AND it's on the screen, then flash to it briefly. If it + * isn't on the screen, don't do anything. + */ +#ifdef RIGHTLEFT + if (p_sm && (State & INSERT) && + ((!curwin->w_p_rl && (c == ')' || c == '}' || c == ']')) || + (curwin->w_p_rl && (c == '(' || c == '{' || c == '[')))) +#else + if (p_sm && (State & INSERT) && (c == ')' || c == '}' || c == ']')) +#endif + showmatch(); + +#ifdef RIGHTLEFT + if (!p_ri || State == REPLACE) /* normal insert: cursor right */ +#endif + ++curwin->w_cursor.col; + CHANGED; +} + +/* + * Insert a string at the cursor position. + * Note: Nothing special for replace mode. + */ + void +ins_str(s) + char_u *s; +{ + register char_u *oldp, *newp; + register int newlen = STRLEN(s); + int oldlen; + colnr_t col = curwin->w_cursor.col; + linenr_t lnum = curwin->w_cursor.lnum; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp); + + newp = alloc_check((unsigned)(oldlen + newlen + 1)); + if (newp == NULL) + return; + vim_memmove(newp, oldp, (size_t)col); + vim_memmove(newp + col, s, (size_t)newlen); + vim_memmove(newp + col + newlen, oldp + col, (size_t)(oldlen - col + 1)); + ml_replace(lnum, newp, FALSE); + curwin->w_cursor.col += newlen; + CHANGED; +} + +/* + * delete one character under the cursor + * + * return FAIL for failure, OK otherwise + */ + int +delchar(fixpos) + int fixpos; /* if TRUE fix the cursor position when done */ +{ + char_u *oldp, *newp; + colnr_t oldlen; + linenr_t lnum = curwin->w_cursor.lnum; + colnr_t col = curwin->w_cursor.col; + int was_alloced; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp); + + if (col >= oldlen) /* can't do anything (happens with replace mode) */ + return FAIL; + +/* + * If the old line has been allocated the deletion can be done in the + * existing line. Otherwise a new line has to be allocated + */ + was_alloced = ml_line_alloced(); /* check if oldp was allocated */ + if (was_alloced) + newp = oldp; /* use same allocated memory */ + else + { + newp = alloc((unsigned)oldlen); /* need to allocated a new line */ + if (newp == NULL) + return FAIL; + vim_memmove(newp, oldp, (size_t)col); + } + vim_memmove(newp + col, oldp + col + 1, (size_t)(oldlen - col)); + if (!was_alloced) + ml_replace(lnum, newp, FALSE); + + /* + * If we just took off the last character of a non-blank line, we don't + * want to end up positioned at the NUL. + */ + if (fixpos && curwin->w_cursor.col > 0 && col == oldlen - 1) + --curwin->w_cursor.col; + + CHANGED; + return OK; +} + +/* + * Delete from cursor to end of line. + * + * return FAIL for failure, OK otherwise + */ + int +truncate_line(fixpos) + int fixpos; /* if TRUE fix the cursor position when done */ +{ + char_u *newp; + linenr_t lnum = curwin->w_cursor.lnum; + colnr_t col = curwin->w_cursor.col; + + if (col == 0) + newp = strsave((char_u *)""); + else + newp = strnsave(ml_get(lnum), col); + + if (newp == NULL) + return FAIL; + + ml_replace(lnum, newp, FALSE); + + /* + * If "fixpos" is TRUE we don't want to end up positioned at the NUL. + */ + if (fixpos && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + + CHANGED; + return OK; +} + + void +dellines(nlines, dowindow, undo) + long nlines; /* number of lines to delete */ + int dowindow; /* if true, update the window */ + int undo; /* if true, prepare for undo */ +{ + int num_plines = 0; + + if (nlines <= 0) + return; + /* + * There's no point in keeping the window updated if redrawing is disabled + * or we're deleting more than a window's worth of lines. + */ + if (RedrawingDisabled) + dowindow = FALSE; + else if (nlines > (curwin->w_height - curwin->w_row) && dowindow) + { + dowindow = FALSE; + /* flaky way to clear rest of window */ + win_del_lines(curwin, curwin->w_row, curwin->w_height, TRUE, TRUE); + } + /* save the deleted lines for undo */ + if (undo && u_savedel(curwin->w_cursor.lnum, nlines) == FAIL) + return; + + /* adjust marks for deleted lines and lines that follow */ + mark_adjust(curwin->w_cursor.lnum, curwin->w_cursor.lnum + nlines - 1, + MAXLNUM, -nlines); + + while (nlines-- > 0) + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ + break; + + /* + * Set up to delete the correct number of physical lines on the + * window + */ + if (dowindow) + num_plines += plines(curwin->w_cursor.lnum); + + ml_delete(curwin->w_cursor.lnum, TRUE); + + CHANGED; + + /* If we delete the last line in the file, stop */ + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + break; + } + } + curwin->w_cursor.col = 0; + /* + * Delete the correct number of physical lines on the window + */ + if (dowindow && num_plines > 0) + win_del_lines(curwin, curwin->w_row, num_plines, TRUE, TRUE); +} + + int +gchar(pos) + FPOS *pos; +{ + return (int)(*(ml_get_pos(pos))); +} + + int +gchar_cursor() +{ + return (int)(*(ml_get_cursor())); +} + +/* + * Write a character at the current cursor position. + * It is directly written into the block. + */ + void +pchar_cursor(c) + int c; +{ + *(ml_get_buf(curbuf, curwin->w_cursor.lnum, TRUE) + + curwin->w_cursor.col) = c; +} + +/* + * Put *pos at end of current buffer + */ + void +goto_endofbuf(pos) + FPOS *pos; +{ + char_u *p; + + pos->lnum = curbuf->b_ml.ml_line_count; + pos->col = 0; + p = ml_get(pos->lnum); + while (*p++) + ++pos->col; +} + +/* + * When extra == 0: Return TRUE if the cursor is before or on the first + * non-blank in the line. + * When extra == 1: Return TRUE if the cursor is before the first non-blank in + * the line. + */ + int +inindent(extra) + int extra; +{ + register char_u *ptr; + register colnr_t col; + + for (col = 0, ptr = ml_get_curline(); vim_iswhite(*ptr); ++col) + ++ptr; + if (col >= curwin->w_cursor.col + extra) + return TRUE; + else + return FALSE; +} + +/* + * skipwhite: skip over ' ' and '\t'. + */ + char_u * +skipwhite(p) + register char_u *p; +{ + while (vim_iswhite(*p)) /* skip to next non-white */ + ++p; + return p; +} + +/* + * skipdigits: skip over digits; + */ + char_u * +skipdigits(p) + register char_u *p; +{ + while (isdigit(*p)) /* skip to next non-digit */ + ++p; + return p; +} + +/* + * skiptowhite: skip over text until ' ' or '\t' or NUL. + */ + char_u * +skiptowhite(p) + register char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + ++p; + return p; +} + +/* + * skiptowhite_esc: Like skiptowhite(), but also skip escaped chars + */ + char_u * +skiptowhite_esc(p) + register char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + { + if ((*p == '\\' || *p == Ctrl('V')) && *(p + 1) != NUL) + ++p; + ++p; + } + return p; +} + +/* + * getdigits: get a number from a string and skip over it + * + * note: you must give a pointer to a char_u pointer! + */ + + long +getdigits(pp) + char_u **pp; +{ + register char_u *p; + long retval; + + p = *pp; + retval = atol((char *)p); + p = skipdigits(p); /* skip to next non-digit */ + *pp = p; + return retval; +} + +/* + * Skip to next part of an option argument: Skip space and comma. + */ + char_u * +skip_to_option_part(p) + char_u *p; +{ + if (*p == ',') + ++p; + while (*p == ' ') + ++p; + return p; +} + + char * +plural(n) + long n; +{ + static char buf[2] = "s"; + + if (n == 1) + return &(buf[1]); + return &(buf[0]); +} + +/* + * set_Changed is called when something in the current buffer is changed + */ + void +set_Changed() +{ + if (!curbuf->b_changed) + { + change_warning(); + curbuf->b_changed = TRUE; + check_status(curbuf); + } + modified = TRUE; /* used for redrawing */ +} + +/* + * unset_Changed is called when the changed flag must be reset for buffer 'buf' + */ + void +unset_Changed(buf) + BUF *buf; +{ + if (buf->b_changed) + { + buf->b_changed = 0; + check_status(buf); + } +} + +/* + * check_status: called when the status bars for the buffer 'buf' + * need to be updated + */ + static void +check_status(buf) + BUF *buf; +{ + WIN *wp; + int i; + + i = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf && wp->w_status_height) + { + wp->w_redr_status = TRUE; + ++i; + } + if (i) + redraw_later(NOT_VALID); +} + +/* + * If the file is readonly, give a warning message with the first change. + * Don't do this for autocommands. + * Don't use emsg(), because it flushes the macro buffer. + * If we have undone all changes b_changed will be FALSE, but b_did_warn + * will be TRUE. + */ + void +change_warning() +{ + if (curbuf->b_did_warn == FALSE && curbuf->b_changed == 0 && +#ifdef AUTOCMD + !autocmd_busy && +#endif + curbuf->b_p_ro) + { + curbuf->b_did_warn = TRUE; + MSG("Warning: Changing a readonly file"); + mch_delay(1000L, TRUE); /* give him some time to think about it */ + } +} + +/* + * Ask for a reply from the user, a 'y' or a 'n'. + * No other characters are accepted, the message is repeated until a valid + * reply is entered or CTRL-C is hit. + * If direct is TRUE, don't use vgetc but mch_inchar, don't get characters from + * any buffers but directly from the user. + * + * return the 'y' or 'n' + */ + int +ask_yesno(str, direct) + char_u *str; + int direct; +{ + int r = ' '; + char_u buf[20]; + int len = 0; + int idx = 0; + + if (exiting) /* put terminal in raw mode for this question */ + settmode(1); + while (r != 'y' && r != 'n') + { + (void)set_highlight('r'); /* same highlighting as for wait_return */ + msg_highlight = TRUE; + smsg((char_u *)"%s (y/n)?", str); + if (direct) + { + flushbuf(); + if (idx >= len) + { + len = mch_inchar(buf, 20, -1L); + idx = 0; + } + r = buf[idx++]; + } + else + r = vgetc(); + if (r == Ctrl('C') || r == ESC) + r = 'n'; + msg_outchar(r); /* show what you typed */ + flushbuf(); + } + return r; +} + +/* + * get a number from the user + */ + int +get_number() +{ + int n = 0; + int c; + + for (;;) + { + windgoto(msg_row, msg_col); + c = vgetc(); + if (isdigit(c)) + { + n = n * 10 + c - '0'; + msg_outchar(c); + } + else if (c == K_DEL || c == K_BS || c == Ctrl('H')) + { + n /= 10; + MSG_OUTSTR("\b \b"); + } + else if (c == CR || c == NL || c == Ctrl('C')) + break; + } + return n; +} + + void +msgmore(n) + long n; +{ + long pn; + + if (global_busy || /* no messages now, wait until global is finished */ + keep_msg) /* there is a message already, skip this one */ + return; + + if (n > 0) + pn = n; + else + pn = -n; + + if (pn > p_report) + { + sprintf((char *)msg_buf, "%ld %s line%s %s", + pn, n > 0 ? "more" : "fewer", plural(pn), + got_int ? "(Interrupted)" : ""); + if (msg(msg_buf)) + keep_msg = msg_buf; + } +} + +/* + * flush map and typeahead buffers and give a warning for an error + */ + void +beep_flush() +{ + flush_buffers(FALSE); + vim_beep(); +} + +/* + * give a warning for an error + */ + void +vim_beep() +{ + if (p_vb) + { +#ifdef DJGPP + ScreenVisualBell(); +#else + outstr(T_VB); +#endif + } + else + { +#if defined MSDOS || defined WIN32 /* ? gvr */ + /* + * The number of beeps outputted is reduced to avoid having to wait + * for all the beeps to finish. This is only a problem on systems + * where the beeps don't overlap. + */ + if (beep_count == 0 || beep_count == 10) + { + outchar('\007'); + beep_count = 1; + } + else + ++beep_count; +#else + outchar('\007'); +#endif + } +} + +/* + * To get the "real" home directory: + * - get value of $HOME + * For Unix: + * - go to that directory + * - do mch_dirname() to get the real name of that directory. + * This also works with mounts and links. + * Don't do this for MS-DOS, it will change the "current dir" for a drive. + */ +static char_u *homedir = NULL; + + void +init_homedir() +{ + char_u *var; + + var = vim_getenv((char_u *)"HOME"); +#if defined(OS2) || defined(MSDOS) || defined(WIN32) + /* + * Default home dir is C:/ + * Best assumption we can make in such a situation. + */ + if (var == NULL) + var = "C:/"; +#endif + if (var != NULL) + { +#ifdef UNIX + if (mch_dirname(NameBuff, MAXPATHL) == OK) + { + if (!vim_chdir((char *)var) && mch_dirname(IObuff, IOSIZE) == OK) + var = IObuff; + vim_chdir((char *)NameBuff); + } +#endif + homedir = strsave(var); + } +} + +/* + * Expand environment variable with path name. + * For Unix and OS/2 "~/" is also expanded, like $HOME. + * If anything fails no expansion is done and dst equals src. + * Note that IObuff must NOT be used as either src or dst! This is because + * vim_getenv() may use IObuff to do its expansion. + */ + void +expand_env(src, dst, dstlen) + char_u *src; /* input string e.g. "$HOME/vim.hlp" */ + char_u *dst; /* where to put the result */ + int dstlen; /* maximum length of the result */ +{ + char_u *tail; + int c; + char_u *var; + int copy_char; +#if defined(UNIX) || defined(OS2) + int mustfree; + int at_start = TRUE; +#endif + + src = skipwhite(src); + --dstlen; /* leave one char space for "\," */ + while (*src && dstlen > 0) + { + copy_char = TRUE; + if (*src == '$' +#if defined(UNIX) || defined(OS2) + || (*src == '~' && at_start) +#endif + ) + { +#if defined(UNIX) || defined(OS2) + mustfree = FALSE; + + /* + * The variable name is copied into dst temporarily, because it may + * be a string in read-only memory and a NUL needs to be inserted. + */ + if (*src == '$') /* environment var */ + { +#endif + tail = src + 1; + var = dst; + c = dstlen - 1; + while (c-- > 0 && *tail && isidchar(*tail)) +#ifdef OS2 + { /* env vars only in uppercase */ + *var++ = toupper(*tail); /* toupper() may be a macro! */ + tail++; + } +#else + *var++ = *tail++; +#endif + *var = NUL; +#if defined(OS2) || defined(MSDOS) || defined(WIN32) + /* use "C:/" when $HOME is not set */ + if (STRCMP(dst, "HOME") == 0) + var = homedir; + else +#endif + var = vim_getenv(dst); +#if defined(UNIX) || defined(OS2) + } + /* home directory */ + else if (src[1] == NUL || + vim_strchr((char_u *)"/ ,\t\n", src[1]) != NULL) + { + var = homedir; + tail = src + 1; + } + else /* user directory */ +# ifdef OS2 + { + /* cannot expand user's home directory, so don't try */ + var = NULL; + tail = ""; /* shut gcc up about "may be used uninitialized" */ + } +# else + { + /* + * Copy ~user to dst[], so we can put a NUL after it. + */ + tail = src; + var = dst; + c = dstlen - 1; + while (c-- > 0 && *tail && + isfilechar(*tail) && !ispathsep(*tail)) + *var++ = *tail++; + *var = NUL; + + /* + * If the system supports getpwnam(), use it. + * Otherwise, or if getpwnam() fails, the shell is used to + * expand ~user. This is slower and may fail if the shell + * does not support ~user (old versions of /bin/sh). + */ +# if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H) + { + struct passwd *pw; + + pw = getpwnam((char *)dst + 1); + if (pw != NULL) + var = (char_u *)pw->pw_dir; + else + var = NULL; + } + if (var == NULL) +# endif + { + var = ExpandOne(dst, NULL, 0, WILD_EXPAND_FREE); + mustfree = TRUE; + } + } +# endif /* OS2 */ +#endif /* UNIX || OS2 */ + if (var != NULL && *var != NUL && + (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) + { + STRCPY(dst, var); + dstlen -= STRLEN(var); + dst += STRLEN(var); + /* if var[] ends in a path separator and tail[] starts + * with it, skip a character */ + if (*var && ispathsep(*(dst-1)) && ispathsep(*tail)) + ++tail; + src = tail; + copy_char = FALSE; + } +#if defined(UNIX) || defined(OS2) + if (mustfree) + vim_free(var); +#endif + } + + if (copy_char) /* copy at least one char */ + { +#if defined(UNIX) || defined(OS2) + /* + * Recogize the start of a new name, for '~'. + */ + at_start = FALSE; +#endif + if (src[0] == '\\') + { + *dst++ = *src++; + --dstlen; + } +#if defined(UNIX) || defined(OS2) + else if (src[0] == ' ' || src[0] == ',') + at_start = TRUE; +#endif + *dst++ = *src++; + --dstlen; + } + } + *dst = NUL; +} + +/* + * Replace home directory by "~/" in each space or comma separated filename in + * 'src'. If anything fails (except when out of space) dst equals src. + */ + void +home_replace(buf, src, dst, dstlen) + BUF *buf; /* when not NULL, check for help files */ + char_u *src; /* input file name */ + char_u *dst; /* where to put the result */ + int dstlen; /* maximum length of the result */ +{ + size_t dirlen = 0, envlen = 0; + size_t len; + char_u *homedir_env; + char_u *p; + + if (src == NULL) + { + *dst = NUL; + return; + } + + /* + * If the file is a help file, remove the path completely. + */ + if (buf != NULL && buf->b_help) + { + STRCPY(dst, gettail(src)); + return; + } + + /* + * We check both the value of the $HOME environment variable and the + * "real" home directory. + */ + if (homedir != NULL) + dirlen = STRLEN(homedir); + homedir_env = vim_getenv((char_u *)"HOME"); + if (homedir_env != NULL) + envlen = STRLEN(homedir_env); + + src = skipwhite(src); + while (*src && dstlen > 0) + { + /* + * Here we are at the beginning of a filename. + * First, check to see if the beginning of the filename matches + * $HOME or the "real" home directory. Check that there is a '/' + * after the match (so that if e.g. the file is "/home/pieter/bla", + * and the home directory is "/home/piet", the file does not end up + * as "~er/bla" (which would seem to indicate the file "bla" in user + * er's home directory)). + */ + p = homedir; + len = dirlen; + for (;;) + { + if (len && fnamencmp(src, p, len) == 0 && (ispathsep(src[len]) || + src[len] == ',' || src[len] == ' ' || src[len] == NUL)) + { + src += len; + if (--dstlen > 0) + *dst++ = '~'; + /* + * If it's just the home directory, make it "~/". + */ + if (!ispathsep(src[0]) && --dstlen > 0) + *dst++ = '/'; + } + if (p == homedir_env) + break; + p = homedir_env; + len = envlen; + } + + /* skip to separator: space or comma */ + while (*src && *src != ',' && *src != ' ' && --dstlen > 0) + *dst++ = *src++; + /* skip separator */ + while ((*src == ' ' || *src == ',') && --dstlen > 0) + *dst++ = *src++; + } + /* if (dstlen == 0) out of space, what to do??? */ + + *dst = NUL; +} + +/* + * Like home_replace, store the replaced string in allocated memory. + * When something fails, NULL is returned. + */ + char_u * +home_replace_save(buf, src) + BUF *buf; /* when not NULL, check for help files */ + char_u *src; /* input file name */ +{ + char_u *dst; + unsigned len; + + if (src == NULL) /* just in case */ + len = 3; + else + len = STRLEN(src) + 3; /* extra space for "~/" and trailing NUL */ + dst = alloc(len); + if (dst != NULL) + home_replace(buf, src, dst, len); + return dst; +} + +/* + * Compare two file names and return: + * FPC_SAME if they both exist and are the same file. + * FPC_DIFF if they both exist and are different files. + * FPC_NOTX if they both don't exist. + * FPC_DIFFX if one of them doesn't exist. + * For the first name environment variables are expanded + */ + int +fullpathcmp(s1, s2) + char_u *s1, *s2; +{ +#ifdef UNIX + char_u buf1[MAXPATHL]; + struct stat st1, st2; + int r1, r2; + + expand_env(s1, buf1, MAXPATHL); + r1 = stat((char *)buf1, &st1); + r2 = stat((char *)s2, &st2); + if (r1 != 0 && r2 != 0) + return FPC_NOTX; + if (r1 != 0 || r2 != 0) + return FPC_DIFFX; + if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) + return FPC_SAME; + return FPC_DIFF; +#else + char_u *buf1 = NULL; + char_u *buf2 = NULL; + int retval = FPC_DIFF; + int r1, r2; + + if ((buf1 = alloc(MAXPATHL)) != NULL && (buf2 = alloc(MAXPATHL)) != NULL) + { + expand_env(s1, buf2, MAXPATHL); + /* + * If FullName() failed, the file probably doesn't exist. + */ + r1 = FullName(buf2, buf1, MAXPATHL, FALSE); + r2 = FullName(s2, buf2, MAXPATHL, FALSE); + if (r1 != OK && r2 != OK) + retval = FPC_NOTX; + else if (r1 != OK || r2 != OK) + retval = FPC_DIFFX; + else if (fnamecmp(buf1, buf2)) + retval = FPC_DIFF; + else + retval = FPC_SAME; + } + vim_free(buf1); + vim_free(buf2); + return retval; +#endif +} + +/* + * get the tail of a path: the file name. + */ + char_u * +gettail(fname) + char_u *fname; +{ + register char_u *p1, *p2; + + if (fname == NULL) + return (char_u *)""; + for (p1 = p2 = fname; *p2; ++p2) /* find last part of path */ + { + if (ispathsep(*p2)) + p1 = p2 + 1; + } + return p1; +} + +/* + * return TRUE if 'c' is a path separator. + */ + int +ispathsep(c) + int c; +{ +#ifdef UNIX + return (c == PATHSEP); /* UNIX has ':' inside file names */ +#else +# ifdef BACKSLASH_IN_FILENAME + return (c == ':' || c == PATHSEP || c == '\\'); +# else + return (c == ':' || c == PATHSEP); +# endif +#endif +} + +/* + * Concatenate filenames fname1 and fname2 into allocated memory. + * Only add a '/' when 'sep' is TRUE and it is neccesary. + */ + char_u * +concat_fnames(fname1, fname2, sep) + char_u *fname1; + char_u *fname2; + int sep; +{ + char_u *dest; + + dest = alloc((unsigned)(STRLEN(fname1) + STRLEN(fname2) + 2)); + if (dest != NULL) + { + STRCPY(dest, fname1); + if (sep && *dest && !ispathsep(*(dest + STRLEN(dest) - 1))) + STRCAT(dest, PATHSEPSTR); + STRCAT(dest, fname2); + } + return dest; +} + +/* + * FullName_save - Make an allocated copy of a full file name. + * Returns NULL when failed. + */ + char_u * +FullName_save(fname) + char_u *fname; +{ + char_u *buf; + char_u *new_fname = NULL; + + buf = alloc((unsigned)MAXPATHL); + if (buf != NULL) + { + if (FullName(fname, buf, MAXPATHL, FALSE) != FAIL) + new_fname = strsave(buf); + vim_free(buf); + } + return new_fname; +} + +#ifdef CINDENT + +/* + * Functions for C-indenting. + * Most of this originally comes from Eric Fischer. + */ +/* + * Below "XXX" means that this function may unlock the current line. + */ + +static int isdefault __ARGS((char_u *)); +static char_u *after_label __ARGS((char_u *l)); +static int get_indent_nolabel __ARGS((linenr_t lnum)); +static int skip_label __ARGS((linenr_t, char_u **pp, int ind_maxcomment)); +static int ispreproc __ARGS((char_u *)); +static int iscomment __ARGS((char_u *)); +static int commentorempty __ARGS((char_u *)); +static int isterminated __ARGS((char_u *)); +static int isfuncdecl __ARGS((char_u *)); +static char_u *skip_string __ARGS((char_u *p)); +static int isif __ARGS((char_u *)); +static int iselse __ARGS((char_u *)); +static int isdo __ARGS((char_u *)); +static int iswhileofdo __ARGS((char_u *, linenr_t, int)); +static FPOS *find_start_comment __ARGS((int ind_maxcomment)); +static FPOS *find_start_brace __ARGS((int)); +static FPOS *find_match_paren __ARGS((int, int)); +static int find_last_paren __ARGS((char_u *l)); +static int find_match __ARGS((int lookfor, linenr_t ourscope, + int ind_maxparen, int ind_maxcomment)); + +/* + * Recognize a label: "label:". + * Note: curwin->w_cursor must be where we are looking for the label. + */ + int +islabel(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + char_u *s; + + s = skipwhite(ml_get_curline()); + + /* + * Exclude "default" from labels, since it should be indented + * like a switch label. + */ + + if (isdefault(s)) + return FALSE; + + if (!isidchar(*s)) /* need at least one ID character */ + return FALSE; + + while (isidchar(*s)) + s++; + + s = skipwhite(s); + + /* "::" is not a label, it's C++ */ + if (*s == ':' && s[1] != ':') + { + /* + * Only accept a label if the previous line is terminated or is a case + * label. + */ + FPOS cursor_save; + FPOS *trypos; + char_u *line; + + cursor_save = curwin->w_cursor; + while (curwin->w_cursor.lnum > 1) + { + --curwin->w_cursor.lnum; + + /* + * If we're in a comment now, skip to the start of the comment. + */ + curwin->w_cursor.col = 0; + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) /* XXX */ + curwin->w_cursor = *trypos; + + line = ml_get_curline(); + if (ispreproc(line)) /* ignore #defines, #if, etc. */ + continue; + if (commentorempty(line)) + continue; + + curwin->w_cursor = cursor_save; + if (isterminated(line) || iscase(line)) + return TRUE; + return FALSE; + } + curwin->w_cursor = cursor_save; + return TRUE; /* label at start of file??? */ + } + return FALSE; +} + +/* + * Recognize a switch label: "case .*:" or "default:". + */ + int +iscase(s) + char_u *s; +{ + s = skipwhite(s); + if (STRNCMP(s, "case", 4) == 0 && !isidchar(s[4])) + { + for (s += 4; *s; ++s) + if (*s == ':') + { + if (s[1] == ':') /* skip over "::" for C++ */ + ++s; + else + return TRUE; + } + return FALSE; + } + + if (isdefault(s)) + return TRUE; + return FALSE; +} + +/* + * Recognize a "default" switch label. + */ + static int +isdefault(s) + char_u *s; +{ + return (STRNCMP(s, "default", 7) == 0 && + *(s = skipwhite(s + 7)) == ':' && + s[1] != ':'); +} + +/* + * Return a pointer to the first non-empty non-comment character after a ':'. + * Return NULL if not found. + * case 234: a = b; + * ^ + */ + static char_u * +after_label(l) + char_u *l; +{ + for ( ; *l; ++l) + if (*l == ':') + { + if (l[1] == ':') /* skip over "::" for C++ */ + ++l; + else + break; + } + if (*l == NUL) + return NULL; + l = skipwhite(l + 1); + if (commentorempty(l)) + return NULL; + return l; +} + +/* + * Get indent of line "lnum", skipping a label. + * Return 0 if there is nothing after the label. + */ + static int +get_indent_nolabel(lnum) /* XXX */ + linenr_t lnum; +{ + char_u *l; + FPOS fp; + colnr_t col; + char_u *p; + + l = ml_get(lnum); + p = after_label(l); + if (p == NULL) + return 0; + + fp.col = p - l; + fp.lnum = lnum; + getvcol(curwin, &fp, &col, NULL, NULL); + return (int)col; +} + +/* + * Find indent for line "lnum", ignoring any case or jump label. + * Also return a pointer to the text (after the label). + * label: if (asdf && asdfasdf) + * ^ + */ + static int +skip_label(lnum, pp, ind_maxcomment) + linenr_t lnum; + char_u **pp; + int ind_maxcomment; +{ + char_u *l; + int amount; + FPOS cursor_save; + + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + l = ml_get_curline(); + if (iscase(l) || islabel(ind_maxcomment)) /* XXX */ + { + amount = get_indent_nolabel(lnum); + l = after_label(ml_get_curline()); + if (l == NULL) /* just in case */ + l = ml_get_curline(); + } + else + { + amount = get_indent(); + l = ml_get_curline(); + } + *pp = l; + + curwin->w_cursor = cursor_save; + return amount; +} + +/* + * Recognize a preprocessor statement: Any line that starts with '#'. + */ + static int +ispreproc(s) + char_u *s; +{ + s = skipwhite(s); + if (*s == '#') + return TRUE; + return 0; +} + +/* + * Recognize the start of a C or C++ comment. + */ + static int +iscomment(p) + char_u *p; +{ + return (p[0] == '/' && (p[1] == '*' || p[1] == '/')); +} + +/* + * Recognize an empty or comment line. + */ + static int +commentorempty(s) + char_u *s; +{ + s = skipwhite(s); + if (*s == NUL || iscomment(s)) + return TRUE; + return FALSE; +} + +/* + * Recognize a line that starts with '{' or '}', or ends with ';' or '}'. + * Also consider a line terminated if it ends in ','. This is not 100% + * correct, but this mostly means we are in initializations and then it's OK. + */ + static int +isterminated(s) + char_u *s; +{ + s = skipwhite(s); + + if (*s == '{' || *s == '}') + return TRUE; + + while (*s) + { + if (iscomment(s)) /* at start of comment ignore rest of line */ + return FALSE; + s = skip_string(s); + if ((*s == ';' || *s == '{' || *s == ',') && commentorempty(s + 1)) + return TRUE; + s++; + } + return FALSE; +} + +/* + * Recognize the basic picture of a function declaration -- it needs to + * have an open paren somewhere and a close paren at the end of the line and + * no semicolons anywhere. + */ + static int +isfuncdecl(s) + char_u *s; +{ + while (*s && *s != '(' && *s != ';') + if (iscomment(s++)) + return FALSE; /* comment before () ??? */ + if (*s != '(') + return FALSE; /* ';' before any () or no '(' */ + + while (*s && *s != ';') + { + if (*s == ')' && commentorempty(s + 1)) + return TRUE; + if (iscomment(s++)) + return FALSE; /* comment between ( and ) ??? */ + } + return FALSE; +} + +/* + * Skip over a "string" and a 'c' character. + */ + static char_u * +skip_string(p) + char_u *p; +{ + int i; + + /* + * We loop, because strings may be concatenated: "date""time". + */ + for ( ; ; ++p) + { + if (p[0] == '\'') /* 'c' or '\n' or '\000' */ + { + if (!p[1]) /* ' at end of line */ + break; + i = 2; + if (p[1] == '\\') /* '\n' or '\000' */ + { + ++i; + while (isdigit(p[i - 1])) /* '\000' */ + ++i; + } + if (p[i] == '\'') /* check for trailing ' */ + { + p += i; + continue; + } + } + else if (p[0] == '"') /* start of string */ + { + for (++p; p[0]; ++p) + { + if (p[0] == '\\' && p[1]) + ++p; + else if (p[0] == '"') /* end of string */ + break; + } + continue; + } + break; /* no string found */ + } + if (!*p) + --p; /* backup from NUL */ + return p; +} + + static int +isif(p) + char_u *p; +{ + return (STRNCMP(p, "if", 2) == 0 && !isidchar(p[2])); +} + + static int +iselse(p) + char_u *p; +{ + return (STRNCMP(p, "else", 4) == 0 && !isidchar(p[4])); +} + + static int +isdo(p) + char_u *p; +{ + return (STRNCMP(p, "do", 2) == 0 && !isidchar(p[2])); +} + +/* + * Check if this is a "while" that should have a matching "do". + * We only accept a "while (condition) ;", with only white space between the + * ')' and ';'. The condition may be spread over several lines. + */ + static int +iswhileofdo(p, lnum, ind_maxparen) /* XXX */ + char_u *p; + linenr_t lnum; + int ind_maxparen; +{ + FPOS cursor_save; + FPOS *trypos; + int retval = FALSE; + + p = skipwhite(p); + if (STRNCMP(p, "while", 5) == 0 && !isidchar(p[5])) + { + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + if ((trypos = findmatchlimit(0, 0, ind_maxparen)) != NULL) + { + p = ml_get_pos(trypos) + 1; + p = skipwhite(p); + if (*p == ';') + retval = TRUE; + } + curwin->w_cursor = cursor_save; + } + return retval; +} + +/* + * Find the start of a comment, not knowing if we are in a comment right now. + * Search starts at w_cursor.lnum and goes backwards. + */ + static FPOS * +find_start_comment(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + FPOS *pos; + char_u *line; + char_u *p; + + if ((pos = findmatchlimit('*', FM_BACKWARD, ind_maxcomment)) == NULL) + return NULL; + + /* + * Check if the comment start we found is inside a string. + */ + line = ml_get(pos->lnum); + for (p = line; *p && (unsigned)(p - line) < pos->col; ++p) + p = skip_string(p); + if ((unsigned)(p - line) > pos->col) + return NULL; + return pos; +} + +/* + * Find the '{' at the start of the block we are in. + * Return NULL of no match found. + * Ignore a '{' that is in a comment, makes indenting the next three lines + * work. */ +/* foo() */ +/* { */ +/* } */ + + static FPOS * +find_start_brace(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + FPOS cursor_save; + FPOS *trypos; + FPOS *pos; + static FPOS pos_copy; + + cursor_save = curwin->w_cursor; + while ((trypos = findmatchlimit('{', FM_BLOCKSTOP, 0)) != NULL) + { + pos_copy = *trypos; /* copy FPOS, next findmatch will change it */ + trypos = &pos_copy; + curwin->w_cursor = *trypos; + pos = NULL; + if (!iscomment(skipwhite(ml_get(trypos->lnum))) && + (pos = find_start_comment(ind_maxcomment)) == NULL) /* XXX */ + break; + if (pos != NULL) + curwin->w_cursor.lnum = pos->lnum; + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Find the matching '(', failing if it is in a comment. + * Return NULL of no match found. + */ + static FPOS * +find_match_paren(ind_maxparen, ind_maxcomment) /* XXX */ + int ind_maxparen; + int ind_maxcomment; +{ + FPOS cursor_save; + FPOS *trypos; + static FPOS pos_copy; + + cursor_save = curwin->w_cursor; + if ((trypos = findmatchlimit('(', 0, ind_maxparen)) != NULL) + { + if (iscomment(skipwhite(ml_get(trypos->lnum)))) + trypos = NULL; + else + { + pos_copy = *trypos; /* copy trypos, findmatch will change it */ + trypos = &pos_copy; + curwin->w_cursor = *trypos; + if (find_start_comment(ind_maxcomment) != NULL) /* XXX */ + trypos = NULL; + } + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Set w_cursor.col to the column number of the last ')' in line "l". + */ + static int +find_last_paren(l) + char_u *l; +{ + int i; + int retval = FALSE; + + curwin->w_cursor.col = 0; /* default is start of line */ + + for (i = 0; l[i]; i++) + { + i = skip_string(l + i) - l; /* ignore parens in quotes */ + if (l[i] == ')') + { + curwin->w_cursor.col = i; + retval = TRUE; + } + } + return retval; +} + + int +get_c_indent() +{ + /* + * spaces from a block's opening brace the prevailing indent for that + * block should be + */ + int ind_level = curbuf->b_p_sw; + + /* + * spaces from the edge of the line an open brace that's at the end of a + * line is imagined to be. + */ + int ind_open_imag = 0; + + /* + * spaces from the prevailing indent for a line that is not precededof by + * an opening brace. + */ + int ind_no_brace = 0; + + /* + * column where the first { of a function should be located + */ + int ind_first_open = 0; + + /* + * spaces from the prevailing indent a leftmost open brace should be + * located + */ + int ind_open_extra = 0; + + /* + * spaces from the matching open brace (real location for one at the left + * edge; imaginary location from one that ends a line) the matching close + * brace should be located + */ + int ind_close_extra = 0; + + /* + * spaces from the edge of the line an open brace sitting in the leftmost + * column is imagined to be + */ + int ind_open_left_imag = 0; + + /* + * spaces from the switch() indent a "case xx" label should be located + */ + int ind_case = curbuf->b_p_sw; + + /* + * spaces from the "case xx:" code after a switch() should be located + */ + int ind_case_code = curbuf->b_p_sw; + + /* + * amount K&R-style parameters should be indented + */ + int ind_param = curbuf->b_p_sw; + + /* + * amount a function type spec should be indented + */ + int ind_func_type = curbuf->b_p_sw; + + /* + * additional spaces beyond the prevailing indent a continuation line + * should be located + */ + int ind_continuation = curbuf->b_p_sw; + + /* + * spaces from the indent of the line with an unclosed parentheses + */ + int ind_unclosed = curbuf->b_p_sw * 2; + + /* + * spaces from the comment opener when there is nothing after it. + */ + int ind_in_comment = 3; + + /* + * max lines to search for an open paren + */ + int ind_maxparen = 20; + + /* + * max lines to search for an open comment + */ + int ind_maxcomment = 30; + + FPOS cur_curpos; + int amount; + int scope_amount; + int cur_amount; + colnr_t col; + char_u *theline; + char_u *linecopy; + FPOS *trypos; + FPOS our_paren_pos; + char_u *start; + int start_brace; +#define BRACE_IN_COL0 1 /* '{' is in comumn 0 */ +#define BRACE_AT_START 2 /* '{' is at start of line */ +#define BRACE_AT_END 3 /* '{' is at end of line */ + linenr_t ourscope; + char_u *l; + char_u *look; + int lookfor; +#define LOOKFOR_IF 1 +#define LOOKFOR_DO 2 +#define LOOKFOR_CASE 3 +#define LOOKFOR_ANY 4 +#define LOOKFOR_TERM 5 +#define LOOKFOR_UNTERM 6 + int whilelevel; + linenr_t lnum; + char_u *options; + int fraction = 0; /* init for GCC */ + int divider; + int n; + + for (options = curbuf->b_p_cino; *options; ) + { + l = options++; + if (*options == '-') + ++options; + n = getdigits(&options); + divider = 0; + if (*options == '.') /* ".5s" means a fraction */ + { + fraction = atol((char *)++options); + while (isdigit(*options)) + { + ++options; + if (divider) + divider *= 10; + else + divider = 10; + } + } + if (*options == 's') /* "2s" means two times 'shiftwidth' */ + { + if (n == 0 && fraction == 0) + n = curbuf->b_p_sw; /* just "s" is one 'shiftwidth' */ + else + { + n *= curbuf->b_p_sw; + if (divider) + n += (curbuf->b_p_sw * fraction + divider / 2) / divider; + } + ++options; + } + if (l[1] == '-') + n = -n; + switch (*l) + { + case '>': ind_level = n; break; + case 'e': ind_open_imag = n; break; + case 'n': ind_no_brace = n; break; + case 'f': ind_first_open = n; break; + case '{': ind_open_extra = n; break; + case '}': ind_close_extra = n; break; + case '^': ind_open_left_imag = n; break; + case ':': ind_case = n; break; + case '=': ind_case_code = n; break; + case 'p': ind_param = n; break; + case 't': ind_func_type = n; break; + case 'c': ind_in_comment = n; break; + case '+': ind_continuation = n; break; + case '(': ind_unclosed = n; break; + case ')': ind_maxparen = n; break; + case '*': ind_maxcomment = n; break; + } + } + + /* remember where the cursor was when we started */ + + cur_curpos = curwin->w_cursor; + + /* get the current contents of the line. + * This is required, because onle the most recent line obtained with + * ml_get is valid! */ + + linecopy = strsave(ml_get(cur_curpos.lnum)); + if (linecopy == NULL) + return 0; + + /* + * In insert mode and the cursor is on a ')' trunctate the line at the + * cursor position. We don't want to line up with the matching '(' when + * inserting new stuff. + */ + if ((State & INSERT) && linecopy[curwin->w_cursor.col] == ')') + linecopy[curwin->w_cursor.col] = NUL; + + theline = skipwhite(linecopy); + + /* move the cursor to the start of the line */ + + curwin->w_cursor.col = 0; + + /* + * #defines and so on always go at the left when included in 'cinkeys'. + */ + if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', TRUE))) + { + amount = 0; + } + + /* + * Is it a non-case label? Then that goes at the left margin too. + */ + else if (islabel(ind_maxcomment)) /* XXX */ + { + amount = 0; + } + + /* + * If we're inside a comment and not looking at the start of the + * comment... + */ + else if (!iscomment(theline) && + (trypos = find_start_comment(ind_maxcomment)) != NULL) /* XXX */ + { + + /* find how indented the line beginning the comment is */ + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + + /* if our line starts with an asterisk, line up with the + * asterisk in the comment opener; otherwise, line up + * with the first character of the comment text. + */ + if (theline[0] == '*') + { + amount += 1; + } + else + { + /* + * If we are more than one line away from the comment opener, take + * the indent of the previous non-empty line. + * If we are just below the comment opener and there are any + * white characters after it line up with the text after it. + * up with them; otherwise, just use a single space. + */ + amount = -1; + for (lnum = cur_curpos.lnum - 1; lnum > trypos->lnum; --lnum) + { + if (linewhite(lnum)) /* skip blank lines */ + continue; + amount = get_indent_lnum(lnum); /* XXX */ + break; + } + if (amount == -1) /* use the comment opener */ + { + start = ml_get(trypos->lnum); + look = start + trypos->col + 2; /* skip / and * */ + if (*look) /* if something after it */ + trypos->col = skipwhite(look) - start; + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + if (!*look) + amount += ind_in_comment; + } + } + } + + /* + * Are we inside parentheses? + */ /* XXX */ + else if ((trypos = find_match_paren(ind_maxparen, ind_maxcomment)) != NULL) + { + /* + * If the matching paren is more than one line away, use the indent of + * a previous non-empty line that matches the same paren. + */ + amount = -1; + our_paren_pos = *trypos; + if (theline[0] != ')') + { + for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) + { + l = skipwhite(ml_get(lnum)); + if (commentorempty(l)) /* skip comment lines */ + continue; + if (ispreproc(l)) /* ignore #defines, #if, etc. */ + continue; + curwin->w_cursor.lnum = lnum; + /* XXX */ + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL && + trypos->lnum == our_paren_pos.lnum && + trypos->col == our_paren_pos.col) + { + amount = get_indent_lnum(lnum); /* XXX */ + break; + } + } + } + + /* + * Line up with line where the matching paren is. + * If the line starts with a '(' or the indent for unclosed + * parentheses is zero, line up with the unclosed parentheses. + */ + if (amount == -1) + { + amount = skip_label(our_paren_pos.lnum, &look, ind_maxcomment); + if (theline[0] == ')' || ind_unclosed == 0 || + *skipwhite(look) == '(') + { + + /* + * If we're looking at a close paren, line up right there; + * otherwise, line up with the next non-white character. + */ + if (theline[0] != ')') + { + col = our_paren_pos.col + 1; + look = ml_get(our_paren_pos.lnum); + while (vim_iswhite(look[col])) + col++; + if (look[col] != NUL) /* In case of trailing space */ + our_paren_pos.col = col; + else + our_paren_pos.col++; + } + + /* + * Find how indented the paren is, or the character after it if + * we did the above "if". + */ + getvcol(curwin, &our_paren_pos, &col, NULL, NULL); + amount = col; + } + else + amount += ind_unclosed; + } + } + + /* + * Are we at least inside braces, then? + */ + else if ((trypos = find_start_brace(ind_maxcomment)) != NULL) /* XXX */ + { + ourscope = trypos->lnum; + start = ml_get(ourscope); + + /* + * Now figure out how indented the line is in general. + * If the brace was at the start of the line, we use that; + * otherwise, check out the indentation of the line as + * a whole and then add the "imaginary indent" to that. + */ + look = skipwhite(start); + if (*look == '{') + { + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + if (*start == '{') + start_brace = BRACE_IN_COL0; + else + start_brace = BRACE_AT_START; + } + else + { + /* + * that opening brace might have been on a continuation + * line. if so, find the start of the line. + */ + curwin->w_cursor.lnum = ourscope; + + /* + * position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + lnum = ourscope; + if (find_last_paren(start) && + (trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + lnum = trypos->lnum; + + /* + * It could have been something like + * case 1: if (asdf && + * ldfd) { + * } + */ + amount = skip_label(lnum, &l, ind_maxcomment); + + start_brace = BRACE_AT_END; + } + + /* + * if we're looking at a closing brace, that's where + * we want to be. otherwise, add the amount of room + * that an indent is supposed to be. + */ + if (theline[0] == '}') + { + /* + * they may want closing braces to line up with something + * other than the open brace. indulge them, if so. + */ + amount += ind_close_extra; + } + else + { + /* + * If we're looking at an "else", try to find an "if" + * to match it with. + * If we're looking at a "while", try to find a "do" + * to match it with. + */ + lookfor = 0; + if (iselse(theline)) + lookfor = LOOKFOR_IF; + else if (iswhileofdo(theline, cur_curpos.lnum, ind_maxparen)) + /* XXX */ + lookfor = LOOKFOR_DO; + if (lookfor) + { + curwin->w_cursor.lnum = cur_curpos.lnum; + if (find_match(lookfor, ourscope, ind_maxparen, + ind_maxcomment) == OK) + { + amount = get_indent(); /* XXX */ + goto theend; + } + } + + /* + * We get here if we are not on an "while-of-do" or "else" (or + * failed to find a matching "if"). + * Search backwards for something to line up with. + * First set amount for when we don't find anything. + */ + + /* + * if the '{' is _really_ at the left margin, use the imaginary + * location of a left-margin brace. Otherwise, correct the + * location for ind_open_extra. + */ + + if (start_brace == BRACE_IN_COL0) /* '{' is in column 0 */ + { + amount = ind_open_left_imag; + } + else + { + if (start_brace == BRACE_AT_END) /* '{' is at end of line */ + amount += ind_open_imag; + else + { + amount -= ind_open_extra; + if (amount < 0) + amount = 0; + } + } + + if (iscase(theline)) /* it's a switch() label */ + { + lookfor = LOOKFOR_CASE; /* find a previous switch() label */ + amount += ind_case; + } + else + { + lookfor = LOOKFOR_ANY; + amount += ind_level; /* ind_level from start of block */ + } + scope_amount = amount; + whilelevel = 0; + + /* + * Search backwards. If we find something we recognize, line up + * with that. + * + * if we're looking at an open brace, indent + * the usual amount relative to the conditional + * that opens the block. + */ + curwin->w_cursor = cur_curpos; + for (;;) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + /* + * If we went all the way back to the start of our scope, line + * up with it. + */ + if (curwin->w_cursor.lnum <= ourscope) + { + if (lookfor == LOOKFOR_UNTERM) + amount += ind_continuation; + else if (lookfor != LOOKFOR_TERM) + amount = scope_amount; + break; + } + + /* + * If we're in a comment now, skip to the start of the comment. + */ /* XXX */ + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + continue; + } + + l = ml_get_curline(); + + /* + * If this is a switch() label, may line up relative to that. + */ + if (iscase(l)) + { + /* + * case xx: + * c = 99 + <- this indent plus continuation + *-> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * case xx: <- line up with this case + * x = 333; + * case yy: + */ + if (lookfor == LOOKFOR_CASE) + { + /* + * Check that this case label is not for another + * switch() + */ /* XXX */ + if ((trypos = find_start_brace(ind_maxcomment)) == + NULL || trypos->lnum == ourscope) + { + amount = get_indent(); /* XXX */ + break; + } + continue; + } + + n = get_indent_nolabel(curwin->w_cursor.lnum); /* XXX */ + + /* + * case xx: if (cond) <- line up with this if + * y = y + 1; + * -> s = 99; + * + * case xx: + * if (cond) <- line up with this line + * y = y + 1; + * -> s = 99; + */ + if (lookfor == LOOKFOR_TERM) + { + if (n) + amount = n; + break; + } + + /* + * case xx: x = x + 1; <- line up with this x + * -> y = y + 1; + * + * case xx: if (cond) <- line up with this if + * -> y = y + 1; + */ + if (n) + { + amount = n; + l = after_label(ml_get_curline()); + if (l != NULL && is_cinword(l)) + amount += ind_level + ind_no_brace; + break; + } + + /* + * Try to get the indent of a statement before the + * switch label. If nothing is found, line up relative + * to the switch label. + * break; <- may line up with this line + * case xx: + * -> y = 1; + */ + scope_amount = get_indent() + ind_case_code; /* XXX */ + lookfor = LOOKFOR_ANY; + continue; + } + + /* + * Looking for a switch() label, ignore other lines. + */ + if (lookfor == LOOKFOR_CASE) + continue; + + /* + * Ignore jump labels with nothing after them. + */ + if (islabel(ind_maxcomment)) + { + l = after_label(ml_get_curline()); + if (l == NULL || commentorempty(l)) + continue; + } + + /* + * Ignore #defines, #if, etc. + * Ignore comment and empty lines. + * (need to get the line again, islabel() may have unlocked it) + */ + l = ml_get_curline(); + if (ispreproc(l) || commentorempty(l)) + continue; + + /* + * What happens next depends on the line being terminated. + */ + if (!isterminated(l)) + { + /* + * if we're in the middle of a paren thing, + * go back to the line that starts it so + * we can get the right prevailing indent + * if ( foo && + * bar ) + */ + /* + * position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + (void)find_last_paren(l); + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + { + /* + * Check if we are on a case label now. This is + * handled above. + * case xx: if ( asdf && + * asdf) + */ + curwin->w_cursor.lnum = trypos->lnum; + l = ml_get_curline(); + if (iscase(l)) + { + ++curwin->w_cursor.lnum; + continue; + } + } + + /* + * Get indent and pointer to text for current line, + * ignoring any jump label. XXX + */ + cur_amount = skip_label(curwin->w_cursor.lnum, + &l, ind_maxcomment); + + /* + * If this is just above the line we are indenting, and it + * starts with a '{', line it up with this line. + * while (not) + * -> { + * } + */ + if (lookfor != LOOKFOR_TERM && theline[0] == '{') + { + amount = cur_amount + ind_open_extra; + break; + } + + /* + * Check if we are after an "if", "while", etc. + */ + if (is_cinword(l)) + { + /* + * Found an unterminated line after an if (), line up + * with the last one. + * if (cond) + * 100 + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * If this is just above the line we are indenting, we + * are finished. + * while (not) + * -> here; + * Otherwise this indent can be used when the line + * before this is terminated. + * yyy; + * if (stat) + * while (not) + * xxx; + * -> here; + */ + amount = cur_amount; + if (lookfor != LOOKFOR_TERM) + { + amount += ind_level + ind_no_brace; + break; + } + + /* + * Special trick: when expecting the while () after a + * do, line up with the while() + * do + * x = 1; + * -> here + */ + l = skipwhite(ml_get_curline()); + if (isdo(l)) + { + if (whilelevel == 0) + break; + --whilelevel; + } + + /* + * When searching for a terminated line, don't use the + * one between the "if" and the "else". + */ + if (iselse(l)) + { + if (find_match(LOOKFOR_IF, ourscope, + ind_maxparen, ind_maxcomment) == FAIL) + break; + } + } + + /* + * If we're below an unterminated line that is not an + * "if" or something, we may line up with this line or + * add someting for a continuation line, depending on + * the line before this one. + */ + else + { + /* + * Found two unterminated lines on a row, line up with + * the last one. + * c = 99 + + * 100 + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + break; + + /* + * Found first unterminated line on a row, may line up + * with this line, remember its indent + * 100 + + * -> here; + */ + amount = cur_amount; + if (lookfor != LOOKFOR_TERM) + lookfor = LOOKFOR_UNTERM; + } + } + + /* + * Check if we are after a while (cond); + * If so: Ignore the matching "do". + */ + /* XXX */ + else if (iswhileofdo(l, curwin->w_cursor.lnum, ind_maxparen)) + { + /* + * Found an unterminated line after a while ();, line up + * with the last one. + * while (cond); + * 100 + <- line up with this one + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + if (whilelevel == 0) + { + lookfor = LOOKFOR_TERM; + amount = get_indent(); /* XXX */ + if (theline[0] == '{') + amount += ind_open_extra; + } + ++whilelevel; + } + + /* + * We are after a "normal" statement. + * If we had another statement we can stop now and use the + * indent of that other statement. + * Otherwise the indent of the current statement may be used, + * search backwards for the next "normal" statement. + */ + else + { + /* + * Found a terminated line above an unterminated line. Add + * the amount for a continuation line. + * x = 1; + * y = foo + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * Found a terminated line above a terminated line or "if" + * etc. line. Use the amount of the line below us. + * x = 1; x = 1; + * if (asdf) y = 2; + * while (asdf) ->here; + * here; + * ->foo; + */ + if (lookfor == LOOKFOR_TERM) + { + if (whilelevel == 0) + break; + } + + /* + * First line above the one we're indenting is terminated. + * To know what needs to be done look further backward for + * a terminated line. + */ + else + { + /* + * position the cursor over the rightmost paren, so + * that matching it will take us back to the start of + * the line. Helps for: + * func(asdr, + * asdfasdf); + * here; + */ + l = ml_get_curline(); + if (find_last_paren(l) && + (trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + { + /* + * Check if we are on a case label now. This is + * handled above. + * case xx: if ( asdf && + * asdf) + */ + curwin->w_cursor.lnum = trypos->lnum; + l = ml_get_curline(); + if (iscase(l)) + { + ++curwin->w_cursor.lnum; + continue; + } + } + + /* + * Get indent and pointer to text for current line, + * ignoring any jump label. + */ + amount = skip_label(curwin->w_cursor.lnum, + &l, ind_maxcomment); + + if (theline[0] == '{') + amount += ind_open_extra; + lookfor = LOOKFOR_TERM; + + /* + * If we're at the end of a block, skip to the start of + * that block. + */ + if (*skipwhite(l) == '}' && + (trypos = find_start_brace(ind_maxcomment)) + != NULL) /* XXX */ + curwin->w_cursor.lnum = trypos->lnum; + } + } + } + } + } + + /* + * ok -- we're not inside any sort of structure at all! + * + * this means we're at the top level, and everything should + * basically just match where the previous line is, except + * for the lines immediately following a function declaration, + * which are K&R-style parameters and need to be indented. + */ + else + { + /* + * if our line starts with an open brace, forget about any + * prevailing indent and make sure it looks like the start + * of a function + */ + + if (theline[0] == '{') + { + amount = ind_first_open; + } + + /* + * If the NEXT line is a function declaration, the current + * line needs to be indented as a function type spec. + * Don't do this if the current line looks like a comment. + */ + else if (cur_curpos.lnum < curbuf->b_ml.ml_line_count && + !commentorempty(theline) && + isfuncdecl(ml_get(cur_curpos.lnum + 1))) + { + amount = ind_func_type; + } + else + { + amount = 0; + curwin->w_cursor = cur_curpos; + + /* search backwards until we find something we recognize */ + + while (curwin->w_cursor.lnum > 1) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + l = ml_get_curline(); + + /* + * If we're in a comment now, skip to the start of the comment. + */ /* XXX */ + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + continue; + } + + /* + * If the line looks like a function declaration, and we're + * not in a comment, put it the left margin. + */ + if (isfuncdecl(theline)) + break; + + /* + * Skip preprocessor directives and blank lines. + */ + if (ispreproc(l)) + continue; + + if (commentorempty(l)) + continue; + + /* + * If the PREVIOUS line is a function declaration, the current + * line (and the ones that follow) needs to be indented as + * parameters. + */ + if (isfuncdecl(l)) + { + amount = ind_param; + break; + } + + /* + * Doesn't look like anything interesting -- so just + * use the indent of this line. + * + * Position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + find_last_paren(l); + + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + curwin->w_cursor.lnum = trypos->lnum; + amount = get_indent(); /* XXX */ + break; + } + } + } + +theend: + /* put the cursor back where it belongs */ + curwin->w_cursor = cur_curpos; + + vim_free(linecopy); + + if (amount < 0) + return 0; + return amount; +} + + static int +find_match(lookfor, ourscope, ind_maxparen, ind_maxcomment) + int lookfor; + linenr_t ourscope; + int ind_maxparen; + int ind_maxcomment; +{ + char_u *look; + FPOS *theirscope; + char_u *mightbeif; + int elselevel; + int whilelevel; + + if (lookfor == LOOKFOR_IF) + { + elselevel = 1; + whilelevel = 0; + } + else + { + elselevel = 0; + whilelevel = 1; + } + + curwin->w_cursor.col = 0; + + while (curwin->w_cursor.lnum > ourscope + 1) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + look = skipwhite(ml_get_curline()); + if (iselse(look) || isif(look) || isdo(look) || + iswhileofdo(look, curwin->w_cursor.lnum, ind_maxparen)) /* XXX */ + { + /* + * if we've gone outside the braces entirely, + * we must be out of scope... + */ + theirscope = find_start_brace(ind_maxcomment); /* XXX */ + if (theirscope == NULL) + break; + + /* + * and if the brace enclosing this is further + * back than the one enclosing the else, we're + * out of luck too. + */ + if (theirscope->lnum < ourscope) + break; + + /* + * and if they're enclosed in a *deeper* brace, + * then we can ignore it because it's in a + * different scope... + */ + if (theirscope->lnum > ourscope) + continue; + + /* + * if it was an "else" (that's not an "else if") + * then we need to go back to another if, so + * increment elselevel + */ + look = skipwhite(ml_get_curline()); + if (iselse(look)) + { + mightbeif = skipwhite(look + 4); + if (!isif(mightbeif)) + ++elselevel; + continue; + } + + /* + * if it was a "while" then we need to go back to + * another "do", so increment whilelevel. + */ + if (iswhileofdo(look, curwin->w_cursor.lnum, ind_maxparen))/* XXX */ + { + ++whilelevel; + continue; + } + + /* If it's an "if" decrement elselevel */ + look = skipwhite(ml_get_curline()); + if (isif(look)) + { + elselevel--; + /* + * When looking for an "if" ignore "while"s that + * get in the way. + */ + if (elselevel == 0 && lookfor == LOOKFOR_IF) + whilelevel = 0; + } + + /* If it's a "do" decrement whilelevel */ + if (isdo(look)) + whilelevel--; + + /* + * if we've used up all the elses, then + * this must be the if that we want! + * match the indent level of that if. + */ + if (elselevel <= 0 && whilelevel <= 0) + { + return OK; + } + } + } + return FAIL; +} + +#endif /* CINDENT */ + +#ifdef LISPINDENT + int +get_lisp_indent() +{ + FPOS *pos, realpos; + long amount = 0; + char_u *that; + colnr_t col; + colnr_t maybe; + colnr_t firsttry; + + + realpos = curwin->w_cursor; + curwin->w_cursor.col = 0; + + if ((pos = findmatch('(')) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = pos->col; + col = pos->col; + + that = ml_get_curline(); + maybe = get_indent(); /* XXX */ + + if (maybe == 0) + amount = 2; + else + { + while (*that && col) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + col--; + that++; + } + + that++; + amount++; + firsttry = amount; + + /* + * Go to the start of the second word. + * If there is no second word, go back to firsttry. + * Also stop at a '('. + */ + + while (vim_iswhite(*that)) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + while (*that && !vim_iswhite(*that) && *that != '(') + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + while (vim_iswhite(*that)) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + if (! *that) + amount = firsttry; + } + } + else /* no matching '(' found, use indent of previous non-empty line */ + { + while (curwin->w_cursor.lnum > 1) + { + --curwin->w_cursor.lnum; + if (!linewhite(curwin->w_cursor.lnum)) + break; + } + amount = get_indent(); /* XXX */ + } + + curwin->w_cursor = realpos; + + if (amount < 0) + amount = 0; + return (int)amount; +} +#endif /* LISPINDENT */ + +#if defined(UNIX) || defined(WIN32) || defined(__EMX__) +/* + * Preserve files and exit. + * When called IObuff must contain a message. + */ + void +preserve_exit() +{ + BUF *buf; + +#ifdef USE_GUI + if (gui.in_use) + { + gui.dying = TRUE; + trash_output_buf(); /* trash any pending output */ + } + else +#endif + { + windgoto((int)Rows - 1, 0); + + /* + * Switch terminal mode back now, so these messages end up on the + * "normal" screen (if there are two screens). + */ + settmode(0); +#ifdef WIN32 + if (can_end_termcap_mode(FALSE) == TRUE) +#endif + stoptermcap(); + flushbuf(); + } + + outstr(IObuff); + screen_start(); /* don't know where cursor is now */ + flushbuf(); + + ml_close_notmod(); /* close all not-modified buffers */ + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) + { + OUTSTR("Vim: preserving files...\n"); + screen_start(); /* don't know where cursor is now */ + flushbuf(); + ml_sync_all(FALSE, FALSE); /* preserve all swap files */ + break; + } + } + + ml_close_all(FALSE); /* close all memfiles, without deleting */ + + OUTSTR("Vim: Finished.\n"); + + getout(1); +} +#endif /* defined(UNIX) || defined(WIN32) || defined(__EMX__) */ + +/* + * return TRUE if "fname" exists. + */ + int +vim_fexists(fname) + char_u *fname; +{ + struct stat st; + + if (stat((char *)fname, &st)) + return FALSE; + return TRUE; +} + +/* + * Check for CTRL-C pressed, but only once in a while. + * Should be used instead of mch_breakcheck() for functions that check for + * each line in the file. Calling mch_breakcheck() each time takes too much + * time, because it can be a system call. + */ + +#ifndef BREAKCHECK_SKIP +# define BREAKCHECK_SKIP 32 +#endif + + void +line_breakcheck() +{ + static int count = 0; + + if (++count == BREAKCHECK_SKIP) + { + count = 0; + mch_breakcheck(); + } +} + +/* + * Free the list of files returned by ExpandWildCards() or other expansion + * functions. + */ + void +FreeWild(num, file) + int num; + char_u **file; +{ + if (file == NULL || num == 0) + return; +#if defined(__EMX__) && defined(__ALWAYS_HAS_TRAILING_NULL_POINTER) /* XXX */ + /* + * Is this still OK for when other functions thatn ExpandWildCards() have + * been used??? + */ + _fnexplodefree((char **)file); +#else + while (num--) + vim_free(file[num]); + vim_free(file); +#endif +} + |