From b1d8a1791bc41edcd9a4136a40fce7ccd69f51d1 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 29 Jun 2020 14:55:25 +0200 Subject: [PATCH] Adds support for OSC-8 in error diagnostics, that is, you can click the file names in the error reports. This is by default auto-detected, can be actively disabled (including auto-detection), or forcefully enabled. A non-supporting terminal will silently ignore the generated hyperlink anchor and just print the file name. --- Changelog.md | 1 + liblangutil/SourceReferenceFormatterHuman.cpp | 6 +- liblangutil/SourceReferenceFormatterHuman.h | 11 +-- libsolutil/hyperlink.h | 72 +++++++++++++++++++ solc/CommandLineInterface.cpp | 24 ++++++- solc/CommandLineInterface.h | 2 + test/libsolidity/ASTJSONTest.cpp | 2 +- test/libsolidity/GasTest.cpp | 2 +- tools/solidityUpgrade/SourceUpgrade.cpp | 2 +- tools/solidityUpgrade/UpgradeChange.cpp | 2 +- 10 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 libsolutil/hyperlink.h diff --git a/Changelog.md b/Changelog.md index 9328f2824..20e462275 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: * NatSpec: Add fields "kind" and "version" to the JSON output. * Commandline Interface: Prevent some incompatible commandline options from being used together. + * Commandline Interface: Adds new option ``--hyperlinks`` and ``--no-hyperlinks`` for clickable file names in diagnostics. Bugfixes: diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp index 998ca7bf5..a227266a4 100644 --- a/liblangutil/SourceReferenceFormatterHuman.cpp +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -23,8 +23,10 @@ #include #include #include +#include #include + using namespace std; using namespace solidity; using namespace solidity::langutil; @@ -88,7 +90,7 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ if (_ref.position.line < 0) { frameColored() << "-->"; - m_stream << ' ' << _ref.sourceName << '\n'; + m_stream << ' ' << m_hyperlink(_ref.sourceName) << '\n'; return; // No line available, nothing else to print } @@ -98,7 +100,7 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ // line 0: source name m_stream << leftpad; frameColored() << "-->"; - m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n"; + m_stream << ' ' << m_hyperlink(_ref.sourceName) << ':' << line << ':' << (_ref.position.column + 1) << ":\n"; string_view text = _ref.text; diff --git a/liblangutil/SourceReferenceFormatterHuman.h b/liblangutil/SourceReferenceFormatterHuman.h index b468f3756..a536fa39c 100644 --- a/liblangutil/SourceReferenceFormatterHuman.h +++ b/liblangutil/SourceReferenceFormatterHuman.h @@ -24,6 +24,7 @@ #include // SourceReferenceFormatterBase #include +#include #include #include @@ -35,8 +36,8 @@ namespace solidity::langutil class SourceReferenceFormatterHuman: public SourceReferenceFormatter { public: - SourceReferenceFormatterHuman(std::ostream& _stream, bool _colored, bool _withErrorIds): - SourceReferenceFormatter{_stream}, m_colored{_colored}, m_withErrorIds(_withErrorIds) + SourceReferenceFormatterHuman(std::ostream& _stream, bool _colored, bool _withErrorIds, bool _hyperlinks): + SourceReferenceFormatter{_stream}, m_colored{_colored}, m_withErrorIds(_withErrorIds), m_hyperlink{_hyperlinks} {} void printSourceLocation(SourceReference const& _ref) override; @@ -47,12 +48,13 @@ public: util::Exception const& _exception, std::string const& _name, bool _colored = false, - bool _withErrorIds = false + bool _withErrorIds = false, + bool _hyperlinks = false ) { std::ostringstream errorOutput; - SourceReferenceFormatterHuman formatter(errorOutput, _colored, _withErrorIds); + SourceReferenceFormatterHuman formatter(errorOutput, _colored, _withErrorIds, _hyperlinks); formatter.printExceptionInformation(_exception, _name); return errorOutput.str(); } @@ -69,6 +71,7 @@ private: private: bool m_colored; bool m_withErrorIds; + util::Hyperlink m_hyperlink; }; } diff --git a/libsolutil/hyperlink.h b/libsolutil/hyperlink.h new file mode 100644 index 000000000..18be954d7 --- /dev/null +++ b/libsolutil/hyperlink.h @@ -0,0 +1,72 @@ +/* + 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 . +*/ + +#pragma once + +#include + +#include +#include + +// Required for gethostname() +#if defined(_WIN32) +#include +#else +#include +#endif + +namespace solidity::util +{ + +struct Hyperlink +{ + bool enabled = false; + + struct Ref { bool enabled; std::string text; }; + + Ref operator()(std::string const& _ref) { return Ref{enabled, _ref}; } +}; + +inline std::ostream& operator<<(std::ostream& _os, Hyperlink::Ref const& _hyperlink) +{ + auto const path = boost::filesystem::path{_hyperlink.text}; + bool const candidate = boost::filesystem::is_regular_file(path) && (&_os == &std::cout || &_os == &std::cerr); + + if (_hyperlink.enabled && candidate) + { + static std::string const hostname = []() -> std::string { + char hostname[80] = {0}; + if (gethostname(hostname, sizeof(hostname)) < 0) + return std::string{}; + return std::string{hostname}; + }(); + + auto const abspath = boost::filesystem::canonical(boost::filesystem::absolute(path)); + auto constexpr OSC8 = "\033]8;;"; + auto constexpr ST = "\033\\"; + + _os << OSC8 << "file://" << hostname << abspath.generic_string() << ST; + _os << _hyperlink.text; + _os << OSC8 << ST; + } + else + _os << _hyperlink.text; + + return _os; +} + +} // end namespace diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index bbf0eeb53..394d3e02d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -178,6 +178,8 @@ static string const g_strVersion = "version"; static string const g_strIgnoreMissingFiles = "ignore-missing"; static string const g_strColor = "color"; static string const g_strNoColor = "no-color"; +static string const g_strHyperlinks = "hyperlinks"; +static string const g_strNoHyperlinks = "no-hyperlinks"; static string const g_strErrorIds = "error-codes"; static string const g_strOldReporter = "old-reporter"; @@ -224,6 +226,8 @@ static string const g_stdinFileName = g_stdinFileNameStr; static string const g_argIgnoreMissingFiles = g_strIgnoreMissingFiles; static string const g_argColor = g_strColor; static string const g_argNoColor = g_strNoColor; +static string const g_argHyperlinks = g_strHyperlinks; +static string const g_argNoHyperlinks = g_strNoHyperlinks; static string const g_argErrorIds = g_strErrorIds; static string const g_argOldReporter = g_strOldReporter; @@ -879,6 +883,14 @@ General Information)").c_str(), g_argNoColor.c_str(), "Explicitly disable colored output, disabling terminal auto-detection." ) + ( + g_argHyperlinks.c_str(), + "Force hyperlink output. This will enable file names to be clickable in error diagnostics." + ) + ( + g_argNoHyperlinks.c_str(), + "Explicitly disable hyperlink output, disabling terminal auto-detection." + ) ( g_argErrorIds.c_str(), "Output error codes." @@ -994,8 +1006,16 @@ General Information)").c_str(), return false; } + if (m_args.count(g_argHyperlinks) && m_args.count(g_argNoHyperlinks)) + { + serr() << "Option " << g_argHyperlinks << " and " << g_argNoHyperlinks << " are mutualy exclusive." << endl; + return false; + } + m_coloredOutput = !m_args.count(g_argNoColor) && (isatty(STDERR_FILENO) || m_args.count(g_argColor)); + m_hyperlinks = !m_args.count(g_argNoHyperlinks) && (isatty(STDERR_FILENO) || m_args.count(g_argHyperlinks)); + m_withErrorIds = m_args.count(g_argErrorIds); if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1)) @@ -1331,7 +1351,7 @@ bool CommandLineInterface::processInput() if (m_args.count(g_argOldReporter)) formatter = make_unique(serr(false)); else - formatter = make_unique(serr(false), m_coloredOutput, m_withErrorIds); + formatter = make_unique(serr(false), m_coloredOutput, m_withErrorIds, m_hyperlinks); try { @@ -1769,7 +1789,7 @@ bool CommandLineInterface::assemble( if (m_args.count(g_argOldReporter)) formatter = make_unique(serr(false)); else - formatter = make_unique(serr(false), m_coloredOutput, m_withErrorIds); + formatter = make_unique(serr(false), m_coloredOutput, m_withErrorIds, m_hyperlinks); for (auto const& error: stack.errors()) { diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 1aed72821..21d46cab8 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -136,6 +136,8 @@ private: bool m_coloredOutput = true; /// Whether or not to output error IDs. bool m_withErrorIds = false; + /// Whether or not error diagnostics may construct text with hyperlink anchors. + bool m_hyperlinks = false; }; } diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index 974e0f486..7d97187a3 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -134,7 +134,7 @@ TestCase::TestResult ASTJSONTest::run(ostream& _stream, string const& _linePrefi c.analyze(); else { - SourceReferenceFormatterHuman formatter(_stream, _formatted, false); + SourceReferenceFormatterHuman formatter(_stream, _formatted, false, false); for (auto const& error: c.errors()) formatter.printErrorInformation(*error); return TestResult::FatalError; diff --git a/test/libsolidity/GasTest.cpp b/test/libsolidity/GasTest.cpp index d76979c9c..deddcb2fe 100644 --- a/test/libsolidity/GasTest.cpp +++ b/test/libsolidity/GasTest.cpp @@ -118,7 +118,7 @@ TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, b if (!compiler().parseAndAnalyze() || !compiler().compile()) { - SourceReferenceFormatterHuman formatter(_stream, _formatted, false); + SourceReferenceFormatterHuman formatter(_stream, _formatted, false, false); for (auto const& error: compiler().errors()) formatter.printErrorInformation(*error); return TestResult::FatalError; diff --git a/tools/solidityUpgrade/SourceUpgrade.cpp b/tools/solidityUpgrade/SourceUpgrade.cpp index 7005fd336..fa7bded77 100644 --- a/tools/solidityUpgrade/SourceUpgrade.cpp +++ b/tools/solidityUpgrade/SourceUpgrade.cpp @@ -390,7 +390,7 @@ void SourceUpgrade::applyChange( void SourceUpgrade::printErrors() const { - auto formatter = make_unique(cout, true, false); + auto formatter = make_unique(cout, true, false, false); for (auto const& error: m_compiler->errors()) if (error->type() != langutil::Error::Type::Warning) diff --git a/tools/solidityUpgrade/UpgradeChange.cpp b/tools/solidityUpgrade/UpgradeChange.cpp index a897a4e13..45d66c8e2 100644 --- a/tools/solidityUpgrade/UpgradeChange.cpp +++ b/tools/solidityUpgrade/UpgradeChange.cpp @@ -36,7 +36,7 @@ void UpgradeChange::apply() void UpgradeChange::log(bool const _shorten) const { stringstream os; - SourceReferenceFormatterHuman formatter{os, true, false}; + SourceReferenceFormatterHuman formatter{os, true, false, false}; string start = to_string(m_location.start); string end = to_string(m_location.end);