15de12bb7SRiver Riddle //===--- SourceMgrUtils.cpp - SourceMgr LSP Utils -------------------------===//
25de12bb7SRiver Riddle //
35de12bb7SRiver Riddle // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45de12bb7SRiver Riddle // See https://llvm.org/LICENSE.txt for license information.
55de12bb7SRiver Riddle // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65de12bb7SRiver Riddle //
75de12bb7SRiver Riddle //===----------------------------------------------------------------------===//
85de12bb7SRiver Riddle
9305d7185SRiver Riddle #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h"
10b0abd489SElliot Goodrich #include "llvm/ADT/StringExtras.h"
11682ca00eSRiver Riddle #include "llvm/Support/Path.h"
12a1fe1f5fSKazu Hirata #include <optional>
135de12bb7SRiver Riddle
145de12bb7SRiver Riddle using namespace mlir;
155de12bb7SRiver Riddle using namespace mlir::lsp;
165de12bb7SRiver Riddle
17682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
18682ca00eSRiver Riddle // Utils
19682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
20682ca00eSRiver Riddle
215de12bb7SRiver Riddle /// Find the end of a string whose contents start at the given `curPtr`. Returns
225de12bb7SRiver Riddle /// the position at the end of the string, after a terminal or invalid character
235de12bb7SRiver Riddle /// (e.g. `"` or `\0`).
lexLocStringTok(const char * curPtr)245de12bb7SRiver Riddle static const char *lexLocStringTok(const char *curPtr) {
255de12bb7SRiver Riddle while (char c = *curPtr++) {
265de12bb7SRiver Riddle // Check for various terminal characters.
275de12bb7SRiver Riddle if (StringRef("\"\n\v\f").contains(c))
285de12bb7SRiver Riddle return curPtr;
295de12bb7SRiver Riddle
305de12bb7SRiver Riddle // Check for escape sequences.
315de12bb7SRiver Riddle if (c == '\\') {
325de12bb7SRiver Riddle // Check a few known escapes and \xx hex digits.
335de12bb7SRiver Riddle if (*curPtr == '"' || *curPtr == '\\' || *curPtr == 'n' || *curPtr == 't')
345de12bb7SRiver Riddle ++curPtr;
355de12bb7SRiver Riddle else if (llvm::isHexDigit(*curPtr) && llvm::isHexDigit(curPtr[1]))
365de12bb7SRiver Riddle curPtr += 2;
375de12bb7SRiver Riddle else
385de12bb7SRiver Riddle return curPtr;
395de12bb7SRiver Riddle }
405de12bb7SRiver Riddle }
415de12bb7SRiver Riddle
425de12bb7SRiver Riddle // If we hit this point, we've reached the end of the buffer. Update the end
435de12bb7SRiver Riddle // pointer to not point past the buffer.
445de12bb7SRiver Riddle return curPtr - 1;
455de12bb7SRiver Riddle }
465de12bb7SRiver Riddle
convertTokenLocToRange(SMLoc loc,StringRef identifierChars)47305d7185SRiver Riddle SMRange lsp::convertTokenLocToRange(SMLoc loc, StringRef identifierChars) {
485de12bb7SRiver Riddle if (!loc.isValid())
495de12bb7SRiver Riddle return SMRange();
505de12bb7SRiver Riddle const char *curPtr = loc.getPointer();
515de12bb7SRiver Riddle
525de12bb7SRiver Riddle // Check if this is a string token.
535de12bb7SRiver Riddle if (*curPtr == '"') {
545de12bb7SRiver Riddle curPtr = lexLocStringTok(curPtr + 1);
555de12bb7SRiver Riddle
565de12bb7SRiver Riddle // Otherwise, default to handling an identifier.
575de12bb7SRiver Riddle } else {
585de12bb7SRiver Riddle // Return if the given character is a valid identifier character.
59305d7185SRiver Riddle auto isIdentifierChar = [=](char c) {
60305d7185SRiver Riddle return isalnum(c) || c == '_' || identifierChars.contains(c);
615de12bb7SRiver Riddle };
625de12bb7SRiver Riddle
635de12bb7SRiver Riddle while (*curPtr && isIdentifierChar(*(++curPtr)))
645de12bb7SRiver Riddle continue;
655de12bb7SRiver Riddle }
665de12bb7SRiver Riddle
675de12bb7SRiver Riddle return SMRange(loc, SMLoc::getFromPointer(curPtr));
685de12bb7SRiver Riddle }
69682ca00eSRiver Riddle
700a81ace0SKazu Hirata std::optional<std::string>
extractSourceDocComment(llvm::SourceMgr & sourceMgr,SMLoc loc)710a81ace0SKazu Hirata lsp::extractSourceDocComment(llvm::SourceMgr &sourceMgr, SMLoc loc) {
723e2ad376SRiver Riddle // This is a heuristic, and isn't intended to cover every case, but should
733e2ad376SRiver Riddle // cover the most common. We essentially look for a comment preceding the
743e2ad376SRiver Riddle // line, and if we find one, use that as the documentation.
753e2ad376SRiver Riddle if (!loc.isValid())
761a36588eSKazu Hirata return std::nullopt;
773e2ad376SRiver Riddle int bufferId = sourceMgr.FindBufferContainingLoc(loc);
783e2ad376SRiver Riddle if (bufferId == 0)
791a36588eSKazu Hirata return std::nullopt;
803e2ad376SRiver Riddle const char *bufferStart =
813e2ad376SRiver Riddle sourceMgr.getMemoryBuffer(bufferId)->getBufferStart();
823e2ad376SRiver Riddle StringRef buffer(bufferStart, loc.getPointer() - bufferStart);
833e2ad376SRiver Riddle
843e2ad376SRiver Riddle // Pop the last line from the buffer string.
850a81ace0SKazu Hirata auto popLastLine = [&]() -> std::optional<StringRef> {
8633b51588Sserge-sans-paille size_t newlineOffset = buffer.find_last_of('\n');
873e2ad376SRiver Riddle if (newlineOffset == StringRef::npos)
881a36588eSKazu Hirata return std::nullopt;
893e2ad376SRiver Riddle StringRef lastLine = buffer.drop_front(newlineOffset).trim();
903e2ad376SRiver Riddle buffer = buffer.take_front(newlineOffset);
913e2ad376SRiver Riddle return lastLine;
923e2ad376SRiver Riddle };
933e2ad376SRiver Riddle
943e2ad376SRiver Riddle // Try to pop the current line.
953e2ad376SRiver Riddle if (!popLastLine())
961a36588eSKazu Hirata return std::nullopt;
973e2ad376SRiver Riddle
983e2ad376SRiver Riddle // Try to parse a comment string from the source file.
993e2ad376SRiver Riddle SmallVector<StringRef> commentLines;
1000a81ace0SKazu Hirata while (std::optional<StringRef> line = popLastLine()) {
1013e2ad376SRiver Riddle // Check for a comment at the beginning of the line.
10288d319a2SKazu Hirata if (!line->starts_with("//"))
1033e2ad376SRiver Riddle break;
1043e2ad376SRiver Riddle
1053e2ad376SRiver Riddle // Extract the document string from the comment.
106*abaa79b2SKazu Hirata commentLines.push_back(line->ltrim('/'));
1073e2ad376SRiver Riddle }
1083e2ad376SRiver Riddle
1093e2ad376SRiver Riddle if (commentLines.empty())
1101a36588eSKazu Hirata return std::nullopt;
1113e2ad376SRiver Riddle return llvm::join(llvm::reverse(commentLines), "\n");
1123e2ad376SRiver Riddle }
1133e2ad376SRiver Riddle
contains(SMRange range,SMLoc loc)11499e24123SRiver Riddle bool lsp::contains(SMRange range, SMLoc loc) {
11599e24123SRiver Riddle return range.Start.getPointer() <= loc.getPointer() &&
11699e24123SRiver Riddle loc.getPointer() < range.End.getPointer();
11799e24123SRiver Riddle }
11899e24123SRiver Riddle
119682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
120682ca00eSRiver Riddle // SourceMgrInclude
121682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
122682ca00eSRiver Riddle
buildHover() const123682ca00eSRiver Riddle Hover SourceMgrInclude::buildHover() const {
124682ca00eSRiver Riddle Hover hover(range);
125682ca00eSRiver Riddle {
126682ca00eSRiver Riddle llvm::raw_string_ostream hoverOS(hover.contents.value);
127682ca00eSRiver Riddle hoverOS << "`" << llvm::sys::path::filename(uri.file()) << "`\n***\n"
128682ca00eSRiver Riddle << uri.file();
129682ca00eSRiver Riddle }
130682ca00eSRiver Riddle return hover;
131682ca00eSRiver Riddle }
132682ca00eSRiver Riddle
gatherIncludeFiles(llvm::SourceMgr & sourceMgr,SmallVectorImpl<SourceMgrInclude> & includes)133682ca00eSRiver Riddle void lsp::gatherIncludeFiles(llvm::SourceMgr &sourceMgr,
134682ca00eSRiver Riddle SmallVectorImpl<SourceMgrInclude> &includes) {
135682ca00eSRiver Riddle for (unsigned i = 1, e = sourceMgr.getNumBuffers(); i < e; ++i) {
136682ca00eSRiver Riddle // Check to see if this file was included by the main file.
137682ca00eSRiver Riddle SMLoc includeLoc = sourceMgr.getBufferInfo(i + 1).IncludeLoc;
138682ca00eSRiver Riddle if (!includeLoc.isValid() || sourceMgr.FindBufferContainingLoc(
139682ca00eSRiver Riddle includeLoc) != sourceMgr.getMainFileID())
140682ca00eSRiver Riddle continue;
141682ca00eSRiver Riddle
142682ca00eSRiver Riddle // Try to build a URI for this file path.
143682ca00eSRiver Riddle auto *buffer = sourceMgr.getMemoryBuffer(i + 1);
144682ca00eSRiver Riddle llvm::SmallString<256> path(buffer->getBufferIdentifier());
145682ca00eSRiver Riddle llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
146682ca00eSRiver Riddle
147682ca00eSRiver Riddle llvm::Expected<URIForFile> includedFileURI = URIForFile::fromFile(path);
148682ca00eSRiver Riddle if (!includedFileURI)
149682ca00eSRiver Riddle continue;
150682ca00eSRiver Riddle
151682ca00eSRiver Riddle // Find the end of the include token.
152682ca00eSRiver Riddle const char *includeStart = includeLoc.getPointer() - 2;
153682ca00eSRiver Riddle while (*(--includeStart) != '\"')
154682ca00eSRiver Riddle continue;
155682ca00eSRiver Riddle
156682ca00eSRiver Riddle // Push this include.
157682ca00eSRiver Riddle SMRange includeRange(SMLoc::getFromPointer(includeStart), includeLoc);
158682ca00eSRiver Riddle includes.emplace_back(*includedFileURI, Range(sourceMgr, includeRange));
159682ca00eSRiver Riddle }
160682ca00eSRiver Riddle }
161