Browse Source

Language client, rename: now open, rename, save and close unopened buffers (as seems to be necessary for some language servers)

pipelines/280567345
eidheim 5 years ago
parent
commit
f709c31ec3
  1. 14
      src/notebook.cpp
  2. 1
      src/notebook.hpp
  3. 217
      src/source_language_protocol.cpp
  4. 1
      src/source_language_protocol.hpp
  5. 24
      src/terminal.cpp

14
src/notebook.cpp

@ -333,7 +333,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position
//Set up tab label //Set up tab label
tab_labels.emplace_back(new TabLabel([this, view]() { tab_labels.emplace_back(new TabLabel([this, view]() {
close(get_index(view)); close(view);
})); }));
view->update_tab_label = [this](Source::BaseView *view) { view->update_tab_label = [this](Source::BaseView *view) {
std::string title = view->file_path.filename().string(); std::string title = view->file_path.filename().string();
@ -582,6 +582,14 @@ bool Notebook::close(size_t index) {
return true; return true;
} }
bool Notebook::close(Source::View *view) {
return close(get_index(view));
}
bool Notebook::close_current() {
return close(get_current_view());
}
void Notebook::delete_cursor_locations(Source::View *view) { void Notebook::delete_cursor_locations(Source::View *view) {
for(auto it = cursor_locations.begin(); it != cursor_locations.end();) { for(auto it = cursor_locations.begin(); it != cursor_locations.end();) {
if(it->view == view) { if(it->view == view) {
@ -607,10 +615,6 @@ void Notebook::delete_cursor_locations(Source::View *view) {
} }
} }
bool Notebook::close_current() {
return close(get_index(get_current_view()));
}
void Notebook::next() { void Notebook::next() {
if(auto view = get_current_view()) { if(auto view = get_current_view()) {
auto notebook_page = get_notebook_page(view); auto notebook_page = get_notebook_page(view);

1
src/notebook.hpp

@ -50,6 +50,7 @@ public:
bool save(size_t index); bool save(size_t index);
bool save_current(); bool save_current();
bool close(size_t index); bool close(size_t index);
bool close(Source::View *view);
bool close_current(); bool close_current();
void next(); void next();
void previous(); void previous();

217
src/source_language_protocol.cpp

@ -121,6 +121,18 @@ LanguageProtocol::Client::~Client() {
process->kill(); process->kill();
} }
boost::optional<LanguageProtocol::Capabilities> LanguageProtocol::Client::get_capabilities(Source::LanguageProtocolView *view) {
if(view) {
LockGuard lock(views_mutex);
views.emplace(view);
}
LockGuard lock(initialize_mutex);
if(initialized)
return capabilities;
return {};
}
LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) { LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) {
if(view) { if(view) {
LockGuard lock(views_mutex); LockGuard lock(views_mutex);
@ -452,35 +464,43 @@ void Source::LanguageProtocolView::initialize() {
update_status_state(this); update_status_state(this);
set_editable(false); set_editable(false);
initialize_thread = std::thread([this] {
auto capabilities = client->initialize(this);
dispatcher.post([this, capabilities] { auto init = [this](const LanguageProtocol::Capabilities &capabilities) {
this->capabilities = capabilities; this->capabilities = capabilities;
set_editable(true); set_editable(true);
std::string text = get_buffer()->get_text(); std::string text = get_buffer()->get_text();
escape_text(text); escape_text(text);
client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}"); client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}");
if(!initialized) { if(!initialized) {
setup_signals(); setup_signals();
setup_autocomplete(); setup_autocomplete();
setup_navigation_and_refactoring(); setup_navigation_and_refactoring();
Menu::get().toggle_menu_items(); Menu::get().toggle_menu_items();
} }
if(status_state == "initializing...") { if(status_state == "initializing...") {
status_state = ""; status_state = "";
if(update_status_state) if(update_status_state)
update_status_state(this); update_status_state(this);
} }
update_type_coverage(); update_type_coverage();
initialized = true;
};
initialized = true; if(auto capabilities = client->get_capabilities(this))
init(*capabilities);
else {
initialize_thread = std::thread([this, init] {
auto capabilities = client->initialize(this);
dispatcher.post([init, capabilities] {
init(capabilities);
});
}); });
}); }
} }
void Source::LanguageProtocolView::close() { void Source::LanguageProtocolView::close() {
@ -743,8 +763,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
if(capabilities.rename || capabilities.document_highlight) { if(capabilities.rename || capabilities.document_highlight) {
rename_similar_tokens = [this](const std::string &text) { rename_similar_tokens = [this](const std::string &text) {
class Changes { struct Changes {
public:
std::string file; std::string file;
std::vector<LanguageProtocol::TextEdit> text_edits; std::vector<LanguageProtocol::TextEdit> text_edits;
}; };
@ -754,10 +773,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
return; return;
auto iter = get_buffer()->get_insert()->get_iter(); auto iter = get_buffer()->get_insert()->get_iter();
std::vector<Changes> changes; std::vector<Changes> changes_vec;
std::promise<void> result_processed; std::promise<void> result_processed;
if(capabilities.rename) { if(capabilities.rename) {
client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
boost::filesystem::path project_path; boost::filesystem::path project_path;
auto build = Project::Build::create(file_path); auto build = Project::Build::create(file_path);
@ -774,7 +793,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
std::vector<LanguageProtocol::TextEdit> edits; std::vector<LanguageProtocol::TextEdit> edits;
for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it)
edits.emplace_back(edit_it->second); edits.emplace_back(edit_it->second);
changes.emplace_back(Changes{std::move(file), std::move(edits)}); changes_vec.emplace_back(Changes{std::move(file), std::move(edits)});
} }
} }
} }
@ -782,28 +801,28 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) {
LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second);
if(filesystem::file_in_path(text_document_edit.file, project_path)) if(filesystem::file_in_path(text_document_edit.file, project_path))
changes.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)}); changes_vec.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)});
} }
} }
} }
catch(...) { catch(...) {
changes.clear(); changes_vec.clear();
} }
} }
result_processed.set_value(); result_processed.set_value();
}); });
} }
else { else {
client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
try { try {
std::vector<LanguageProtocol::TextEdit> edits; std::vector<LanguageProtocol::TextEdit> edits;
for(auto it = result.begin(); it != result.end(); ++it) for(auto it = result.begin(); it != result.end(); ++it)
edits.emplace_back(it->second, text); edits.emplace_back(it->second, text);
changes.emplace_back(Changes{file_path.string(), std::move(edits)}); changes_vec.emplace_back(Changes{file_path.string(), std::move(edits)});
} }
catch(...) { catch(...) {
changes.clear(); changes_vec.clear();
} }
} }
result_processed.set_value(); result_processed.set_value();
@ -811,95 +830,105 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
} }
result_processed.get_future().get(); result_processed.get_future().get();
std::vector<Changes *> changes_renamed; auto current_view = Notebook::get().get_current_view();
std::vector<Changes *> changes_in_unopened_files; struct ChangesAndView {
std::vector<std::pair<Changes *, Source::View *>> changes_in_opened_files; Changes *changes;
for(auto &change : changes) { Source::View *view;
auto view_it = views.end(); bool close;
};
std::vector<ChangesAndView> changes_and_views;
for(auto &changes : changes_vec) {
Source::View *view = nullptr;
for(auto it = views.begin(); it != views.end(); ++it) { for(auto it = views.begin(); it != views.end(); ++it) {
if((*it)->file_path == change.file) { if((*it)->file_path == changes.file) {
view_it = it; view = *it;
break; break;
} }
} }
if(view_it != views.end()) if(!view) {
changes_in_opened_files.emplace_back(&change, *view_it); if(!Notebook::get().open(changes.file))
return;
view = Notebook::get().get_current_view();
changes_and_views.emplace_back(ChangesAndView{&changes, view, true});
}
else else
changes_in_unopened_files.emplace_back(&change); changes_and_views.emplace_back(ChangesAndView{&changes, view, false});
} }
// Write changes to unopened files first, since this might improve server handling of opened files that will be changed after if(current_view)
for(auto &change : changes_in_unopened_files) { Notebook::get().open(current_view);
Glib::ustring buffer;
{ if(!changes_and_views.empty()) {
std::ifstream stream(change->file, std::ifstream::binary); Terminal::get().print("Renamed ");
if(stream) Terminal::get().print(previous_text, true);
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); Terminal::get().print(" to ");
} Terminal::get().print(text, true);
std::ofstream stream(change->file, std::ifstream::binary); Terminal::get().print(" at:\n");
if(!buffer.empty() && stream) {
std::vector<size_t> lines_start_pos = {0};
for(size_t c = 0; c < buffer.size(); ++c) {
if(buffer[c] == '\n')
lines_start_pos.emplace_back(c + 1);
}
for(auto edit_it = change->text_edits.rbegin(); edit_it != change->text_edits.rend(); ++edit_it) {
auto start_line = edit_it->range.start.line;
auto end_line = edit_it->range.end.line;
if(static_cast<size_t>(start_line) < lines_start_pos.size()) {
auto start = lines_start_pos[start_line] + edit_it->range.start.character;
size_t end;
if(static_cast<size_t>(end_line) >= lines_start_pos.size())
end = buffer.size();
else
end = lines_start_pos[end_line] + edit_it->range.end.character;
if(start < buffer.size() && end <= buffer.size()) {
buffer.replace(start, end - start, edit_it->new_text);
}
}
}
stream.write(buffer.data(), buffer.bytes());
changes_renamed.emplace_back(change);
}
else
Terminal::get().print("\e[31mError\e[m: could not write to file " + filesystem::get_short_path(change->file).string() + '\n', true);
} }
for(auto &pair : changes_in_opened_files) { for(auto &changes_and_view : changes_and_views) {
auto change = pair.first; auto changes = changes_and_view.changes;
auto view = pair.second; auto view = changes_and_view.view;
auto buffer = view->get_buffer(); auto buffer = view->get_buffer();
buffer->begin_user_action(); buffer->begin_user_action();
auto end_iter = buffer->end(); auto end_iter = buffer->end();
// If entire buffer is replaced // If entire buffer is replaced
if(change->text_edits.size() == 1 && if(changes->text_edits.size() == 1 &&
change->text_edits[0].range.start.line == 0 && change->text_edits[0].range.start.character == 0 && changes->text_edits[0].range.start.line == 0 && changes->text_edits[0].range.start.character == 0 &&
(change->text_edits[0].range.end.line > end_iter.get_line() || (changes->text_edits[0].range.end.line > end_iter.get_line() ||
(change->text_edits[0].range.end.line == end_iter.get_line() && change->text_edits[0].range.end.character >= end_iter.get_line_offset()))) (changes->text_edits[0].range.end.line == end_iter.get_line() && changes->text_edits[0].range.end.character >= end_iter.get_line_offset()))) {
view->replace_text(change->text_edits[0].new_text); view->replace_text(changes->text_edits[0].new_text);
Terminal::get().print(filesystem::get_short_path(view->file_path).string() + ":1:1\n");
}
else { else {
for(auto edit_it = change->text_edits.rbegin(); edit_it != change->text_edits.rend(); ++edit_it) { struct TerminalOutput {
std::string prefix;
std::string new_text;
std::string postfix;
};
std::list<TerminalOutput> terminal_output_list;
for(auto edit_it = changes->text_edits.rbegin(); edit_it != changes->text_edits.rend(); ++edit_it) {
auto start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); auto start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character);
auto end_iter = view->get_iter_at_line_pos(edit_it->range.end.line, edit_it->range.end.character); auto end_iter = view->get_iter_at_line_pos(edit_it->range.end.line, edit_it->range.end.character);
if(view != current_view)
view->get_buffer()->place_cursor(start_iter);
buffer->erase(start_iter, end_iter); buffer->erase(start_iter, end_iter);
start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character);
auto start_line_iter = buffer->get_iter_at_line(start_iter.get_line());
while((*start_line_iter == ' ' || *start_line_iter == '\t') && start_line_iter < start_iter && start_line_iter.forward_char()) {
}
auto end_line_iter = start_iter;
while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {
}
terminal_output_list.emplace_front(TerminalOutput{filesystem::get_short_path(view->file_path).string() + ':' + std::to_string(edit_it->range.start.line + 1) + ':' + std::to_string(edit_it->range.start.character + 1) + ": " + buffer->get_text(start_line_iter, start_iter),
edit_it->new_text,
buffer->get_text(start_iter, end_line_iter) + '\n'});
buffer->insert(start_iter, edit_it->new_text); buffer->insert(start_iter, edit_it->new_text);
} }
for(auto &output : terminal_output_list) {
Terminal::get().print(output.prefix);
Terminal::get().print(output.new_text, true);
Terminal::get().print(output.postfix);
}
} }
buffer->end_user_action(); buffer->end_user_action();
view->save(); if(!view->save())
changes_renamed.emplace_back(change); return;
} }
if(!changes_renamed.empty()) { for(auto &changes_and_view : changes_and_views) {
Terminal::get().print("Renamed "); if(changes_and_view.close)
Terminal::get().print(previous_text, true); Notebook::get().close(changes_and_view.view);
Terminal::get().print(" to "); changes_and_view.close = false;
Terminal::get().print(text, true);
Terminal::get().print("\n");
} }
}; };
} }

