diff --git a/docs/language_servers.md b/docs/language_servers.md index 5a547a3..bf702cf 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -78,17 +78,12 @@ ln -s `which pyls` /usr/local/bin/python-language-server - Prerequisites: - [Rust](https://www.rust-lang.org/tools/install) -Install language server, and create symbolic link to enable server in juCi++: +Install language server: ```sh rustup component add rust-src - -git clone https://github.com/rust-analyzer/rust-analyzer -cd rust-analyzer -cargo xtask install --server - -# Usually as root: -ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server +rustup toolchain install nightly +rustup component add --toolchain nightly rust-src rust-analyzer-preview ``` - Additional setup within a Rust project: diff --git a/src/filesystem.cpp b/src/filesystem.cpp index a1a75aa..f28fa85 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -129,9 +129,12 @@ boost::filesystem::path filesystem::get_home_path() noexcept { boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { auto rust_sysroot_path = [] { std::string path; - TinyProcessLib::Process process("rustc --print sysroot", "", [&path](const char *buffer, size_t length) { - path += std::string(buffer, length); - }); + TinyProcessLib::Process process( + "rustc --print sysroot", "", + [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }, + [](const char *buffer, size_t n) {}); if(process.get_exit_status() == 0) { while(!path.empty() && (path.back() == '\n' || path.back() == '\r')) path.pop_back(); @@ -144,6 +147,15 @@ boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { return path; } +boost::filesystem::path filesystem::get_rust_nightly_sysroot_path() noexcept { + auto path = get_rust_sysroot_path(); + if(path.empty()) + return {}; + auto filename = path.filename().string(); + auto pos = filename.find('-'); + return path.parent_path() / (pos != std::string::npos ? "nightly" + filename.substr(pos) : "nightly"); +} + boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { #ifdef _WIN32 return path; diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 4cd2135..41955ca 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -22,6 +22,8 @@ public: static boost::filesystem::path get_home_path() noexcept; /// Returns empty path on failure static boost::filesystem::path get_rust_sysroot_path() noexcept; + /// Returns empty path on failure + static boost::filesystem::path get_rust_nightly_sysroot_path() noexcept; /// Replaces home path with ~ static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; /// Replaces ~ with home path (boost::filesystem does not recognize ~) diff --git a/src/notebook.cpp b/src/notebook.cpp index f5bd89c..1fdf325 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -173,7 +173,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || language_id == "cpp" || language_id == "objc") source_views.emplace_back(new Source::ClangView(file_path, language)); else if(language && !language_protocol_language_id.empty() && !filesystem::find_executable(language_protocol_language_id + "-language-server").empty()) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, language_protocol_language_id + "-language-server")); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(language_protocol_language_id + "-language-server"))); else if(language && language_protocol_language_id == "rust") { if(!filesystem::find_executable("rust-analyzer").empty()) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rust-analyzer")); @@ -183,37 +183,54 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; boost::system::error_code ec; if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); else { - static bool first = true; - std::stringstream stdin_stream, stdout_stream; - if(first && !filesystem::find_executable("rustup").empty()) { - first = false; - if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { - std::string line; - while(std::getline(stdout_stream, line)) { - if(starts_with(line, "rust-analyzer")) { - Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); - int result = dialog.run(); - if(result == Gtk::RESPONSE_YES) { - boost::optional exit_status; - Terminal::get().async_process(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"), "", [&exit_status](int exit_status_) { - exit_status = exit_status_; - }); - while(!exit_status) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - if(exit_status == 0) { - Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); - if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); + if(!nightly_sysroot.empty()) { + auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(nightly_rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); + else { + auto install_rust_analyzer = [this](const std::string &command) { + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); + int result = dialog.run(); + if(result == Gtk::RESPONSE_YES) { + boost::optional exit_status; + Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + while(!exit_status) { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(exit_status == 0) { + Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); + return true; + } + } + return false; + }; + static bool first = true; + if(first && !filesystem::find_executable("rustup").empty()) { + first = false; + std::stringstream stdin_stream, stdout_stream; + if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + std::string line; + bool found = false; + while(std::getline(stdout_stream, line)) { + if(starts_with(line, "rust-analyzer")) { + if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); + found = true; + break; } } - break; + if(!found && install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); } } } @@ -238,10 +255,15 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position } else if(language_id == "rust") { auto rust_installed = !filesystem::get_rust_sysroot_path().empty(); - Terminal::get().print(std::string("\e[33mWarning\e[m: could not find Rust ") + (rust_installed ? "language server" : "installation") + ".\n"); - Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); - if(!rust_installed) + if(!rust_installed) { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); + Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); + } + else { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + } shown.emplace(language_id); } else if(language_id == "go") { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 0555548..8c980d4 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -102,7 +102,7 @@ LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const JSON &workspace_edit, boost LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( - filesystem::escape_argument(language_server), root_path.string(), + language_server, root_path.string(), [this](const char *bytes, size_t n) { server_message_stream.write(bytes, n); parse_server_message(); diff --git a/src/window.cpp b/src/window.cpp index 0e463d7..171e3b0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -449,8 +449,8 @@ void Window::set_menu_actions() { menu.add_action("file_new_project_rust", []() { auto sysroot = filesystem::get_rust_sysroot_path(); if(sysroot.empty()) { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust installation.\n"); - Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + Terminal::get().print("\e[31mError\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); + Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); return; }