#include "LibLsp/lsp/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include "LibLsp/lsp/lsPosition.h" #include "utf8.h" #ifdef _WIN32 #include #endif // DEFAULT_RESOURCE_DIRECTORY is passed with quotes for non-MSVC compilers, ie, // foo vs "foo". #if defined(_MSC_VER) #define _STRINGIFY(x) #x #define ENSURE_STRING_MACRO_ARGUMENT(x) _STRINGIFY(x) #else #define ENSURE_STRING_MACRO_ARGUMENT(x) x #endif #include #include #include #include namespace lsp { // See http://stackoverflow.com/a/2072890 bool EndsWith(std::string value, std::string ending) { if (ending.size() > value.size()) return false; return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } bool StartsWith(std::string value, std::string start) { if (start.size() > value.size()) return false; return std::equal(start.begin(), start.end(), value.begin()); } bool AnyStartsWith(const std::vector& values, const std::string& start) { return std::any_of( std::begin(values), std::end(values), [&start](const std::string& value) { return StartsWith(value, start); }); } bool StartsWithAny(const std::string& value, const std::vector& startings) { return std::any_of(std::begin(startings), std::end(startings), [&value](const std::string& starting) { return StartsWith(value, starting); }); } bool EndsWithAny(const std::string& value, const std::vector& endings) { return std::any_of( std::begin(endings), std::end(endings), [&value](const std::string& ending) { return EndsWith(value, ending); }); } bool FindAnyPartial(const std::string& value, const std::vector& values) { return std::any_of(std::begin(values), std::end(values), [&value](const std::string& v) { return value.find(v) != std::string::npos; }); } std::string GetDirName(std::string path) { ReplaceAll(path, "\\", "/"); if (path.size() && path.back() == '/') path.pop_back(); size_t last_slash = path.find_last_of('/'); if (last_slash == std::string::npos) return "./"; return path.substr(0, last_slash + 1); } std::string GetBaseName(const std::string& path) { size_t last_slash = path.find_last_of('/'); if (last_slash != std::string::npos && (last_slash + 1) < path.size()) return path.substr(last_slash + 1); return path; } std::string StripFileType(const std::string& path) { size_t last_period = path.find_last_of('.'); if (last_period != std::string::npos) return path.substr(0, last_period); return path; } // See http://stackoverflow.com/a/29752943 std::string ReplaceAll(const std::string& source, const std::string& from, const std::string& to) { std::string result; result.reserve(source.length()); // avoids a few memory allocations std::string::size_type last_pos = 0; std::string::size_type find_pos; while (std::string::npos != (find_pos = source.find(from, last_pos))) { result.append(source, last_pos, find_pos - last_pos); result += to; last_pos = find_pos + from.length(); } // Care for the rest after last occurrence result += source.substr(last_pos); return result; } std::vector SplitString(const std::string& str, const std::string& delimiter) { // http://stackoverflow.com/a/13172514 std::vector strings; std::string::size_type pos = 0; std::string::size_type prev = 0; while ((pos = str.find(delimiter, prev)) != std::string::npos) { strings.emplace_back(str.substr(prev, pos - prev)); prev = pos + 1; } // To get the last substring (or only, if delimiter is not found) strings.emplace_back(str.substr(prev)); return strings; } void EnsureEndsInSlash(std::string& path) { if (path.empty() || path[path.size() - 1] != '/') path += '/'; } std::string EscapeFileName(std::string path) { if (path.size() && path.back() == '/') path.pop_back(); std::replace(path.begin(), path.end(), '\\', '@'); std::replace(path.begin(), path.end(), '/', '@'); std::replace(path.begin(), path.end(), ':', '@'); return path; } // http://stackoverflow.com/a/6089413 std::istream& SafeGetline(std::istream& is, std::string& t) { t.clear(); // The characters in the stream are read one-by-one using a std::streambuf. // That is faster than reading them one-by-one using the std::istream. Code // that uses streambuf this way must be guarded by a sentry object. The sentry // object performs various tasks, such as thread synchronization and updating // the stream state. std::istream::sentry se(is, true); std::streambuf* sb = is.rdbuf(); for (;;) { int c = sb->sbumpc(); if (c == EOF) { // Also handle the case when the last line has no line ending if (t.empty()) is.setstate(std::ios::eofbit); return is; } t += (char)c; if (c == '\n') return is; } } bool FileExists(const std::string& filename) { std::ifstream cache(filename); return cache.is_open(); } optional ReadContent(const AbsolutePath& filename) { std::ifstream cache; cache.open(filename.path); try { return std::string(std::istreambuf_iterator(cache), std::istreambuf_iterator()); } catch (std::ios_base::failure&) { return {}; } } std::vector ReadLinesWithEnding(const AbsolutePath& filename) { std::vector result; std::ifstream input(filename.path); for (std::string line; SafeGetline(input, line);) result.emplace_back(line); return result; } bool WriteToFile(const std::string& filename, const std::string& content) { std::ofstream file(filename, std::ios::out | std::ios::trunc | std::ios::binary); if (!file.good()) { return false; } file << content; return true; } std::string FormatMicroseconds(long long microseconds) { long long milliseconds = microseconds / 1000; long long remaining = microseconds - milliseconds; // Only show two digits after the dot. while (remaining >= 100) remaining /= 10; return std::to_string(milliseconds) + "." + std::to_string(remaining) + "ms"; } std::string UpdateToRnNewlines(std::string output) { size_t idx = 0; while (true) { idx = output.find('\n', idx); // No more matches. if (idx == std::string::npos) break; // Skip an existing "\r\n" match. if (idx > 0 && output[idx - 1] == '\r') { ++idx; continue; } // Replace "\n" with "\r|n". output.replace(output.begin() + idx, output.begin() + idx + 1, "\r\n"); } return output; } bool IsAbsolutePath(const std::string& path) { return IsUnixAbsolutePath(path) || IsWindowsAbsolutePath(path); } bool IsUnixAbsolutePath(const std::string& path) { return !path.empty() && path[0] == '/'; } bool IsWindowsAbsolutePath(const std::string& path) { auto is_drive_letter = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }; return path.size() > 3 && path[1] == ':' && (path[2] == '/' || path[2] == '\\') && is_drive_letter(path[0]); } bool IsDirectory(const std::string& path) { struct stat path_stat; if (stat(path.c_str(), &path_stat) != 0) { perror("cannot access path"); return false; } return path_stat.st_mode & S_IFDIR; } std::string ws2s(std::wstring const& wstr) { if(sizeof(wchar_t) == 2){ std::string narrow; utf8::utf16to8(wstr.begin(), wstr.end(), std::back_inserter(narrow)); return narrow; }else{ std::string narrow; utf8::utf32to8(wstr.begin(), wstr.end(), std::back_inserter(narrow)); return narrow; } } std::wstring s2ws(const std::string& str) { std::wstring wide; if(sizeof(wchar_t) == 2){ utf8::utf8to16(str.begin(), str.end(), std::back_inserter(wide)); return wide; }else{ utf8::utf8to32(str.begin(), str.end(), std::back_inserter(wide)); return wide; } } #ifdef _WIN32 #else // Returns the canonicalized absolute pathname, without expanding symbolic // links. This is a variant of realpath(2), C++ rewrite of // https://github.com/freebsd/freebsd/blob/master/lib/libc/stdlib/realpath.c AbsolutePath RealPathNotExpandSymlink(std::string path, bool ensure_exists) { if (path.empty()) { errno = EINVAL; return {}; } if (path[0] == '\0') { errno = ENOENT; return {}; } // Do not use PATH_MAX because it is tricky on Linux. // See https://eklitzke.org/path-max-is-tricky char tmp[1024]; std::string resolved; size_t i = 0; struct stat sb; if (path[0] == '/') { resolved = "/"; i = 1; } else { if (!getcwd(tmp, sizeof tmp) && ensure_exists) return {}; resolved = tmp; } while (i < path.size()) { auto j = path.find('/', i); if (j == std::string::npos) j = path.size(); auto next_token = path.substr(i, j - i); i = j + 1; if (resolved.back() != '/') resolved += '/'; if (next_token.empty() || next_token == ".") { // Handle consequential slashes and "." continue; } else if (next_token == "..") { // Strip the last path component except when it is single "/" if (resolved.size() > 1) resolved.resize(resolved.rfind('/', resolved.size() - 2) + 1); continue; } // Append the next path component. // Here we differ from realpath(3), we use stat(2) instead of // lstat(2) because we do not want to resolve symlinks. resolved += next_token; if (stat(resolved.c_str(), &sb) != 0 && ensure_exists) return {}; if (!S_ISDIR(sb.st_mode) && j < path.size() && ensure_exists) { errno = ENOTDIR; return {}; } } // Remove trailing slash except when a single "/". if (resolved.size() > 1 && resolved.back() == '/') resolved.pop_back(); return AbsolutePath(resolved, true /*validate*/); } #endif AbsolutePath NormalizePath(const std::string& path0, bool ensure_exists , bool force_lower_on_windows) { #ifdef _WIN32 std::wstring path = lsp::s2ws(path0); wchar_t buffer[MAX_PATH] = (L""); // Normalize the path name, ie, resolve `..`. unsigned long len = GetFullPathNameW(path.c_str(), MAX_PATH, buffer, nullptr); if (!len) return {}; path = std::wstring(buffer, len); // Get the actual casing of the path, ie, if the file on disk is `C:\FooBar` // and this function is called with `c:\fooBar` this will return `c:\FooBar`. // (drive casing is lowercase). if (ensure_exists) { len = GetLongPathNameW(path.c_str(), buffer, MAX_PATH); if (!len) return {}; path = std::wstring(buffer, len); } // Empty paths have no meaning. if (path.empty()) return {}; // We may need to normalize the drive name to upper-case; at the moment // vscode sends lower-case path names. /* path[0] = toupper(path[0]); */ // Make the path all lower-case, since windows is case-insensitive. if (force_lower_on_windows) { for (size_t i = 0; i < path.size(); ++i) path[i] = (wchar_t)tolower(path[i]); } // cquery assumes forward-slashes. std::replace(path.begin(), path.end(), '\\', '/'); return AbsolutePath(lsp::ws2s(path), false /*validate*/); #else return RealPathNotExpandSymlink(path0, ensure_exists); #endif } // VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent // text documents. // We use a UTF-8 iterator to approximate UTF-16 in the specification (weird). // This is good enough and fails only for UTF-16 surrogate pairs. int GetOffsetForPosition(lsPosition position, const std::string& content) { size_t i = 0; // Iterate lines until we have found the correct line. while (position.line > 0 && i < content.size()) { if (content[i] == '\n') position.line--; i++; } // Iterate characters on the target line. while (position.character > 0 && i < content.size()) { if (uint8_t(content[i++]) >= 128) { // Skip 0b10xxxxxx while (i < content.size() && uint8_t(content[i]) >= 128 && uint8_t(content[i]) < 192) i++; } position.character--; } return int(i); } lsPosition GetPositionForOffset(size_t offset,const std::string& content) { lsPosition result; for (size_t i = 0; i < offset && i < content.length(); ++i) { if (content[i] == '\n') { result.line++; result.character = 0; } else { result.character++; } } return result; } lsPosition CharPos(const std::string& search, char character, int character_offset) { lsPosition result; size_t index = 0; while (index < search.size()) { char c = search[index]; if (c == character) break; if (c == '\n') { result.line += 1; result.character = 0; } else { result.character += 1; } ++index; } assert(index < search.size()); result.character += character_offset; return result; } void scanDirsUseRecursive(const std::wstring& rootPath, std::vector& ret) { namespace fs = boost::filesystem; fs::path fullpath(rootPath); if (!fs::exists(fullpath)) { return; } fs::recursive_directory_iterator end_iter; for (fs::recursive_directory_iterator iter(fullpath); iter != end_iter; iter++) { try { if (fs::is_directory(*iter)) { ret.push_back(iter->path().wstring()); } } catch (const std::exception& ex) { continue; } } } void scanDirsNoRecursive(const std::wstring& rootPath, std::vector& ret) { namespace fs = boost::filesystem; boost::filesystem::path myPath(rootPath); if (!fs::exists(rootPath)) { return; } boost::filesystem::directory_iterator endIter; for (boost::filesystem::directory_iterator iter(myPath); iter != endIter; iter++) { if (boost::filesystem::is_directory(*iter)) { ret.push_back(iter->path().wstring()); } } } void scanFilesUseRecursive( const std::wstring& rootPath, std::vector& ret, std::wstring suf) { namespace fs = boost::filesystem; boost::to_lower(suf); fs::path fullpath(rootPath); if (!fs::exists(fullpath)) { return; } fs::recursive_directory_iterator end_iter; for (fs::recursive_directory_iterator iter(fullpath); iter != end_iter; iter++) { try { if (!fs::is_directory(*iter) && fs::is_regular_file(*iter)) { auto temp_path = iter->path().wstring(); auto size = suf.size(); if (!size) { ret.push_back(std::move(temp_path)); } else { if (temp_path.size() < size) continue; auto suf_temp = temp_path.substr(temp_path.size() - size); boost::to_lower(suf_temp); if (suf_temp == suf) { ret.push_back(std::move(temp_path)); } } } } catch (const std::exception&) { continue; } } } void scanFileNamesUseRecursive(const std::wstring& rootPath, std::vector& ret, std::wstring strSuf) { scanFilesUseRecursive(rootPath, ret, strSuf); std::vector names; for (auto& it : ret) { if (it.size() >= rootPath.size()) { names.push_back(it.substr(rootPath.size())); } } ret.swap(names); } void scanFileNamesUseRecursive(const std::string& rootPath, std::vector& ret, std::string strSuf) { std::vector out; scanFileNamesUseRecursive(s2ws(rootPath), out, s2ws(strSuf)); for (auto& it : out) { ret.push_back(ws2s(it)); } } void scanFilesUseRecursive(const std::string& rootPath, std::vector& ret, std::string strSuf) { std::vector out; scanFilesUseRecursive(s2ws(rootPath), out, s2ws(strSuf)); for (auto& it : out) { ret.push_back(ws2s(it)); } } }