Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/include/Protocol/LanguageFeatures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,51 @@ struct InlayHintParams
};
NLOHMANN_DEFINE_OPTIONAL(InlayHintParams, textDocument, range)

/**
* An inlay hint label part allows for interactive and composite labels
* of inlay hints.
*
* @since 3.17.0
*/
struct InlayHintLabelPart
{
/**
* The value of this label part.
*/
std::string value;

/**
* The tooltip text when you hover over this label part. Depending on
* the client capability `inlayHint.resolveSupport` clients might resolve
* this property late using the resolve request.
*/
std::optional<lsp::MarkupContent> tooltip = std::nullopt;

/**
* An optional source code location that represents this
* label part.
*
* The editor will use this location for the hover and for code navigation
* features: This part will become a clickable link that resolves to the
* definition of the symbol at the given location (not necessarily the
* location itself), it shows the hover that shows at the given location,
* and it shows a context menu with further code navigation commands.
*
* Depending on the client capability `inlayHint.resolveSupport` clients
* might resolve this property late using the resolve request.
*/
std::optional<lsp::Location> location = std::nullopt;

/**
* An optional command for this label part.
*
* Depending on the client capability `inlayHint.resolveSupport` clients
* might resolve this property late using the resolve request.
*/
std::optional<lsp::Command> command = std::nullopt;
};
NLOHMANN_DEFINE_OPTIONAL(InlayHintLabelPart, value, tooltip, location, command)

enum struct InlayHintKind
{
Type = 1,
Expand All @@ -136,7 +181,7 @@ enum struct InlayHintKind
struct InlayHint
{
Position position;
std::string label;
std::vector<InlayHintLabelPart> label;
std::optional<InlayHintKind> kind = std::nullopt;
std::vector<TextEdit> textEdits{};
std::optional<std::string> tooltip = std::nullopt;
Expand Down
126 changes: 73 additions & 53 deletions src/operations/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void makeInsertable(const ClientConfiguration& config, lsp::InlayHint& hint, Lua
auto result = Luau::toStringDetailed(ty, opts);
if (result.invalid || result.truncated || result.error || result.cycle)
return;
hint.textEdits.emplace_back(lsp::TextEdit{{hint.position, hint.position}, ": " + result.name});
hint.textEdits.emplace_back();
}

void makeInsertable(const ClientConfiguration& config, lsp::InlayHint& hint, Luau::TypePackId ty, bool removeLeadingEllipsis = false)
Expand All @@ -47,14 +47,17 @@ void makeInsertable(const ClientConfiguration& config, lsp::InlayHint& hint, Lua
}
struct InlayHintVisitor : public Luau::AstVisitor
{
WorkspaceFileResolver* fileResolver = nullptr;
const Luau::ModulePtr& module;
const ClientConfiguration& config;
const TextDocument* textDocument;
std::vector<lsp::InlayHint> hints{};
Luau::ToStringOptions stringOptions;

explicit InlayHintVisitor(const Luau::ModulePtr& module, const ClientConfiguration& config, const TextDocument* textDocument)
: module(module)
explicit InlayHintVisitor(
WorkspaceFileResolver* fileResolver, const Luau::ModulePtr& module, const ClientConfiguration& config, const TextDocument* textDocument)
: fileResolver(fileResolver)
, module(module)
, config(config)
, textDocument(textDocument)

Expand All @@ -63,6 +66,54 @@ struct InlayHintVisitor : public Luau::AstVisitor
stringOptions.maxTypeLength = config.inlayHints.typeHintMaxLength;
}

void createInlayHintForType(const Luau::TypeId& ty, const lsp::Position& position,
const std::optional<std::function<bool(const std::string&)>>& shouldSkip = std::nullopt)
{
auto result = Luau::toStringDetailed(ty, stringOptions);
if (shouldSkip && (*shouldSkip)(result.name))
return;

lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.position = position;

hint.label.emplace_back(lsp::InlayHintLabelPart{": "});

// TODO: Decompose this further with https://github.com/luau-lang/luau/issues/1591
std::optional<lsp::Location> goToLocation = std::nullopt;
if (auto location = getLocation(ty))
{
if (auto definitionModuleName = Luau::getDefinitionModuleName(ty))
{
if (auto document = fileResolver->getOrCreateTextDocumentFromModuleName(*definitionModuleName))
{
goToLocation = lsp::Location{
document->uri(), lsp::Range{document->convertPosition(location->begin), document->convertPosition(location->end)}};
}
}
}
hint.label.emplace_back(lsp::InlayHintLabelPart{result.name, std::nullopt, goToLocation});

if (config.inlayHints.makeInsertable && !(result.invalid || result.truncated || result.error || result.cycle))
hint.textEdits.emplace_back(lsp::TextEdit{{hint.position, hint.position}, ": " + result.name});

hints.emplace_back(hint);
}

void createInlayHintForType(const Luau::TypePackId& ty, const lsp::Position& position, bool forVarArg = false)
{
auto typeString = types::toStringReturnType(ty, stringOptions);
if (forVarArg)
typeString = removePrefix(typeString, "...");

lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label.emplace_back(lsp::InlayHintLabelPart{": " + typeString});
hint.position = position;
makeInsertable(config, hint, ty, forVarArg);
hints.emplace_back(hint);
}

bool visit(Luau::AstStatLocal* local) override
{
if (!config.inlayHints.variableTypes)
Expand Down Expand Up @@ -97,19 +148,13 @@ struct InlayHintVisitor : public Luau::AstVisitor
if (config.inlayHints.hideHintsForErrorTypes && Luau::get<Luau::ErrorType>(followedTy))
continue;

auto typeString = Luau::toString(followedTy, stringOptions);

// If the stringified type is equivalent to the variable name, don't bother
// showing an inlay hint
if (Luau::equalsLower(typeString, var->name.value))
continue;

lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label = ": " + typeString;
hint.position = textDocument->convertPosition(var->location.end);
makeInsertable(config, hint, followedTy);
hints.emplace_back(hint);
createInlayHintForType(followedTy, textDocument->convertPosition(var->location.end),
[var](const std::string& typeString)
{
// If the stringified type is equivalent to the variable name, don't bother
// showing an inlay hint
return Luau::equalsLower(typeString, var->name.value);
});
}
}
}
Expand Down Expand Up @@ -143,19 +188,13 @@ struct InlayHintVisitor : public Luau::AstVisitor
if (config.inlayHints.hideHintsForErrorTypes && Luau::get<Luau::ErrorType>(followedTy))
continue;

