feat: handle "file:/" and "file:///" for 'gf' command

* Handle hexcode for Windows environment

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
Jaehoon Hwang 2023-10-13 00:16:24 -07:00
parent 3a3e025126
commit 9e67cc189d
3 changed files with 100 additions and 2 deletions

View File

@ -1396,7 +1396,10 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
// filename on the first call.
if (first == true) {
if (path_with_url(*file_to_find)) {
file_name = xstrdup(*file_to_find);
file_name =
strncmp(*file_to_find, "file:/", 6) == 0 ?
xstrdup(handle_file_path_prefix(*file_to_find)) :
xstrdup(*file_to_find);
goto theend;
}

View File

@ -2467,3 +2467,73 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize)
xstrlcpy(buf, argv0, bufsize);
}
}
/// Handles file path that starts with `file:[/|///]`
///
/// One and three slashes mean that file location is localhost/local.
/// Example:
/// - file:/path/to/file refers to /path/to/file
/// - file:///path/to/file refers to /path/to/file (host is omitted)
/// But, with two slashes, it refers to a file location on a host machine.
/// - file://host/path/to/file
/// Example:
/// - file://localhost/path/to/file refers to /path/to/file locally
/// When encountering two slashes, don't alter file_path.
///
/// Reference:
/// https://en.wikipedia.org/wiki/File_URI_scheme#Number_of_slash_characters
///
/// @param[in] file_path Full name of file path starts with "file:/"
///
/// @return either truncated version of file path or its input.
char *handle_file_path_prefix(char *file_path)
{
const char *reader = file_path + 5; // file: <- start there
int slash_count = 0;
while (reader[0] == '/') {
slash_count += 1;
reader += 1;
}
if (slash_count == 1 || slash_count == 3) {
#ifdef MSWIN
return decode_window_filepath(file_path, slash_count);
#else
return strchr(file_path, '/');
#endif
}
return file_path;
}
/// Decode hexcode that could be part of file path.
///
/// Convert hex code, such as "%3A" into ":"
/// Example: 'file:///C%3A/My%20Docuemnts' -> 'file:///C:/My Documents'
///
/// @param[in] file_path Full name of file path starts with "file:/"
/// @param[in] slash_count count of slashes after "file"
///
/// @return either truncated version of file path or its input.
char *decode_window_filepath(char *file_path, int slash_count)
{
char *buf = xmalloc(MAXPATHL);
char *starting_point = file_path + 5 + slash_count;
int buf_counter = 0;
for (char *ptr = starting_point; ptr != NULL; ptr++) {
if (ptr[0] == '%'
&& (('0' <= ptr[1] && ptr[1] <= '9') || ('A' <= ptr[1] && ptr[1] <= 'F'))
&& (('0' <= ptr[2] && ptr[2] <= '9') || ('A' <= ptr[2] && ptr[2] <= 'F'))) {
// Deal with hex code
char hex_code[2] = { ptr[1], ptr[2] };
buf[buf_counter] = (char)strtol(hex_code, NULL, 16);
ptr += 3;
} else {
// Copy the character
buf[buf_counter] = ptr[0];
}
buf_counter += 1;
}
xstrlcpy(file_path, buf, (unsigned long)buf_counter); // truncate
return file_path;
}

View File

@ -13,7 +13,7 @@ local write_file = helpers.write_file
local function join_path(...)
local pathsep = (is_os('win') and '\\' or '/')
return table.concat({...}, pathsep)
return table.concat({ ... }, pathsep)
end
describe('path collapse', function()
@ -103,6 +103,31 @@ describe('file search', function()
eq('filename_with_unicode_ααα', eval('expand("%:t")'))
end)
it('finds "file:[/|///]" URI on the local filesystem #24032', function()
local iswin = is_os('win')
local function test_gf(input, expected_default, expected_win)
local expected = (iswin and expected_win or expected_default)
command('%delete')
insert(input)
command('norm! 0')
feed('gf')
eq(expected, eval('expand("%:p")'))
end
test_gf("file:/a", "/a")
test_gf("file:/foo/bar/slash.txt", "/foo/bar/slash.txt")
test_gf("file:///foo/bar/3slashes.txt", "///foo/bar/3slashes.txt")
test_gf("file:/c:/test/window/slash.txt", "/c:/test/window/slash.txt")
test_gf("file:///c:/test/window/3slash.txt", "///c:/test/window/3slash.txt")
-- Test out window cases.
test_gf("file:///c%3A/test%20with%20%25/path", "///c%3A/test%20with%20%25/path", [[c:\test with %\path]])
test_gf("file:///c%3A/documents%20and%24zero%/", "///c%3A/documents%20and%24zero%/", [[c:/documents and$zero$]])
test_gf("http://foo/bar/file:/with/url", "http://foo/bar/file:/with/url")
test_gf("file://host/foo/bar/2slashes.txt", "file://host/foo/bar/2slashes.txt")
test_gf("file://host/c:/2slashes.txt", "file://host/c:/2slashes.txt")
end)
it('gf/<cfile> matches Windows drive-letter filepaths (without ":" in &isfname)', function()
local iswin = is_os('win')
local function test_cfile(input, expected, expected_win)