/* vi:ts=4:sw=4 * * VIM - Vi IMproved * * Code Contributions By: Bram Moolenaar mool@oce.nl * Tim Thompson twitch!tjt * Tony Andrews onecom!wldrdg!tony * G. R. (Fred) Walter watmath!watcgl!grwalter */ /* * misccmds.c: functions that didn't seem to fit elsewhere */ #include "vim.h" #include "globals.h" #include "proto.h" #include "param.h" #ifdef JP #include "jp.h" #endif static char *(si_tab[]) = {"if", "else", "while", "for", "do"}; /* * count the size of the indent in the current line */ int get_indent() { register char *ptr; register int count = 0; for (ptr = nr2ptr(Curpos.lnum); *ptr; ++ptr) { if (*ptr == TAB) /* count a tab for what it is worth */ count += (int)p_ts - (count % (int)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, delete) register int size; int delete; { int oldstate = State; State = INSERT; /* don't want REPLACE for State */ Curpos.col = 0; if (delete) { while (isspace(gcharCurpos())) /* delete old indent */ delchar(FALSE); } if (!p_et) /* if 'expandtab' is set, don't use TABs */ while (size >= (int)p_ts) { inschar(TAB); size -= (int)p_ts; } while (size) { inschar(' '); --size; } State = oldstate; script_winsize_pp(); } /* * Opencmd * * Add a blank line below or above the current line. */ int Opencmd(dir, redraw, delspaces) int dir; int redraw; int delspaces; { char *l; char *ptr, *pp; FPOS oldCurpos; /* old cursor position */ int newcol = 0; /* new cursor column */ int newindent = 0; /* auto-indent of the new line */ int extra = 0; /* number of bytes to be copied from current line */ int n; int truncate = FALSE; /* truncate current line afterwards */ int no_si = FALSE; /* reset did_si afterwards */ ptr = nr2ptr(Curpos.lnum); u_clearline(); /* cannot do "U" command when adding lines */ did_si = FALSE; if (p_ai || p_si) { /* * 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) truncate = TRUE; else if (p_si && *ptr != NUL) { char *p; char *pp; int i, save; if (dir == FORWARD) { p = ptr + strlen(ptr) - 1; while (p > ptr && isspace(*p)) /* find last non-blank in line */ --p; if (*p == '{') /* line ends in '{': do indent */ { did_si = TRUE; no_si = TRUE; } else /* look for "if" and the like */ { p = ptr; skipspace(&p); for (pp = p; islower(*pp); ++pp) ; if (!isidchar(*pp)) /* careful for vars starting with "if" */ { save = *pp; *pp = NUL; for (i = sizeof(si_tab)/sizeof(char *); --i >= 0; ) if (strcmp(p, si_tab[i]) == 0) { did_si = TRUE; break; } *pp = save; } } } else { p = ptr; skipspace(&p); if (*p == '}') /* if line starts with '}': do indent */ did_si = TRUE; } } did_ai = TRUE; if (p_si) can_si = TRUE; } if (State == INSERT || State == REPLACE) /* only when dir == FORWARD */ { pp = ptr + Curpos.col; if (p_ai && delspaces) skipspace(&pp); extra = strlen(pp); } if ((l = alloc_line(extra)) == NULL) return (FALSE); if (extra) { strcpy(l, pp); did_ai = FALSE; /* don't trucate now */ } oldCurpos = Curpos; if (dir == BACKWARD) --Curpos.lnum; if (appendline(Curpos.lnum, l) == FALSE) return FALSE; if (newindent || did_si) { ++Curpos.lnum; if (did_si) { if (p_sr) newindent -= newindent % (int)p_sw; newindent += (int)p_sw; } set_indent(newindent, FALSE); newcol = Curpos.col; if (no_si) did_si = FALSE; } Curpos = oldCurpos; if (dir == FORWARD) { if (truncate || State == INSERT || State == REPLACE) { if (truncate) *ptr = NUL; else *(ptr + Curpos.col) = NUL; /* truncate current line at cursor */ if (!canincrease(0)) return FALSE; } /* * Get the cursor to the start of the line, so that 'Cursrow' gets * set to the right physical line number for the stuff that * follows... */ Curpos.col = 0; if (redraw) { cursupdate(); /* * 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. We use calls to plines() in case the cursor is * resting on a long line. */ n = Cursrow + plines(Curpos.lnum); if (n == (Rows - 1)) scrollup(1L); else s_ins(n, 1, TRUE); } ++Curpos.lnum; /* cursor moves down */ } else if (redraw) s_ins(Cursrow, 1, TRUE); /* insert physical line */ Curpos.col = newcol; if (redraw) { updateScreen(VALID_TO_CURSCHAR); cursupdate(); /* update Cursrow */ } CHANGED; return (TRUE); } /* * plines(p) - return the number of physical screen lines taken by line 'p' */ int plines(p) linenr_t p; { register int col = 0; register u_char *s; #ifdef JP register int row = 1; #endif if (!p_wrap) return 1; s = (u_char *)nr2ptr(p); if (*s == NUL) /* empty line */ return 1; #ifdef JP if (p_nu) col += 8; while(*s) { if (IsKanji(*s)) { if (col >= Columns - 1) /* wrapping on last column kanji */ { row ++; col = 0; } s += 2; col += 2; } else col += chartabsize(* s++, col); if (col >= Columns) { row ++; col = 0; } } if (p_list && ++ col >= Columns) row ++; if (col == 0 && row > 1) row --; if (row < Rows) return row; return (int)(Rows - 1); /* maximum length */ #else while (*s != NUL) col += chartabsize(*s++, col); /* * If list mode is on, then the '$' at the end of the line takes up one * extra column. */ if (p_list) col += 1; /* * If 'number' mode is on, add another 8. */ if (p_nu) col += 8; col = (col + ((int)Columns - 1)) / (int)Columns; if (col < Rows) return col; return (int)(Rows - 1); /* maximum length */ #endif } /* * Count the physical lines (rows) for the lines "first" to "last" inclusive. */ int plines_m(first, last) linenr_t first, last; { int count = 0; while (first <= last) count += plines(first++); return (count); } void fileinfo(fullname) int fullname; { if (bufempty()) { if (p_fm) screen_msg(TRUE, "Buffer Empty"); else msg("Buffer Empty"); return; } sprintf(IObuff, "\"%s\"%s%s%s line %ld of %ld -- %d %% --", (!fullname && sFilename != NULL) ? sFilename : ((Filename != NULL) ? Filename : "No File"), Changed ? " [Modified]" : "", NotEdited ? " [Not edited]" : "", p_ro ? " [readonly]" : "", (long)Curpos.lnum, (long)line_count, (int)(((long)Curpos.lnum * 100L) / (long)line_count)); if (numfiles > 1) sprintf(IObuff + strlen(IObuff), " (file %d of %d)", curfile + 1, numfiles); #ifdef JP if (JP_FCODE != JP_NONE) sprintf(IObuff + strlen(IObuff), " [%c]", JP_FCODE); #endif msg(IObuff); } /* * Set the current file name to 's'. * The file name with the full path is also remembered, for when :cd is used. */ void setfname(s, ss) char *s, *ss; { free(Filename); free(sFilename); if (s == NULL || *s == NUL) { Filename = NULL; sFilename = NULL; } else { if (ss == NULL) ss = s; sFilename = (char *)strsave(ss); FullName(s, IObuff, IOSIZE); Filename = (char *)strsave(IObuff); } if (did_cd) xFilename = Filename; else xFilename = sFilename; #if !defined(MSDOS) || defined(WIN32) thisfile_sn = FALSE; #endif } /* * return nonzero if "s" is not the same file as current file */ int otherfile(s) char *s; { if (s == NULL || *s == NUL || Filename == NULL) /* no name is different */ return TRUE; FullName(s, IObuff, IOSIZE); return fnamecmp(IObuff, Filename); } /* * put filename in title bar of window */ void maketitle() { #if defined(AMIGA) || defined(WIN32) if (Filename == NULL) settitle(""); else { if (numfiles <= 1) settitle(Filename); else { sprintf(IObuff, "%s (%d of %d)", Filename, curfile + 1, numfiles); settitle(IObuff); } } #endif } void #ifdef JP inschar(c, k) int k; #else inschar(c) #endif int c; { register char *p; int rir0; /* reverse replace in column 0 */ p = Curpos2ptr(); rir0 = (State == REPLACE && p_ri && Curpos.col == 0); #ifdef JP if (rir0 || State != REPLACE || *p == NUL || (!IsKanji(*p) && IsKanji(c))) #else if (rir0 || State != REPLACE || *p == NUL) #endif { int n; #ifdef JP n = (IsKanji(c) && (State != REPLACE || *p == NUL || (rir0 && !IsKanji(*p)))) ? 2 : 1; #else n = 1; #endif /* make room for the new char. */ if (!canincrease(n)) /* make room for the new char */ return; p = Curpos2ptr(); /* get p again, canincrease() may have changed it */ memmove(p + n, p, strlen(p) + 1); /* move following text and NUL */ } #ifdef JP if (State == REPLACE && !IsKanji(c) && IsKanji(*p)) memmove(p + 1, p + 2, strlen(p + 2) + 1); /* shrink 1 char. to replace Kanji to ASCII */ #endif if (rir0) /* reverse replace in column 0 */ { *(p + 1) = c; /* replace the char that was in column 0 */ #ifdef JP if (IsKanji(c)) *(p + 2) = k; #endif c = ' '; /* insert a space */ extraspace = TRUE; } *p = c; #ifdef JP if (IsKanji(c)) *(p + 1) = k; #endif /* * If we're in insert mode and showmatch mode 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. */ if (p_sm && State == INSERT && (c == ')' || c == '}' || c == ']')) { FPOS *lpos, csave; if ((lpos = showmatch()) == NULL) /* no match, so beep */ beep(); else if (lpos->lnum >= Topline) { updateScreen(VALID_TO_CURSCHAR); /* show the new char first */ csave = Curpos; Curpos = *lpos; /* move to matching char */ cursupdate(); showruler(0); setcursor(); flushbuf(); vim_delay(); /* brief pause */ Curpos = csave; /* restore cursor position */ cursupdate(); } } #ifdef JP if (!p_ri) /* normal insert: cursor right */ Curpos.col += IsKanji(c) ? 2 : 1; else if (State == REPLACE && !rir0) /* reverse replace mode: cursor left */ { Curpos.col --; if (IsKanji(gcharCurpos())) Curpos.col --; } #else if (!p_ri) /* normal insert: cursor right */ ++Curpos.col; else if (State == REPLACE && !rir0) /* reverse replace mode: cursor left */ --Curpos.col; #endif CHANGED; } void insstr(s) register char *s; { register char *p; register int n = strlen(s); if (!canincrease(n)) /* make room for the new string */ return; p = Curpos2ptr(); memmove(p + n, p, strlen(p) + 1); memmove(p, s, (size_t)n); Curpos.col += n; CHANGED; } int delchar(fixpos) int fixpos; /* if TRUE fix the cursor position when done */ { char *ptr; int lastchar; ptr = Curpos2ptr(); if (*ptr == NUL) /* can't do anything (happens with replace mode) */ return FALSE; lastchar = (*++ptr == NUL); /* Delete the char. at Curpos by shifting everything in the line down. */ do *(ptr - 1) = *ptr; while (*ptr++); /* * If we just took off the last character of a non-blank line, we don't * want to end up positioned at the newline. */ if (fixpos && Curpos.col > 0 && lastchar) { --Curpos.col; #ifdef JP if (IsKanji(gcharCurpos())) --Curpos.col; #endif } (void)canincrease(0); CHANGED; return TRUE; } void dellines(nlines, doscreen, undo) long nlines; /* number of lines to delete */ int doscreen; /* if true, update the screen */ int undo; /* if true, prepare for undo */ { int num_plines = 0; char *ptr; if (nlines <= 0) return; /* * There's no point in keeping the screen updated if we're deleting more * than a screen's worth of lines. */ if (nlines > (Rows - 1 - Cursrow) && doscreen) { doscreen = FALSE; /* flaky way to clear rest of screen */ s_del(Cursrow, (int)Rows - 1, TRUE); } if (undo && !u_savedel(Curpos.lnum, nlines)) return; while (nlines-- > 0) { if (bufempty()) /* nothing to delete */ break; /* * Set up to delete the correct number of physical lines on the * screen */ if (doscreen) num_plines += plines(Curpos.lnum); ptr = delsline(Curpos.lnum, TRUE); if (!undo) free_line(ptr); CHANGED; /* If we delete the last line in the file, stop */ if (Curpos.lnum > line_count) { Curpos.lnum = line_count; break; } } Curpos.col = 0; /* * Delete the correct number of physical lines on the screen */ if (doscreen && num_plines > 0) s_del(Cursrow, num_plines, TRUE); } int gchar(pos) FPOS *pos; { return (int)(*((u_char *)pos2ptr(pos))); } int gcharCurpos() { return (int)(*((u_char *)Curpos2ptr())); } void pcharCurpos(c) int c; { *(Curpos2ptr()) = c; } /* * return TRUE if the cursor is before or on the first non-blank in the line */ int inindent() { register char *ptr; register int col; for (col = 0, ptr = nr2ptr(Curpos.lnum); isspace(*ptr++); ++col) ; if (col >= Curpos.col) return TRUE; else return FALSE; } /* * skipspace: skip over ' ' and '\t'. * * note: you must give a pointer to a char pointer! */ void skipspace(pp) char **pp; { register char *p; #ifdef JP for(p = *pp; 1; ++p) { if (*p == ' ' || *p == '\t') continue; if (IsKanji(*p) && jpcls(*p, *(p+1)) == 0) ++ p; else break; } #else for (p = *pp; *p == ' ' || *p == '\t'; ++p) /* skip to next non-white */ ; #endif *pp = p; } /* * skiptospace: skip over text until ' ' or '\t'. * * note: you must give a pointer to a char pointer! */ void skiptospace(pp) char **pp; { register char *p; for (p = *pp; *p != ' ' && *p != '\t' && *p != NUL; ++p) #ifdef JP if (IsKanji(*p)) { if (jpcls(*p, *(p + 1)) == 0) break; ++p; } #else ; #endif *pp = p; } /* * skiptodigit: skip over text until digit found * * note: you must give a pointer to a char pointer! */ void skiptodigit(pp) char **pp; { register char *p; for (p = *pp; !isdigit(*p) && *p != NUL; ++p) ; *pp = p; } /* * getdigits: get a number from a string and skip over it * * note: you must give a pointer to a char pointer! */ long getdigits(pp) char **pp; { register char *p; long retval; p = *pp; retval = atol(p); while (isdigit(*p)) /* skip to next non-digit */ ++p; *pp = p; return retval; } char * plural(n) long n; { static char buf[2] = "s"; if (n == 1) return &(buf[1]); return &(buf[0]); } /* * set_Changed is called whenever something in the file is changed * If the file is readonly, give a warning message with the first change. * Don't use emsg(), because it flushes the macro buffer. */ void set_Changed() { change_warning(); Changed = 1; Updated = 1; } void change_warning() { if (Changed == 0 && p_ro) { if (p_fm) { screen_msg(TRUE, "Warning: Changing a readonly file"); } else { msg("Warning: Changing a readonly file"); sleep(1); /* give him some time to think about it */ } } } int ask_yesno(str) char *str; { int r = ' '; while (r != 'y' && r != 'n') { smsg("%s (y/n)? ", str); r = vgetc(); if (r == Ctrl('C')) r = 'n'; outchar(r); /* show what you typed */ flushbuf(); } return r; } void msgmore(n) long n; { long pn; if (n > 0) pn = n; else pn = -n; if (pn > p_report) smsg("%ld %s line%s %s", pn, n > 0 ? "more" : "fewer", plural(pn), got_int ? "(Interrupted)" : ""); } /* * give a warning for an error */ void beep() { flush_buffers(FALSE); /* flush internal buffers */ #ifndef WIN32 if (p_vb) { if (T_VB && *T_VB) outstr(T_VB); else { /* very primitive visual bell */ msg(" ^G"); msg(" ^G"); msg(" ^G "); msg(" ^G"); msg(" "); showmode(); /* may have deleted the mode message */ } } else #endif /* WIN32 */ outchar('\007'); } /* * Expand environment variable with path name. * If anything fails no expansion is done and dst equals src. */ void expand_env(src, dst, dstlen) char *src; /* input string e.g. "$HOME/vim.hlp" */ char *dst; /* where to put the result */ int dstlen; /* maximum length of the result */ { char *tail; int c; char *var; if (*src == '$') { /* * The variable name is copied into dst temporarily, because it may be * a string in read-only memory. */ tail = src + 1; var = dst; c = dstlen - 1; while (c-- > 0 && *tail && isidchar(*tail)) *var++ = *tail++; *var = NUL; /* * It is possible that vimgetenv() uses IObuff for the expansion, and that the * 'dst' is also IObuff. This works, as long as 'var' is the first to be copied * to 'dst'! */ var = (char *)vimgetenv(dst); if (var && (strlen(var) + strlen(tail) + 1 < (unsigned)dstlen)) { strcpy(dst, var); strcat(dst, tail); return; } } strncpy(dst, src, (size_t)dstlen); } /* * Compare two file names and return TRUE if they are different files. * For the first name environment variables are expanded */ int fullpathcmp(s1, s2) char *s1, *s2; { #ifdef UNIX struct stat st1, st2; char buf1[MAXPATHL]; expand_env(s1, buf1, MAXPATHL); if (stat(buf1, &st1) == 0 && stat(s2, &st2) == 0 && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) return FALSE; return TRUE; #else char buf1[MAXPATHL]; char buf2[MAXPATHL]; expand_env(s1, buf2, MAXPATHL); if (FullName(buf2, buf1, MAXPATHL) && FullName(s2, buf2, MAXPATHL)) return strcmp(buf1, buf2); /* * one of the FullNames() failed, file probably doesn't exist. */ return TRUE; #endif } /* * get the tail of a path: the file name. */ char * gettail(fname) char *fname; { register char *p1, *p2; 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 MSDOS return (c == ':' || c == PATHSEP || c == '/'); # else return (c == ':' || c == PATHSEP); # endif #endif }