auto typeString = Luau::toString(followedTy, stringOptions);

// If the stringified type is equivalent to the variable name, don't bother
// showing an inlay hint
if (Luau::equalsLower(typeString, var->name.value))
continue;

lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label = ": " + typeString;
hint.position = textDocument->convertPosition(var->location.end);
makeInsertable(config, hint, followedTy);
hints.emplace_back(hint);
createInlayHintForType(followedTy, textDocument->convertPosition(var->location.end),
[var](const std::string& typeString)
{
// If the stringified type is equivalent to the variable name, don't bother
// showing an inlay hint
return Luau::equalsLower(typeString, var->name.value);
});
}
}
}
Expand All @@ -176,14 +215,7 @@ struct InlayHintVisitor : public Luau::AstVisitor
if (config.inlayHints.functionReturnTypes)
{
if (!func->returnAnnotation && func->argLocation && !isNoOpFunction(func))
{
lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label = ": " + types::toStringReturnType(ftv->retTypes, stringOptions);
hint.position = textDocument->convertPosition(func->argLocation->end);
makeInsertable(config, hint, ftv->retTypes);
hints.emplace_back(hint);
}
createInlayHintForType(ftv->retTypes, textDocument->convertPosition(func->argLocation->end));
}

// Parameter types hint
Expand All @@ -203,14 +235,7 @@ struct InlayHintVisitor : public Luau::AstVisitor

auto argType = *it;
if (!param->annotation && param->name != "_")
{
lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label = ": " + Luau::toString(argType, stringOptions);
hint.position = textDocument->convertPosition(param->location.end);
makeInsertable(config, hint, argType);
hints.emplace_back(hint);
}
createInlayHintForType(argType, textDocument->convertPosition(param->location.end));

it++;
}
Expand All @@ -221,12 +246,7 @@ struct InlayHintVisitor : public Luau::AstVisitor
auto varargType = *it.tail();
if (!func->varargAnnotation)
{
lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Type;
hint.label = ": " + removePrefix(Luau::toString(varargType, stringOptions), "...");
hint.position = textDocument->convertPosition(func->varargLocation.end);
makeInsertable(config, hint, varargType, /* removeLeadingEllipsis: */ true);
hints.emplace_back(hint);
createInlayHintForType(varargType, textDocument->convertPosition(func->varargLocation.end), /* forVarArg= */ true);
}
}
}
Expand Down Expand Up @@ -292,7 +312,7 @@ struct InlayHintVisitor : public Luau::AstVisitor
{
lsp::InlayHint hint;
hint.kind = lsp::InlayHintKind::Parameter;
hint.label = paramName + ":";
hint.label.emplace_back(lsp::InlayHintLabelPart{paramName + ":"});
hint.position = textDocument->convertPosition(param->location.begin);
hint.paddingRight = true;
hints.emplace_back(hint);
Expand Down Expand Up @@ -334,7 +354,7 @@ lsp::InlayHintResult WorkspaceFolder::inlayHint(const lsp::InlayHintParams& para
if (!sourceModule || !module)
return {};

InlayHintVisitor visitor{module, config, textDocument};
InlayHintVisitor visitor{&fileResolver, module, config, textDocument};
visitor.visit(sourceModule->root);

return visitor.hints;
Expand Down
Loading