diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf162fc..bed80b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ set(JUCI_SHARED_FILES documentation_cppreference.cc filesystem.cc git.cc + grep.cc menu.cc meson.cc project_build.cc diff --git a/src/ctags.cc b/src/ctags.cc index db69420..8211649 100644 --- a/src/ctags.cc +++ b/src/ctags.cc @@ -17,8 +17,8 @@ std::pair> Ctags::ge std::string exclude = " --exclude=node_modules"; if(!build->project_path.empty()) { run_path = build->project_path; - exclude += " --exclude=" + filesystem::get_relative_path(build->get_default_path(), run_path).string(); - exclude += " --exclude=" + filesystem::get_relative_path(build->get_debug_path(), run_path).string(); + exclude += " --exclude=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_default_path(), run_path).string()); + exclude += " --exclude=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_debug_path(), run_path).string()); } else run_path = path; @@ -36,7 +36,7 @@ std::pair> Ctags::ge return {run_path, std::move(stdout_stream)}; } -Ctags::Location Ctags::get_location(const std::string &line_, bool markup) { +Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup) { Location location; #ifdef _WIN32 @@ -118,7 +118,7 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool markup) { if(pos != std::string::npos) location.index += pos; - if(markup) { + if(add_markup) { location.source = Glib::Markup::escape_text(location.source); std::string symbol = Glib::Markup::escape_text(location.symbol); pos = -1; diff --git a/src/ctags.h b/src/ctags.h index 0bfe43d..f688a7f 100644 --- a/src/ctags.h +++ b/src/ctags.h @@ -20,7 +20,7 @@ public: static std::pair> get_result(const boost::filesystem::path &path); - static Location get_location(const std::string &line, bool markup); + static Location get_location(const std::string &line, bool add_markup); static std::vector get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type); diff --git a/src/grep.cc b/src/grep.cc new file mode 100644 index 0000000..2a91a41 --- /dev/null +++ b/src/grep.cc @@ -0,0 +1,137 @@ +#include "grep.h" +#include "config.h" +#include "filesystem.h" +#include "project_build.h" +#include "terminal.h" + +std::pair> Grep::get_result(const boost::filesystem::path &path, const std::string &pattern, bool case_sensitive, bool extended_regex) { + boost::filesystem::path run_path; + auto build = Project::Build::create(path); + std::string exclude = "--exclude-dir=node_modules"; + if(!build->project_path.empty()) { + run_path = build->project_path; + exclude += " --exclude-dir=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_default_path(), build->project_path).string()); + exclude += " --exclude-dir=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_debug_path(), build->project_path).string()); + } + else + run_path = path; + + std::string flags; + if(!case_sensitive) + flags += " -i"; + if(extended_regex) + flags += " -E"; + + auto escaped_pattern = '\'' + pattern + '\''; + for(size_t i = 1; i < escaped_pattern.size() - 1; ++i) { + if(escaped_pattern[i] == '\'') { + escaped_pattern.insert(i, "\\"); + ++i; + } + } + + std::string command = Config::get().project.grep_command + " -R " + flags + " --color=always --binary-files=without-match " + exclude + " -n " + escaped_pattern + " *"; + + std::stringstream stdin_stream; + //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below + auto stdout_stream = std::make_unique(); + Terminal::get().process(stdin_stream, *stdout_stream, command, run_path); + return {run_path, std::move(stdout_stream)}; +} + +Grep::Location Grep::get_location(std::string line, bool color_codes_to_markup, bool include_offset, const std::string &only_for_file) { + std::vector> positions; + size_t file_end = std::string::npos, line_end = std::string::npos; + if(color_codes_to_markup) { + std::string escaped = Glib::Markup::escape_text(line); + auto decode_escape_sequence = [](const std::string &line, size_t &i) -> bool { + if(line.compare(i, 7, "[") != 0) + return false; + i += 7; + for(; i < line.size(); ++i) { + if((line[i] >= '0' && line[i] <= '9') || line[i] == ';') + continue; + return true; + } + return false; + }; + bool open = false; + size_t start; + line.clear(); + line.reserve(escaped.size()); + for(size_t i = 0; i < escaped.size(); ++i) { + if(decode_escape_sequence(escaped, i)) { + if(escaped[i] == 'm') { + if(!open) + start = line.size(); + else + positions.emplace_back(start, line.size()); + open = !open; + } + continue; + } + if(escaped[i] == ':') { + if(file_end == std::string::npos) + file_end = line.size(); + else if(line_end == std::string::npos) + line_end = line.size(); + } + line += escaped[i]; + } + if(file_end == std::string::npos || line_end == std::string::npos) + return {}; + + for(auto it = positions.rbegin(); it != positions.rend(); ++it) { + if(it->first > line_end) { + line.insert(it->second, ""); + line.insert(it->first, ""); + } + } + } + else { + file_end = line.find(':'); + if(file_end == std::string::npos) + return {}; + line_end = line.find(':', file_end + 1); + if(file_end == std::string::npos) + return {}; + } + + Location location; + location.markup = std::move(line); + + auto file = location.markup.substr(0, file_end); + if(!only_for_file.empty() && file != only_for_file) + return location; + location.file_path = std::move(file); + try { + location.line = std::stoul(location.markup.substr(file_end + 1, line_end - file_end)) - 1; + if(!include_offset) { + location.offset = 0; + return location; + } + + // Find line offset by searching for first match marked with + Glib::ustring ustr = location.markup.substr(line_end + 1); + size_t offset = 0; + bool escaped = false; + for(auto chr : ustr) { + if(chr == '<') + break; + else if(chr == '&') + escaped = true; + else if(chr == ';') { + escaped = false; + continue; + } + else if(escaped) + continue; + offset++; + } + location.offset = offset; + return location; + } + catch(...) { + return {}; + } +} diff --git a/src/grep.h b/src/grep.h new file mode 100644 index 0000000..93495b5 --- /dev/null +++ b/src/grep.h @@ -0,0 +1,18 @@ +#pragma once +#include + +class Grep { +public: + class Location { + public: + std::string file_path; + unsigned long line; + unsigned long offset; + std::string markup; + operator bool() const { return !file_path.empty(); } + }; + + static std::pair> get_result(const boost::filesystem::path &path, const std::string &pattern, bool case_sensitive, bool extended_regex); + + static Location get_location(std::string line, bool color_codes_to_markup, bool include_offset, const std::string &only_for_file = {}); +}; \ No newline at end of file diff --git a/src/project.cc b/src/project.cc index f7eba52..2e01e8d 100644 --- a/src/project.cc +++ b/src/project.cc @@ -219,8 +219,7 @@ void Project::Base::recreate_build() { } void Project::Base::show_symbols() { - auto view_folder = get_preferably_view_folder(); - auto pair = Ctags::get_result(view_folder); + auto pair = Ctags::get_result(get_preferably_view_folder()); auto path = std::move(pair.first); auto stream = std::move(pair.second); diff --git a/src/window.cc b/src/window.cc index 1977d12..622bcbb 100644 --- a/src/window.cc +++ b/src/window.cc @@ -7,6 +7,7 @@ #include "directories.h" #include "entrybox.h" #include "filesystem.h" +#include "grep.h" #include "info.h" #include "menu.h" #include "notebook.h" @@ -786,105 +787,60 @@ void Window::set_menu_actions() { menu.add_action("source_find_pattern", [this]() { std::string excludes = "--exclude-dir=node_modules"; - auto view_folder = Project::get_preferably_view_folder(); - auto build = Project::Build::create(view_folder); - boost::filesystem::path default_path, debug_path; - if(!build->project_path.empty()) { - view_folder = build->project_path; - excludes += " --exclude-dir=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_default_path(), build->project_path).string()); - excludes += " --exclude-dir=" + filesystem::escape_argument(filesystem::get_relative_path(build->get_debug_path(), build->project_path).string()); - } - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back(last_find_pattern, [this, view_folder = std::move(view_folder), excludes = std::move(excludes)](const std::string &pattern) { + EntryBox::get().entries.emplace_back(last_find_pattern, [this](const std::string &pattern_) { + auto pattern = pattern_; // Store pattern to safely hide entrybox + EntryBox::get().hide(); if(!pattern.empty()) { - std::stringstream stdin_stream; - std::string flags; - if(!find_pattern_case_sensitive) - flags += " -i"; - if(find_pattern_extended_regex) - flags += " -E"; - auto escaped_pattern = '\'' + pattern + '\''; - for(size_t i = 1; i < escaped_pattern.size() - 1; ++i) { - if(escaped_pattern[i] == '\'') { - escaped_pattern.insert(i, "\\"); - ++i; - } - } - std::string command = Config::get().project.grep_command + " -R " + flags + " --color=always --binary-files=without-match " + excludes + " -n " + escaped_pattern + " *"; - //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below - auto stdout_stream = std::make_unique(); - Terminal::get().process(stdin_stream, *stdout_stream, command, view_folder); - stdout_stream->seekg(0, std::ios::end); - if(stdout_stream->tellg() == 0) { + auto pair = Grep::get_result(Project::get_preferably_view_folder(), pattern, find_pattern_case_sensitive, find_pattern_extended_regex); + auto path = std::move(pair.first); + auto stream = std::move(pair.second); + stream->seekg(0, std::ios::end); + if(stream->tellg() == 0) { Info::get().print("Pattern not found"); EntryBox::get().hide(); return; } - stdout_stream->seekg(0, std::ios::beg); + stream->seekg(0, std::ios::beg); if(auto view = Notebook::get().get_current_view()) SelectionDialog::create(view, true, true); else SelectionDialog::create(true, true); std::string line; - while(std::getline(*stdout_stream, line)) { - auto line_markup = Glib::Markup::escape_text(line); - size_t start = 0; - while((start = line_markup.find("[", start)) != std::string::npos) { - auto start_end = line_markup.find("", start + 7); - if(start_end == std::string::npos) - break; - auto start_size = start_end - start + 8; - auto end = line_markup.find("", start + start_size); - if(end == std::string::npos) - break; - line_markup.replace(end, 16, ""); - line_markup.replace(start, start_size, ""); - start = end - start_size + 3 + 4; - } - SelectionDialog::get()->add_row(line_markup); + + std::string current_path; + unsigned int current_line = 0; + auto view = Notebook::get().get_current_view(); + if(view) { + current_path = filesystem::get_relative_path(view->file_path, path).string(); + current_line = view->get_buffer()->get_insert()->get_iter().get_line(); } - SelectionDialog::get()->on_select = [view_folder = std::move(view_folder)](unsigned int index, const std::string &text, bool hide_window) { - auto remove_markup = [](std::string &markup) { - auto start = markup.end(); - for(auto it = markup.begin(); it != markup.end();) { - if(*it == '<') { - start = it; - it++; - } - else if(*it == '>' && start != markup.end()) { - it = markup.erase(start, ++it); - start = markup.end(); - } - else - it++; + bool set_cursor_at_path = true; + while(std::getline(*stream, line)) { + auto location = Grep::get_location(std::move(line), true, false, current_path); + SelectionDialog::get()->add_row(location.markup); + if(view && location) { + if(set_cursor_at_path) { + SelectionDialog::get()->set_cursor_at_last_row(); + set_cursor_at_path = false; } - }; - auto file_end = text.find(':'); - if(file_end != std::string::npos) { - auto file = text.substr(0, file_end); - remove_markup(file); - auto line_end = text.find(':', file_end + 1); - if(line_end != std::string::npos) { - try { - auto line_str = text.substr(file_end + 1, line_end - file_end); - remove_markup(line_str); - auto line = std::stoi(line_str); - Notebook::get().open(view_folder / file); - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_pos(line - 1, 0); - view->scroll_to_cursor_delayed(true, false); - } - catch(...) { - } + else { + if(current_line >= location.line) + SelectionDialog::get()->set_cursor_at_last_row(); } } + } + SelectionDialog::get()->on_select = [path = std::move(path)](unsigned int index, const std::string &text, bool hide_window) { + auto location = Grep::get_location(text, false, true); + Notebook::get().open(path / location.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_offset(location.line, location.offset); + view->scroll_to_cursor_delayed(true, false); }; SelectionDialog::get()->show(); } - EntryBox::get().hide(); }); auto entry_it = EntryBox::get().entries.begin(); entry_it->set_placeholder_text("Pattern");