今日の関数 vim_chdirfile (https://github.com/vim)
misc2.c
int vim_chdirfile(char_u *fname) { char_u dir[MAXPATHL]; vim_strncpy(dir, fname, MAXPATHL - 1); *gettail_sep(dir) = NUL; return mch_chdir((char *)dir) == 0 ? OK : FAIL; }
ファイル名からそのファイルの親ディレクトリでchdirを呼ぶ関数です。
dirにファイルのパスをコピーします。
次にそのパスの最後のseparatorを探しそこにNULを入れ、それをchdirに送るという流れです。
vim.h
#define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n))
strncpyを呼ぶだけ。
misc1.c
/* * Get pointer to tail of "fname", including path separators. Putting a NUL * here leaves the directory name. Takes care of "c:/" and "//". * Always returns a valid pointer. */ char_u * gettail_sep(char_u *fname) { char_u *p; char_u *t; p = get_past_head(fname); /* don't remove the '/' from "c:/file" */ t = gettail(fname); while (t > p && after_pathsep(fname, t)) --t; #ifdef VMS /* path separator is part of the path */ ++t; #endif return t; }
pにはファイルパスの一番最初のseparatorの次の文字を指しているポインタが得られます。例えば/Users/my_name/my_file.txtならUを指しているポインタです。(get_past_head)
tにはファイルパスの最後のseparatorから後ろ、つまりファイル名をとります。例えば/Users/my_name/my_file.txtならmy_file.txtのmを指しているポインタです。(gettail)
tのポインタがpより大きくその一つ前がseparatorなら/Users/my_name/my_file.txtのmy_fileのmから左にポインタが動きます。
/Users/my_name/my_file.txtはmy_file.txtのmから始まってafter_pathsepがtrueを返すので一つ戻って/my_file.txtの/を指すポインタが返ります。
そしてその場所にNULLを入れるので/Users/my_name\000my_file.txtがdirに入ってchdirが呼ばれます。つまりファイルの親ディレクトリにchdirすることになります。
misc1.c
/* * Get a pointer to one character past the head of a path name. * Unix: after "/"; DOS: after "c:\"; Amiga: after "disk:/"; Mac: no head. * If there is no head, path is returned. */ char_u * get_past_head(char_u *path) { char_u *retval; #if defined(MSWIN) /* may skip "c:" */ if (isalpha(path[0]) && path[1] == ':') retval = path + 2; else retval = path; #else # if defined(AMIGA) /* may skip "label:" */ retval = vim_strchr(path, ':'); if (retval == NULL) retval = path; # else /* Unix */ retval = path; # endif #endif while (vim_ispathsep(*retval)) ++retval; return retval; }
windowsのc:などに対処しながら最初のseparatorの次の位置を返します。(++retval)(←)
misc1.c
/* * Get the tail of a path: the file name. * When the path ends in a path separator the tail is the NUL after it. * Fail safe: never returns NULL. */ char_u * gettail(char_u *fname) { char_u *p1, *p2; if (fname == NULL) return (char_u *)""; for (p1 = p2 = get_past_head(fname); *p2; ) /* find last part of path */ { if (vim_ispathsep_nocolon(*p2)) p1 = p2 + 1; mb_ptr_adv(p2); } return p1; }
p1はp2の次のアドレス。p2はmb_ptr_advで進められていきます。separatorである時+1したものをp1に入れます。p2がNULLなったら最後にseparatorを指していたポインタに1を足したp1を返します。(←)
misc1.c
/* * Like vim_ispathsep(c), but exclude the colon for MS-Windows. */ int vim_ispathsep_nocolon(int c) { return vim_ispathsep(c) #ifdef BACKSLASH_IN_FILENAME && c != ':' #endif ; }
ポインタが指している文字がseparatorならTrueを返す。vim_ispathlistsep BACKSLASH_IN_FILENAME が定義されているなら cが:でないも条件に入る。
macros.h
# define mb_ptr_adv(p) p += has_mbyte ? (*mb_ptr2len)(p) : 1
マルチバイトでもptrを進める。
misc1.c
/* * Return TRUE if "p" points to just after a path separator. * Takes care of multi-byte characters. * "b" must point to the start of the file name */ int after_pathsep(char_u *b, char_u *p) { return p > b && vim_ispathsep(p[-1]) && (!has_mbyte || (*mb_head_off)(b, p - 1) == 0); }
p がseparatorの次にいればtrueを返します。 (←)