#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 #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(std::vector const& values, std::string const& start) { return std::any_of( std::begin(values), std::end(values), [&start](std::string const& value) { return StartsWith(value, start); } ); } bool StartsWithAny(std::string const& value, std::vector const& startings) { return std::any_of( std::begin(startings), std::end(startings), [&value](std::string const& starting) { return StartsWith(value, starting); } ); } bool EndsWithAny(std::string const& value, std::vector const& endings) { return std::any_of( std::begin(endings), std::end(endings), [&value](std::string const& ending) { return EndsWith(value, ending); } ); } bool FindAnyPartial(std::string const& value, std::vector const& values) { return std::any_of( std::begin(values), std::end(values), [&value](std::string const& 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(std::string const& 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(std::string const& 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(std::string const& source, std::string const& from, std::string const& 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(std::string const& str, std::string const& 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(std::string const& filename) { std::ifstream cache(filename); return cache.is_open(); } optional ReadContent(AbsolutePath const& 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(AbsolutePath const& filename) { std::vector result; std::ifstream input(filename.path); for (std::string line; SafeGetline(input, line);) { result.emplace_back(line); } return result; } bool WriteToFile(std::string const& filename, std::string const& 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(std::string const& path) { return IsUnixAbsolutePath(path) || IsWindowsAbsolutePath(path); } bool IsUnixAbsolutePath(std::string const& path) { return !path.empty() && path[0] == '/'; } bool IsWindowsAbsolutePath(std::string const& 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(std::string const& 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) { BOOST_IF_CONSTEXPR(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(std::string const& str) { std::wstring wide; BOOST_IF_CONSTEXPR(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(std::string const& 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, std::string const& 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, std::string const& 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(std::string const& 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(std::wstring const& 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 (std::exception const&) { continue; } } } void scanDirsNoRecursive(std::wstring const& 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(std::wstring const& 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 (std::exception const&) { continue; } } } void scanFileNamesUseRecursive(std::wstring const& 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(std::string const& 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(std::string const& 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)); } } } // namespace lsp