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. 179
      src/source_language_protocol.cpp
  4. 1
      src/source_language_protocol.hpp
  5. 2
      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();

179
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,10 +464,8 @@ 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);
@ -479,9 +489,19 @@ void Source::LanguageProtocolView::initialize() {
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() {
autocomplete_delayed_show_arguments_connection.disconnect(); autocomplete_delayed_show_arguments_connection.disconnect();
@ -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))
else return;
changes_in_unopened_files.emplace_back(&change); view = Notebook::get().get_current_view();
changes_and_views.emplace_back(ChangesAndView{&changes, view, true});
} }
// Write changes to unopened files first, since this might improve server handling of opened files that will be changed after
for(auto &change : changes_in_unopened_files) {
Glib::ustring buffer;
{
std::ifstream stream(change->file, std::ifstream::binary);
if(stream)
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
}
std::ofstream stream(change->file, std::ifstream::binary);
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 else
end = lines_start_pos[end_line] + edit_it->range.end.character; changes_and_views.emplace_back(ChangesAndView{&changes, view, false});
if(start < buffer.size() && end <= buffer.size()) {
buffer.replace(start, end - start, edit_it->new_text);
}
} }
}
stream.write(buffer.data(), buffer.bytes()); if(current_view)
changes_renamed.emplace_back(change); Notebook::get().open(current_view);
}
else if(!changes_and_views.empty()) {
Terminal::get().print("\e[31mError\e[m: could not write to file " + filesystem::get_short_path(change->file).string() + '\n', true); Terminal::get().print("Renamed ");
Terminal::get().print(previous_text, true);
Terminal::get().print(" to ");
Terminal::get().print(text, true);
Terminal::get().print(" at:\n");
} }
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);

2
src/terminal.cpp

@ -415,7 +415,7 @@ boost::optional<Terminal::Link> Terminal::find_link(const std::string &line) {
"^ +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