diff --git a/juci/CMakeLists.txt b/juci/CMakeLists.txt index b7a518e..b8474db 100644 --- a/juci/CMakeLists.txt +++ b/juci/CMakeLists.txt @@ -125,6 +125,10 @@ add_executable(${project_name} directories.cc terminal.h terminal.cc + tooltips.h + tooltips.cc + singletons.h + singletons.cc ) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/juci/config.cc b/juci/config.cc index 22418eb..9fa2a69 100644 --- a/juci/config.cc +++ b/juci/config.cc @@ -2,7 +2,7 @@ #include "logging.h" MainConfig::MainConfig() : - keybindings_cfg(), source_cfg() { + keybindings_cfg() { INFO("Reading config file"); boost::property_tree::json_parser::read_json("config.json", cfg_); INFO("Config file read"); @@ -13,6 +13,7 @@ MainConfig::MainConfig() : } void MainConfig::GenerateSource() { + auto source_cfg=Singletons::Config::source(); DEBUG("Fetching source cfg"); // boost::property_tree::ptree auto source_json = cfg_.get_child("source"); @@ -22,30 +23,36 @@ void MainConfig::GenerateSource() { auto visual_json = source_json.get_child("visual"); for (auto &i : visual_json) { if (i.first == "background") { - source_cfg.background = i.second.get_value(); + source_cfg->background = i.second.get_value(); } - if (i.first == "show_line_numbers") { - source_cfg.show_line_numbers = i.second.get_value() == "1" ? true : false; + else if (i.first == "background_selected") { + source_cfg->background_selected = i.second.get_value(); } - if (i.first == "highlight_current_line") { - source_cfg.highlight_current_line = i.second.get_value() == "1" ? true : false; + else if (i.first == "background_tooltips") { + source_cfg->background_tooltips = i.second.get_value(); } - if (i.first == "font") { - source_cfg.font = i.second.get_value(); + else if (i.first == "show_line_numbers") { + source_cfg->show_line_numbers = i.second.get_value() == "1" ? true : false; + } + else if (i.first == "highlight_current_line") { + source_cfg->highlight_current_line = i.second.get_value() == "1" ? true : false; + } + else if (i.first == "font") { + source_cfg->font = i.second.get_value(); } } - source_cfg.tab_size = source_json.get("tab_size"); - for (unsigned c = 0; c < source_cfg.tab_size; c++) { - source_cfg.tab+=" "; + source_cfg->tab_size = source_json.get("tab_size"); + for (unsigned c = 0; c < source_cfg->tab_size; c++) { + source_cfg->tab+=" "; } for (auto &i : colors_json) { - source_cfg.tags[i.first]=i.second.get_value(); + source_cfg->tags[i.first]=i.second.get_value(); } for (auto &i : syntax_json) { - source_cfg.types[i.first]=i.second.get_value(); + source_cfg->types[i.first]=i.second.get_value(); } for (auto &i : extensions_json) { - source_cfg.extensions.emplace_back(i.second.get_value()); + source_cfg->extensions.emplace_back(i.second.get_value()); } DEBUG("Source cfg fetched"); } diff --git a/juci/config.h b/juci/config.h index bbe03cf..c6913fc 100644 --- a/juci/config.h +++ b/juci/config.h @@ -4,6 +4,7 @@ #include #include #include +#include "singletons.h" #include "keybindings.h" #include "source.h" #include "directories.h" @@ -11,7 +12,6 @@ class MainConfig { public: - Source::Config source_cfg; Terminal::Config terminal_cfg; Keybindings::Config keybindings_cfg; Directories::Config dir_cfg; diff --git a/juci/config.json b/juci/config.json index 0d35a91..42effbb 100644 --- a/juci/config.json +++ b/juci/config.json @@ -8,7 +8,9 @@ "type": "#0066FF", "keyword": "blue", "comment": "grey", - "own": "pink" + "own": "pink", + "diagnostic_warning": "orange", + "diagnostic_error": "red" }, "syntax": { "43": "type", @@ -32,6 +34,8 @@ ], "visual": { "background": "white", + "background_selected": "blue", + "background_tooltips": "yellow", "font": "Monospace", "show_line_numbers": 1, "highlight_current_line": 1 diff --git a/juci/notebook.cc b/juci/notebook.cc index fa52666..1398225 100644 --- a/juci/notebook.cc +++ b/juci/notebook.cc @@ -11,11 +11,9 @@ Notebook::View::View() { Notebook::Controller::Controller(Keybindings::Controller& keybindings, Terminal::Controller& terminal, - Source::Config& source_cfg, Directories::Config& dir_cfg) : terminal(terminal), - directories(dir_cfg), - source_config(source_cfg) { + directories(dir_cfg) { INFO("Create notebook"); refClipboard_ = Gtk::Clipboard::get(); view.pack1(directories.widget(), true, true); @@ -173,7 +171,7 @@ Notebook::Controller::~Controller() { void Notebook::Controller::OnOpenFile(std::string path) { INFO("Notebook open file"); INFO("Notebook create page"); - text_vec_.emplace_back(new Source::Controller(source_config, path, project_path, terminal)); + text_vec_.emplace_back(new Source::Controller(path, project_path, terminal)); scrolledtext_vec_.push_back(new Gtk::ScrolledWindow()); editor_vec_.push_back(new Gtk::HBox()); scrolledtext_vec_.back()->add(*text_vec_.back()->view); @@ -187,6 +185,7 @@ void Notebook::Controller::OnOpenFile(std::string path) { Notebook().set_current_page(Pages()-1); Notebook().set_focus_child(*text_vec_.back()->view); //Add star on tab label when the page is not saved: + //TODO: instead use Gtk::TextBuffer::set_modified and Gtk::TextBuffer::get_modified text_vec_.back()->buffer()->signal_changed().connect([this]() { if(text_vec_.at(CurrentPage())->is_saved) { std::string path=CurrentTextView().file_path; diff --git a/juci/notebook.h b/juci/notebook.h index 5aba1bf..8ff76b1 100644 --- a/juci/notebook.h +++ b/juci/notebook.h @@ -24,7 +24,6 @@ namespace Notebook { public: Controller(Keybindings::Controller& keybindings, Terminal::Controller& terminal, - Source::Config& config, Directories::Config& dir_cfg); ~Controller(); Source::View& CurrentTextView(); @@ -51,7 +50,6 @@ namespace Notebook { Glib::RefPtr m_refBuilder; Glib::RefPtr refActionGroup; Terminal::Controller& terminal; - Source::Config& source_config; std::vector scrolledtext_vec_; std::vector editor_vec_; diff --git a/juci/selectiondialog.cc b/juci/selectiondialog.cc index 8eca51d..ca794bc 100644 --- a/juci/selectiondialog.cc +++ b/juci/selectiondialog.cc @@ -1,85 +1,229 @@ #include "selectiondialog.h" -SelectionDialog::SelectionDialog(Gtk::TextView& text_view): Gtk::Dialog(), text_view(text_view), - list_view_text(1, false, Gtk::SelectionMode::SELECTION_SINGLE) { - scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); - list_view_text.set_enable_search(true); - list_view_text.set_headers_visible(false); - list_view_text.set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); - list_view_text.set_activate_on_single_click(true); -} +SelectionDialog::SelectionDialog(Gtk::TextView& text_view): text_view(text_view) {} -void SelectionDialog::show(const std::map& rows) { - for (auto &i : rows) { - list_view_text.append(i.first); - } - scrolled_window.add(list_view_text); - get_vbox()->pack_start(scrolled_window); - set_transient_for((Gtk::Window&)(*text_view.get_toplevel())); - show_all(); - int popup_x = get_width(); - int popup_y = rows.size() * 20; - adjust(popup_x, popup_y); +void SelectionDialog::show() { + if(rows.size()==0) + return; - list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) { - if(on_select) - on_select(list_view_text); - response(Gtk::RESPONSE_DELETE_EVENT); + window=std::unique_ptr(new Gtk::Window(Gtk::WindowType::WINDOW_POPUP)); + scrolled_window=std::unique_ptr(new Gtk::ScrolledWindow()); + list_view_text=std::unique_ptr(new Gtk::ListViewText(1, false, Gtk::SelectionMode::SELECTION_BROWSE)); + + window->set_default_size(0, 0); + window->property_decorated()=false; + window->set_skip_taskbar_hint(true); + scrolled_window->set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); + list_view_text->set_enable_search(true); + list_view_text->set_headers_visible(false); + list_view_text->set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); + list_view_text->set_activate_on_single_click(true); + list_view_text->set_hover_selection(false); + list_view_text->set_rules_hint(true); + //list_view_text->set_fixed_height_mode(true); //TODO: This is buggy on OS X, remember to post an issue on GTK+ 3 + + last_selected=-1; + + list_view_text->signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) { + if(shown) { + select(); + } + }); + list_view_text->signal_cursor_changed().connect(sigc::mem_fun(*this, &SelectionDialog::cursor_changed), true); + list_view_text->signal_realize().connect([this](){ + resize(); }); - signal_focus_out_event().connect(sigc::mem_fun(*this, &SelectionDialog::close), false); + show_offset=text_view.get_buffer()->get_insert()->get_iter().get_offset(); + list_view_text->clear_items(); + for (auto &i : rows) { + list_view_text->append(i.first); + } + + scrolled_window->add(*list_view_text); + window->add(*scrolled_window); + + if(rows.size()>0) { + list_view_text->get_selection()->select(*list_view_text->get_model()->children().begin()); + list_view_text->scroll_to_row(list_view_text->get_selection()->get_selected_rows()[0]); + } + + move(); - run(); + window->show_all(); + shown=true; + row_in_entry=false; + auto text=text_view.get_buffer()->get_text(start_mark->get_iter(), text_view.get_buffer()->get_insert()->get_iter()); + if(text.size()>0) { + search_entry.set_text(text); + list_view_text->set_search_entry(search_entry); + } } -bool SelectionDialog::close(GdkEventFocus*) { - response(Gtk::RESPONSE_DELETE_EVENT); - return true; +void SelectionDialog::hide() { + window->hide(); + shown=false; + if(tooltips) + tooltips->hide(); } -void SelectionDialog::adjust(int current_x, int current_y) { - INFO("SelectionDialog set size"); - int view_x = text_view.get_width(); - int view_y = 150; - bool is_never_scroll_x = true; - bool is_never_scroll_y = true; - if (current_x > view_x) { - current_x = view_x; - is_never_scroll_x = false; +void SelectionDialog::select(bool hide_window) { + row_in_entry=true; + auto selected=list_view_text->get_selected(); + std::pair select; + if(selected.size()>0) { + select = rows.at(list_view_text->get_text(selected[0])); + text_view.get_buffer()->erase(start_mark->get_iter(), text_view.get_buffer()->get_insert()->get_iter()); + text_view.get_buffer()->insert(start_mark->get_iter(), select.first); } - if (current_y > view_y) { - current_y = view_y; - is_never_scroll_y = false; + if(hide_window) { + hide(); + char find_char=select.first.back(); + if(find_char==')' || find_char=='>') { + if(find_char==')') + find_char='('; + else + find_char='<'; + size_t pos=select.first.find(find_char); + if(pos!=std::string::npos) { + auto start_offset=start_mark->get_iter().get_offset()+pos+1; + auto end_offset=start_mark->get_iter().get_offset()+select.first.size()-1; + if(start_offset!=end_offset) + text_view.get_buffer()->select_range(text_view.get_buffer()->get_iter_at_offset(start_offset), text_view.get_buffer()->get_iter_at_offset(end_offset)); + } + } } - scrolled_window.set_size_request(current_x, current_y); - if (!is_never_scroll_x && !is_never_scroll_y) { - scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); - } else if (!is_never_scroll_x && is_never_scroll_y) { - scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_NEVER); - } else if (is_never_scroll_x && !is_never_scroll_y) { - scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); +} + +bool SelectionDialog::on_key_release(GdkEventKey* key) { + if(key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_Up) + return false; + + if(show_offset>text_view.get_buffer()->get_insert()->get_iter().get_offset()) { + hide(); } - - INFO("SelectionDialog set position"); - Gdk::Rectangle temp1, temp2; - text_view.get_cursor_locations(text_view.get_buffer()->get_insert()->get_iter(), temp1, temp2); - int view_edge_x = 0; - int view_edge_y = 0; - int x, y; - text_view.buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_WIDGET, - temp1.get_x(), temp1.get_y(), x, y); - Glib::RefPtr gdkw = text_view.get_window(Gtk::TextWindowType::TEXT_WINDOW_WIDGET); - gdkw->get_origin(view_edge_x, view_edge_y); + else { + auto text=text_view.get_buffer()->get_text(start_mark->get_iter(), text_view.get_buffer()->get_insert()->get_iter()); + if(text.size()>0) { + search_entry.set_text(text); + list_view_text->set_search_entry(search_entry); + } + cursor_changed(); + } + return false; +} - x += view_edge_x; - y += view_edge_y; - if ((view_edge_x-x)*-1 > text_view.get_width()-current_x) { - x -= current_x; - if (x < view_edge_x) x = view_edge_x; +bool SelectionDialog::on_key_press(GdkEventKey* key) { + if((key->keyval>=GDK_KEY_0 && key->keyval<=GDK_KEY_9) || + (key->keyval>=GDK_KEY_A && key->keyval<=GDK_KEY_Z) || + (key->keyval>=GDK_KEY_a && key->keyval<=GDK_KEY_z) || + key->keyval==GDK_KEY_underscore || key->keyval==GDK_KEY_BackSpace) { + if(row_in_entry) { + text_view.get_buffer()->erase(start_mark->get_iter(), text_view.get_buffer()->get_insert()->get_iter()); + row_in_entry=false; + if(key->keyval==GDK_KEY_BackSpace) { + return true; + } + } + return false; + } + if(key->keyval==GDK_KEY_Shift_L || key->keyval==GDK_KEY_Shift_R || key->keyval==GDK_KEY_Alt_L || key->keyval==GDK_KEY_Alt_R || key->keyval==GDK_KEY_Control_L || key->keyval==GDK_KEY_Control_R || key->keyval==GDK_KEY_Meta_L || key->keyval==GDK_KEY_Meta_R) + return false; + if(key->keyval==GDK_KEY_Down) { + auto it=list_view_text->get_selection()->get_selected(); + if(it) { + it++; + if(it) { + list_view_text->get_selection()->select(it); + list_view_text->scroll_to_row(list_view_text->get_selection()->get_selected_rows()[0]); + } + } + select(false); + cursor_changed(); + return true; + } + if(key->keyval==GDK_KEY_Up) { + auto it=list_view_text->get_selection()->get_selected(); + if(it) { + it--; + if(it) { + list_view_text->get_selection()->select(it); + list_view_text->scroll_to_row(list_view_text->get_selection()->get_selected_rows()[0]); + } + } + select(false); + cursor_changed(); + return true; } - if ((view_edge_y-y)*-1 > text_view.get_height()-current_y) { - y -= (current_y+14) + 15; - if (x < view_edge_y) y = view_edge_y +15; + if(key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) { + select(); + return true; + } + hide(); + return false; +} + +void SelectionDialog::cursor_changed() { + auto selected=list_view_text->get_selected(); + if(selected.size()>0) { + if(selected[0]!=last_selected || last_selected==-1) { + if(tooltips) + tooltips->hide(); + auto row = rows.at(list_view_text->get_text(selected[0])); + if(row.second.size()>0) { + tooltips=std::unique_ptr(new Tooltips()); + auto tooltip_text=row.second; + auto get_tooltip_buffer=[this, tooltip_text]() { + auto tooltip_buffer=Gtk::TextBuffer::create(text_view.get_buffer()->get_tag_table()); + //TODO: Insert newlines to tooltip_text (use 80 chars, then newline?) + tooltip_buffer->insert_at_cursor(tooltip_text); + return tooltip_buffer; + }; + tooltips->emplace_back(get_tooltip_buffer, text_view, text_view.get_buffer()->create_mark(start_mark->get_iter()), text_view.get_buffer()->create_mark(text_view.get_buffer()->get_insert()->get_iter())); + tooltips->show(true); + } + } + } + else if(tooltips) + tooltips->hide(); + if(selected.size()>0) + last_selected=selected[0]; + else + last_selected=-1; +} + +void SelectionDialog::move() { + INFO("SelectionDialog set position"); + Gdk::Rectangle rectangle; + text_view.get_iter_location(start_mark->get_iter(), rectangle); + int buffer_x=rectangle.get_x(); + int buffer_y=rectangle.get_y()+rectangle.get_height(); + int window_x, window_y; + text_view.buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, window_x, window_y); + int root_x, root_y; + text_view.get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y); + window->move(root_x, root_y+1); //TODO: replace 1 with some margin +} + +void SelectionDialog::resize() { + INFO("SelectionDialog set size"); + + if(list_view_text->get_realized()) { + int row_width=0, row_height; + Gdk::Rectangle rect; + list_view_text->get_cell_area(list_view_text->get_model()->get_path(list_view_text->get_model()->children().begin()), *(list_view_text->get_column(0)), rect); + row_width=rect.get_width(); + row_height=rect.get_height(); + + row_width+=rect.get_x()*2; //TODO: Add correct margin x and y + row_height+=rect.get_y()*2; + + if(row_width>text_view.get_width()/2) + row_width=text_view.get_width()/2; + else + scrolled_window->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); + + int window_height=std::min(row_height*(int)rows.size(), row_height*10); + window->resize(row_width, window_height); } - move(x, y+15); -} \ No newline at end of file +} diff --git a/juci/selectiondialog.h b/juci/selectiondialog.h index f8479ce..f93274f 100644 --- a/juci/selectiondialog.h +++ b/juci/selectiondialog.h @@ -3,22 +3,36 @@ #include "gtkmm.h" #include "logging.h" -#include "source.h" +#include "tooltips.h" -class SelectionDialog : public Gtk::Dialog { +class SelectionDialog { public: SelectionDialog(Gtk::TextView& text_view); - void show(const std::map& rows); + void show(); + void hide(); bool close(GdkEventFocus*); + void move(); + bool on_key_release(GdkEventKey* key); + bool on_key_press(GdkEventKey* key); - std::function on_select; - + std::map > rows; + bool shown=false; + Glib::RefPtr start_mark; private: - void adjust(int current_x, int current_y); + void resize(); + void select(bool hide_window=true); + void cursor_changed(); + + Gtk::Entry search_entry; + int show_offset; + bool row_in_entry; Gtk::TextView& text_view; - Gtk::ScrolledWindow scrolled_window; - Gtk::ListViewText list_view_text; + std::unique_ptr window; + std::unique_ptr scrolled_window; + std::unique_ptr list_view_text; + std::unique_ptr tooltips; + int last_selected; }; #endif // JUCI_SELECTIONDIALOG_H_ \ No newline at end of file diff --git a/juci/singletons.cc b/juci/singletons.cc new file mode 100644 index 0000000..eaa2909 --- /dev/null +++ b/juci/singletons.cc @@ -0,0 +1,3 @@ +#include "singletons.h" + +std::unique_ptr Singletons::Config::source_=std::unique_ptr(new Source::Config()); diff --git a/juci/singletons.h b/juci/singletons.h new file mode 100644 index 0000000..6098094 --- /dev/null +++ b/juci/singletons.h @@ -0,0 +1,20 @@ +#ifndef JUCI_SINGLETONS_H_ +#define JUCI_SINGLETONS_H_ + +#include "keybindings.h" +#include "source.h" +#include "directories.h" +#include "terminal.h" + +namespace Singletons { + class Config { + public: + static Source::Config *source() { + return source_.get(); + } + private: + static std::unique_ptr source_; + }; +} + +#endif // JUCI_SINGLETONS_H_ diff --git a/juci/source.cc b/juci/source.cc index 1275cca..2c1d7e6 100644 --- a/juci/source.cc +++ b/juci/source.cc @@ -6,7 +6,11 @@ #include "logging.h" #include #include -#include "selectiondialog.h" +#include "singletons.h" + +namespace sigc { + SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE +} bool Source::Config::legal_extension(std::string e) const { std::transform(e.begin(), e.end(),e.begin(), ::tolower); @@ -21,17 +25,25 @@ bool Source::Config::legal_extension(std::string e) const { ////////////// //// View //// ////////////// -Source::View::View(const Source::Config& config, const std::string& file_path, const std::string& project_path): -config(config), file_path(file_path), project_path(project_path) { +Source::View::View(const std::string& file_path, const std::string& project_path): +file_path(file_path), project_path(project_path) { Gsv::init(); set_smart_home_end(Gsv::SMART_HOME_END_BEFORE); - set_show_line_numbers(config.show_line_numbers); - set_highlight_current_line(config.highlight_current_line); + set_show_line_numbers(Singletons::Config::source()->show_line_numbers); + set_highlight_current_line(Singletons::Config::source()->highlight_current_line); sourcefile s(file_path); get_source_buffer()->get_undo_manager()->begin_not_undoable_action(); get_source_buffer()->set_text(s.get_content()); get_source_buffer()->get_undo_manager()->end_not_undoable_action(); search_start = search_end = this->get_buffer()->end(); + + override_font(Pango::FontDescription(Singletons::Config::source()->font)); + + override_background_color(Gdk::RGBA(Singletons::Config::source()->background)); + override_background_color(Gdk::RGBA(Singletons::Config::source()->background_selected), Gtk::StateFlags::STATE_FLAG_SELECTED); + for (auto &item : Singletons::Config::source()->tags) { + get_source_buffer()->create_tag(item.first)->property_foreground() = item.second; + } } string Source::View::get_line(size_t line_number) { @@ -51,8 +63,9 @@ string Source::View::get_line_before_insert() { } //Basic indentation -bool Source::View::on_key_press(GdkEventKey* key) { - const std::regex spaces_regex(std::string("^(")+config.tab_char+"*).*$"); +bool Source::View::on_key_press_event(GdkEventKey* key) { + auto config=Singletons::Config::source(); + const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent as in next or previous line if(key->keyval==GDK_KEY_Return && key->state==0) { int line_nr=get_source_buffer()->get_insert()->get_iter().get_line(); @@ -83,7 +96,7 @@ bool Source::View::on_key_press(GdkEventKey* key) { int line_end=selection_end.get_line(); for(int line=line_start;line<=line_end;line++) { Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(line); - get_source_buffer()->insert(line_it, config.tab); + get_source_buffer()->insert(line_it, config->tab); } return true; } @@ -96,7 +109,7 @@ bool Source::View::on_key_press(GdkEventKey* key) { for(int line_nr=line_start;line_nr<=line_end;line_nr++) { string line=get_line(line_nr); - if(!(line.size()>=config.tab_size && line.substr(0, config.tab_size)==config.tab)) + if(!(line.size()>=config->tab_size && line.substr(0, config->tab_size)==config->tab)) return true; } @@ -104,7 +117,7 @@ bool Source::View::on_key_press(GdkEventKey* key) { Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(line_nr); Gtk::TextIter line_plus_it=line_it; - for(unsigned c=0;ctab_size;c++) line_plus_it++; get_source_buffer()->erase(line_it, line_plus_it); } @@ -119,14 +132,14 @@ bool Source::View::on_key_press(GdkEventKey* key) { string previous_line=get_line(line_nr-1); smatch sm; if(std::regex_match(previous_line, sm, spaces_regex)) { - if(line==sm[1] || line==(std::string(sm[1])+config.tab) || (line+config.tab==sm[1])) { + if(line==sm[1] || line==(std::string(sm[1])+config->tab) || (line+config->tab==sm[1])) { Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(line_nr); get_source_buffer()->erase(line_it, insert_it); } } } } - return false; + return Gsv::View::on_key_press_event(key); } ////////////////// @@ -134,15 +147,9 @@ bool Source::View::on_key_press(GdkEventKey* key) { ////////////////// clang::Index Source::ClangView::clang_index(0, 0); -Source::ClangView::ClangView(const Source::Config& config, const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal): -Source::View(config, file_path, project_path), terminal(terminal), -parse_thread_go(true), parse_thread_mapped(false), parse_thread_stop(false) { - override_font(Pango::FontDescription(config.font)); - override_background_color(Gdk::RGBA(config.background)); - for (auto &item : config.tags) { - get_source_buffer()->create_tag(item.first)->property_foreground() = item.second; - } - +Source::ClangView::ClangView(const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal): +Source::View(file_path, project_path), terminal(terminal), +parse_thread_go(true), parse_thread_mapped(false), parse_thread_stop(false) { int start_offset = get_source_buffer()->begin().get_offset(); int end_offset = get_source_buffer()->end().get_offset(); auto buffer_map=get_buffer_map(); @@ -163,7 +170,7 @@ parse_thread_go(true), parse_thread_mapped(false), parse_thread_stop(false) { start_offset, end_offset, &ClangView::clang_index); - update_syntax(extract_tokens(0, get_source_buffer()->get_text().size())); //TODO: replace get_source_buffer()->get_text().size() + update_syntax(); //GTK-calls must happen in main thread, so the parse_thread //sends signals to the main thread that it is to call the following functions: @@ -179,10 +186,16 @@ parse_thread_go(true), parse_thread_mapped(false), parse_thread_stop(false) { parsing_in_progress=this->terminal.print_in_progress("Parsing "+file_path); parse_done.connect([this](){ if(parse_thread_mapped) { - INFO("Updating syntax"); - update_syntax(extract_tokens(0, get_source_buffer()->get_text().size())); + if(parsing_mutex.try_lock()) { + INFO("Updating syntax"); + update_syntax(); + update_diagnostics(); + update_types(); + clang_readable=true; + parsing_mutex.unlock(); + INFO("Syntax updated"); + } parsing_in_progress->done("done"); - INFO("Syntax updated"); } else { parse_thread_go=true; @@ -208,14 +221,20 @@ parse_thread_go(true), parse_thread_mapped(false), parse_thread_stop(false) { } } }); - + get_source_buffer()->signal_changed().connect([this]() { parse_thread_mapped=false; - parse_thread_go=true; + delayed_reparse_connection.disconnect(); + delayed_reparse_connection=Glib::signal_timeout().connect([this]() { + clang_readable=false; + parse_thread_go=true; + return false; + }, 1000); + type_tooltips.hide(); + diagnostic_tooltips.hide(); }); - signal_key_press_event().connect(sigc::mem_fun(*this, &Source::ClangView::on_key_press), false); - signal_key_release_event().connect(sigc::mem_fun(*this, &Source::ClangView::on_key_release), false); + get_buffer()->signal_mark_set().connect(sigc::mem_fun(*this, &Source::ClangView::on_mark_set), false); } Source::ClangView::~ClangView() { @@ -235,10 +254,14 @@ init_syntax_highlighting(const std::map int end_offset, clang::Index *index) { std::vector arguments = get_compilation_commands(); - tu_ = std::unique_ptr(new clang::TranslationUnit(index, + clang_tu = std::unique_ptr(new clang::TranslationUnit(index, file_path, arguments, buffers)); + clang::SourceLocation start(clang_tu.get(), file_path, 0); + clang::SourceLocation end(clang_tu.get(), file_path, buffers.find(file_path)->second.size()-1); + clang::SourceRange range(&start, &end); + clang_tokens=std::unique_ptr(new clang::Tokens(clang_tu.get(), &range)); } std::map Source::ClangView:: @@ -250,34 +273,12 @@ get_buffer_map() const { int Source::ClangView:: reparse(const std::map &buffer) { - return tu_->ReparseTranslationUnit(file_path, buffer); -} - -std::vector Source::ClangView:: -get_autocomplete_suggestions(int line_number, int column) { - INFO("Getting auto complete suggestions"); - std::vector suggestions; - std::map buffer_map; - buffer_map[file_path]=get_source_buffer()->get_text(get_source_buffer()->begin(), get_source_buffer()->get_insert()->get_iter()); - buffer_map[file_path]+="\n"; - parsing_mutex.lock(); - clang::CodeCompleteResults results(tu_.get(), - file_path, - buffer_map, - line_number, - column-1); - for (int i = 0; i < results.size(); i++) { - const vector chunks_ = results.get(i).get_chunks(); - std::vector chunks; - for (auto &chunk : chunks_) { - chunks.emplace_back(chunk); - } - suggestions.emplace_back(chunks); - } - parsing_mutex.unlock(); - DEBUG("Number of suggestions"); - DEBUG_VAR(suggestions.size()); - return suggestions; + int status = clang_tu->ReparseTranslationUnit(file_path, buffer); + clang::SourceLocation start(clang_tu.get(), file_path, 0); + clang::SourceLocation end(clang_tu.get(), file_path, parse_thread_buffer_map.find(file_path)->second.size()-1); + clang::SourceRange range(&start, &end); + clang_tokens=std::unique_ptr(new clang::Tokens(clang_tu.get(), &range)); + return status; } std::vector Source::ClangView:: @@ -297,15 +298,9 @@ get_compilation_commands() { return arguments; } -std::vector Source::ClangView:: -extract_tokens(int start_offset, int end_offset) { +void Source::ClangView::update_syntax() { std::vector ranges; - clang::SourceLocation start(tu_.get(), file_path, start_offset); - clang::SourceLocation end(tu_.get(), file_path, end_offset); - clang::SourceRange range(&start, &end); - clang::Tokens tokens(tu_.get(), &range); - std::vector tks = tokens.tokens(); - for (auto &token : tks) { + for (auto &token : *clang_tokens) { switch (token.kind()) { case 0: highlight_cursor(&token, &ranges); break; // PunctuationToken case 1: highlight_token(&token, &ranges, 702); break; // KeywordToken @@ -314,10 +309,6 @@ extract_tokens(int start_offset, int end_offset) { case 4: highlight_token(&token, &ranges, 705); break; // CommentToken } } - return ranges; -} - -void Source::ClangView::update_syntax(const std::vector &ranges) { if (ranges.empty() || ranges.size() == 0) { return; } @@ -326,7 +317,7 @@ void Source::ClangView::update_syntax(const std::vector &ranges) for (auto &range : ranges) { std::string type = std::to_string(range.kind); try { - config.types.at(type); + Singletons::Config::source()->types.at(type); } catch (std::exception) { continue; } @@ -341,16 +332,138 @@ void Source::ClangView::update_syntax(const std::vector &ranges) buffer->get_iter_at_line_offset(linum_start, begin); Gtk::TextIter end_iter = buffer->get_iter_at_line_offset(linum_end, end); - buffer->apply_tag_by_name(config.types.at(type), + buffer->apply_tag_by_name(Singletons::Config::source()->types.at(type), begin_iter, end_iter); } } +void Source::ClangView::update_diagnostics() { + diagnostic_tooltips.clear(); + clang_tu->update_diagnostics(); + for(size_t c=0;cdiagnostics.size();c++) { + if(clang_tu->diagnostics[c].path==file_path) { + auto start=get_buffer()->get_iter_at_offset(clang_tu->diagnostics[c].start_location.offset); + auto end=get_buffer()->get_iter_at_offset(clang_tu->diagnostics[c].end_location.offset); + std::string diagnostic_tag_name; + if(clang_tu->diagnostics[c].severity<=CXDiagnostic_Warning) + diagnostic_tag_name="diagnostic_warning"; + else + diagnostic_tag_name="diagnostic_error"; + + auto get_tooltip_buffer=[this, c, diagnostic_tag_name]() { + auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), clang_tu->diagnostics[c].severity_spelling, diagnostic_tag_name); + tooltip_buffer->insert_at_cursor(":\n"+clang_tu->diagnostics[c].spelling); + //TODO: Insert newlines to clang_tu->diagnostics[c].spelling (use 80 chars, then newline?) + return tooltip_buffer; + }; + diagnostic_tooltips.emplace_back(get_tooltip_buffer, *this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + + auto tag=get_buffer()->create_tag(); + tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; + auto tag_class=G_OBJECT_GET_CLASS(tag->gobj()); //For older GTK+ 3 versions: + auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); + if(param_spec!=NULL) { + auto diagnostic_tag=get_buffer()->get_tag_table()->lookup(diagnostic_tag_name); + if(diagnostic_tag!=0) + tag->set_property("underline-rgba", diagnostic_tag->property_foreground_rgba().get_value()); + } + get_buffer()->apply_tag(tag, start, end); + } + } +} + +void Source::ClangView::update_types() { + type_tooltips.clear(); + clang_tokens->update_types(clang_tu.get()); + for(size_t c=0;csize();c++) { + if((*clang_tokens)[c].type!="") { + clang::SourceRange range(clang_tu.get(), &((*clang_tokens)[c])); + clang::SourceLocation start(&range, true); + clang::SourceLocation end(&range, false); + std::string path; + unsigned start_offset, end_offset; + start.get_location_info(&path, NULL, NULL, &start_offset); + end.get_location_info(NULL, NULL, NULL, &end_offset); + if(path==file_path) { + auto start=get_buffer()->get_iter_at_offset(start_offset); + auto end=get_buffer()->get_iter_at_offset(end_offset); + auto get_tooltip_buffer=[this, c]() { + auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_at_cursor("Type: "+(*clang_tokens)[c].type); + auto brief_comment=clang_tokens->get_brief_comments(c); + if(brief_comment!="") + tooltip_buffer->insert_at_cursor("\n\n"+brief_comment+"."); + return tooltip_buffer; + }; + + type_tooltips.emplace_back(get_tooltip_buffer, *this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + } + } + } +} + +bool Source::ClangView::on_motion_notify_event(GdkEventMotion* event) { + delayed_tooltips_connection.disconnect(); + if(clang_readable) { + Gdk::Rectangle rectangle(event->x, event->y, 1, 1); + diagnostic_tooltips.init(); + type_tooltips.show(rectangle); + diagnostic_tooltips.show(rectangle); + } + else { + type_tooltips.hide(); + diagnostic_tooltips.hide(); + } + + return Source::View::on_motion_notify_event(event); +} + +void Source::ClangView::on_mark_set(const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { + if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") + delayed_tooltips_connection.disconnect(); + + if(mark->get_name()=="insert") { + delayed_tooltips_connection.disconnect(); + delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { + if(clang_readable) { + Gdk::Rectangle rectangle; + get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); + int location_window_x, location_window_y; + buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); + rectangle.set_x(location_window_x-2); + rectangle.set_y(location_window_y); + rectangle.set_width(4); + diagnostic_tooltips.init(); + type_tooltips.show(rectangle); + diagnostic_tooltips.show(rectangle); + } + return false; + }, 500); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + } +} + +bool Source::ClangView::on_focus_out_event(GdkEventFocus* event) { + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + return Source::View::on_focus_out_event(event); +} + +bool Source::ClangView::on_scroll_event(GdkEventScroll* event) { + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + return Source::View::on_scroll_event(event); +} + void Source::ClangView:: highlight_cursor(clang::Token *token, std::vector *source_ranges) { - clang::SourceLocation location = token->get_source_location(tu_.get()); - clang::Cursor cursor(tu_.get(), &location); + clang::SourceLocation location = token->get_source_location(clang_tu.get()); + clang::Cursor cursor(clang_tu.get(), &location); clang::SourceRange range(&cursor); clang::SourceLocation begin(&range, true); clang::SourceLocation end(&range, false); @@ -366,7 +479,7 @@ void Source::ClangView:: highlight_token(clang::Token *token, std::vector *source_ranges, int token_kind) { - clang::SourceRange range = token->get_source_range(tu_.get()); + clang::SourceRange range = token->get_source_range(clang_tu.get()); unsigned begin_line_num, begin_offset, end_line_num, end_offset; clang::SourceLocation begin(&range, true); clang::SourceLocation end(&range, false); @@ -378,83 +491,14 @@ highlight_token(clang::Token *token, end_offset), token_kind); } -bool Source::ClangView::on_key_release(GdkEventKey* key) { - INFO("Source::ClangView::on_key_release getting iters"); - // Get function to fill popup with suggests item vector under is for testing - Gtk::TextIter beg = get_source_buffer()->get_insert()->get_iter(); - Gtk::TextIter end = get_source_buffer()->get_insert()->get_iter(); - Gtk::TextIter tmp = get_source_buffer()->get_insert()->get_iter(); - Gtk::TextIter tmp1 = get_source_buffer()->get_insert()->get_iter(); - Gtk::TextIter line = get_source_buffer()->get_iter_at_line(tmp.get_line()); - if (end.backward_char() && end.backward_char()) { - bool illegal_chars = - end.backward_search("\"", Gtk::TEXT_SEARCH_VISIBLE_ONLY, tmp, tmp1, line) - || - end.backward_search("//", Gtk::TEXT_SEARCH_VISIBLE_ONLY, tmp, tmp1, line); - INFO("Source::ClangView::on_key_release checking key->keyval"); - if (illegal_chars) { - return false; - } - std::string c = get_source_buffer()->get_text(end, beg); - switch (key->keyval) { - case 46: - break; - case 58: - if (c != "::") return false; - break; - case 60: - if (c != "->") return false; - break; - case 62: - if (c != "->") return false; - break; - default: - return false; - } - } else { - return false; - } - INFO("Source::ClangView::on_key_release getting autocompletions"); - std::vector acdata=get_autocomplete_suggestions(beg.get_line()+1, - beg.get_line_offset()+2); - std::map rows; - for (auto &data : acdata) { - std::stringstream ss; - std::string return_value; - for (auto &chunk : data.chunks) { - switch (chunk.kind) { - case clang::CompletionChunk_ResultType: - return_value = chunk.chunk; - break; - case clang::CompletionChunk_Informative: break; - default: ss << chunk.chunk; break; - } - } - if (ss.str().length() > 0) { // if length is 0 the result is empty - rows[ss.str() + " --> " + return_value] = ss.str(); - } - } - if (rows.empty()) { - rows["No suggestions found..."] = ""; - } - - SelectionDialog selection_dialog(*this); - selection_dialog.on_select=[this, &rows](Gtk::ListViewText& list_view_text){ - std::string selected = rows.at(list_view_text.get_text(list_view_text.get_selected()[0])); - get_source_buffer()->insert_at_cursor(selected); - }; - selection_dialog.show(rows); - - return true; -} - //Clang indentation //TODO: replace indentation methods with a better implementation or maybe use libclang -bool Source::ClangView::on_key_press(GdkEventKey* key) { - const std::regex bracket_regex(std::string("^(")+config.tab_char+"*).*\\{ *$"); - const std::regex no_bracket_statement_regex(std::string("^(")+config.tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); - const std::regex no_bracket_no_para_statement_regex(std::string("^(")+config.tab_char+"*)(else|try|do) *$"); - const std::regex spaces_regex(std::string("^(")+config.tab_char+"*).*$"); +bool Source::ClangView::on_key_press_event(GdkEventKey* key) { + auto config=Singletons::Config::source(); + const std::regex bracket_regex(std::string("^(")+config->tab_char+"*).*\\{ *$"); + const std::regex no_bracket_statement_regex(std::string("^(")+config->tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); + const std::regex no_bracket_no_para_statement_regex(std::string("^(")+config->tab_char+"*)(else|try|do) *$"); + const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent depending on if/else/etc and brackets if(key->keyval==GDK_KEY_Return && key->state==0) { @@ -466,35 +510,35 @@ bool Source::ClangView::on_key_press(GdkEventKey* key) { string next_line=get_line(line_nr+1); std::smatch sm2; if(std::regex_match(next_line, sm2, spaces_regex)) { - if(sm2[1].str()==sm[1].str()+config.tab) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config.tab); + if(sm2[1].str()==sm[1].str()+config->tab) { + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); scroll_to(get_source_buffer()->get_insert()); return true; } } } - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config.tab+"\n"+sm[1].str()+"}"); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab+"\n"+sm[1].str()+"}"); auto insert_it = get_source_buffer()->get_insert()->get_iter(); - for(size_t c=0;ctab_size+sm[1].str().size();c++) insert_it--; scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->place_cursor(insert_it); return true; } else if(std::regex_match(line, sm, no_bracket_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config.tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); scroll_to(get_source_buffer()->get_insert()); return true; } else if(std::regex_match(line, sm, no_bracket_no_para_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config.tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); scroll_to(get_source_buffer()->get_insert()); return true; } else if(std::regex_match(line, sm, spaces_regex)) { std::smatch sm2; size_t line_nr=get_source_buffer()->get_insert()->get_iter().get_line(); - if(line_nr>0 && sm[1].str().size()>=config.tab_size) { + if(line_nr>0 && sm[1].str().size()>=config->tab_size) { string previous_line=get_line(line_nr-1); if(!std::regex_match(previous_line, sm2, bracket_regex)) { if(std::regex_match(previous_line, sm2, no_bracket_statement_regex)) { @@ -514,15 +558,15 @@ bool Source::ClangView::on_key_press(GdkEventKey* key) { //Indent left when writing } on a new line else if(key->keyval==GDK_KEY_braceright) { string line=get_line_before_insert(); - if(line.size()>=config.tab_size) { + if(line.size()>=config->tab_size) { for(auto c: line) { - if(c!=config.tab_char) + if(c!=config->tab_char) return false; } Gtk::TextIter insert_it = get_source_buffer()->get_insert()->get_iter(); Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(insert_it.get_line()); Gtk::TextIter line_plus_it=line_it; - for(unsigned c=0;ctab_size;c++) line_plus_it++; get_source_buffer()->erase(line_it, line_plus_it); @@ -530,7 +574,161 @@ bool Source::ClangView::on_key_press(GdkEventKey* key) { return false; } - return Source::View::on_key_press(key); + return Source::View::on_key_press_event(key); +} + +////////////////////////////// +//// ClangViewAutocomplete /// +////////////////////////////// + +Source::ClangViewAutocomplete::ClangViewAutocomplete(const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal): +Source::ClangView(file_path, project_path, terminal), selection_dialog(*this) { + get_buffer()->signal_changed().connect([this](){ + if(last_keyval==GDK_KEY_BackSpace) + return; + std::string line=" "+get_line_before_insert(); + if((std::count(line.begin(), line.end(), '\"')%2)!=1 && line.find("//")==std::string::npos) { + const std::regex in_specified_namespace("^(.*[a-zA-Z0-9_])(->|\\.|::)([a-zA-Z0-9_]*)$"); + const std::regex within_namespace("^(.*)([^a-zA-Z0-9_]+)([a-zA-Z0-9_]{3,})$"); + std::smatch sm; + if(std::regex_match(line, sm, in_specified_namespace)) { + prefix=sm[3].str(); + if((prefix.size()==0 || prefix[0]<'0' || prefix[0]>'9') && !autocomplete_starting && !selection_dialog.shown) { + autocomplete(); + } + else if(last_keyval=='.' && autocomplete_starting) + autocomplete_cancel_starting=true; + } + else if(std::regex_match(line, sm, within_namespace)) { + prefix=sm[3].str(); + if((prefix.size()==0 || prefix[0]<'0' || prefix[0]>'9') && !autocomplete_starting && !selection_dialog.shown) { + autocomplete(); + } + } + else + autocomplete_cancel_starting=true; + if(autocomplete_starting || selection_dialog.shown) + delayed_reparse_connection.disconnect(); + } + }); + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark){ + if(mark->get_name()=="insert") { + autocomplete_cancel_starting=true; + if(selection_dialog.shown) { + selection_dialog.hide(); + } + } + }); + signal_scroll_event().connect([this](GdkEventScroll* event){ + if(selection_dialog.shown) + selection_dialog.move(); + return false; + }, false); + signal_key_release_event().connect([this](GdkEventKey* key){ + if(selection_dialog.shown) { + if(selection_dialog.on_key_release(key)) + return true; + } + + return false; + }, false); +} + +bool Source::ClangViewAutocomplete::on_key_press_event(GdkEventKey *key) { + last_keyval=key->keyval; + if(selection_dialog.shown) { + delayed_reparse_connection.disconnect(); + if(selection_dialog.on_key_press(key)) + return true; + } + return ClangView::on_key_press_event(key); +} +void Source::ClangViewAutocomplete::autocomplete() { + if(!autocomplete_starting) { + autocomplete_starting=true; + autocomplete_cancel_starting=false; + INFO("Source::ClangView::on_key_release getting autocompletions"); + std::shared_ptr > ac_data=std::make_shared >(); + autocomplete_done_connection.disconnect(); + autocomplete_done_connection=autocomplete_done.connect([this, ac_data](){ + if(!autocomplete_cancel_starting) { + if(selection_dialog.start_mark) + get_buffer()->delete_mark(selection_dialog.start_mark); + auto start_iter=get_buffer()->get_insert()->get_iter(); + for(size_t c=0;ccreate_mark(start_iter); + + std::map > rows; + for (auto &data : *ac_data) { + std::stringstream ss; + std::string return_value; + for (auto &chunk : data.chunks) { + switch (chunk.kind) { + case clang::CompletionChunk_ResultType: + return_value = chunk.chunk; + break; + case clang::CompletionChunk_Informative: break; + default: ss << chunk.chunk; break; + } + } + if (ss.str().length() > 0) { // if length is 0 the result is empty + if(prefix.size()==0 || ss.str().find(prefix)==0) { + auto pair=std::pair(ss.str(), data.brief_comments); + rows[ss.str() + " --> " + return_value] = pair; + } + } + } + if (rows.empty()) { + rows["No suggestions found..."] = std::pair(); + } + selection_dialog.rows=std::move(rows); + selection_dialog.show(); + } + autocomplete_starting=false; + }); + + std::shared_ptr > buffer_map=std::make_shared >(); + auto& buffer=(*buffer_map)[this->file_path]; + buffer=get_buffer()->get_text(get_buffer()->begin(), get_buffer()->get_insert()->get_iter()); + auto iter = get_source_buffer()->get_insert()->get_iter(); + auto line_nr=iter.get_line()+1; + auto column_nr=iter.get_line_offset()+1; + while((buffer.back()>='a' && buffer.back()<='z') || (buffer.back()>='A' && buffer.back()<='Z') || (buffer.back()>='0' && buffer.back()<='9') || buffer.back()=='_') { + buffer.pop_back(); + column_nr--; + } + buffer+="\n"; + std::thread autocomplete_thread([this, ac_data, line_nr, column_nr, buffer_map](){ + parsing_mutex.lock(); + *ac_data=move(get_autocomplete_suggestions(line_nr, column_nr, *buffer_map)); + autocomplete_done(); + parsing_mutex.unlock(); + }); + + autocomplete_thread.detach(); + } +} + +std::vector Source::ClangViewAutocomplete:: +get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map) { + INFO("Getting auto complete suggestions"); + std::vector suggestions; + clang::CodeCompleteResults results(clang_tu.get(), + file_path, + buffer_map, + line_number, + column); + for (int i = 0; i < results.size(); i++) { + auto result=results.get(i); + if(result.available()) { + suggestions.emplace_back(result.get_chunks()); + suggestions.back().brief_comments=result.get_brief_comments(); + } + } + DEBUG("Number of suggestions"); + DEBUG_VAR(suggestions.size()); + return suggestions; } //////////////////// @@ -539,15 +737,14 @@ bool Source::ClangView::on_key_press(GdkEventKey* key) { // Source::Controller::Controller() // Constructor for Controller -Source::Controller::Controller(const Source::Config &config, - const std::string& file_path, std::string project_path, Terminal::Controller& terminal) { +Source::Controller::Controller(const std::string& file_path, std::string project_path, Terminal::Controller& terminal) { if(project_path=="") { project_path=boost::filesystem::path(file_path).parent_path().string(); } - if (config.legal_extension(file_path.substr(file_path.find_last_of(".") + 1))) - view=std::unique_ptr(new ClangView(config, file_path, project_path, terminal)); + if (Singletons::Config::source()->legal_extension(file_path.substr(file_path.find_last_of(".") + 1))) + view=std::unique_ptr(new ClangViewAutocomplete(file_path, project_path, terminal)); else - view=std::unique_ptr(new GenericView(config, file_path, project_path)); + view=std::unique_ptr(new GenericView(file_path, project_path)); INFO("Source Controller with childs constructed"); } diff --git a/juci/source.h b/juci/source.h index d6ec5b1..a86654c 100644 --- a/juci/source.h +++ b/juci/source.h @@ -11,6 +11,8 @@ #include #include "gtksourceviewmm.h" #include "terminal.h" +#include "tooltips.h" +#include "selectiondialog.h" namespace Source { class Config { @@ -18,7 +20,7 @@ namespace Source { bool legal_extension(std::string e) const ; unsigned tab_size; bool show_line_numbers, highlight_current_line; - std::string tab, background, font; + std::string tab, background, background_selected, background_tooltips, font; char tab_char=' '; std::vector extensions; std::unordered_map tags, types; @@ -41,77 +43,76 @@ namespace Source { int kind; }; - class AutoCompleteChunk { - public: - explicit AutoCompleteChunk(const clang::CompletionChunk &clang_chunk) : - chunk(clang_chunk.chunk()), kind(clang_chunk.kind()) { } - std::string chunk; - enum clang::CompletionChunkKind kind; - }; - class AutoCompleteData { public: - explicit AutoCompleteData(const std::vector &chunks) : + explicit AutoCompleteData(const std::vector &chunks) : chunks(chunks) { } - std::vector chunks; + std::vector chunks; + std::string brief_comments; }; class View : public Gsv::View { public: - View(const Source::Config& config, const std::string& file_path, const std::string& project_path); + View(const std::string& file_path, const std::string& project_path); std::string get_line(size_t line_number); std::string get_line_before_insert(); std::string file_path; std::string project_path; Gtk::TextIter search_start, search_end; protected: - const Source::Config& config; - bool on_key_press(GdkEventKey* key); + bool on_key_press_event(GdkEventKey* key); }; // class View class GenericView : public View { public: - GenericView(const Source::Config& config, const std::string& file_path, const std::string& project_path): - View(config, file_path, project_path) { - signal_key_press_event().connect(sigc::mem_fun(*this, &Source::GenericView::on_key_press), false); - } - private: - bool on_key_press(GdkEventKey* key) { - return Source::View::on_key_press(key); + GenericView(const std::string& file_path, const std::string& project_path): + View(file_path, project_path) {} + protected: + bool on_key_press_event(GdkEventKey* key) { + return Source::View::on_key_press_event(key); } }; class ClangView : public View { public: - ClangView(const Source::Config& config, const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal); + ClangView(const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal); ~ClangView(); + protected: + std::unique_ptr clang_tu; + std::map get_buffer_map() const; + std::mutex parsing_mutex; + sigc::connection delayed_reparse_connection; + bool on_key_press_event(GdkEventKey* key); + private: // inits the syntax highligthing on file open void init_syntax_highlighting(const std::map &buffers, int start_offset, int end_offset, clang::Index *index); - std::vector get_autocomplete_suggestions(int line_number, int column); int reparse(const std::map &buffers); - std::vector extract_tokens(int, int); - void update_syntax(const std::vector &locations); - + void update_syntax(); + void update_diagnostics(); + void update_types(); + Tooltips diagnostic_tooltips; + Tooltips type_tooltips; + bool on_motion_notify_event(GdkEventMotion* event); + void on_mark_set(const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark); + sigc::connection delayed_tooltips_connection; + bool on_focus_out_event(GdkEventFocus* event); + bool on_scroll_event(GdkEventScroll* event); static clang::Index clang_index; - std::map get_buffer_map() const; - std::mutex parsing_mutex; - private: - std::unique_ptr tu_; //use unique_ptr since it is not initialized in constructor + std::unique_ptr clang_tokens; + bool clang_readable=false; void highlight_token(clang::Token *token, std::vector *source_ranges, int token_kind); void highlight_cursor(clang::Token *token, std::vector *source_ranges); std::vector get_compilation_commands(); - bool on_key_press(GdkEventKey* key); - bool on_key_release(GdkEventKey* key); Terminal::Controller& terminal; + std::shared_ptr parsing_in_progress; - Glib::Dispatcher parse_done; Glib::Dispatcher parse_start; std::thread parse_thread; @@ -121,11 +122,27 @@ namespace Source { std::atomic parse_thread_mapped; std::atomic parse_thread_stop; }; + + class ClangViewAutocomplete : public ClangView { + public: + ClangViewAutocomplete(const std::string& file_path, const std::string& project_path, Terminal::Controller& terminal); + protected: + bool on_key_press_event(GdkEventKey* key); + private: + void autocomplete(); + SelectionDialog selection_dialog; + std::vector get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map); + Glib::Dispatcher autocomplete_done; + sigc::connection autocomplete_done_connection; + bool autocomplete_starting=false; + bool autocomplete_cancel_starting=false; + guint last_keyval=0; + std::string prefix; + }; class Controller { public: - Controller(const Source::Config &config, - const std::string& file_path, std::string project_path, Terminal::Controller& terminal); + Controller(const std::string& file_path, std::string project_path, Terminal::Controller& terminal); Glib::RefPtr buffer(); bool is_saved = true; diff --git a/juci/tooltips.cc b/juci/tooltips.cc new file mode 100644 index 0000000..01d05f5 --- /dev/null +++ b/juci/tooltips.cc @@ -0,0 +1,109 @@ +#include "tooltips.h" +#include "singletons.h" + +Gdk::Rectangle Tooltips::drawn_tooltips_rectangle=Gdk::Rectangle(); + +Tooltip::Tooltip(std::function()> get_buffer, Gtk::TextView& text_view, +Glib::RefPtr start_mark, Glib::RefPtr end_mark): +get_buffer(get_buffer), text_view(text_view), +start_mark(start_mark), end_mark(end_mark) {} + +Tooltip::~Tooltip() { + text_view.get_buffer()->delete_mark(start_mark); + text_view.get_buffer()->delete_mark(end_mark); +} + +void Tooltip::update() { + auto iter=start_mark->get_iter(); + auto end_iter=end_mark->get_iter(); + text_view.get_iter_location(iter, activation_rectangle); + if(iter.get_offset()(new Gtk::Window(Gtk::WindowType::WINDOW_POPUP)); + + window->set_events(Gdk::POINTER_MOTION_MASK); + window->signal_motion_notify_event().connect(sigc::mem_fun(*this, &Tooltip::tooltip_on_motion_notify_event), false); + window->property_decorated()=false; + window->set_accept_focus(false); + window->set_skip_taskbar_hint(true); + window->set_default_size(0, 0); + + tooltip_widget=std::unique_ptr(new Gtk::TextView(this->get_buffer())); + tooltip_widget->set_editable(false); + tooltip_widget->override_background_color(Gdk::RGBA(Singletons::Config::source()->background_tooltips)); + window->add(*tooltip_widget); + + auto layout=Pango::Layout::create(tooltip_widget->get_pango_context()); + layout->set_text(tooltip_widget->get_buffer()->get_text()); + layout->get_pixel_size(tooltip_width, tooltip_height); + tooltip_height+=2; + } + + int root_x, root_y; + text_view.get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle.get_x(), activation_rectangle.get_y(), root_x, root_y); + Gdk::Rectangle rectangle; + rectangle.set_x(root_x); + rectangle.set_y(root_y-tooltip_height); + rectangle.set_width(tooltip_width); + rectangle.set_height(tooltip_height); + + if(!disregard_drawn) { + if(Tooltips::drawn_tooltips_rectangle.get_width()!=0) { + if(rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) + rectangle.set_y(Tooltips::drawn_tooltips_rectangle.get_y()-tooltip_height); + Tooltips::drawn_tooltips_rectangle.join(rectangle); + } + else + Tooltips::drawn_tooltips_rectangle=rectangle; + } + + window->move(rectangle.get_x(), rectangle.get_y()); +} + +bool Tooltip::tooltip_on_motion_notify_event(GdkEventMotion* event) { + window->hide(); + return false; +} + +void Tooltips::show(const Gdk::Rectangle& rectangle, bool disregard_drawn) { + for(auto& tooltip: *this) { + tooltip.update(); + if(rectangle.intersects(tooltip.activation_rectangle)) { + tooltip.adjust(disregard_drawn); + tooltip.window->show_all(); + } + else if(tooltip.window) + tooltip.window->hide(); + } +} + +void Tooltips::show(bool disregard_drawn) { + for(auto& tooltip: *this) { + tooltip.update(); + tooltip.adjust(disregard_drawn); + tooltip.window->show_all(); + } +} + +void Tooltips::hide() { + for(auto& tooltip: *this) { + if(tooltip.window) + tooltip.window->hide(); + } +} diff --git a/juci/tooltips.h b/juci/tooltips.h new file mode 100644 index 0000000..4bd4466 --- /dev/null +++ b/juci/tooltips.h @@ -0,0 +1,38 @@ +#ifndef JUCI_TOOLTIPS_H_ +#define JUCI_TOOLTIPS_H_ +#include "gtkmm.h" +#include +#include + +class Tooltip { +public: + Tooltip(std::function()> get_buffer, Gtk::TextView& text_view, Glib::RefPtr start_mark, Glib::RefPtr end_mark); + ~Tooltip(); + + void update(); + void adjust(bool disregard_drawn=false); + + Gdk::Rectangle activation_rectangle; + std::unique_ptr window; +private: + bool tooltip_on_motion_notify_event(GdkEventMotion* event); + + std::function()> get_buffer; + std::unique_ptr tooltip_widget; + Glib::RefPtr start_mark; + Glib::RefPtr end_mark; + Gtk::TextView& text_view; + int tooltip_width, tooltip_height; +}; + +class Tooltips : public std::list { +public: + void init() {drawn_tooltips_rectangle=Gdk::Rectangle();} + void show(const Gdk::Rectangle& rectangle, bool disregard_drawn=false); + void show(bool disregard_drawn=false); + void hide(); + + static Gdk::Rectangle drawn_tooltips_rectangle; +}; + +#endif // JUCI_TOOLTIPS_H_ diff --git a/juci/window.cc b/juci/window.cc index 6c54878..e281538 100644 --- a/juci/window.cc +++ b/juci/window.cc @@ -3,64 +3,64 @@ Window::Window() : window_box_(Gtk::ORIENTATION_VERTICAL), - main_config_(), - keybindings_(main_config_.keybindings_cfg), - terminal(main_config_.terminal_cfg), - notebook(keybindings(), terminal, - main_config_.source_cfg, - main_config_.dir_cfg), - menu_(keybindings()), - api_(menu_, notebook) { + main_config(), + keybindings(main_config.keybindings_cfg), + terminal(main_config.terminal_cfg), + notebook(keybindings, terminal, + main_config.dir_cfg), + menu(keybindings), + api(menu, notebook) { INFO("Create Window"); set_title("juCi++"); set_default_size(600, 400); + set_events(Gdk::POINTER_MOTION_MASK|Gdk::FOCUS_CHANGE_MASK|Gdk::SCROLL_MASK); add(window_box_); - keybindings_.action_group_menu()->add(Gtk::Action::create("FileQuit", + keybindings.action_group_menu()->add(Gtk::Action::create("FileQuit", "Quit juCi++"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["quit"]), [this]() { OnWindowHide(); }); - keybindings_.action_group_menu()->add(Gtk::Action::create("FileOpenFile", + keybindings.action_group_menu()->add(Gtk::Action::create("FileOpenFile", "Open file"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["open_file"]), [this]() { OnOpenFile(); }); - keybindings_.action_group_menu()->add(Gtk::Action::create("FileOpenFolder", + keybindings.action_group_menu()->add(Gtk::Action::create("FileOpenFolder", "Open folder"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["open_folder"]), [this]() { OnFileOpenFolder(); }); - keybindings_. + keybindings. action_group_menu()-> add(Gtk::Action::create("FileSaveAs", "Save as"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["save_as"]), [this]() { SaveFileAs(); }); - keybindings_. + keybindings. action_group_menu()-> add(Gtk::Action::create("FileSave", "Save"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["save"]), [this]() { SaveFile(); }); - keybindings_. + keybindings. action_group_menu()-> add(Gtk::Action::create("ProjectCompileAndRun", "Compile And Run"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["compile_and_run"]), [this]() { SaveFile(); @@ -82,11 +82,11 @@ Window::Window() : } }); - keybindings_. + keybindings. action_group_menu()-> add(Gtk::Action::create("ProjectCompile", "Compile"), - Gtk::AccelKey(keybindings_.config_ + Gtk::AccelKey(keybindings.config_ .key_map()["compile"]), [this]() { SaveFile(); @@ -105,11 +105,11 @@ Window::Window() : } }); - add_accel_group(keybindings_.ui_manager_menu()->get_accel_group()); - add_accel_group(keybindings_.ui_manager_hidden()->get_accel_group()); - keybindings_.BuildMenu(); + add_accel_group(keybindings.ui_manager_menu()->get_accel_group()); + add_accel_group(keybindings.ui_manager_hidden()->get_accel_group()); + keybindings.BuildMenu(); - window_box_.pack_start(menu_.view(), Gtk::PACK_SHRINK); + window_box_.pack_start(menu.view(), Gtk::PACK_SHRINK); window_box_.pack_start(notebook.entry, Gtk::PACK_SHRINK); paned_.set_position(300); diff --git a/juci/window.h b/juci/window.h index dd63e6a..782029a 100644 --- a/juci/window.h +++ b/juci/window.h @@ -10,19 +10,17 @@ class Window : public Gtk::Window { public: Window(); - MainConfig& main_config() { return main_config_; } // std::string OnSaveFileAs(); Gtk::Box window_box_; virtual ~Window() { } - MainConfig main_config_; - Keybindings::Controller keybindings_; - Menu::Controller menu_; + MainConfig main_config; + Keybindings::Controller keybindings; + Menu::Controller menu; Notebook::Controller notebook; Terminal::Controller terminal; - PluginApi api_; - - Keybindings::Controller& keybindings() { return keybindings_; } + PluginApi api; + private: std::mutex running; Gtk::VPaned paned_;