| //===--- SourceMgrUtils.cpp - SourceMgr LSP Utils -------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/Support/Path.h" |
| #include <optional> |
| |
| using namespace mlir; |
| using namespace mlir::lsp; |
| |
| //===----------------------------------------------------------------------===// |
| // Utils |
| //===----------------------------------------------------------------------===// |
| |
| /// Find the end of a string whose contents start at the given `curPtr`. Returns |
| /// the position at the end of the string, after a terminal or invalid character |
| /// (e.g. `"` or `\0`). |
| static const char *lexLocStringTok(const char *curPtr) { |
| while (char c = *curPtr++) { |
| // Check for various terminal characters. |
| if (StringRef("\"\n\v\f").contains(c)) |
| return curPtr; |
| |
| // Check for escape sequences. |
| if (c == '\\') { |
| // Check a few known escapes and \xx hex digits. |
| if (*curPtr == '"' || *curPtr == '\\' || *curPtr == 'n' || *curPtr == 't') |
| ++curPtr; |
| else if (llvm::isHexDigit(*curPtr) && llvm::isHexDigit(curPtr[1])) |
| curPtr += 2; |
| else |
| return curPtr; |
| } |
| } |
| |
| // If we hit this point, we've reached the end of the buffer. Update the end |
| // pointer to not point past the buffer. |
| return curPtr - 1; |
| } |
| |
| SMRange lsp::convertTokenLocToRange(SMLoc loc, StringRef identifierChars) { |
| if (!loc.isValid()) |
| return SMRange(); |
| const char *curPtr = loc.getPointer(); |
| |
| // Check if this is a string token. |
| if (*curPtr == '"') { |
| curPtr = lexLocStringTok(curPtr + 1); |
| |
| // Otherwise, default to handling an identifier. |
| } else { |
| // Return if the given character is a valid identifier character. |
| auto isIdentifierChar = [=](char c) { |
| return isalnum(c) || c == '_' || identifierChars.contains(c); |
| }; |
| |
| while (*curPtr && isIdentifierChar(*(++curPtr))) |
| continue; |
| } |
| |
| return SMRange(loc, SMLoc::getFromPointer(curPtr)); |
| } |
| |
| std::optional<std::string> |
| lsp::extractSourceDocComment(llvm::SourceMgr &sourceMgr, SMLoc loc) { |
| // This is a heuristic, and isn't intended to cover every case, but should |
| // cover the most common. We essentially look for a comment preceding the |
| // line, and if we find one, use that as the documentation. |
| if (!loc.isValid()) |
| return std::nullopt; |
| int bufferId = sourceMgr.FindBufferContainingLoc(loc); |
| if (bufferId == 0) |
| return std::nullopt; |
| const char *bufferStart = |
| sourceMgr.getMemoryBuffer(bufferId)->getBufferStart(); |
| StringRef buffer(bufferStart, loc.getPointer() - bufferStart); |
| |
| // Pop the last line from the buffer string. |
| auto popLastLine = [&]() -> std::optional<StringRef> { |
| size_t newlineOffset = buffer.find_last_of('\n'); |
| if (newlineOffset == StringRef::npos) |
| return std::nullopt; |
| StringRef lastLine = buffer.drop_front(newlineOffset).trim(); |
| buffer = buffer.take_front(newlineOffset); |
| return lastLine; |
| }; |
| |
| // Try to pop the current line. |
| if (!popLastLine()) |
| return std::nullopt; |
| |
| // Try to parse a comment string from the source file. |
| SmallVector<StringRef> commentLines; |
| while (std::optional<StringRef> line = popLastLine()) { |
| // Check for a comment at the beginning of the line. |
| if (!line->starts_with("//")) |
| break; |
| |
| // Extract the document string from the comment. |
| commentLines.push_back(line->ltrim('/')); |
| } |
| |
| if (commentLines.empty()) |
| return std::nullopt; |
| return llvm::join(llvm::reverse(commentLines), "\n"); |
| } |
| |
| bool lsp::contains(SMRange range, SMLoc loc) { |
| return range.Start.getPointer() <= loc.getPointer() && |
| loc.getPointer() < range.End.getPointer(); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // SourceMgrInclude |
| //===----------------------------------------------------------------------===// |
| |
| Hover SourceMgrInclude::buildHover() const { |
| Hover hover(range); |
| { |
| llvm::raw_string_ostream hoverOS(hover.contents.value); |
| hoverOS << "`" << llvm::sys::path::filename(uri.file()) << "`\n***\n" |
| << uri.file(); |
| } |
| return hover; |
| } |
| |
| void lsp::gatherIncludeFiles(llvm::SourceMgr &sourceMgr, |
| SmallVectorImpl<SourceMgrInclude> &includes) { |
| for (unsigned i = 1, e = sourceMgr.getNumBuffers(); i < e; ++i) { |
| // Check to see if this file was included by the main file. |
| SMLoc includeLoc = sourceMgr.getBufferInfo(i + 1).IncludeLoc; |
| if (!includeLoc.isValid() || sourceMgr.FindBufferContainingLoc( |
| includeLoc) != sourceMgr.getMainFileID()) |
| continue; |
| |
| // Try to build a URI for this file path. |
| auto *buffer = sourceMgr.getMemoryBuffer(i + 1); |
| llvm::SmallString<256> path(buffer->getBufferIdentifier()); |
| llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true); |
| |
| llvm::Expected<URIForFile> includedFileURI = URIForFile::fromFile(path); |
| if (!includedFileURI) |
| continue; |
| |
| // Find the end of the include token. |
| const char *includeStart = includeLoc.getPointer() - 2; |
| while (*(--includeStart) != '\"') |
| continue; |
| |
| // Push this include. |
| SMRange includeRange(SMLoc::getFromPointer(includeStart), includeLoc); |
| includes.emplace_back(*includedFileURI, Range(sourceMgr, includeRange)); |
| } |
| } |