1
src/source_language_protocol.hpp

@ -145,6 +145,7 @@ namespace LanguageProtocol {
~Client(); ~Client();
boost::optional<Capabilities> get_capabilities(Source::LanguageProtocolView *view);
Capabilities initialize(Source::LanguageProtocolView *view); Capabilities initialize(Source::LanguageProtocolView *view);
void close(Source::LanguageProtocolView *view); void close(Source::LanguageProtocolView *view);

24
src/terminal.cpp

@ -404,18 +404,18 @@ boost::optional<Terminal::Link> Terminal::find_link(const std::string &line) {
if(line.size() >= 1000) // Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164 if(line.size() >= 1000) // Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164
return {}; return {};
const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages
"^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info
"^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc)
"^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust
"^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" // clang assert() "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" // clang assert()
"^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" // gcc assert() "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" // gcc assert()
"^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$|" // g_assert (glib.h) "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$|" // g_assert (glib.h)
"^([A-Z]:)?([\\\\/][^:]+):([0-9]+)$|" // Node.js "^([A-Z]:)?([\\\\/][^:]+):([0-9]+)$|" // Node.js
"^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace
"^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace
"^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python
"^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix path:line:column "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/~][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix path:line:column
std::regex::optimize); std::regex::optimize);
std::smatch sm; std::smatch sm;
if(std::regex_match(line, sm, link_regex)) { if(std::regex_match(line, sm, link_regex)) {

Loading…
Cancel
Save