Browse Source

Cleanup and improvement to Source->Find Pattern: now also moves cursor offset to first match when line is selected. Also, when showing match results, line in current view is highlighted.

pipelines/143601543
eidheim 6 years ago
parent
commit
e849fc0f45
  1. 1
      src/CMakeLists.txt
  2. 8
      src/ctags.cc
  3. 2
      src/ctags.h
  4. 137
      src/grep.cc
  5. 18
      src/grep.h
  6. 3
      src/project.cc
  7. 114
      src/window.cc

1
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

8
src/ctags.cc

@ -17,8 +17,8 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream>> 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<boost::filesystem::path, std::unique_ptr<std::stringstream>> 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;

2
src/ctags.h

@ -20,7 +20,7 @@ public:
static std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream>> 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<Location> get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type);

137
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<boost::filesystem::path, std::unique_ptr<std::stringstream>> 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<std::stringstream>();
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<std::pair<size_t, size_t>> 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, "&#x1b;[") != 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, "</b>");
line.insert(it->first, "<b>");
}
}
}
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 <b></b>
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 {};
}
}

18
src/grep.h

@ -0,0 +1,18 @@
#pragma once
#include <boost/filesystem.hpp>
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<boost::filesystem::path, std::unique_ptr<std::stringstream>> 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 = {});
};

3
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);

114
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<std::stringstream>();
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("&#x1b;[", start)) != std::string::npos) {
auto start_end = line_markup.find("&#x1b;[K", start + 7);
if(start_end == std::string::npos)
break;
auto start_size = start_end - start + 8;
auto end = line_markup.find("&#x1b;[m&#x1b;[K", start + start_size);
if(end == std::string::npos)
break;
line_markup.replace(end, 16, "</b>");
line_markup.replace(start, start_size, "<b>");
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");

Loading…
Cancel
Save