今日の関数 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を返します。 ()