mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Always allow full filesystem access to LSP.
This commit is contained in:
parent
49d27eaa5d
commit
f308f1a1f8
@ -8,6 +8,7 @@ Compiler Features:
|
||||
* Assembly-Json: Export: Include source list in `sourceList` field.
|
||||
* Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``.
|
||||
* SMTChecker: Support ``abi.encodeCall`` taking into account the called selector.
|
||||
* Language Server: Allow full filesystem access to language server.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -176,3 +176,4 @@ set(sources
|
||||
|
||||
add_library(solidity ${sources})
|
||||
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only)
|
||||
|
||||
|
@ -17,48 +17,130 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/CommonIO.h>
|
||||
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
#include <regex>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::lsp;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
namespace
|
||||
{
|
||||
using solidity::util::readFileAsString;
|
||||
using solidity::util::joinHumanReadable;
|
||||
|
||||
string stripFilePrefix(string const& _path)
|
||||
FileRepository::FileRepository(boost::filesystem::path _basePath): m_basePath(std::move(_basePath))
|
||||
{
|
||||
if (_path.find("file://") == 0)
|
||||
return _path.substr(7);
|
||||
else
|
||||
return _path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string FileRepository::sourceUnitNameToClientPath(string const& _sourceUnitName) const
|
||||
string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
|
||||
{
|
||||
if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName))
|
||||
return m_sourceUnitNamesToClientPaths.at(_sourceUnitName);
|
||||
regex const windowsDriveLetterPath("^[a-zA-Z]:/");
|
||||
|
||||
if (m_sourceUnitNamesToUri.count(_sourceUnitName))
|
||||
return m_sourceUnitNamesToUri.at(_sourceUnitName);
|
||||
else if (_sourceUnitName.find("file://") == 0)
|
||||
return _sourceUnitName;
|
||||
else if (regex_search(_sourceUnitName, windowsDriveLetterPath))
|
||||
return "file:///" + _sourceUnitName;
|
||||
else if (_sourceUnitName.find("/") == 0)
|
||||
return "file://" + _sourceUnitName;
|
||||
else
|
||||
return "file://" + (m_fileReader.basePath() / _sourceUnitName).generic_string();
|
||||
return "file://" + m_basePath.generic_string() + "/" + _sourceUnitName;
|
||||
}
|
||||
|
||||
string FileRepository::clientPathToSourceUnitName(string const& _path) const
|
||||
string FileRepository::uriToSourceUnitName(string const& _path) const
|
||||
{
|
||||
return m_fileReader.cliPathToSourceUnitName(stripFilePrefix(_path));
|
||||
return stripFileUriSchemePrefix(_path);
|
||||
}
|
||||
|
||||
map<string, string> const& FileRepository::sourceUnits() const
|
||||
{
|
||||
return m_fileReader.sourceUnits();
|
||||
}
|
||||
|
||||
void FileRepository::setSourceByClientPath(string const& _uri, string _text)
|
||||
void FileRepository::setSourceByUri(string const& _uri, string _source)
|
||||
{
|
||||
// This is needed for uris outside the base path. It can lead to collisions,
|
||||
// but we need to mostly rewrite this in a future version anyway.
|
||||
m_sourceUnitNamesToClientPaths.emplace(clientPathToSourceUnitName(_uri), _uri);
|
||||
m_fileReader.addOrUpdateFile(stripFilePrefix(_uri), move(_text));
|
||||
auto sourceUnitName = uriToSourceUnitName(_uri);
|
||||
m_sourceUnitNamesToUri.emplace(sourceUnitName, _uri);
|
||||
m_sourceCodes[sourceUnitName] = std::move(_source);
|
||||
}
|
||||
|
||||
frontend::ReadCallback::Result FileRepository::readFile(string const& _kind, string const& _sourceUnitName)
|
||||
{
|
||||
solAssert(
|
||||
_kind == ReadCallback::kindString(ReadCallback::Kind::ReadFile),
|
||||
"ReadFile callback used as callback kind " + _kind
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
// File was read already. Use local store.
|
||||
if (m_sourceCodes.count(_sourceUnitName))
|
||||
return ReadCallback::Result{true, m_sourceCodes.at(_sourceUnitName)};
|
||||
|
||||
string const strippedSourceUnitName = stripFileUriSchemePrefix(_sourceUnitName);
|
||||
|
||||
if (
|
||||
boost::filesystem::path(strippedSourceUnitName).has_root_path() &&
|
||||
boost::filesystem::exists(strippedSourceUnitName)
|
||||
)
|
||||
{
|
||||
auto contents = readFileAsString(strippedSourceUnitName);
|
||||
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
|
||||
m_sourceCodes[_sourceUnitName] = contents;
|
||||
return ReadCallback::Result{true, move(contents)};
|
||||
}
|
||||
|
||||
vector<boost::filesystem::path> candidates;
|
||||
vector<reference_wrapper<boost::filesystem::path>> prefixes = {m_basePath};
|
||||
prefixes += (m_includePaths | ranges::to<vector<reference_wrapper<boost::filesystem::path>>>);
|
||||
|
||||
auto const pathToQuotedString = [](boost::filesystem::path const& _path) { return "\"" + _path.string() + "\""; };
|
||||
|
||||
for (auto const& prefix: prefixes)
|
||||
{
|
||||
boost::filesystem::path canonicalPath = boost::filesystem::path(prefix) / boost::filesystem::path(strippedSourceUnitName);
|
||||
|
||||
if (boost::filesystem::exists(canonicalPath))
|
||||
candidates.push_back(move(canonicalPath));
|
||||
}
|
||||
|
||||
if (candidates.empty())
|
||||
return ReadCallback::Result{
|
||||
false,
|
||||
"File not found. Searched the following locations: " +
|
||||
joinHumanReadable(prefixes | ranges::views::transform(pathToQuotedString), ", ") +
|
||||
"."
|
||||
};
|
||||
|
||||
if (candidates.size() >= 2)
|
||||
return ReadCallback::Result{
|
||||
false,
|
||||
"Ambiguous import. "
|
||||
"Multiple matching files found inside base path and/or include paths: " +
|
||||
joinHumanReadable(candidates | ranges::views::transform(pathToQuotedString), ", ") +
|
||||
"."
|
||||
};
|
||||
|
||||
if (!boost::filesystem::is_regular_file(candidates[0]))
|
||||
return ReadCallback::Result{false, "Not a valid file."};
|
||||
|
||||
auto contents = readFileAsString(candidates[0]);
|
||||
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
|
||||
m_sourceCodes[_sourceUnitName] = contents;
|
||||
return ReadCallback::Result{true, move(contents)};
|
||||
}
|
||||
catch (std::exception const& _exception)
|
||||
{
|
||||
return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,26 +28,42 @@ namespace solidity::lsp
|
||||
class FileRepository
|
||||
{
|
||||
public:
|
||||
explicit FileRepository(boost::filesystem::path const& _basePath):
|
||||
m_fileReader(_basePath) {}
|
||||
explicit FileRepository(boost::filesystem::path _basePath);
|
||||
|
||||
boost::filesystem::path const& basePath() const { return m_fileReader.basePath(); }
|
||||
boost::filesystem::path const& basePath() const { return m_basePath; }
|
||||
|
||||
/// Translates a compiler-internal source unit name to an LSP client path.
|
||||
std::string sourceUnitNameToClientPath(std::string const& _sourceUnitName) const;
|
||||
/// Translates an LSP client path into a compiler-internal source unit name.
|
||||
std::string clientPathToSourceUnitName(std::string const& _uri) const;
|
||||
std::string sourceUnitNameToUri(std::string const& _sourceUnitName) const;
|
||||
|
||||
/// Translates an LSP file URI into a compiler-internal source unit name.
|
||||
std::string uriToSourceUnitName(std::string const& _uri) const;
|
||||
|
||||
/// @returns all sources by their compiler-internal source unit name.
|
||||
std::map<std::string, std::string> const& sourceUnits() const;
|
||||
/// Changes the source identified by the LSP client path _uri to _text.
|
||||
void setSourceByClientPath(std::string const& _uri, std::string _text);
|
||||
StringMap const& sourceUnits() const noexcept { return m_sourceCodes; }
|
||||
|
||||
frontend::ReadCallback::Callback reader() { return m_fileReader.reader(); }
|
||||
/// Changes the source identified by the LSP client path _uri to _text.
|
||||
void setSourceByUri(std::string const& _uri, std::string _text);
|
||||
|
||||
void addOrUpdateFile(boost::filesystem::path const& _path, frontend::SourceCode _source);
|
||||
void setSourceUnits(StringMap _sources);
|
||||
frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName);
|
||||
frontend::ReadCallback::Callback reader()
|
||||
{
|
||||
return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); };
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> m_sourceUnitNamesToClientPaths;
|
||||
frontend::FileReader m_fileReader;
|
||||
/// Base path without URI scheme.
|
||||
boost::filesystem::path m_basePath;
|
||||
|
||||
/// Additional directories used for resolving relative paths in imports.
|
||||
std::vector<boost::filesystem::path> m_includePaths;
|
||||
|
||||
/// Mapping of source unit names to their URIs as understood by the client.
|
||||
StringMap m_sourceUnitNamesToUri;
|
||||
|
||||
/// Mapping of source unit names to their file content.
|
||||
StringMap m_sourceCodes;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
|
||||
{
|
||||
solAssert(_location.sourceName);
|
||||
Json::Value item = Json::objectValue;
|
||||
item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName);
|
||||
item["uri"] = fileRepository().sourceUnitNameToUri(*_location.sourceName);
|
||||
item["range"] = toRange(_location);
|
||||
return item;
|
||||
}
|
||||
@ -55,7 +55,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
|
||||
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
|
||||
{
|
||||
string const uri = _args["textDocument"]["uri"].asString();
|
||||
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
|
||||
string const sourceUnitName = fileRepository().uriToSourceUnitName(uri);
|
||||
if (!fileRepository().sourceUnits().count(sourceUnitName))
|
||||
BOOST_THROW_EXCEPTION(
|
||||
RequestError(ErrorCode::RequestFailed) <<
|
||||
|
@ -37,8 +37,6 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
@ -114,9 +112,9 @@ void LanguageServer::compile()
|
||||
swap(oldRepository, m_fileRepository);
|
||||
|
||||
for (string const& fileName: m_openFiles)
|
||||
m_fileRepository.setSourceByClientPath(
|
||||
m_fileRepository.setSourceByUri(
|
||||
fileName,
|
||||
oldRepository.sourceUnits().at(oldRepository.clientPathToSourceUnitName(fileName))
|
||||
oldRepository.sourceUnits().at(oldRepository.uriToSourceUnitName(fileName))
|
||||
);
|
||||
|
||||
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
|
||||
@ -178,7 +176,7 @@ void LanguageServer::compileAndUpdateDiagnostics()
|
||||
for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit)
|
||||
{
|
||||
Json::Value params;
|
||||
params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName);
|
||||
params["uri"] = m_fileRepository.sourceUnitNameToUri(sourceUnitName);
|
||||
if (!diagnostics.empty())
|
||||
m_nonemptyDiagnostics.insert(sourceUnitName);
|
||||
params["diagnostics"] = move(diagnostics);
|
||||
@ -252,13 +250,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
|
||||
ErrorCode::InvalidParams,
|
||||
"rootUri only supports file URI scheme."
|
||||
);
|
||||
|
||||
rootPath = rootPath.substr(7);
|
||||
rootPath = stripFileUriSchemePrefix(rootPath);
|
||||
}
|
||||
else if (Json::Value rootPath = _args["rootPath"])
|
||||
rootPath = rootPath.asString();
|
||||
|
||||
m_fileRepository = FileRepository(boost::filesystem::path(rootPath));
|
||||
m_fileRepository = FileRepository(rootPath);
|
||||
if (_args["initializationOptions"].isObject())
|
||||
changeConfiguration(_args["initializationOptions"]);
|
||||
|
||||
@ -309,7 +306,7 @@ void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args)
|
||||
string text = _args["textDocument"]["text"].asString();
|
||||
string uri = _args["textDocument"]["uri"].asString();
|
||||
m_openFiles.insert(uri);
|
||||
m_fileRepository.setSourceByClientPath(uri, move(text));
|
||||
m_fileRepository.setSourceByUri(uri, move(text));
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
||||
|
||||
@ -327,7 +324,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
|
||||
"Invalid content reference."
|
||||
);
|
||||
|
||||
string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri);
|
||||
string const sourceUnitName = m_fileRepository.uriToSourceUnitName(uri);
|
||||
lspAssert(
|
||||
m_fileRepository.sourceUnits().count(sourceUnitName),
|
||||
ErrorCode::RequestFailed,
|
||||
@ -348,7 +345,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
|
||||
buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text));
|
||||
text = move(buffer);
|
||||
}
|
||||
m_fileRepository.setSourceByClientPath(uri, move(text));
|
||||
m_fileRepository.setSourceByUri(uri, move(text));
|
||||
}
|
||||
|
||||
compileAndUpdateDiagnostics();
|
||||
|
@ -92,7 +92,7 @@ private:
|
||||
Transport& m_client;
|
||||
std::map<std::string, MessageHandler> m_handlers;
|
||||
|
||||
/// Set of files known to be open by the client.
|
||||
/// Set of files (names in URI form) known to be open by the client.
|
||||
std::set<std::string> m_openFiles;
|
||||
/// Set of source unit names for which we sent diagnostics to the client in the last iteration.
|
||||
std::set<std::string> m_nonemptyDiagnostics;
|
||||
|
@ -22,31 +22,25 @@
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::lsp;
|
||||
|
||||
IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out):
|
||||
m_input{_in},
|
||||
m_output{_out}
|
||||
{
|
||||
}
|
||||
|
||||
IOStreamTransport::IOStreamTransport():
|
||||
IOStreamTransport(cin, cout)
|
||||
{
|
||||
}
|
||||
|
||||
bool IOStreamTransport::closed() const noexcept
|
||||
{
|
||||
return m_input.eof();
|
||||
}
|
||||
|
||||
optional<Json::Value> IOStreamTransport::receive()
|
||||
// {{{ Transport
|
||||
optional<Json::Value> Transport::receive()
|
||||
{
|
||||
auto const headers = parseHeaders();
|
||||
if (!headers)
|
||||
@ -61,7 +55,7 @@ optional<Json::Value> IOStreamTransport::receive()
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
string const data = util::readBytes(m_input, stoul(headers->at("content-length")));
|
||||
string const data = readBytes(stoul(headers->at("content-length")));
|
||||
|
||||
Json::Value jsonMessage;
|
||||
string jsonParsingErrors;
|
||||
@ -75,29 +69,6 @@ optional<Json::Value> IOStreamTransport::receive()
|
||||
return {move(jsonMessage)};
|
||||
}
|
||||
|
||||
void IOStreamTransport::notify(string _method, Json::Value _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["method"] = move(_method);
|
||||
json["params"] = move(_message);
|
||||
send(move(json));
|
||||
}
|
||||
|
||||
void IOStreamTransport::reply(MessageID _id, Json::Value _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["result"] = move(_message);
|
||||
send(move(json), _id);
|
||||
}
|
||||
|
||||
void IOStreamTransport::error(MessageID _id, ErrorCode _code, string _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["error"]["code"] = static_cast<int>(_code);
|
||||
json["error"]["message"] = move(_message);
|
||||
send(move(json), _id);
|
||||
}
|
||||
|
||||
void Transport::trace(std::string _message, Json::Value _extra)
|
||||
{
|
||||
if (m_logTrace != TraceValue::Off)
|
||||
@ -110,30 +81,13 @@ void Transport::trace(std::string _message, Json::Value _extra)
|
||||
}
|
||||
}
|
||||
|
||||
void IOStreamTransport::send(Json::Value _json, MessageID _id)
|
||||
{
|
||||
solAssert(_json.isObject());
|
||||
_json["jsonrpc"] = "2.0";
|
||||
if (_id != Json::nullValue)
|
||||
_json["id"] = _id;
|
||||
|
||||
string const jsonString = solidity::util::jsonCompactPrint(_json);
|
||||
|
||||
m_output << "Content-Length: " << jsonString.size() << "\r\n";
|
||||
m_output << "\r\n";
|
||||
m_output << jsonString;
|
||||
|
||||
m_output.flush();
|
||||
}
|
||||
|
||||
optional<map<string, string>> IOStreamTransport::parseHeaders()
|
||||
optional<map<string, string>> Transport::parseHeaders()
|
||||
{
|
||||
map<string, string> headers;
|
||||
|
||||
while (true)
|
||||
{
|
||||
string line;
|
||||
getline(m_input, line);
|
||||
auto line = getline();
|
||||
if (boost::trim_copy(line).empty())
|
||||
break;
|
||||
|
||||
@ -141,13 +95,127 @@ optional<map<string, string>> IOStreamTransport::parseHeaders()
|
||||
if (delimiterPos == string::npos)
|
||||
return nullopt;
|
||||
|
||||
string name = boost::to_lower_copy(line.substr(0, delimiterPos));
|
||||
string value = line.substr(delimiterPos + 1);
|
||||
if (!headers.emplace(
|
||||
boost::trim_copy(name),
|
||||
boost::trim_copy(value)
|
||||
).second)
|
||||
auto const name = boost::to_lower_copy(line.substr(0, delimiterPos));
|
||||
auto const value = line.substr(delimiterPos + 1);
|
||||
if (!headers.emplace(boost::trim_copy(name), boost::trim_copy(value)).second)
|
||||
return nullopt;
|
||||
}
|
||||
return {move(headers)};
|
||||
}
|
||||
|
||||
void Transport::notify(string _method, Json::Value _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["method"] = move(_method);
|
||||
json["params"] = move(_message);
|
||||
send(move(json));
|
||||
}
|
||||
|
||||
void Transport::reply(MessageID _id, Json::Value _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["result"] = move(_message);
|
||||
send(move(json), _id);
|
||||
}
|
||||
|
||||
void Transport::error(MessageID _id, ErrorCode _code, string _message)
|
||||
{
|
||||
Json::Value json;
|
||||
json["error"]["code"] = static_cast<int>(_code);
|
||||
json["error"]["message"] = move(_message);
|
||||
send(move(json), _id);
|
||||
}
|
||||
|
||||
void Transport::send(Json::Value _json, MessageID _id)
|
||||
{
|
||||
solAssert(_json.isObject());
|
||||
_json["jsonrpc"] = "2.0";
|
||||
if (_id != Json::nullValue)
|
||||
_json["id"] = _id;
|
||||
|
||||
// Trailing CRLF only for easier readability.
|
||||
string const jsonString = solidity::util::jsonCompactPrint(_json);
|
||||
|
||||
writeBytes(fmt::format("Content-Length: {}\r\n\r\n", jsonString.size()));
|
||||
writeBytes(jsonString);
|
||||
flushOutput();
|
||||
}
|
||||
// }}}
|
||||
|
||||
// {{{ IOStreamTransport
|
||||
IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out):
|
||||
m_input{_in},
|
||||
m_output{_out}
|
||||
{
|
||||
}
|
||||
|
||||
bool IOStreamTransport::closed() const noexcept
|
||||
{
|
||||
return m_input.eof();
|
||||
}
|
||||
|
||||
std::string IOStreamTransport::readBytes(size_t _length)
|
||||
{
|
||||
return util::readBytes(m_input, _length);
|
||||
}
|
||||
|
||||
std::string IOStreamTransport::getline()
|
||||
{
|
||||
string line;
|
||||
std::getline(m_input, line);
|
||||
return line;
|
||||
}
|
||||
|
||||
void IOStreamTransport::writeBytes(std::string_view _data)
|
||||
{
|
||||
m_output.write(_data.data(), static_cast<std::streamsize>(_data.size()));
|
||||
}
|
||||
|
||||
void IOStreamTransport::flushOutput()
|
||||
{
|
||||
m_output.flush();
|
||||
}
|
||||
// }}}
|
||||
|
||||
// {{{ StdioTransport
|
||||
StdioTransport::StdioTransport()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// Attempt to change the modes of stdout from text to binary.
|
||||
setmode(fileno(stdout), O_BINARY);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool StdioTransport::closed() const noexcept
|
||||
{
|
||||
return feof(stdin);
|
||||
}
|
||||
|
||||
std::string StdioTransport::readBytes(size_t _byteCount)
|
||||
{
|
||||
std::string buffer;
|
||||
buffer.resize(_byteCount);
|
||||
auto const n = fread(buffer.data(), 1, _byteCount, stdin);
|
||||
if (n < _byteCount)
|
||||
buffer.resize(n);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string StdioTransport::getline()
|
||||
{
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
return line;
|
||||
}
|
||||
|
||||
void StdioTransport::writeBytes(std::string_view _data)
|
||||
{
|
||||
auto const bytesWritten = fwrite(_data.data(), 1, _data.size(), stdout);
|
||||
solAssert(bytesWritten == _data.size());
|
||||
}
|
||||
|
||||
void StdioTransport::flushOutput()
|
||||
{
|
||||
fflush(stdout);
|
||||
}
|
||||
// }}}
|
||||
|
@ -91,20 +91,44 @@ class Transport
|
||||
public:
|
||||
virtual ~Transport() = default;
|
||||
|
||||
std::optional<Json::Value> receive();
|
||||
void notify(std::string _method, Json::Value _params);
|
||||
void reply(MessageID _id, Json::Value _result);
|
||||
void error(MessageID _id, ErrorCode _code, std::string _message);
|
||||
|
||||
virtual bool closed() const noexcept = 0;
|
||||
virtual std::optional<Json::Value> receive() = 0;
|
||||
virtual void notify(std::string _method, Json::Value _params) = 0;
|
||||
virtual void reply(MessageID _id, Json::Value _result) = 0;
|
||||
virtual void error(MessageID _id, ErrorCode _code, std::string _message) = 0;
|
||||
|
||||
void trace(std::string _message, Json::Value _extra = Json::nullValue);
|
||||
|
||||
TraceValue traceValue() const noexcept { return m_logTrace; }
|
||||
void setTrace(TraceValue _value) noexcept { m_logTrace = _value; }
|
||||
|
||||
|
||||
private:
|
||||
TraceValue m_logTrace = TraceValue::Off;
|
||||
|
||||
protected:
|
||||
/// Reads from the transport and parses the headers until the beginning
|
||||
/// of the contents.
|
||||
std::optional<std::map<std::string, std::string>> parseHeaders();
|
||||
|
||||
/// Consumes exactly @p _byteCount bytes, as needed for consuming
|
||||
/// the message body from the transport line.
|
||||
virtual std::string readBytes(size_t _byteCount) = 0;
|
||||
|
||||
// Mimmicks std::getline() on this Transport API.
|
||||
virtual std::string getline() = 0;
|
||||
|
||||
/// Writes the given payload @p _data to transport.
|
||||
/// This call may or may not buffer.
|
||||
virtual void writeBytes(std::string_view _data) = 0;
|
||||
|
||||
/// Ensures transport output is flushed.
|
||||
virtual void flushOutput() = 0;
|
||||
|
||||
/// Sends an arbitrary raw message to the client.
|
||||
///
|
||||
/// Used by the notify/reply/error function family.
|
||||
virtual void send(Json::Value _message, MessageID _id = Json::nullValue);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -119,27 +143,34 @@ public:
|
||||
/// @param _out for example std::cout (stdout)
|
||||
IOStreamTransport(std::istream& _in, std::ostream& _out);
|
||||
|
||||
// Constructs a JSON transport using standard I/O streams.
|
||||
IOStreamTransport();
|
||||
|
||||
bool closed() const noexcept override;
|
||||
std::optional<Json::Value> receive() override;
|
||||
void notify(std::string _method, Json::Value _params) override;
|
||||
void reply(MessageID _id, Json::Value _result) override;
|
||||
void error(MessageID _id, ErrorCode _code, std::string _message) override;
|
||||
|
||||
protected:
|
||||
/// Sends an arbitrary raw message to the client.
|
||||
///
|
||||
/// Used by the notify/reply/error function family.
|
||||
virtual void send(Json::Value _message, MessageID _id = Json::nullValue);
|
||||
|
||||
/// Parses header section from the client including message-delimiting empty line.
|
||||
std::optional<std::map<std::string, std::string>> parseHeaders();
|
||||
std::string readBytes(size_t _byteCount) override;
|
||||
std::string getline() override;
|
||||
void writeBytes(std::string_view _data) override;
|
||||
void flushOutput() override;
|
||||
|
||||
private:
|
||||
std::istream& m_input;
|
||||
std::ostream& m_output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Standard I/O transport Layer utilizing stdin/stdout for communication.
|
||||
*/
|
||||
class StdioTransport: public Transport
|
||||
{
|
||||
public:
|
||||
StdioTransport();
|
||||
|
||||
bool closed() const noexcept override;
|
||||
|
||||
protected:
|
||||
std::string readBytes(size_t _byteCount) override;
|
||||
std::string getline() override;
|
||||
void writeBytes(std::string_view _data) override;
|
||||
void flushOutput() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
|
||||
namespace solidity::lsp
|
||||
@ -115,4 +115,15 @@ optional<SourceLocation> parseRange(FileRepository const& _fileRepository, strin
|
||||
return start;
|
||||
}
|
||||
|
||||
string stripFileUriSchemePrefix(string const& _path)
|
||||
{
|
||||
regex const windowsDriveLetterPath("^file:///[a-zA-Z]:/");
|
||||
if (regex_search(_path, windowsDriveLetterPath))
|
||||
return _path.substr(8);
|
||||
if (_path.find("file://") == 0)
|
||||
return _path.substr(7);
|
||||
else
|
||||
return _path;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,13 @@ std::optional<langutil::SourceLocation> parseRange(
|
||||
Json::Value const& _range
|
||||
);
|
||||
|
||||
/// Strips the file:// URI prefix off the given path, if present,
|
||||
/// also taking special care of Windows-drive-letter paths.
|
||||
///
|
||||
/// So file:///path/to/some/file.txt returns /path/to/some/file.txt, as well as,
|
||||
/// file:///C:/file.txt will return C:/file.txt (forward-slash is okay on Windows).
|
||||
std::string stripFileUriSchemePrefix(std::string const& _path);
|
||||
|
||||
/// Extracts the resolved declaration of the given expression AST node.
|
||||
///
|
||||
/// This may for example be the type declaration of an identifier,
|
||||
|
@ -912,7 +912,7 @@ void CommandLineInterface::handleAst()
|
||||
|
||||
void CommandLineInterface::serveLSP()
|
||||
{
|
||||
lsp::IOStreamTransport transport;
|
||||
lsp::StdioTransport transport;
|
||||
if (!lsp::LanguageServer{transport}.run())
|
||||
solThrow(CommandLineExecutionError, "LSP terminated abnormally.");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user