Browse Source

Made use of prettier library when possible to speed up prettier formatting

pipelines/353213535
eidheim 4 years ago
parent
commit
d5eabfd4e7
  1. 192
      src/source.cpp
  2. 3
      src/source.hpp
  3. 23
      src/utility.cpp
  4. 5
      src/utility.hpp
  5. 6
      src/window.cpp
  6. 1
      tests/CMakeLists.txt

192
src/source.cpp

@ -33,6 +33,8 @@ inline pid_t get_current_process_id() {
}
#endif
std::unique_ptr<TinyProcessLib::Process> Source::View::prettier_background_process = {};
Glib::RefPtr<Gsv::LanguageManager> Source::LanguageManager::get_default() {
static auto instance = Gsv::LanguageManager::create();
return instance;
@ -748,7 +750,6 @@ void Source::View::setup_format_style(bool is_generic_view) {
});
}
format_style = [this, is_generic_view](bool continue_without_style_file) {
auto command = prettier.string();
if(!continue_without_style_file) {
auto search_path = file_path.parent_path();
while(true) {
@ -779,6 +780,180 @@ void Source::View::setup_format_style(bool is_generic_view) {
}
}
size_t num_warnings = 0, num_errors = 0, num_fix_its = 0;
if(is_generic_view)
clear_diagnostic_tooltips();
static auto get_prettier_library = [] {
std::string library;
TinyProcessLib::Process process(
"npm root -g", "",
[&library](const char *buffer, size_t length) {
library += std::string(buffer, length);
},
[](const char *, size_t) {});
if(process.get_exit_status() == 0) {
while(!library.empty() && (library.back() == '\n' || library.back() == '\r'))
library.pop_back();
library += "/prettier";
boost::system::error_code ec;
if(boost::filesystem::is_directory(library, ec))
return library;
else {
auto parent_path = prettier.parent_path();
if(parent_path.filename() == "bin") {
auto path = parent_path.parent_path() / "lib" / "prettier";
if(boost::filesystem::is_directory(path, ec))
return path.string();
}
// Try find prettier library installed with homebrew on MacOS
boost::filesystem::path path = "/usr/local/opt/prettier/libexec/lib/node_modules/prettier";
if(boost::filesystem::is_directory(path, ec))
return path.string();
path = "/opt/homebrew/opt/prettier/libexec/lib/node_modules/prettier";
if(boost::filesystem::is_directory(path, ec))
return path.string();
}
}
return std::string();
};
static auto prettier_library = get_prettier_library();
auto buffer = get_buffer()->get_text().raw();
if(!prettier_library.empty() && buffer.size() < 25000) { // Node.js repl seems to be limited to around 28786 bytes
struct Error {
std::string message;
int line = -1, index = -1;
};
struct Result {
std::string text;
int cursor_offset;
};
static Mutex mutex;
static boost::optional<Result> result GUARDED_BY(mutex);
static boost::optional<Error> error GUARDED_BY(mutex);
{
LockGuard lock(mutex);
result = {};
error = {};
}
if(prettier_background_process) {
int exit_status;
if(prettier_background_process->try_get_exit_status(exit_status))
prettier_background_process = {};
}
if(!prettier_background_process) {
prettier_background_process = std::make_unique<TinyProcessLib::Process>(
"node -e \"const repl = require('repl');repl.start({prompt: '', ignoreUndefined: true, preview: false});\"",
"",
[](const char *bytes, size_t n) {
try {
JSON json(std::string(bytes, n));
LockGuard lock(mutex);
result = Result{json.string("formatted"), static_cast<int>(json.integer_or("cursorOffset", -1))};
}
catch(const std::exception &e) {
LockGuard lock(mutex);
error = Error{e.what()};
error->message += "\nOutput from prettier: " + std::string(bytes, n);
}
},
[](const char *bytes, size_t n) {
size_t i = 0;
for(; i < n; ++i) {
if(bytes[i] == '\n')
break;
}
std::string first_line(bytes, i);
std::string message;
int line = -1, line_index = -1;
if(starts_with(first_line, "ConfigError: "))
message = std::string(bytes + 13, n - 13);
else if(starts_with(first_line, "ParseError: ")) {
const static std::regex regex(R"(^(.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize);
std::smatch sm;
first_line.erase(0, 12);
if(std::regex_match(first_line, sm, regex)) {
message = sm[1].str();
try {
line = std::stoi(sm[2].str());
line_index = std::stoi(sm[3].str());
}
catch(...) {
line = -1;
line_index = -1;
}
}
else
message = std::string(bytes + 12, n - 12);
}
else
message = std::string(bytes, n);
LockGuard lock(mutex);
error = Error{std::move(message), line, line_index};
},
true, TinyProcessLib::Config{1048576});
prettier_background_process->write("const prettier = require(\"" + escape(prettier_library, {'"'}) + "\");\n");
}
std::string options = "filepath: \"" + escape(file_path.string(), {'"'}) + "\"";
if(get_buffer()->get_has_selection()) { // Cannot be used together with cursorOffset
Gtk::TextIter start, end;
get_buffer()->get_selection_bounds(start, end);
options += ", rangeStart: " + std::to_string(start.get_offset()) + ", rangeEnd: " + std::to_string(end.get_offset());
}
else
options += ", cursorOffset: " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset());
prettier_background_process->write("{prettier.clearConfigCache();let _ = prettier.resolveConfig(\"" + escape(file_path.string(), {'"'}) + "\").then(options => {try{let _ = process.stdout.write(JSON.stringify(prettier.formatWithCursor(Buffer.from('");
prettier_background_process->write(to_hex_string(buffer));
buffer.clear();
prettier_background_process->write("', 'hex').toString(), {...options, " + options + "})));}catch(error){let _ = process.stderr.write('ParseError: ' + error.message);}}).catch(error => {let _ = process.stderr.write('ConfigError: ' + error.message);});}\n");
while(true) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
int exit_status;
if(prettier_background_process->try_get_exit_status(exit_status))
break;
LockGuard lock(mutex);
if(result || error)
break;
}
{
LockGuard lock(mutex);
if(result) {
replace_text(result->text);
if(result->cursor_offset >= 0 && result->cursor_offset < get_buffer()->size()) {
get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(result->cursor_offset));
hide_tooltips();
}
}
else if(error) {
if(error->line != -1 && error->index != -1) {
if(is_generic_view) {
auto start = get_iter_at_line_offset(error->line - 1, error->index - 1);
++num_errors;
while(start.ends_line() && start.backward_char()) {
}
auto end = start;
end.forward_char();
if(start == end)
start.backward_char();
add_diagnostic_tooltip(start, end, true, [error_message = std::move(error->message)](Tooltip &tooltip) {
tooltip.insert_with_links_tagged(error_message);
});
}
}
else
Terminal::get().print("\e[31mError (prettier)\e[m: " + error->message + '\n', true);
}
}
}
else {
auto command = prettier.string();
command += " --stdin-filepath " + filesystem::escape_argument(this->file_path.string());
if(get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset
@ -790,11 +965,8 @@ void Source::View::setup_format_style(bool is_generic_view) {
else
command += " --cursor-offset " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset());
size_t num_warnings = 0, num_errors = 0, num_fix_its = 0;
if(is_generic_view)
clear_diagnostic_tooltips();
std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream;
std::stringstream stdin_stream(buffer), stdout_stream, stderr_stream;
buffer.clear();
auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path(), &stderr_stream);
if(exit_status == 0) {
replace_text(stdout_stream.str());
@ -812,12 +984,13 @@ void Source::View::setup_format_style(bool is_generic_view) {
}
}
}
else if(is_generic_view) {
else {
const static std::regex regex(R"(^\[.*error.*\] [^:]*: (.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize);
std::string line;
std::getline(stderr_stream, line);
std::smatch sm;
if(std::regex_match(line, sm, regex)) {
if(is_generic_view) {
try {
auto start = get_iter_at_line_offset(std::stoi(sm[2].str()) - 1, std::stoi(sm[3].str()) - 1);
++num_errors;
@ -836,6 +1009,11 @@ void Source::View::setup_format_style(bool is_generic_view) {
}
}
}
else
Terminal::get().print("\e[31mError (prettier)\e[m: " + stderr_stream.str(), true);
}
}
if(is_generic_view) {
status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its);
if(update_status_diagnostics)

3
src/source.hpp

@ -1,4 +1,5 @@
#pragma once
#include "process.hpp"
#include "source_diff.hpp"
#include "source_spellcheck.hpp"
#include "tooltips.hpp"
@ -115,6 +116,8 @@ namespace Source {
virtual void soft_reparse(bool delayed = false) { soft_reparse_needed = false; }
virtual void full_reparse() { full_reparse_needed = false; }
static std::unique_ptr<TinyProcessLib::Process> prettier_background_process;
protected:
std::atomic<bool> parsed = {true};
Tooltips diagnostic_tooltips;

23
src/utility.cpp

@ -1,4 +1,5 @@
#include "utility.hpp"
#include <algorithm>
#include <cstring>
ScopeGuard::~ScopeGuard() {
@ -169,3 +170,25 @@ bool ends_with(const std::string &str, const char *test) noexcept {
return false;
return str.compare(str.size() - test_size, test_size, test) == 0;
}
std::string escape(const std::string &input, const std::set<char> &escape_chars) {
std::string result;
result.reserve(input.size());
for(auto &chr : input) {
if(escape_chars.find(chr) != escape_chars.end())
result += '\\';
result += chr;
}
return result;
}
std::string to_hex_string(const std::string &input) {
std::string result;
result.reserve(input.size() * 2);
std::string hex_chars = "0123456789abcdef";
for(auto &chr : input) {
result += hex_chars[static_cast<unsigned char>(chr) >> 4];
result += hex_chars[static_cast<unsigned char>(chr) & 0x0f];
}
return result;
}

5
src/utility.hpp

@ -1,5 +1,6 @@
#pragma once
#include <functional>
#include <set>
#include <string>
class ScopeGuard {
@ -27,3 +28,7 @@ bool starts_with(const std::string &str, size_t pos, const char *test) noexcept;
bool ends_with(const std::string &str, const std::string &test) noexcept;
bool ends_with(const std::string &str, const char *test) noexcept;
std::string escape(const std::string &input, const std::set<char> &escape_chars);
std::string to_hex_string(const std::string &input);

6
src/window.cpp

@ -1999,6 +1999,12 @@ bool Window::on_delete_event(GdkEventAny *event) {
}
Terminal::get().kill_async_processes();
if(Source::View::prettier_background_process) {
int exit_status;
if(!Source::View::prettier_background_process->try_get_exit_status(exit_status))
Source::View::prettier_background_process->kill();
}
#ifdef JUCI_ENABLE_DEBUG
Debug::LLDB::destroy();
#endif

1
tests/CMakeLists.txt

@ -6,6 +6,7 @@ endif()
add_definitions(-DJUCI_BUILD_PATH="${CMAKE_BINARY_DIR}" -DJUCI_TESTS_PATH="${CMAKE_CURRENT_SOURCE_DIR}")
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_SOURCE_DIR}/lib/tiny-process-library)
add_library(test_stubs OBJECT
stubs/config.cpp

Loading…
Cancel
Save