mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[Language Server]: Add basic document hover support.
This commit is contained in:
parent
1d85eb5ccf
commit
9e7fe985bf
@ -7,6 +7,7 @@ Compiler Features:
|
||||
* Commandline Interface: Add `--no-cbor-metadata` that skips CBOR metadata from getting appended at the end of the bytecode.
|
||||
* Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode.
|
||||
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
|
||||
* Language Server: Add basic document hover support.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -155,6 +155,8 @@ set(sources
|
||||
interface/StorageLayout.h
|
||||
interface/Version.cpp
|
||||
interface/Version.h
|
||||
lsp/DocumentHoverHandler.cpp
|
||||
lsp/DocumentHoverHandler.h
|
||||
lsp/FileRepository.cpp
|
||||
lsp/FileRepository.h
|
||||
lsp/GotoDefinition.cpp
|
||||
|
126
libsolidity/lsp/DocumentHoverHandler.cpp
Normal file
126
libsolidity/lsp/DocumentHoverHandler.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/lsp/DocumentHoverHandler.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace solidity::lsp;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct MarkdownBuilder
|
||||
{
|
||||
stringstream result;
|
||||
|
||||
MarkdownBuilder& solidityCode(string const& _code)
|
||||
{
|
||||
auto constexpr SolidityLanguageId = "solidity";
|
||||
result << "```" << SolidityLanguageId << '\n' << _code << "\n```\n\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
MarkdownBuilder& paragraph(string const& _text)
|
||||
{
|
||||
if (!_text.empty())
|
||||
{
|
||||
result << _text << '\n';
|
||||
if (_text.back() != '\n') // We want double-LF to ensure constructing a paragraph.
|
||||
result << '\n';
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void DocumentHoverHandler::operator()(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
auto const [sourceUnitName, lineColumn] = HandlerBase(*this).extractSourceUnitNameAndLineColumn(_args);
|
||||
auto const [sourceNode, sourceOffset] = m_server.astNodeAndOffsetAtSourceLocation(sourceUnitName, lineColumn);
|
||||
|
||||
MarkdownBuilder markdown{};
|
||||
auto rangeToHighlight = toRange(sourceNode->location());
|
||||
|
||||
// Try getting the type definition of the underlying AST node, if available.
|
||||
if (auto const* expression = dynamic_cast<Expression const*>(sourceNode))
|
||||
{
|
||||
if (auto const* declaration = ASTNode::referencedDeclaration(*expression))
|
||||
if (declaration->type())
|
||||
markdown.solidityCode(declaration->type()->toString(false));
|
||||
}
|
||||
else if (auto const* declaration = dynamic_cast<Declaration const*>(sourceNode))
|
||||
{
|
||||
if (declaration->type())
|
||||
markdown.solidityCode(declaration->type()->toString(false));
|
||||
}
|
||||
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(sourceNode))
|
||||
{
|
||||
for (size_t i = 0; i < identifierPath->path().size(); ++i)
|
||||
{
|
||||
if (identifierPath->pathLocations()[i].containsOffset(sourceOffset))
|
||||
{
|
||||
rangeToHighlight = toRange(identifierPath->pathLocations()[i]);
|
||||
|
||||
if (i < identifierPath->annotation().pathDeclarations.size())
|
||||
{
|
||||
Declaration const* declaration = identifierPath->annotation().pathDeclarations[i];
|
||||
if (declaration && declaration->type())
|
||||
markdown.solidityCode(declaration->type()->toString(false));
|
||||
if (auto const* structurallyDocumented = dynamic_cast<StructurallyDocumented const*>(declaration))
|
||||
if (structurallyDocumented->documentation()->text())
|
||||
markdown.paragraph(*structurallyDocumented->documentation()->text());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this AST node contains documentation itself, append it.
|
||||
if (auto const* documented = dynamic_cast<StructurallyDocumented const*>(sourceNode))
|
||||
{
|
||||
if (documented->documentation())
|
||||
markdown.paragraph(*documented->documentation()->text());
|
||||
}
|
||||
|
||||
auto tooltipText = markdown.result.str();
|
||||
|
||||
if (tooltipText.empty())
|
||||
{
|
||||
client().reply(_id, Json::nullValue);
|
||||
return;
|
||||
}
|
||||
|
||||
Json::Value reply = Json::objectValue;
|
||||
reply["range"] = rangeToHighlight;
|
||||
reply["contents"]["kind"] = "markdown";
|
||||
reply["contents"]["value"] = std::move(tooltipText);
|
||||
|
||||
client().reply(_id, reply);
|
||||
}
|
||||
|
||||
}
|
32
libsolidity/lsp/DocumentHoverHandler.h
Normal file
32
libsolidity/lsp/DocumentHoverHandler.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#pragma once
|
||||
#include <libsolidity/lsp/HandlerBase.h>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
class DocumentHoverHandler: public HandlerBase
|
||||
{
|
||||
public:
|
||||
using HandlerBase::HandlerBase;
|
||||
|
||||
void operator()(MessageID, Json::Value const&);
|
||||
};
|
||||
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
// LSP feature implementations
|
||||
#include <libsolidity/lsp/DocumentHoverHandler.h>
|
||||
#include <libsolidity/lsp/GotoDefinition.h>
|
||||
#include <libsolidity/lsp/RenameSymbol.h>
|
||||
#include <libsolidity/lsp/SemanticTokensBuilder.h>
|
||||
@ -144,6 +145,7 @@ LanguageServer::LanguageServer(Transport& _transport):
|
||||
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
|
||||
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
|
||||
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
|
||||
{"textDocument/hover", DocumentHoverHandler(*this) },
|
||||
{"textDocument/rename", RenameSymbol(*this) },
|
||||
{"textDocument/implementation", GotoDefinition(*this) },
|
||||
{"textDocument/semanticTokens/full", bind(&LanguageServer::semanticTokensFull, this, _1, _2)},
|
||||
@ -417,6 +419,7 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
|
||||
replyArgs["capabilities"]["semanticTokensProvider"]["range"] = false;
|
||||
replyArgs["capabilities"]["semanticTokensProvider"]["full"] = true; // XOR requests.full.delta = true
|
||||
replyArgs["capabilities"]["renameProvider"] = true;
|
||||
replyArgs["capabilities"]["hoverProvider"] = true;
|
||||
|
||||
m_client.reply(_id, std::move(replyArgs));
|
||||
}
|
||||
@ -541,19 +544,21 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
||||
|
||||
|
||||
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
||||
{
|
||||
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
|
||||
return nullptr;
|
||||
|
||||
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
|
||||
return nullptr;
|
||||
|
||||
if (optional<int> sourcePos =
|
||||
m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos))
|
||||
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
|
||||
else
|
||||
return nullptr;
|
||||
return get<ASTNode const*>(astNodeAndOffsetAtSourceLocation(_sourceUnitName, _filePos));
|
||||
}
|
||||
|
||||
tuple<ASTNode const*, int> LanguageServer::astNodeAndOffsetAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
||||
{
|
||||
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
|
||||
return {nullptr, -1};
|
||||
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
|
||||
return {nullptr, -1};
|
||||
|
||||
optional<int> sourcePos = m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos);
|
||||
if (!sourcePos)
|
||||
return {nullptr, -1};
|
||||
|
||||
return {locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName)), *sourcePos};
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public:
|
||||
|
||||
FileRepository& fileRepository() noexcept { return m_fileRepository; }
|
||||
Transport& client() noexcept { return m_client; }
|
||||
std::tuple<frontend::ASTNode const*, int> astNodeAndOffsetAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
|
||||
frontend::ASTNode const* astNodeAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
|
||||
frontend::CompilerStack const& compilerStack() const noexcept { return m_compilerStack; }
|
||||
|
||||
|
94
test/libsolidity/lsp/hover/hover.sol
Normal file
94
test/libsolidity/lsp/hover/hover.sol
Normal file
@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
/// Documenting another contract here.
|
||||
contract AnotherContract {}
|
||||
|
||||
/// User being documented.
|
||||
contract User
|
||||
{
|
||||
/// Some enum value.
|
||||
enum SomeEnum
|
||||
{
|
||||
Red,
|
||||
Blue
|
||||
}
|
||||
|
||||
/// publicVariable being documented.
|
||||
SomeEnum public publicVariable;
|
||||
// ^ @Cursor1
|
||||
// ^^^^^^^^ @Cursor1Range
|
||||
|
||||
// not documented
|
||||
mapping(int => User.SomeEnum) someRemapping;
|
||||
// ^ @Cursor2
|
||||
// ^^^^^^^^ @Cursor2Range
|
||||
|
||||
/// Documenting the setContract().
|
||||
function setValue(User.SomeEnum _value) public
|
||||
// ^ @Cursor3
|
||||
// ^^^^ @Cursor3Range
|
||||
{
|
||||
publicVariable = _value;
|
||||
// ^ @Cursor4
|
||||
// ^^^^^^^^^^^^^^ @Cursor4Range
|
||||
}
|
||||
|
||||
function createAnotherContract() public returns (AnotherContract)
|
||||
{
|
||||
return new AnotherContract();
|
||||
// ^ @Cursor5
|
||||
// ^^^^^^^^^^^^^^^ @Cursor5Range
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// -> textDocument/hover {
|
||||
// "position": @Cursor1
|
||||
// }
|
||||
// <- {
|
||||
// "contents": {
|
||||
// "kind": "markdown",
|
||||
// "value": "```solidity\ntype(enum User.SomeEnum)\n```\n\n"
|
||||
// },
|
||||
// "range": @Cursor1Range
|
||||
// }
|
||||
// -> textDocument/hover {
|
||||
// "position": @Cursor2
|
||||
// }
|
||||
// <- {
|
||||
// "contents": {
|
||||
// "kind": "markdown",
|
||||
// "value": "```solidity\ntype(enum User.SomeEnum)\n```\n\n"
|
||||
// },
|
||||
// "range": @Cursor2Range
|
||||
// }
|
||||
// -> textDocument/hover {
|
||||
// "position": @Cursor3
|
||||
// }
|
||||
// <- {
|
||||
// "contents": {
|
||||
// "kind": "markdown",
|
||||
// "value": "```solidity\ntype(contract User)\n```\n\nUser being documented.\n\n"
|
||||
// },
|
||||
// "range": @Cursor3Range
|
||||
// }
|
||||
// -> textDocument/hover {
|
||||
// "position": @Cursor4
|
||||
// }
|
||||
// <- {
|
||||
// "contents": {
|
||||
// "kind": "markdown",
|
||||
// "value": "```solidity\nenum User.SomeEnum\n```\n\n"
|
||||
// },
|
||||
// "range": @Cursor4Range
|
||||
// }
|
||||
// -> textDocument/hover {
|
||||
// "position": @Cursor5
|
||||
// }
|
||||
// <- {
|
||||
// "contents": {
|
||||
// "kind": "markdown",
|
||||
// "value": "```solidity\ntype(contract AnotherContract)\n```\n\nDocumenting another contract here.\n\n"
|
||||
// },
|
||||
// "range": @Cursor5Range
|
||||
// }
|
Loading…
Reference in New Issue
Block a user