1 // SciTE - Scintilla based Text Editor
3 ** Encapsulate a file path.
5 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
27 const char pathSepString
[] = "/";
28 const char pathSepChar
= '/';
29 const char listSepString
[] = ":";
30 const char configFileVisibilityString
[] = ".";
31 const char fileRead
[] = "rb";
32 const char fileWrite
[] = "wb";
35 const char pathSepString
[] = "/";
36 const char pathSepChar
= '/';
37 const char listSepString
[] = ":";
38 const char configFileVisibilityString
[] = "";
39 const char fileRead
[] = "r";
40 const char fileWrite
[] = "w";
44 const char pathSepString
[] = "\\";
45 const char pathSepChar
= '\\';
46 const char listSepString
[] = ";";
47 const char configFileVisibilityString
[] = "";
48 const char fileRead
[] = "rb";
49 const char fileWrite
[] = "wb";
52 FilePath::FilePath(const char *fileName_
) : fileName(fileName_
) {}
54 FilePath::FilePath(FilePath
const &directory
, FilePath
const &name
) {
58 void FilePath::Set(const char *fileName_
) {
62 const char *FilePath::AsFileSystem() const {
66 void FilePath::Set(FilePath
const &other
) {
67 fileName
= other
.fileName
;
70 void FilePath::Set(FilePath
const &directory
, FilePath
const &name
) {
71 if (name
.IsAbsolute()) {
72 fileName
= name
.fileName
;
74 fileName
= directory
.fileName
;
75 fileName
.appendwithseparator(name
.fileName
.c_str(),
76 fileName
.endswith(pathSepString
) ? '\0' : pathSepChar
);
80 void FilePath::SetDirectory(FilePath directory
) {
81 FilePath
curName(*this);
82 Set(directory
, curName
);
85 void FilePath::Init() {
89 bool FilePath::SameNameAs(const char *other
) const {
91 return EqualCaseInsensitive(fileName
.c_str(), other
);
93 return fileName
== other
;
97 bool FilePath::SameNameAs(const FilePath
&other
) const {
98 return SameNameAs(other
.fileName
.c_str());
101 bool FilePath::IsSet() const {
102 return fileName
.length() > 0;
105 bool FilePath::IsUntitled() const {
106 const char *dirEnd
= strrchr(AsInternal(), pathSepChar
);
107 return !dirEnd
|| !dirEnd
[1];
110 bool FilePath::IsAbsolute() const {
111 if (fileName
.length() == 0)
114 if (fileName
[0] == '/')
118 if (fileName
[0] == '/')
122 if (fileName
[0] == pathSepChar
|| fileName
[1] == ':') // UNC path or drive separator
129 bool FilePath::IsRoot() const {
131 if ((fileName
[0] == pathSepChar
) && (fileName
[1] == pathSepChar
) && (fileName
.search(pathSepString
, 2) < 0))
132 return true; // UNC path like \\server
133 return (fileName
.length() == 3) && (fileName
[1] == ':') && (fileName
[2] == pathSepChar
);
135 return fileName
== "/";
139 int FilePath::RootLength() {
147 const char *FilePath::AsInternal() const {
148 return fileName
.c_str();
151 FilePath
FilePath::Name() const {
152 const char *dirEnd
= strrchr(fileName
.c_str(), pathSepChar
);
156 return fileName
.c_str();
159 FilePath
FilePath::BaseName() const {
160 const char *dirEnd
= strrchr(fileName
.c_str(), pathSepChar
);
161 const char *extStart
= strrchr(fileName
.c_str(), '.');
163 if (extStart
> dirEnd
) {
164 return FilePath(SString(dirEnd
+ 1, 0, extStart
- dirEnd
- 1).c_str());
166 return FilePath(dirEnd
+ 1);
168 } else if (extStart
) {
169 return FilePath(SString(fileName
.c_str(), 0, extStart
- fileName
.c_str()).c_str());
171 return fileName
.c_str();
175 FilePath
FilePath::Extension() const {
176 const char *dirEnd
= strrchr(fileName
.c_str(), pathSepChar
);
177 const char *extStart
= strrchr(fileName
.c_str(), '.');
178 if (extStart
> dirEnd
)
184 FilePath
FilePath::Directory() const {
186 return FilePath(fileName
.c_str());
188 const char *dirEnd
= strrchr(fileName
.c_str(), pathSepChar
);
190 int lenDirectory
= dirEnd
- fileName
.c_str();
191 if (lenDirectory
< RootLength()) {
192 lenDirectory
= RootLength();
194 return FilePath(fileName
.substr(0, lenDirectory
).c_str());
201 static char *split(char*& s
, char c
) {
203 if (s
&& (s
= strchr(s
, c
)) != NULL
)
208 FilePath
FilePath::NormalizePath() const {
209 char *path
= new char[fileName
.length() + 1];
210 strcpy(path
, AsInternal());
212 // Convert unix path separators to Windows
213 for (char *cp
= path
; *cp
; cp
++) {
218 char *absPath
= new char[fileName
.length() + 1];
222 if (*tmp
== pathSepChar
) {
223 *cur
++ = pathSepChar
;
228 while ((part
= split(tmp
, pathSepChar
)) != NULL
) {
230 if (strcmp(part
, ".") == 0)
232 else if (strcmp(part
, "..") == 0 && (last
= strrchr(absPath
, pathSepChar
)) != NULL
) {
239 if (cur
> absPath
&& *(cur
- 1) != pathSepChar
)
240 *cur
++ = pathSepChar
;
245 FilePath
ret(absPath
);
253 FilePath
FilePath::VMSToUnixStyle() {
255 // o disk:[dir.dir]file.type
256 // o logical:file.type
257 // o [dir.dir]file.type
259 // o /disk//dir/dir/file.type
260 // o /disk/dir/dir/file.type
262 char unixStyleFileName
[MAX_PATH
+ 20];
263 const char *vmsName
= FullPath();
265 if (strchr(vmsName
, ':') == NULL
&& strchr(vmsName
, '[') == NULL
) {
267 // o /disk//dir/dir/file.type
268 // o /disk/dir/dir/file.type
269 if (strstr (vmsName
, "//") == NULL
) {
270 return FilePath(vmsName
);
272 strcpy(unixStyleFileName
, vmsName
);
274 while ((p
= strstr (unixStyleFileName
, "//")) != NULL
) {
277 return FilePath(unixStyleFileName
);
280 // o disk:[dir.dir]file.type
281 // o logical:file.type
282 // o [dir.dir]file.type
284 if (vmsName
[0] == '/') {
285 strcpy(unixStyleFileName
, vmsName
);
287 unixStyleFileName
[0] = '/';
288 strcpy(unixStyleFileName
+ 1, vmsName
);
289 char *p
= strstr(unixStyleFileName
, ":[");
291 // o logical:file.type
292 p
= strchr(unixStyleFileName
, ':');
296 strcpy(p
+ 1, p
+ 2);
297 char *end
= strchr(unixStyleFileName
, ']');
301 while (p
= strchr(unixStyleFileName
, '.'), p
!= NULL
&& p
< end
) {
306 return FilePath(unixStyleFileName
);
312 * Take a filename or relative path and put it at the end of the current path.
313 * If the path is absolute, return the same path.
315 FilePath
FilePath::AbsolutePath() const {
317 // The runtime libraries for GCC and Visual C++ give different results for _fullpath
322 ::GetFullPathNameA(AsFileSystem(), sizeof(absPath
), absPath
, &fileBit
);
323 return FilePath(absPath
);
326 return NormalizePath();
328 return FilePath(GetWorkingDirectory(), *this).NormalizePath();
333 // Only used on Windows to fix the case of file names
335 FilePath
FilePath::GetWorkingDirectory() {
336 char dir
[MAX_PATH
+ 1];
338 if (getcwd(dir
, MAX_PATH
)) {
339 dir
[MAX_PATH
] = '\0';
340 // In Windows, getcwd returns a trailing backslash
341 // when the CWD is at the root of a disk, so remove it
342 size_t endOfPath
= strlen(dir
) - 1;
343 if (dir
[endOfPath
] == pathSepChar
) {
344 dir
[endOfPath
] = '\0';
347 return FilePath(dir
);
350 bool FilePath::SetWorkingDirectory() const {
351 return chdir(AsFileSystem()) == 0;
354 void FilePath::FixCase() {}
356 void FilePath::List(FilePathSet
&directories
, FilePathSet
&files
) {
358 FilePath
wildCard(*this, "*.*");
359 bool complete
= false;
360 WIN32_FIND_DATA findFileData
;
361 HANDLE hFind
= ::FindFirstFile(wildCard
.AsFileSystem(), &findFileData
);
362 if (hFind
!= INVALID_HANDLE_VALUE
) {
364 if ((strcmp(findFileData
.cFileName
, ".") != 0) && (strcmp(findFileData
.cFileName
, "..") != 0)) {
365 if (findFileData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
366 directories
.Append(FilePath(AsInternal(), findFileData
.cFileName
));
368 files
.Append(FilePath(AsInternal(), findFileData
.cFileName
));
371 if (!::FindNextFile(hFind
, &findFileData
)) {
379 DIR *dp
= opendir(AsInternal());
381 //~ fprintf(stderr, "%s: cannot open for reading: %s\n", AsInternal(), strerror(errno));
385 while ((ent
= readdir(dp
)) != NULL
) {
386 if ((strcmp(ent
->d_name
, ".") != 0) && (strcmp(ent
->d_name
, "..") != 0)) {
387 FilePath
pathFull(AsInternal(), ent
->d_name
);
388 if (pathFull
.IsDirectory()) {
389 directories
.Append(pathFull
);
391 files
.Append(pathFull
);
402 FILE *FilePath::Open(const char *mode
) const {
404 return fopen(fileName
.c_str(), mode
);
410 void FilePath::Remove() const {
411 unlink(AsFileSystem());
415 // Neither Borland nor Microsoft define the constants used to call access
419 time_t FilePath::ModifiedTime() const {
422 if (access(AsFileSystem(), R_OK
) == -1)
424 struct stat statusFile
;
425 if (stat(AsFileSystem(), &statusFile
) != -1)
426 return statusFile
.st_mtime
;
431 int FilePath::GetFileLength() const {
434 FILE *fp
= Open(fileRead
);
436 fseek(fp
, 0, SEEK_END
);
438 fseek(fp
, 0, SEEK_SET
);
445 bool FilePath::Exists() const {
448 FILE *fp
= Open(fileRead
);
457 bool FilePath::IsDirectory() const {
458 struct stat statusFile
;
459 if (stat(AsFileSystem(), &statusFile
) != -1)
461 return statusFile
.st_mode
& _S_IFDIR
;
463 return statusFile
.st_mode
& S_IFDIR
;
469 bool FilePath::Matches(const char *pattern
) const {
470 SString
pat(pattern
);
471 pat
.substitute(' ', '\0');
472 SString
nameCopy(Name().fileName
);
473 nameCopy
.lowercase();
475 while (start
< pat
.length()) {
476 const char *patElement
= pat
.c_str() + start
;
477 if (patElement
[0] == '*') {
478 if (nameCopy
.endswith(patElement
+ 1)) {
482 if (nameCopy
== SString(patElement
).lowercase()) {
486 start
+= strlen(patElement
) + 1;
493 * Makes a long path from a given, possibly short path/file.
495 * The short path/file must exist, and if it is a file it must be fully specified
496 * otherwise the function fails.
498 * sizeof @a longPath buffer must be a least _MAX_PATH
499 * @returns true on success, and the long path in @a longPath buffer,
500 * false on failure, and copies the @a shortPath arg to the @a longPath buffer.
502 bool MakeLongPath(const char* shortPath
, char* longPath
) {
503 // when we have pfnGetLong, we assume it never changes as kernel32 is always loaded
504 static DWORD (STDAPICALLTYPE
* pfnGetLong
)(const char* lpszShortPath
, char* lpszLongPath
, DWORD cchBuffer
) = NULL
;
505 static bool kernelTried
= FALSE
;
511 hModule
= ::GetModuleHandleA("KERNEL32");
512 //assert(hModule != NULL); // must not call FreeLibrary on such handle
514 // attempt to get GetLongPathName (implemented in Win98/2000 only!)
515 (FARPROC
&)pfnGetLong
= ::GetProcAddress(hModule
, "GetLongPathNameA");
518 // the kernel GetLongPathName proc is faster and (hopefully) more reliable
519 if (pfnGetLong
!= NULL
) {
521 ok
= (pfnGetLong
)(shortPath
, longPath
, _MAX_PATH
) != 0;
523 char short_path
[_MAX_PATH
]; // copy, so we can modify it
528 lstrcpyn(short_path
, shortPath
, _MAX_PATH
);
531 tok
= strtok(short_path
, "\\");
535 if ((strlen(shortPath
) > 3) &&
536 (shortPath
[0] == '\\') && (shortPath
[1] == '\\')) {
537 // UNC, skip first seps
538 strcat(longPath
, "\\\\");
539 strcat(longPath
, tok
);
540 strcat(longPath
, "\\");
542 tok
= strtok(NULL
, "\\");
546 strcat(longPath
, tok
);
555 tok
= strtok(NULL
, "\\");
559 strcat(longPath
, "\\");
560 tokend
= longPath
+ strlen(longPath
);
562 // temporary add short component
565 hfind
= ::FindFirstFile(longPath
, &fd
);
566 if (hfind
== INVALID_HANDLE_VALUE
)
569 isDir
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) != 0;
571 // finally add long component we got
572 strcpy(tokend
, fd
.cFileName
);
579 strcat(longPath
, "\\");
586 lstrcpyn(longPath
, shortPath
, _MAX_PATH
);
592 void FilePath::FixName() {
594 // Only used on Windows to use long file names and fix the case of file names
595 char longPath
[_MAX_PATH
];
596 // first try MakeLongPath which corrects the path and the case of filename too
597 if (MakeLongPath(AsFileSystem(), longPath
)) {
600 // On Windows file comparison is done case insensitively so the user can
601 // enter scite.cxx and still open this file, SciTE.cxx. To ensure that the file
602 // is saved with correct capitalisation FindFirstFile is used to find out the
603 // real name of the file.
604 WIN32_FIND_DATA FindFileData
;
605 HANDLE hFind
= ::FindFirstFile(AsFileSystem(), &FindFileData
);
606 FilePath dir
= Directory();
607 if (hFind
!= INVALID_HANDLE_VALUE
) { // FindFirstFile found the file
608 Set(dir
, FindFileData
.cFileName
);
615 FilePathSet
&FilePathSet::operator=(const FilePathSet
&) {
616 // Private so won't be called.
620 FilePathSet::FilePathSet(int size_
) {
622 body
= new FilePath
[size
];
626 FilePathSet::FilePathSet(const FilePathSet
&other
) {
628 lengthBody
= other
.lengthBody
;
629 body
= new FilePath
[size
];
630 for (size_t i
= 0; i
< lengthBody
; i
++) {
631 body
[i
] = other
.body
[i
];
635 FilePathSet::~FilePathSet() {
642 FilePath
FilePathSet::At(size_t pos
) const {
646 void FilePathSet::Append(FilePath fp
) {
647 if (lengthBody
>= size
) {
649 FilePath
*bodyNew
= new FilePath
[size
];
650 for (size_t i
= 0; i
< lengthBody
; i
++) {
651 bodyNew
[i
] = body
[i
];
656 body
[lengthBody
++] = fp
;
659 size_t FilePathSet::Length() const {