text: // slang-doc-extractor.cpp
  #include "slang-doc-extractor.h"
  
  #include "../core/slang-string-util.h"
  
  namespace Slang {
  
  /* TODO(JS):
  
  * If Decls hand SourceRange, then we could use the range to simplify getting the Post markup, as will be trivial to get to the 'end'
  * Need to handle preceeding * in some markup styles
  * If we want to be able to disable markup we need a mechanism to do this. Probably define source ranges.
  
  * Need a way to take the extracted markup and produce suitable markdown
  ** This will need to display the decoration appropriately
  */
  
  /* static */UnownedStringSlice DocMarkupExtractor::removeStart(MarkupType type, const UnownedStringSlice& comment)
  {
      switch (type)
      {
          case MarkupType::BlockBefore:
          {
              if (comment.startsWith(UnownedStringSlice::fromLiteral("/**")) ||
                  comment.startsWith(UnownedStringSlice::fromLiteral("/*!")))
              {
                  /// /**  */ or /*!  */.
                  return comment.tail(3);
              }
              return comment;
          }   
          case MarkupType::BlockAfter:
          {
              
              if (comment.startsWith(UnownedStringSlice::fromLiteral("/**<")) ||
                  comment.startsWith(UnownedStringSlice::fromLiteral("/*!<")))
              {
                  /// /*!< */ or /**< */
                  return comment.tail(4);
              }
              return comment;
          }
  
          case MarkupType::LineBangBefore:
          {
              return comment.startsWith(UnownedStringSlice::fromLiteral("//!")) ? comment.tail(3) : comment;
          }
          case MarkupType::LineSlashBefore:
          {
              return comment.startsWith(UnownedStringSlice::fromLiteral("///")) ? comment.tail(3) : comment;
          }
  
          case MarkupType::LineBangAfter:
          {
              /// //!< Can be multiple lines
              return comment.startsWith(UnownedStringSlice::fromLiteral("//!<")) ? comment.tail(4) : comment;
          }
          case MarkupType::LineSlashAfter:
          {
              return comment.startsWith(UnownedStringSlice::fromLiteral("///<")) ? comment.tail(4) : comment;
          }
          default: break;
      }
      return comment;
  }
  
  static Index _findTokenIndex(SourceLoc loc, const Token* toks, Index numToks)
  {
      // Use a binary search to find the token
      Index lo = 0;
      Index hi = numToks;
  
      while (lo + 1 < hi)
      {
          const Index mid = (hi + lo) >> 1;
          const Token& midToken = toks[mid];
  
          if (midToken.loc == loc)
          {
              return mid;
          }
  
          if (midToken.loc.getRaw() <= loc.getRaw())
          {
              lo = mid;
          }
          else
          {
              hi = mid;
          }
      }
  
      // Not found
      return -1;
  }
  
  /* static */DocMarkupExtractor::MarkupFlags DocMarkupExtractor::getFlags(MarkupType type)
  {
      switch (type)
      {
          default:
          case MarkupType::None:              return 0;
          case MarkupType::BlockBefore:      return MarkupFlag::Before | MarkupFlag::IsBlock; 
          case MarkupType::BlockAfter:       return MarkupFlag::After  | MarkupFlag::IsBlock;
  
          case MarkupType::LineBangBefore:     return MarkupFlag::Before | MarkupFlag::IsMultiToken; 
          case MarkupType::LineSlashBefore:    return MarkupFlag::Before | MarkupFlag::IsMultiToken; 
  
          case MarkupType::LineBangAfter:      return MarkupFlag::After | MarkupFlag::IsMultiToken; 
          case MarkupType::LineSlashAfter:     return MarkupFlag::After | MarkupFlag::IsMultiToken;
      }
  }
  
  /* static */DocMarkupExtractor::MarkupType DocMarkupExtractor::findMarkupType(const Token& tok)
  {
      switch (tok.type)
      {
          case TokenType::BlockComment:
          {
              UnownedStringSlice slice = tok.getContent();
              if (slice.getLength() >= 3 && (slice[2] == '!' || slice[2] == '*'))
              {
                  return (slice.getLength() >= 4 && slice[3] == '<') ? MarkupType::BlockAfter : MarkupType::BlockBefore;
              }
              break;
          }
          case TokenType::LineComment:
          {
              UnownedStringSlice slice = tok.getContent();
              if (slice.getLength() >= 3)
              {
                  if (slice[2] == '!')
                  {
                      return (slice.getLength() >= 4 && slice[3] == '<') ? MarkupType::LineBangAfter : MarkupType::LineBangBefore;
                  }
                  else if (slice[2] == '/')
                  {
                      return (slice.getLength() >= 4 && slice[3] == '<') ? MarkupType::LineSlashAfter : MarkupType::LineSlashBefore;
                  }
              }
              break;
          }
          default: break;
      }
      return MarkupType::None;
  }
  
  static Index _calcWhitespaceIndent(const UnownedStringSlice& line)
  {
      // TODO(JS): For now we ignore tabs and just work out indentation based on spaces/assume ASCII
      Index indent = 0;
      const Index count = line.getLength();
      for (; indent < count && line[indent] == ' '; indent++);
      return indent;
  }
  
  static Index _calcIndent(const UnownedStringSlice& line)
  {
      // TODO(JS): For now we just assume no tabs, and that every char is ASCII
      return line.getLength();
  }
  
  static void _appendUnindenttedLine(const UnownedStringSlice& line, Index maxIndent, StringBuilder& out)
  {
      Index indent = _calcWhitespaceIndent(line);
  
      // We want to remove indenting remove no more than maxIndent
      if (maxIndent >= 0)
      {
          indent = (indent > maxIndent) ? maxIndent : indent;
      }
  
      // Remove the indenting, and append to out
      out.append(line.tail(indent));
  }
  
  SlangResult DocMarkupExtractor::_extractMarkup(const FindInfo& info, const FoundMarkup& foundMarkup, StringBuilder& out)
  {
      SourceView* sourceView = info.sourceView;
      SourceFile* sourceFile = sourceView->getSourceFile();
  
      // Here we want to produce the text that is implied by the markup tokens.
      // We want to removing surrounding markup, and to also keep appropriate indentation
      
      switch (foundMarkup.type)
      {
          case MarkupType::BlockBefore:
          case MarkupType::BlockAfter:
          {
              // We should only have a single line
              SLANG_ASSERT(foundMarkup.range.getCount() == 1);
  
              const auto& tok = info.tokenList->m_tokens[foundMarkup.range.start];
              uint32_t offset = sourceView->getRange().getOffset(tok.loc);
  
              const UnownedStringSlice startLine = sourceFile->getLineContainingOffset(offset);
  
              UnownedStringSlice content = tok.getContent();
  
              // Split into lines
              List<UnownedStringSlice> lines;
  
              StringUtil::calcLines(content, lines);
  
              Index maxIndent = -1;
  
              StringBuilder unindentedLine;
  
              const Index linesCount = lines.getCount();
              for (Index i = 0; i < linesCount; ++i)
              {
                  UnownedStringSlice line = lines[i];
                  unindentedLine.Clear();
  
                  if (i == 0)
                  {
                      if (startLine.isMemoryContained(line.begin()))
                      {
                          // For now we'll ignore tabs, and that the indent amount is, the amount of *byte*
                          // NOTE! This is only appropriate for ASCII without tabs.
                          maxIndent = _calcIndent(UnownedStringSlice(startLine.begin(), line.begin()));
  
                          // Let's strip the start stuff
                          line = removeStart(foundMarkup.type, line);
                      }
                  }
  
                  if (i == linesCount - 1)
                  {
                      SLANG_ASSERT(line.tail(line.getLength() - 2) == UnownedStringSlice::fromLiteral("*/"));
                      // Remove the */ at the end of the line
                      line = line.head(line.getLength() - 2);
                  }
  
                  if (i > 0)
                  {
                      _appendUnindenttedLine(line, maxIndent, unindentedLine);
                  }
                  else
                  {
                      unindentedLine.append(line);
                  }
                  
                  // If the first or last line are all white space, just ignore them
                  if ((i == linesCount - 1 || i == 0) && unindentedLine.getUnownedSlice().trim().getLength() == 0)
                  {
                      continue;
                  }
  
                  out.append(unindentedLine);
                  out.appendChar('\n');
              }
  
              break;
          }
          case MarkupType::LineBangBefore:      
          case MarkupType::LineSlashBefore:
          case MarkupType::LineBangAfter:
          case MarkupType::LineSlashAfter:
          {
              // Holds the lines extracted, they may have some white space indenting (like the space at the start of //) 
              List<UnownedStringSlice> lines;
  
              const auto& range = foundMarkup.range;
              for (Index i = range.start; i < range.end; ++ i)
              {
                  const auto& tok = info.tokenList->m_tokens[i];
                  UnownedStringSlice line = tok.getContent();
                  line = removeStart(foundMarkup.type, line);
  
                  // If the first or last line are all white space, just ignore them
                  if ((i == range.start || i == range.end - 1) && line.trim().getLength() == 0)
                  {
                      continue;
                  }
                  lines.add(line);
              }
  
              if (lines.getCount() == 0)
              {
                  // If there are no lines, theres no content
                  return SLANG_OK;
              }
  
              Index minIndent = 0x7fffffff;
              for (const auto& line : lines)
              {
                  const Index indent = _calcWhitespaceIndent(line);
                  minIndent = (indent < minIndent) ? indent : minIndent;
              }
  
              for (const auto& line : lines)
              {
                  _appendUnindenttedLine(line, minIndent, out);
                  out.appendChar('\n');
              }
  
              break;
          }
          default:    return SLANG_FAIL;
      }
  
      return SLANG_OK;
  }
  
  Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location location)
  {
      Index openCount = 0;
  
      const TokenList& toks = *info.tokenList;
      const Index tokIndex = info.tokenIndex;
  
      Index direction = isBefore(location) ? -1 : 1;
  
      const Index count = toks.m_tokens.getCount();
      for (Index i = tokIndex; i >= 0 && i < count; i += direction)
      {
          const Token& tok = toks.m_tokens[i];
  
          switch (tok.type)
          {
              case TokenType::LBrace:
              case TokenType::LBracket:
              case TokenType::LParent:
              case TokenType::OpLess:
              {
                  openCount += direction;
                  if (openCount < 0) return -1;
                  break;
              }
              case TokenType::RBracket:
              {
                  openCount -= direction;
                  if (openCount < 0) return -1;
                  break;
              }
              case TokenType::OpGreater:
              {
                  if (location == Location::AfterGenericParam && openCount == 0)
                  {
                      return i + 1;
                  }
  
                  openCount -= direction;
                  if (openCount < 0) return -1;
  
                  break;
              }
              case TokenType::RParent:
              {
                  if (openCount == 0 && location == Location::AfterParam)
                  {
                      return i + 1;
                  }
  
                  openCount -= direction;
                  if (openCount < 0) return -1;
                  break;
              }
              case TokenType::RBrace:
              {
                  // If we haven't hit a candidate yet before hitting } it's not going to work
                  if (location == Location::Before || location == Location::AfterEnumCase)
                  {
                      return -1;
                  }
                  break;
              }
              case TokenType::BlockComment:
              case TokenType::LineComment:
              {
                  if (openCount == 0)
                  {
                      // Determine the markup type
                      const MarkupType markupType = findMarkupType(tok);
                      // If the location wanted is before and the markup is, we'll assume this is it
                      if (isBefore(location) && isBefore(markupType))
                      {
                          return i;
                      }
                      // If we are looking for enum cases, and the markup is after, we'll assume this is it
                      if (isAfter(location) && isAfter(markupType))
                      {
                          return i;
                      }
                  }
                  break;
              }
              case TokenType::Comma:
              {
                  if (openCount == 0)
                  {   
                      if (location == Location::AfterParam || location == Location::AfterEnumCase || location == Location::AfterGenericParam)
                      {
                          return i + 1;
                      }
  
                      if (location == Location::Before)
                      {
                          return -1;
                      }
                  }
  
                  break;
              }
              case TokenType::Semicolon:
              {
                  // If we haven't hit a candidate yet it's not going to work
                  if (location == Location::Before)
                  {
                      return -1;
                  }
                  if (openCount == 0 && location == Location::AfterSemicolon)
                  {
                      return i + 1;
                  }
                  break;
              }
              default: break;
          }
      }
  
      return -1;
  }
  
  /* static */bool DocMarkupExtractor::_isTokenOnLineIndex(SourceView* sourceView, MarkupType type, const Token& tok, Index lineIndex)
  {
      SourceFile* sourceFile = sourceView->getSourceFile();
      const int offset = sourceView->getRange().getOffset(tok.loc);
  
      auto const flags = getFlags(type);
  
      if (flags & MarkupFlag::IsBlock)
      {
          // Either the start or the end of the block have to be on the specified line
          return sourceFile->isOffsetOnLine(offset, lineIndex) || sourceFile->isOffsetOnLine(offset + tok.charsCount, lineIndex);
      }
      else
      {
          // Has to be exactly on the specified line
          return sourceFile->isOffsetOnLine(offset, lineIndex);
      }
  }
  
  SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, Location location, FoundMarkup& out)
  {
      out.reset();
  
      const auto& toks = info.tokenList->m_tokens;
      const Index tokIndex = info.tokenIndex;
  
      // The starting token index
      Index startIndex = _findStartIndex(info, location);
      if (startIndex <= 0)
      {
          return SLANG_E_NOT_FOUND;
      }
  
      SourceView* sourceView = info.sourceView;
      SourceFile* sourceFile = sourceView->getSourceFile();
  
      // Let's lookup the line index where this occurred
      const int startOffset = sourceView->getRange().getOffset(toks[startIndex - 1].loc);
  
      // The line index that the markoff starts from 
      Index lineIndex = sourceFile->calcLineIndexFromOffset(startOffset);
      if (lineIndex < 0)
      {
          return SLANG_E_NOT_FOUND;
      }
  
      const Index searchDirection = isBefore(location) ? -1 : 1;
      
      // Get the type and flags
      const MarkupType type = findMarkupType(toks[startIndex]);
      const MarkupFlags flags = getFlags(type);
  
      const MarkupFlag::Enum requiredFlag = isBefore(location) ? MarkupFlag::Before : MarkupFlag::After;
      if ((flags & requiredFlag) == 0)
      {
          return SLANG_E_NOT_FOUND;
      }
  
  #if 0
      // The token still isn't accepted, unless it's on the expected line
      if (_isTokenOnLineIndex(info.sourceView, type, toks[startIndex], expectedLineIndex))
      {
          return SLANG_E_NOT_FOUND;
      }
  #endif
  
      Index endIndex = startIndex;
  
      // If it's multiline, so look for the end index
      if (flags & MarkupFlag::IsMultiToken)
      {
          Index expectedLineIndex = lineIndex;
  
          // TODO(JS):
          // We should probably do the work here to confirm  indentation - but that
          // requires knowing something about tabs, so for now we leave.
  
          while (true)
          {
              endIndex += searchDirection;
              expectedLineIndex += searchDirection;
  
              if (endIndex < 0 || endIndex >= toks.getCount())
              {
                  break;
              }
  
              // Do we find a token of the right type?
              if (findMarkupType(toks[endIndex]) != type)
              {
                  break;
              }
  
              // Is it on the right line?
              if (_isTokenOnLineIndex(info.sourceView, type, toks[startIndex], expectedLineIndex))
              {
                  break;
              }
          }
  
          // Fix the end index (it's the last one that worked)
          endIndex -= searchDirection;
      }
  
      // Put start < end order
      if (endIndex < startIndex)
      {
          Swap(endIndex, startIndex);
      }
      // The range excludes end so increase
      endIndex++;
  
      // Okay we've found the markup
      out.type = type;
      out.location = location;
      out.range = IndexRange{ startIndex, endIndex };
  
      SLANG_ASSERT(out.range.getCount() > 0);
  
      return SLANG_OK;
  }
  
  SlangResult DocMarkupExtractor::_findFirstMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out, Index& outIndex)
  {
      Index i = 0;
      for (; i < locCount; ++i)
      {
          SlangResult res = _findMarkup(info, locs[i], out);
          if (SLANG_SUCCEEDED(res) || (SLANG_FAILED(res) && res != SLANG_E_NOT_FOUND))
          {
              outIndex = i;
              return res;
          }
      }
      return SLANG_E_NOT_FOUND;
  }
  
  SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out)
  {
      Index foundIndex;
      SLANG_RETURN_ON_FAIL(_findFirstMarkup(info, locs, locCount, out, foundIndex));
  
      // Lets see if the remaining ones match
      {
          FoundMarkup otherMarkup;
          for (Index i = foundIndex + 1; i < locCount; ++i)
          {
              SlangResult res = _findMarkup(info, locs[i], otherMarkup);
              if (SLANG_SUCCEEDED(res))
              {
                  // TODO(JS): Warning found markup in another location
              }
          }
      }
  
      return SLANG_OK;
  }
  
  
  SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, SearchStyle searchStyle, FoundMarkup& out)
  {
      switch (searchStyle)
      {
          default:
          case SearchStyle::None:
          {
              return SLANG_E_NOT_FOUND;
          }
          case SearchStyle::EnumCase:
          {
              Location locs[] = { Location::Before, Location::AfterEnumCase };
              return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out);
          }
          case SearchStyle::Param:
          {
              Location locs[] = { Location::Before, Location::AfterParam };
              return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out);
          }
          case SearchStyle::Before:
          {
              return _findMarkup(info, Location::Before, out);
          }
          case SearchStyle::Function:
          {
              return _findMarkup(info, Location::Before, out);
          }
          case SearchStyle::Variable:
          {
              Location locs[] = { Location::Before, Location::AfterSemicolon };
              return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out);
          }
          case SearchStyle::GenericParam:
          {
              Location locs[] = { Location::Before, Location::AfterGenericParam };
              return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out);
          }
      }
  }
  
  static void _calcLineVisibility(SourceView* sourceView, const TokenList& toks, List<MarkupVisibility>& outLineVisibility)
  {
      SourceFile* sourceFile = sourceView->getSourceFile();
      const auto& lineOffsets = sourceFile->getLineBreakOffsets();
  
      outLineVisibility.setCount(lineOffsets.getCount() + 1);
  
      MarkupVisibility lastVisibility = MarkupVisibility::Public;
      Index lastLine = 0;
  
      for (const auto& tok : toks)
      {
          if (tok.type == TokenType::LineComment)
          {
              UnownedStringSlice contents = tok.getContent();
  
              MarkupVisibility newVisibility = lastVisibility;
  
              // Distinct from other markup
              if (contents.startsWith(toSlice("//@")))
              {
                  UnownedStringSlice access = contents.tail(3).trim();
                  if (access == "hidden:" || access == "private:")
                  {
                      newVisibility = MarkupVisibility::Hidden;
                  }
                  else if (access == "internal:")
                  {
                      newVisibility = MarkupVisibility::Internal;
                  }
                  else if (access == "public:")
                  {
                      newVisibility = MarkupVisibility::Public;
                  }
              }
  
              if (newVisibility != lastVisibility)
              {
                  // Work up the line it's on
                  const int offset = sourceView->getRange().getOffset(tok.loc);
                  Index line = sourceFile->calcLineIndexFromOffset(offset);
  
                  // Fill in the span
                  for (Index i = lastLine; i < line; ++i)
                  {
                      outLineVisibility[i] = lastVisibility;
                  }
  
                  // Record the new access and where we are up to
                  lastLine = line;
                  lastVisibility = newVisibility;
              }
          }
      }
  
      // Fill in the remaining
      for (Index i = lastLine; i < outLineVisibility.getCount(); ++ i)
      {
          outLineVisibility[i] = lastVisibility;
      }
  }
  
  SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inputCount, SourceManager* sourceManager, DiagnosticSink* sink, List<SourceView*>& outViews, List<SearchItemOutput>& out)
  {
      struct Entry
      {
          typedef Entry ThisType;
  
          Index viewIndex;                    ///< The view/file index this loc is found in
          SourceLoc::RawValue locOrOffset;    ///< Can be a loc or an offset into the file
  
          SearchStyle searchStyle;            ///< The search style when looking for an item
          Index inputIndex;                   ///< The index to this item in the input
      };
  
      List<Entry> entries;
  
      {
          entries.setCount(inputCount);
          for (Index i = 0; i < inputCount; ++i)
          {
              const auto& input = inputs[i];
              Entry& entry = entries[i];
              entry.inputIndex = i;
              entry.viewIndex = -1;            //< We don't know what file/view it's in
              entry.locOrOffset = input.sourceLoc.getRaw();
              entry.searchStyle = input.searchStyle;
          }
      }
  
      // Sort them into loc order
      entries.sort([](const Entry& a, const Entry& b) -> bool { return a.locOrOffset < b.locOrOffset; });
  
      {
          SourceView* sourceView = nullptr;
          Index viewIndex = -1;
  
          for (auto& entry : entries)
          {
              if (entry.searchStyle == SearchStyle::None)
              {
                  continue;
              }
  
              const SourceLoc loc = SourceLoc::fromRaw(entry.locOrOffset);
  
              if (sourceView == nullptr || !sourceView->getRange().contains(loc))
              {
                  // Find the new view
                  sourceView = sourceManager->findSourceView(loc);
                  SLANG_ASSERT(sourceView);
  
                  // We want only one view per SourceFile
                  SourceFile* sourceFile = sourceView->getSourceFile();
  
                  // NOTE! The view found might be different than sourceView. 
                  viewIndex = outViews.findFirstIndex([&](SourceView* currentView) -> bool { return currentView->getSourceFile() == sourceFile; });
  
                  if (viewIndex < 0)
                  {
                      viewIndex = outViews.getCount();
                      outViews.add(sourceView);
                  }
              }
  
              SLANG_ASSERT(viewIndex >= 0);
              SLANG_ASSERT(sourceView && sourceView->getRange().contains(loc));
  
              // Set the file index
              entry.viewIndex = viewIndex;
              // Set as the offset within the file 
              entry.locOrOffset = sourceView->getRange().getOffset(loc);
          }
  
          // Sort into view/file and then offset order
          entries.sort([](const Entry& a, const Entry& b) -> bool { return (a.viewIndex < b.viewIndex) || ((a.viewIndex == b.viewIndex) && a.locOrOffset < b.locOrOffset); });
      }
  
      {
          TokenList tokens;
          List<MarkupVisibility> lineVisibility;
  
          MemoryArena memoryArena(4096);
  
          RootNamePool rootNamePool;
          NamePool namePool;
          namePool.setRootNamePool(&rootNamePool);
  
          Index viewIndex = -1;
          SourceView* sourceView = nullptr;
  
          const Int entryCount = entries.getCount();
  
          out.setCount(entryCount);
  
          for (Index i = 0; i < entryCount; ++i)
          {
              const auto& entry = entries[i];
              auto& dst = out[i];
  
              dst.viewIndex = -1;
              dst.inputIndex = entry.inputIndex;
              dst.visibilty = MarkupVisibility::Public;
  
              // If there isn't a mechanism to search with, just move on
              if (entry.searchStyle == SearchStyle::None)
              {
                  continue;
              }
  
              if (viewIndex != entry.viewIndex)
              {
                  viewIndex = entry.viewIndex;
                  sourceView = outViews[viewIndex];
  
                  // Make all memory free again
                  memoryArena.reset();
  
                  // Run the lexer
                  Lexer lexer;
                  lexer.initialize(sourceView, sink, &namePool, &memoryArena);
  
                  // Lex everything
                  tokens = lexer.lexAllMarkupTokens();
  
                  // Let's work out the access
  
                  _calcLineVisibility(sourceView, tokens, lineVisibility);
              }
  
              dst.viewIndex = viewIndex;
  
              // Get the offset within the source file
              const uint32_t offset = entry.locOrOffset;
  
              // We need to get the loc in the source views space, so we look up appropriately in the list of tokens (which uses the views loc range)
              const SourceLoc loc = sourceView->getRange().getSourceLocFromOffset(offset);
  
              // Work out the line number
              SourceFile* sourceFile = sourceView->getSourceFile();
              const Index lineIndex = sourceFile->calcLineIndexFromOffset(int(offset));
  
              dst.visibilty = lineVisibility[lineIndex];
  
              // Okay, lets find the token index with a binary chop
              Index tokenIndex = _findTokenIndex(loc, tokens.m_tokens.getBuffer(), tokens.m_tokens.getCount());
              if (tokenIndex >= 0 && lineIndex >= 0)
              {
                  FindInfo findInfo;
                  findInfo.tokenIndex = tokenIndex;
                  findInfo.lineIndex = lineIndex;
                  findInfo.tokenList = &tokens;
                  findInfo.sourceView = sourceView;
  
                  // Okay let's see if we extract some documentation then for this.
                  FoundMarkup foundMarkup;
                  SlangResult res = _findMarkup(findInfo, entry.searchStyle, foundMarkup);
  
                  if (SLANG_SUCCEEDED(res))
                  {
                      // We need to extract
                      StringBuilder buf;
                      SLANG_RETURN_ON_FAIL(_extractMarkup(findInfo, foundMarkup, buf));
  
                      // Save the extracted text in the output
                      dst.text = buf;
  
                  }
                  else if (res != SLANG_E_NOT_FOUND)
                  {
                      return res;
                  }
              }
          }
      }
  
      return SLANG_OK;
  }
  
  } // namespace Slang
  