1 //===- TableGenServer.cpp - TableGen Language Server ----------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "TableGenServer.h" 10 11 #include "mlir/Support/IndentedOstream.h" 12 #include "mlir/Tools/lsp-server-support/CompilationDatabase.h" 13 #include "mlir/Tools/lsp-server-support/Logging.h" 14 #include "mlir/Tools/lsp-server-support/Protocol.h" 15 #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h" 16 #include "llvm/ADT/IntervalMap.h" 17 #include "llvm/ADT/PointerUnion.h" 18 #include "llvm/ADT/StringMap.h" 19 #include "llvm/Support/Path.h" 20 #include "llvm/TableGen/Parser.h" 21 #include "llvm/TableGen/Record.h" 22 #include <optional> 23 24 using namespace mlir; 25 using llvm::Record; 26 using llvm::RecordKeeper; 27 using llvm::RecordVal; 28 using llvm::SourceMgr; 29 30 /// Returns the range of a lexical token given a SMLoc corresponding to the 31 /// start of an token location. The range is computed heuristically, and 32 /// supports identifier-like tokens, strings, etc. 33 static SMRange convertTokenLocToRange(SMLoc loc) { 34 return lsp::convertTokenLocToRange(loc, "$"); 35 } 36 37 /// Returns a language server uri for the given source location. `mainFileURI` 38 /// corresponds to the uri for the main file of the source manager. 39 static lsp::URIForFile getURIFromLoc(const SourceMgr &mgr, SMLoc loc, 40 const lsp::URIForFile &mainFileURI) { 41 int bufferId = mgr.FindBufferContainingLoc(loc); 42 if (bufferId == 0 || bufferId == static_cast<int>(mgr.getMainFileID())) 43 return mainFileURI; 44 llvm::Expected<lsp::URIForFile> fileForLoc = lsp::URIForFile::fromFile( 45 mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier()); 46 if (fileForLoc) 47 return *fileForLoc; 48 lsp::Logger::error("Failed to create URI for include file: {0}", 49 llvm::toString(fileForLoc.takeError())); 50 return mainFileURI; 51 } 52 53 /// Returns a language server location from the given source range. 54 static lsp::Location getLocationFromLoc(SourceMgr &mgr, SMRange loc, 55 const lsp::URIForFile &uri) { 56 return lsp::Location(getURIFromLoc(mgr, loc.Start, uri), 57 lsp::Range(mgr, loc)); 58 } 59 static lsp::Location getLocationFromLoc(SourceMgr &mgr, SMLoc loc, 60 const lsp::URIForFile &uri) { 61 return getLocationFromLoc(mgr, convertTokenLocToRange(loc), uri); 62 } 63 64 /// Convert the given TableGen diagnostic to the LSP form. 65 static std::optional<lsp::Diagnostic> 66 getLspDiagnoticFromDiag(const llvm::SMDiagnostic &diag, 67 const lsp::URIForFile &uri) { 68 auto *sourceMgr = const_cast<SourceMgr *>(diag.getSourceMgr()); 69 if (!sourceMgr || !diag.getLoc().isValid()) 70 return std::nullopt; 71 72 lsp::Diagnostic lspDiag; 73 lspDiag.source = "tablegen"; 74 lspDiag.category = "Parse Error"; 75 76 // Try to grab a file location for this diagnostic. 77 lsp::Location loc = getLocationFromLoc(*sourceMgr, diag.getLoc(), uri); 78 lspDiag.range = loc.range; 79 80 // Skip diagnostics that weren't emitted within the main file. 81 if (loc.uri != uri) 82 return std::nullopt; 83 84 // Convert the severity for the diagnostic. 85 switch (diag.getKind()) { 86 case SourceMgr::DK_Warning: 87 lspDiag.severity = lsp::DiagnosticSeverity::Warning; 88 break; 89 case SourceMgr::DK_Error: 90 lspDiag.severity = lsp::DiagnosticSeverity::Error; 91 break; 92 case SourceMgr::DK_Note: 93 // Notes are emitted separately from the main diagnostic, so we just treat 94 // them as remarks given that we can't determine the diagnostic to relate 95 // them to. 96 case SourceMgr::DK_Remark: 97 lspDiag.severity = lsp::DiagnosticSeverity::Information; 98 break; 99 } 100 lspDiag.message = diag.getMessage().str(); 101 102 return lspDiag; 103 } 104 105 /// Get the base definition of the given record value, or nullptr if one 106 /// couldn't be found. 107 static std::pair<const Record *, const RecordVal *> 108 getBaseValue(const Record *record, const RecordVal *value) { 109 if (value->isTemplateArg()) 110 return {nullptr, nullptr}; 111 112 // Find a base value for the field in the super classes of the given record. 113 // On success, `record` is updated to the new parent record. 114 StringRef valueName = value->getName(); 115 auto findValueInSupers = [&](const Record *&record) -> const RecordVal * { 116 for (auto [parentRecord, loc] : record->getSuperClasses()) { 117 if (auto *newBase = parentRecord->getValue(valueName)) { 118 record = parentRecord; 119 return newBase; 120 } 121 } 122 return nullptr; 123 }; 124 125 // Try to find the lowest definition of the record value. 126 std::pair<const Record *, const RecordVal *> baseValue = {}; 127 while (const RecordVal *newBase = findValueInSupers(record)) 128 baseValue = {record, newBase}; 129 130 // Check that the base isn't the same as the current value (e.g. if the value 131 // wasn't overridden). 132 if (!baseValue.second || baseValue.second->getLoc() == value->getLoc()) 133 return {nullptr, nullptr}; 134 return baseValue; 135 } 136 137 //===----------------------------------------------------------------------===// 138 // TableGenIndex 139 //===----------------------------------------------------------------------===// 140 141 namespace { 142 /// This class represents a single symbol definition within a TableGen index. It 143 /// contains the definition of the symbol, the location of the symbol, and any 144 /// recorded references. 145 struct TableGenIndexSymbol { 146 TableGenIndexSymbol(const Record *record) 147 : definition(record), 148 defLoc(convertTokenLocToRange(record->getLoc().front())) {} 149 TableGenIndexSymbol(const RecordVal *value) 150 : definition(value), defLoc(convertTokenLocToRange(value->getLoc())) {} 151 virtual ~TableGenIndexSymbol() = default; 152 153 // The main definition of the symbol. 154 PointerUnion<const Record *, const RecordVal *> definition; 155 156 /// The source location of the definition. 157 SMRange defLoc; 158 159 /// The source location of the references of the definition. 160 SmallVector<SMRange> references; 161 }; 162 /// This class represents a single record symbol. 163 struct TableGenRecordSymbol : public TableGenIndexSymbol { 164 TableGenRecordSymbol(const Record *record) : TableGenIndexSymbol(record) {} 165 ~TableGenRecordSymbol() override = default; 166 167 static bool classof(const TableGenIndexSymbol *symbol) { 168 return isa<const Record *>(symbol->definition); 169 } 170 171 /// Return the value of this symbol. 172 const Record *getValue() const { return cast<const Record *>(definition); } 173 }; 174 /// This class represents a single record value symbol. 175 struct TableGenRecordValSymbol : public TableGenIndexSymbol { 176 TableGenRecordValSymbol(const Record *record, const RecordVal *value) 177 : TableGenIndexSymbol(value), record(record) {} 178 ~TableGenRecordValSymbol() override = default; 179 180 static bool classof(const TableGenIndexSymbol *symbol) { 181 return isa<const RecordVal *>(symbol->definition); 182 } 183 184 /// Return the value of this symbol. 185 const RecordVal *getValue() const { 186 return cast<const RecordVal *>(definition); 187 } 188 189 /// The parent record of this symbol. 190 const Record *record; 191 }; 192 193 /// This class provides an index for definitions/uses within a TableGen 194 /// document. It provides efficient lookup of a definition given an input source 195 /// range. 196 class TableGenIndex { 197 public: 198 TableGenIndex() : intervalMap(allocator) {} 199 200 /// Initialize the index with the given RecordKeeper. 201 void initialize(const RecordKeeper &records); 202 203 /// Lookup a symbol for the given location. Returns nullptr if no symbol could 204 /// be found. If provided, `overlappedRange` is set to the range that the 205 /// provided `loc` overlapped with. 206 const TableGenIndexSymbol *lookup(SMLoc loc, 207 SMRange *overlappedRange = nullptr) const; 208 209 private: 210 /// The type of interval map used to store source references. SMRange is 211 /// half-open, so we also need to use a half-open interval map. 212 using MapT = llvm::IntervalMap< 213 const char *, const TableGenIndexSymbol *, 214 llvm::IntervalMapImpl::NodeSizer<const char *, 215 const TableGenIndexSymbol *>::LeafSize, 216 llvm::IntervalMapHalfOpenInfo<const char *>>; 217 218 /// Get or insert a symbol for the given record. 219 TableGenIndexSymbol *getOrInsertDef(const Record *record) { 220 auto it = defToSymbol.try_emplace(record, nullptr); 221 if (it.second) 222 it.first->second = std::make_unique<TableGenRecordSymbol>(record); 223 return &*it.first->second; 224 } 225 /// Get or insert a symbol for the given record value. 226 TableGenIndexSymbol *getOrInsertDef(const Record *record, 227 const RecordVal *value) { 228 auto it = defToSymbol.try_emplace(value, nullptr); 229 if (it.second) { 230 it.first->second = 231 std::make_unique<TableGenRecordValSymbol>(record, value); 232 } 233 return &*it.first->second; 234 } 235 236 /// An allocator for the interval map. 237 MapT::Allocator allocator; 238 239 /// An interval map containing a corresponding definition mapped to a source 240 /// interval. 241 MapT intervalMap; 242 243 /// A mapping between definitions and their corresponding symbol. 244 DenseMap<const void *, std::unique_ptr<TableGenIndexSymbol>> defToSymbol; 245 }; 246 } // namespace 247 248 void TableGenIndex::initialize(const RecordKeeper &records) { 249 intervalMap.clear(); 250 defToSymbol.clear(); 251 252 auto insertRef = [&](TableGenIndexSymbol *sym, SMRange refLoc, 253 bool isDef = false) { 254 const char *startLoc = refLoc.Start.getPointer(); 255 const char *endLoc = refLoc.End.getPointer(); 256 257 // If the location we got was empty, try to lex a token from the start 258 // location. 259 if (startLoc == endLoc) { 260 refLoc = convertTokenLocToRange(SMLoc::getFromPointer(startLoc)); 261 startLoc = refLoc.Start.getPointer(); 262 endLoc = refLoc.End.getPointer(); 263 264 // If the location is still empty, bail on trying to use this reference 265 // location. 266 if (startLoc == endLoc) 267 return; 268 } 269 270 // Check to see if a symbol is already attached to this location. 271 // IntervalMap doesn't allow overlapping inserts, and we don't really 272 // want multiple symbols attached to a source location anyways. This 273 // shouldn't really happen in practice, but we should handle it gracefully. 274 if (!intervalMap.overlaps(startLoc, endLoc)) 275 intervalMap.insert(startLoc, endLoc, sym); 276 277 if (!isDef) 278 sym->references.push_back(refLoc); 279 }; 280 auto classes = 281 llvm::make_pointee_range(llvm::make_second_range(records.getClasses())); 282 auto defs = 283 llvm::make_pointee_range(llvm::make_second_range(records.getDefs())); 284 for (const Record &def : llvm::concat<Record>(classes, defs)) { 285 auto *sym = getOrInsertDef(&def); 286 insertRef(sym, sym->defLoc, /*isDef=*/true); 287 288 // Add references to the definition. 289 for (SMLoc loc : def.getLoc().drop_front()) 290 insertRef(sym, convertTokenLocToRange(loc)); 291 for (SMRange loc : def.getReferenceLocs()) 292 insertRef(sym, loc); 293 294 // Add definitions for any values. 295 for (const RecordVal &value : def.getValues()) { 296 auto *sym = getOrInsertDef(&def, &value); 297 insertRef(sym, sym->defLoc, /*isDef=*/true); 298 for (SMRange refLoc : value.getReferenceLocs()) 299 insertRef(sym, refLoc); 300 } 301 } 302 } 303 304 const TableGenIndexSymbol * 305 TableGenIndex::lookup(SMLoc loc, SMRange *overlappedRange) const { 306 auto it = intervalMap.find(loc.getPointer()); 307 if (!it.valid() || loc.getPointer() < it.start()) 308 return nullptr; 309 310 if (overlappedRange) { 311 *overlappedRange = SMRange(SMLoc::getFromPointer(it.start()), 312 SMLoc::getFromPointer(it.stop())); 313 } 314 return it.value(); 315 } 316 317 //===----------------------------------------------------------------------===// 318 // TableGenTextFile 319 //===----------------------------------------------------------------------===// 320 321 namespace { 322 /// This class represents a text file containing one or more TableGen documents. 323 class TableGenTextFile { 324 public: 325 TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents, 326 int64_t version, 327 const std::vector<std::string> &extraIncludeDirs, 328 std::vector<lsp::Diagnostic> &diagnostics); 329 330 /// Return the current version of this text file. 331 int64_t getVersion() const { return version; } 332 333 /// Update the file to the new version using the provided set of content 334 /// changes. Returns failure if the update was unsuccessful. 335 LogicalResult update(const lsp::URIForFile &uri, int64_t newVersion, 336 ArrayRef<lsp::TextDocumentContentChangeEvent> changes, 337 std::vector<lsp::Diagnostic> &diagnostics); 338 339 //===--------------------------------------------------------------------===// 340 // Definitions and References 341 //===--------------------------------------------------------------------===// 342 343 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, 344 std::vector<lsp::Location> &locations); 345 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, 346 std::vector<lsp::Location> &references); 347 348 //===--------------------------------------------------------------------===// 349 // Document Links 350 //===--------------------------------------------------------------------===// 351 352 void getDocumentLinks(const lsp::URIForFile &uri, 353 std::vector<lsp::DocumentLink> &links); 354 355 //===--------------------------------------------------------------------===// 356 // Hover 357 //===--------------------------------------------------------------------===// 358 359 std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 360 const lsp::Position &hoverPos); 361 lsp::Hover buildHoverForRecord(const Record *record, 362 const SMRange &hoverRange); 363 lsp::Hover buildHoverForTemplateArg(const Record *record, 364 const RecordVal *value, 365 const SMRange &hoverRange); 366 lsp::Hover buildHoverForField(const Record *record, const RecordVal *value, 367 const SMRange &hoverRange); 368 369 private: 370 /// Initialize the text file from the given file contents. 371 void initialize(const lsp::URIForFile &uri, int64_t newVersion, 372 std::vector<lsp::Diagnostic> &diagnostics); 373 374 /// The full string contents of the file. 375 std::string contents; 376 377 /// The version of this file. 378 int64_t version; 379 380 /// The include directories for this file. 381 std::vector<std::string> includeDirs; 382 383 /// The source manager containing the contents of the input file. 384 SourceMgr sourceMgr; 385 386 /// The record keeper containing the parsed tablegen constructs. 387 std::unique_ptr<RecordKeeper> recordKeeper; 388 389 /// The index of the parsed file. 390 TableGenIndex index; 391 392 /// The set of includes of the parsed file. 393 SmallVector<lsp::SourceMgrInclude> parsedIncludes; 394 }; 395 } // namespace 396 397 TableGenTextFile::TableGenTextFile( 398 const lsp::URIForFile &uri, StringRef fileContents, int64_t version, 399 const std::vector<std::string> &extraIncludeDirs, 400 std::vector<lsp::Diagnostic> &diagnostics) 401 : contents(fileContents.str()), version(version) { 402 // Build the set of include directories for this file. 403 llvm::SmallString<32> uriDirectory(uri.file()); 404 llvm::sys::path::remove_filename(uriDirectory); 405 includeDirs.push_back(uriDirectory.str().str()); 406 includeDirs.insert(includeDirs.end(), extraIncludeDirs.begin(), 407 extraIncludeDirs.end()); 408 409 // Initialize the file. 410 initialize(uri, version, diagnostics); 411 } 412 413 LogicalResult 414 TableGenTextFile::update(const lsp::URIForFile &uri, int64_t newVersion, 415 ArrayRef<lsp::TextDocumentContentChangeEvent> changes, 416 std::vector<lsp::Diagnostic> &diagnostics) { 417 if (failed(lsp::TextDocumentContentChangeEvent::applyTo(changes, contents))) { 418 lsp::Logger::error("Failed to update contents of {0}", uri.file()); 419 return failure(); 420 } 421 422 // If the file contents were properly changed, reinitialize the text file. 423 initialize(uri, newVersion, diagnostics); 424 return success(); 425 } 426 427 void TableGenTextFile::initialize(const lsp::URIForFile &uri, 428 int64_t newVersion, 429 std::vector<lsp::Diagnostic> &diagnostics) { 430 version = newVersion; 431 sourceMgr = SourceMgr(); 432 recordKeeper = std::make_unique<RecordKeeper>(); 433 434 // Build a buffer for this file. 435 auto memBuffer = llvm::MemoryBuffer::getMemBuffer(contents, uri.file()); 436 if (!memBuffer) { 437 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 438 return; 439 } 440 sourceMgr.setIncludeDirs(includeDirs); 441 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); 442 443 // This class provides a context argument for the SourceMgr diagnostic 444 // handler. 445 struct DiagHandlerContext { 446 std::vector<lsp::Diagnostic> &diagnostics; 447 const lsp::URIForFile &uri; 448 } handlerContext{diagnostics, uri}; 449 450 // Set the diagnostic handler for the tablegen source manager. 451 sourceMgr.setDiagHandler( 452 [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) { 453 auto *ctx = reinterpret_cast<DiagHandlerContext *>(rawHandlerContext); 454 if (auto lspDiag = getLspDiagnoticFromDiag(diag, ctx->uri)) 455 ctx->diagnostics.push_back(*lspDiag); 456 }, 457 &handlerContext); 458 bool failedToParse = llvm::TableGenParseFile(sourceMgr, *recordKeeper); 459 460 // Process all of the include files. 461 lsp::gatherIncludeFiles(sourceMgr, parsedIncludes); 462 if (failedToParse) 463 return; 464 465 // If we successfully parsed the file, we can now build the index. 466 index.initialize(*recordKeeper); 467 } 468 469 //===----------------------------------------------------------------------===// 470 // TableGenTextFile: Definitions and References 471 //===----------------------------------------------------------------------===// 472 473 void TableGenTextFile::getLocationsOf(const lsp::URIForFile &uri, 474 const lsp::Position &defPos, 475 std::vector<lsp::Location> &locations) { 476 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr); 477 const TableGenIndexSymbol *symbol = index.lookup(posLoc); 478 if (!symbol) 479 return; 480 481 // If this symbol is a record value and the def position is already the def of 482 // the symbol, check to see if the value has a base definition. This allows 483 // for a "go-to-def" on a "let" to resolve the definition in the base class. 484 auto *valSym = dyn_cast<TableGenRecordValSymbol>(symbol); 485 if (valSym && lsp::contains(valSym->defLoc, posLoc)) { 486 if (auto *val = getBaseValue(valSym->record, valSym->getValue()).second) { 487 locations.push_back(getLocationFromLoc(sourceMgr, val->getLoc(), uri)); 488 return; 489 } 490 } 491 492 locations.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri)); 493 } 494 495 void TableGenTextFile::findReferencesOf( 496 const lsp::URIForFile &uri, const lsp::Position &pos, 497 std::vector<lsp::Location> &references) { 498 SMLoc posLoc = pos.getAsSMLoc(sourceMgr); 499 const TableGenIndexSymbol *symbol = index.lookup(posLoc); 500 if (!symbol) 501 return; 502 503 references.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri)); 504 for (SMRange refLoc : symbol->references) 505 references.push_back(getLocationFromLoc(sourceMgr, refLoc, uri)); 506 } 507 508 //===--------------------------------------------------------------------===// 509 // TableGenTextFile: Document Links 510 //===--------------------------------------------------------------------===// 511 512 void TableGenTextFile::getDocumentLinks(const lsp::URIForFile &uri, 513 std::vector<lsp::DocumentLink> &links) { 514 for (const lsp::SourceMgrInclude &include : parsedIncludes) 515 links.emplace_back(include.range, include.uri); 516 } 517 518 //===----------------------------------------------------------------------===// 519 // TableGenTextFile: Hover 520 //===----------------------------------------------------------------------===// 521 522 std::optional<lsp::Hover> 523 TableGenTextFile::findHover(const lsp::URIForFile &uri, 524 const lsp::Position &hoverPos) { 525 // Check for a reference to an include. 526 for (const lsp::SourceMgrInclude &include : parsedIncludes) 527 if (include.range.contains(hoverPos)) 528 return include.buildHover(); 529 530 // Find the symbol at the given location. 531 SMRange hoverRange; 532 SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr); 533 const TableGenIndexSymbol *symbol = index.lookup(posLoc, &hoverRange); 534 if (!symbol) 535 return std::nullopt; 536 537 // Build hover for a Record. 538 if (auto *record = dyn_cast<TableGenRecordSymbol>(symbol)) 539 return buildHoverForRecord(record->getValue(), hoverRange); 540 541 // Build hover for a RecordVal, which is either a template argument or a 542 // field. 543 auto *recordVal = cast<TableGenRecordValSymbol>(symbol); 544 const RecordVal *value = recordVal->getValue(); 545 if (value->isTemplateArg()) 546 return buildHoverForTemplateArg(recordVal->record, value, hoverRange); 547 return buildHoverForField(recordVal->record, value, hoverRange); 548 } 549 550 lsp::Hover TableGenTextFile::buildHoverForRecord(const Record *record, 551 const SMRange &hoverRange) { 552 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 553 { 554 llvm::raw_string_ostream hoverOS(hover.contents.value); 555 556 // Format the type of record this is. 557 if (record->isClass()) { 558 hoverOS << "**class** `" << record->getName() << "`"; 559 } else if (record->isAnonymous()) { 560 hoverOS << "**anonymous class**"; 561 } else { 562 hoverOS << "**def** `" << record->getName() << "`"; 563 } 564 hoverOS << "\n***\n"; 565 566 // Check if this record has summary/description fields. These are often used 567 // to hold documentation for the record. 568 auto printAndFormatField = [&](StringRef fieldName) { 569 // Check that the record actually has the given field, and that it's a 570 // string. 571 const RecordVal *value = record->getValue(fieldName); 572 if (!value || !value->getValue()) 573 return; 574 auto *stringValue = dyn_cast<llvm::StringInit>(value->getValue()); 575 if (!stringValue) 576 return; 577 578 raw_indented_ostream ros(hoverOS); 579 ros.printReindented(stringValue->getValue().rtrim(" \t")); 580 hoverOS << "\n***\n"; 581 }; 582 printAndFormatField("summary"); 583 printAndFormatField("description"); 584 585 // Check for documentation in the source file. 586 if (std::optional<std::string> doc = 587 lsp::extractSourceDocComment(sourceMgr, record->getLoc().front())) { 588 hoverOS << "\n" << *doc << "\n"; 589 } 590 } 591 return hover; 592 } 593 594 lsp::Hover TableGenTextFile::buildHoverForTemplateArg( 595 const Record *record, const RecordVal *value, const SMRange &hoverRange) { 596 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 597 { 598 llvm::raw_string_ostream hoverOS(hover.contents.value); 599 StringRef name = value->getName().rsplit(':').second; 600 601 hoverOS << "**template arg** `" << name << "`\n***\nType: `"; 602 value->getType()->print(hoverOS); 603 hoverOS << "`\n"; 604 } 605 return hover; 606 } 607 608 lsp::Hover TableGenTextFile::buildHoverForField(const Record *record, 609 const RecordVal *value, 610 const SMRange &hoverRange) { 611 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 612 { 613 llvm::raw_string_ostream hoverOS(hover.contents.value); 614 hoverOS << "**field** `" << value->getName() << "`\n***\nType: `"; 615 value->getType()->print(hoverOS); 616 hoverOS << "`\n***\n"; 617 618 // Check for documentation in the source file. 619 if (std::optional<std::string> doc = 620 lsp::extractSourceDocComment(sourceMgr, value->getLoc())) { 621 hoverOS << "\n" << *doc << "\n"; 622 hoverOS << "\n***\n"; 623 } 624 625 // Check to see if there is a base value that we can use for 626 // documentation. 627 auto [baseRecord, baseValue] = getBaseValue(record, value); 628 if (baseValue) { 629 if (std::optional<std::string> doc = 630 lsp::extractSourceDocComment(sourceMgr, baseValue->getLoc())) { 631 hoverOS << "\n *From `" << baseRecord->getName() << "`*:\n\n" 632 << *doc << "\n"; 633 } 634 } 635 } 636 return hover; 637 } 638 639 //===----------------------------------------------------------------------===// 640 // TableGenServer::Impl 641 //===----------------------------------------------------------------------===// 642 643 struct lsp::TableGenServer::Impl { 644 explicit Impl(const Options &options) 645 : options(options), compilationDatabase(options.compilationDatabases) {} 646 647 /// TableGen LSP options. 648 const Options &options; 649 650 /// The compilation database containing additional information for files 651 /// passed to the server. 652 lsp::CompilationDatabase compilationDatabase; 653 654 /// The files held by the server, mapped by their URI file name. 655 llvm::StringMap<std::unique_ptr<TableGenTextFile>> files; 656 }; 657 658 //===----------------------------------------------------------------------===// 659 // TableGenServer 660 //===----------------------------------------------------------------------===// 661 662 lsp::TableGenServer::TableGenServer(const Options &options) 663 : impl(std::make_unique<Impl>(options)) {} 664 lsp::TableGenServer::~TableGenServer() = default; 665 666 void lsp::TableGenServer::addDocument(const URIForFile &uri, StringRef contents, 667 int64_t version, 668 std::vector<Diagnostic> &diagnostics) { 669 // Build the set of additional include directories. 670 std::vector<std::string> additionalIncludeDirs = impl->options.extraDirs; 671 const auto &fileInfo = impl->compilationDatabase.getFileInfo(uri.file()); 672 llvm::append_range(additionalIncludeDirs, fileInfo.includeDirs); 673 674 impl->files[uri.file()] = std::make_unique<TableGenTextFile>( 675 uri, contents, version, additionalIncludeDirs, diagnostics); 676 } 677 678 void lsp::TableGenServer::updateDocument( 679 const URIForFile &uri, ArrayRef<TextDocumentContentChangeEvent> changes, 680 int64_t version, std::vector<Diagnostic> &diagnostics) { 681 // Check that we actually have a document for this uri. 682 auto it = impl->files.find(uri.file()); 683 if (it == impl->files.end()) 684 return; 685 686 // Try to update the document. If we fail, erase the file from the server. A 687 // failed updated generally means we've fallen out of sync somewhere. 688 if (failed(it->second->update(uri, version, changes, diagnostics))) 689 impl->files.erase(it); 690 } 691 692 std::optional<int64_t> 693 lsp::TableGenServer::removeDocument(const URIForFile &uri) { 694 auto it = impl->files.find(uri.file()); 695 if (it == impl->files.end()) 696 return std::nullopt; 697 698 int64_t version = it->second->getVersion(); 699 impl->files.erase(it); 700 return version; 701 } 702 703 void lsp::TableGenServer::getLocationsOf(const URIForFile &uri, 704 const Position &defPos, 705 std::vector<Location> &locations) { 706 auto fileIt = impl->files.find(uri.file()); 707 if (fileIt != impl->files.end()) 708 fileIt->second->getLocationsOf(uri, defPos, locations); 709 } 710 711 void lsp::TableGenServer::findReferencesOf(const URIForFile &uri, 712 const Position &pos, 713 std::vector<Location> &references) { 714 auto fileIt = impl->files.find(uri.file()); 715 if (fileIt != impl->files.end()) 716 fileIt->second->findReferencesOf(uri, pos, references); 717 } 718 719 void lsp::TableGenServer::getDocumentLinks( 720 const URIForFile &uri, std::vector<DocumentLink> &documentLinks) { 721 auto fileIt = impl->files.find(uri.file()); 722 if (fileIt != impl->files.end()) 723 return fileIt->second->getDocumentLinks(uri, documentLinks); 724 } 725 726 std::optional<lsp::Hover> 727 lsp::TableGenServer::findHover(const URIForFile &uri, 728 const Position &hoverPos) { 729 auto fileIt = impl->files.find(uri.file()); 730 if (fileIt != impl->files.end()) 731 return fileIt->second->findHover(uri, hoverPos); 732 return std::nullopt; 733 } 734