summaryrefslogtreecommitdiffstats
path: root/src/findfile.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-03-31 19:40:07 +0200
committerBram Moolenaar <Bram@vim.org>2019-03-31 19:40:07 +0200
commitb4a6020ac6a0638167013f1e45ff440ddc8a1671 (patch)
treec862765096dc61d14e1e8a4c7825e30189c9fe8a /src/findfile.c
parent95946f1209ad088bfe55c83256c299156c11d8e0 (diff)
patch 8.1.1099: the do_tag() function is too longv8.1.1099
Problem: The do_tag() function is too long. Solution: Factor parts out to separate functions. Move simplify_filename() to a file where it fits better. (Andy Massimino, closes #4195)
Diffstat (limited to 'src/findfile.c')
-rw-r--r--src/findfile.c212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/findfile.c b/src/findfile.c
index 690cd8fb19..3d3aec0df9 100644
--- a/src/findfile.c
+++ b/src/findfile.c
@@ -2605,3 +2605,215 @@ expand_in_path(
}
#endif // FEAT_SEARCHPATH
+
+/*
+ * Converts a file name into a canonical form. It simplifies a file name into
+ * its simplest form by stripping out unneeded components, if any. The
+ * resulting file name is simplified in place and will either be the same
+ * length as that supplied, or shorter.
+ */
+ void
+simplify_filename(char_u *filename)
+{
+#ifndef AMIGA // Amiga doesn't have "..", it uses "/"
+ int components = 0;
+ char_u *p, *tail, *start;
+ int stripping_disabled = FALSE;
+ int relative = TRUE;
+
+ p = filename;
+# ifdef BACKSLASH_IN_FILENAME
+ if (p[1] == ':') // skip "x:"
+ p += 2;
+# endif
+
+ if (vim_ispathsep(*p))
+ {
+ relative = FALSE;
+ do
+ ++p;
+ while (vim_ispathsep(*p));
+ }
+ start = p; // remember start after "c:/" or "/" or "///"
+
+ do
+ {
+ // At this point "p" is pointing to the char following a single "/"
+ // or "p" is at the "start" of the (absolute or relative) path name.
+# ifdef VMS
+ // VMS allows device:[path] - don't strip the [ in directory
+ if ((*p == '[' || *p == '<') && p > filename && p[-1] == ':')
+ {
+ // :[ or :< composition: vms directory component
+ ++components;
+ p = getnextcomp(p + 1);
+ }
+ // allow remote calls as host"user passwd"::device:[path]
+ else if (p[0] == ':' && p[1] == ':' && p > filename && p[-1] == '"' )
+ {
+ // ":: composition: vms host/passwd component
+ ++components;
+ p = getnextcomp(p + 2);
+ }
+ else
+# endif
+ if (vim_ispathsep(*p))
+ STRMOVE(p, p + 1); // remove duplicate "/"
+ else if (p[0] == '.' && (vim_ispathsep(p[1]) || p[1] == NUL))
+ {
+ if (p == start && relative)
+ p += 1 + (p[1] != NUL); // keep single "." or leading "./"
+ else
+ {
+ // Strip "./" or ".///". If we are at the end of the file name
+ // and there is no trailing path separator, either strip "/." if
+ // we are after "start", or strip "." if we are at the beginning
+ // of an absolute path name .
+ tail = p + 1;
+ if (p[1] != NUL)
+ while (vim_ispathsep(*tail))
+ MB_PTR_ADV(tail);
+ else if (p > start)
+ --p; // strip preceding path separator
+ STRMOVE(p, tail);
+ }
+ }
+ else if (p[0] == '.' && p[1] == '.' &&
+ (vim_ispathsep(p[2]) || p[2] == NUL))
+ {
+ // Skip to after ".." or "../" or "..///".
+ tail = p + 2;
+ while (vim_ispathsep(*tail))
+ MB_PTR_ADV(tail);
+
+ if (components > 0) // strip one preceding component
+ {
+ int do_strip = FALSE;
+ char_u saved_char;
+ stat_T st;
+
+ /* Don't strip for an erroneous file name. */
+ if (!stripping_disabled)
+ {
+ // If the preceding component does not exist in the file
+ // system, we strip it. On Unix, we don't accept a symbolic
+ // link that refers to a non-existent file.
+ saved_char = p[-1];
+ p[-1] = NUL;
+# ifdef UNIX
+ if (mch_lstat((char *)filename, &st) < 0)
+# else
+ if (mch_stat((char *)filename, &st) < 0)
+# endif
+ do_strip = TRUE;
+ p[-1] = saved_char;
+
+ --p;
+ // Skip back to after previous '/'.
+ while (p > start && !after_pathsep(start, p))
+ MB_PTR_BACK(start, p);
+
+ if (!do_strip)
+ {
+ // If the component exists in the file system, check
+ // that stripping it won't change the meaning of the
+ // file name. First get information about the
+ // unstripped file name. This may fail if the component
+ // to strip is not a searchable directory (but a regular
+ // file, for instance), since the trailing "/.." cannot
+ // be applied then. We don't strip it then since we
+ // don't want to replace an erroneous file name by
+ // a valid one, and we disable stripping of later
+ // components.
+ saved_char = *tail;
+ *tail = NUL;
+ if (mch_stat((char *)filename, &st) >= 0)
+ do_strip = TRUE;
+ else
+ stripping_disabled = TRUE;
+ *tail = saved_char;
+# ifdef UNIX
+ if (do_strip)
+ {
+ stat_T new_st;
+
+ // On Unix, the check for the unstripped file name
+ // above works also for a symbolic link pointing to
+ // a searchable directory. But then the parent of
+ // the directory pointed to by the link must be the
+ // same as the stripped file name. (The latter
+ // exists in the file system since it is the
+ // component's parent directory.)
+ if (p == start && relative)
+ (void)mch_stat(".", &new_st);
+ else
+ {
+ saved_char = *p;
+ *p = NUL;
+ (void)mch_stat((char *)filename, &new_st);
+ *p = saved_char;
+ }
+
+ if (new_st.st_ino != st.st_ino ||
+ new_st.st_dev != st.st_dev)
+ {
+ do_strip = FALSE;
+ // We don't disable stripping of later
+ // components since the unstripped path name is
+ // still valid.
+ }
+ }
+# endif
+ }
+ }
+
+ if (!do_strip)
+ {
+ // Skip the ".." or "../" and reset the counter for the
+ // components that might be stripped later on.
+ p = tail;
+ components = 0;
+ }
+ else
+ {
+ // Strip previous component. If the result would get empty
+ // and there is no trailing path separator, leave a single
+ // "." instead. If we are at the end of the file name and
+ // there is no trailing path separator and a preceding
+ // component is left after stripping, strip its trailing
+ // path separator as well.
+ if (p == start && relative && tail[-1] == '.')
+ {
+ *p++ = '.';
+ *p = NUL;
+ }
+ else
+ {
+ if (p > start && tail[-1] == '.')
+ --p;
+ STRMOVE(p, tail); // strip previous component
+ }
+
+ --components;
+ }
+ }
+ else if (p == start && !relative) // leading "/.." or "/../"
+ STRMOVE(p, tail); // strip ".." or "../"
+ else
+ {
+ if (p == start + 2 && p[-2] == '.') // leading "./../"
+ {
+ STRMOVE(p - 2, p); // strip leading "./"
+ tail -= 2;
+ }
+ p = tail; // skip to char after ".." or "../"
+ }
+ }
+ else
+ {
+ ++components; // simple path component
+ p = getnextcomp(p);
+ }
+ } while (*p != NUL);
+#endif // !AMIGA
+}